Browse Source

php2js alpha

me@helium 8 years ago
parent
commit
fcd4c99e93
1 changed files with 460 additions and 2 deletions
  1. 460 2
      php2js.md

+ 460 - 2
php2js.md

@@ -120,7 +120,7 @@ Javascript-интерпретатор в браузере может работ
 - Функциональный подход превалирует над "обычным" ООП-подходом, привычным после **PHP**/**C++**/**Java**.
 - Функции являются объектами первого класса, т. е. могут быть сохранены как переменные и переданы/возвращены в/из других функций.
 - Объект и ассоциативный массив - это одно и то же.
-- В ассоциативных массивах наряду с другими типами данных можно хранить функции, что превращает их в объекты. (**инкапсуляция**)
+- В ассоциативных массивах наряду с другими типами данных можно хранить функции, что превращает массивы в объекты. (**инкапсуляция**)
 - Функции можно заносить в объекты, запускать как методы, или как отдельные функции. `this` обычно равен объекту, указанному до `.`, однако
   есть возможность передать функции в качестве `this` любой объект. 
 - Можно менять и переопределять структуру *почти* всех объектов, включая стандартные, что позволяет кардинально менять их поведение.(**полиморфизм**)
@@ -131,7 +131,7 @@ Javascript-интерпретатор в браузере может работ
 - `private`, `protected` и `public` нет.
 - Функция исполняется одновременно в *динамическом* (значение параметров при вызове) и *лексическом* (переменные из областей видимости *декларации* функции)
   контекстах. Таким образом реализуются **замыкания**: переменные области видимости *завершенной* функции сохраняются для использования функциями, 
-  определенных внутри, *в случае* если внутренние функции были переданы как результат работы функции или другими способами остались существовать
+  определенными внутри, *в случае* если внутренние функции были переданы "наружу" как результат работы функции или другими способами остались существовать
   после окончания внешней функции. Таким образом реализуются аналог `private` полей объекта, а так же сохраняется контекст для отложенных операций или
   обработчиков событий. **Замыкания** являются *объектами* в общем смысле этого слова, так как инкапсулируют код и данные.
 - *Синтаксически* **JS** *более* объектно-ориентирован чем **PHP**, так как *все* типы данных являются объектами с набором методов.
@@ -532,3 +532,461 @@ echo (substr("123string456",3,6)); // "string"
 
 ### Массивы
 
+Под массивом подразумевается нумерованный целочисленными индексами массив. В **PHP** обычные массивы и ассоциативные объединены в единый тип **array**, в 
+**JS** же для "обычных" массивов применяется объект типа **Array**, а для ассоциативных массивов - объекты как таковые.
+
+```javascript
+var a1 = [1,2,3]
+var a2 = new Array(4,5,6)
+
+alert(a1[1]) // 
+a1.push(5) 
+alert(a1.indexOf(3)) // array_search
+alert(a2.length) // count
+
+//Можно добавить именованное поле в обычный массив как и в любой другой объект JS, но...
+
+a2.someKey = 15;
+a1["someOtherKey"] = 25;
+
+//от этого они не появятся среди других нумерованных элементов
+
+alert(a2);
+alert(a1);
+
+//но будут доступны наряду со стандартными полями объекта Array (length, indexOf и другие)
+alert(a2["someKey"]);
+alert(a1.someOtherKey);
+```
+
+Посему используйте для массивов типа `Array` только целочисленные ключи.
+
+### Ассоциативные массивы.
+
+В **JS** ассоциативные массивы и объекты -  это *одно и тоже*. Так же любой объект, даже встроенный, может быть изменен в любой момент.
+
+```javascript
+var person = {
+    name: "Ivan",
+    surname: "Ivanovv",
+    "fatherName": "Petrovich",
+}
+
+typeof person
+```
+
+Нет разницы, определять ключи *литерально* или через строку (в кавычках `"fatherName"`).
+
+```javascript
+person.fatherName
+person["name"]
+```
+
+Обратите внимание, что `person.fatherName` работает так же как и `person["name"]`, несмотря на то, что определены наоборот.
+
+Для обращения через ключ в переменной используется нотация с квадратными скобками:
+```javascript
+var key = "surname";
+
+person[key]
+person.key
+```
+
+Если просто написать `person.key`, то **JavaScript** будет искать ключ `key` *литерально*, а не по значению переменной `key` ("surname")
+
+Вы можете определить новый элемент массива просто присвоив ему значение:
+
+```javascript
+person.age = 98;
+
+person
+```
+
+Также можно создавать массив через конструктор Object:
+```javascript
+
+var a = new Object();
+a.name = "Petr"
+a.surname = "Petrov";
+a["age"] = 17;
+```
+
+Получить ключи ассоциативного массива можно с помощью функции `Object.keys`:
+```javascript
+Object.keys(person)
+```
+
+В качестве значений в ассоциативном массиве могут быть любые типы данных, в том числе и другие ассоциативные массивы:
+```javascript
+var tree = {
+    name: "Ivan",
+    fatherName: "Petrovich",
+    surname: "Ivanov",
+    children: [
+        {
+            name: "Maria",
+            fatherName: "Ivanovna",
+            surname: "Ivanova",
+        },
+        {
+            name: "Petr",
+            fatherName: "Ivanovich",
+            surname: "Ivanov",
+        },
+        {
+            name: "Nikolay",
+            fatherName: "Ivanovich",
+            surname: "Ivanov",
+        },
+    ]
+}
+```
+
+Нет сложностей с циклическими ссылками:
+```javascript
+var a = {}
+var b = {}
+a.b = b
+b.a = a
+
+b.b = b
+a.a = a
+
+a.name = "A"
+b.name = "B"
+
+for (var i=0,child=tree.children[i];     i<tree.children.length;  i++,child=tree.children[i]){
+    child.father = tree;
+}
+```
+
+## `in`
+
+Ключевое слово `in` используется для двух задач:
+- проверка наличия ключа в ассоциативном массиве
+
+```javascript
+"fatherName" in a
+"age" in person
+```
+
+(аналог `array_key_exists`)
+
+- конструкция `for (var key in arr)` для перебора всех элементов ассоциативного массива
+
+```javascript
+for (var key in person){
+    console.log(key+": "+person[key]);
+}
+```
+
+### Задание
+Нарисовать HTML таблицу из двух колонок, в которой слева будут ключи, а справа - значения:
+<table><tr><th>name</th><td>Ivan</td></tr><tr><th>surname</th><td>Ivanovv</td></tr><tr><th>fatherName</th><td>Petrovich</td></tr></table>
+
+## Функции
+
+В семантике **JS** функциям отведена огромная роль:
+Они могут использоваться
+- Как обычные функции в процедурном стиле написания кода (как в **PHP**)
+- Как *объекты первого класса*, т. е. как переменные. Это позволяет их передавать в другие функции, сохранять в переменных и так далее:
+  - Функциональный подход (`filter`, `map`, `reduce`, `sort`)
+  - Функции, сохраненные в ассоциативном массиве становятся методами
+  - Асинхронное программирование - обработчики событий и длительных операций ввода-вывода передаются в виде функций.
+  - Замыкания.
+
+**PHP** тоже позволяет делать многое из этого, однако исторически такой подход редко используется в PHP-коде.
+
+### Основные синтаксические различия c **PHP**
+
+Основные синтаксические различия касаются параметров функции:
+- В функциях в **JS** нет параметров по умолчанию
+- Набор параметров при вызове функции может отличаться от набора параметров при декларации, и это не вызывает ошибок
+- Вы всегда можете узнать актуальный набор параметров в форме псевдомассива `arguments`.
+
+
+```javascript
+function add2(a,b){
+    if (typeof a === 'undefined'){
+        a = 0;
+    }
+    b = b || 0;
+    return a + b;
+}
+alert(add2());
+alert(add2(5));
+alert(add2(5,7));
+```
+
+```javascript
+function add(){
+    var result = 0;
+    for (var i=0;i<arguments.length;i++){
+        result += arguments[i];
+    }
+    return result;
+}
+alert(add());
+alert(add(5,6,7));
+alert(add(1,2,3,4,5,6,7));
+```
+
+Если очень нужны именованные параметры, используйте ассоциативные массивы, благо это почти не добавляет ничего, загромождающего синтаксис:
+
+```javascript
+function someJQueryPlugin(options){
+    alert(options.size);   
+    alert(options.selector);   
+}
+
+someJQueryPlugin({size: 15, selector: "#id"});
+```
+
+### Определение функций
+
+#### Function Declaration
+Функцию можно определить "как обычно"
+```javascript
+a("Hello");
+
+function a(text){
+    alert("function a: " + a);
+}
+
+var b = a; //сохраняем функцию в переменной b
+b("Hi"); //работает
+
+a = alert;
+a("Hello"); //a заменена встроенной функцией alert
+
+a = b;      //возвращаем изначальную функцию в a
+```
+
+Обратите внимание, что:
+- Имя функции - это такая же переменная что и все остальные переменные
+- При определении "как обычно" (**Function Declaration**) вы получаете возможность вызывать функцию *до* её определения.
+
+#### Function Expression
+
+```javascript
+var func = function(a){
+    alert(a);
+};
+
+func("Hello");
+```
+
+В примере выше написано *выражение*, которое создает *анонимную* функцию. И эта функция связывается с переменной `func`. В дальнейшем её можно
+использовать **так же** как и в **Function Declaration**, за одним исключением - нельзя обращаться к переменной *до* её определения:
+
+```javascript
+func("Hello"); //ошибка, переменная func не определена.
+var func = function(a){
+    alert(a);
+};
+```
+
+#### Self-Invoked Function
+
+Название немного сбивает с толку, так как намекает на рекурсию, однако рекурсии в этом нет. Суть в том, что если есть выражение, которое создает функцию,
+то есть возможность к ней прямо сразу и обратится, не занося её в переменную.
+
+```javascript
+"abcdef".indexOf("f") //создаем строку и тут же используем её
+{"name": "Ivan", surname: "Petroff"}.name // создаем ассоциативный массив и тут же достаем из него поле name
+
+(function(param){
+    alert("param: " + param);
+})("lalala"); //создаем функцию и тут же её вызываем с параметром "lalala"
+```
+
+Обычно это используется для создания обособленной области видимости и/или замыканий.
+
+#### `call` и `apply` и чуть-чуть ООП.
+
+Эти два метода объекта `Function` позволяют вызвать функцию, указав `this` и параметры:
+
+```javascript
+function Person(name, surname){
+    this.name    = name
+    this.surname = surname
+
+    this.getFullName = function(){
+        return this.name + (this.fatherName ? " " + this.fatherName + " " : " ") + this.surname
+    }
+}
+
+function say(greet, aftergreet){
+    alert(greet + " " + this.getFullName() + " " +  aftergreet)
+}
+
+var person        = new Person("Ivan", "Petroff")
+
+say.call(person,"Hello", "!!111!!!")
+say.apply(person,["Hi", "by apply"]) //feel the difference
+```
+
+То, что `apply` принимает массив в качестве набора параметров функции помогает **творить чудеса**:
+
+```javascript
+var someArray = [1,5,7,-17,100500];
+
+Math.min(someArray) //Math.min так не умеет
+Math.min(1,5,7,-17,100500,-123) // зато умеет так
+Math.min.apply(Math,someArray) // ...и мы этим воспользуемся...
+
+// или же сделаем свой min в someArray:
+
+someArray.min = function(){
+    return Math.min.apply(Math,someArray);
+}
+
+someArray.min()
+
+// или же сделаем свой min для всех массивов 
+
+Array.prototype.min = function(){ 
+    return Math.min.apply(Math, this) 
+};
+
+
+
+[100, 500,  -100500].min()
+```
+
+## Замыкания, приватные методы и данные и функциональное ООП.
+
+Функция в **JS** находится сразу в двух контекстах: *динамическом* (`this`, значения параметров) и *лексическом* (переменные из более высоких областей
+видимости в **месте определения функции**). *Динамический контекст* - это контекст **вызова** функции - значение параметров и окружение на момент вызова;
+*лексический контекст* - контекст **определения** функции, её вложенности в другие области видимости, доступ к которым функция имеет и после окончания
+выполнения функций-владельцев этих областей видимости.
+
+### makeAdder
+
+```javascript
+function makeAdder(x){
+    function adder(y){
+        return x + y;
+    }
+    return adder
+}
+
+var add5 = makeAdder(5);
+var greeter = makeAdder("Hi, ");
+
+alert(add5(2));
+alert(greeter("John"));
+```
+
+В примере выше `5`,`"Hi, "`, `2` и `"John"` находятся в динамическом контексте вызова функции. А вот значение `x` в `adder` находится в *лексическом* 
+контексте. На момент запуска функций `add5` и `greeter` функции `makeAdder` уже давно отработали, однако значение `x` и область видимости продолжают
+присутствовать в функциях `add5` и `greeter`. Это называется **замыканием** - данные в локальной области видимости отработавшей функции и код, который
+использует эти данные впоследствии. В широком смысле **замыкание** является *объектом*, так как хранит в единой сущности код и данные.
+
+В контексте **JS** *замыкания* очень удобны для огораживания кода, сохранения состояния переменных "на будущее", для функций обратного вызова. Так же
+*замыкания* удобны для организации приватных полей у объектов **JS**.
+
+```javascript
+
+function makeCounter(){
+    var counter = 0;
+
+    return function(){
+        return counter++;
+    }
+}
+```
+
+Функция выше создает счетчик, значение которого можно узнать из возвращаемой анонимной функции. При этом счетчик увеличится на 1. Если расширить функционал
+данного примера для чтения и декремента счетчика, получим, например:
+
+```javascript
+
+function makeCounter(){
+    var counter = 0;
+
+    function inc(){
+        return counter++;
+    }
+
+    function dec(){
+        return counter--;
+    }
+
+    function read(){
+        return counter;
+    }
+
+    return [inc,dec,read];
+}
+```
+Результатом выполнения `makeCounter` будет *массив функций*. Однако намного более наглядным будет создание именованного массива, т. е. объекта:
+
+```javascript
+
+function makeCounter(){
+    var counter = 0;
+
+    function inc(){
+        return counter++;
+    }
+
+    function dec(){
+        return counter--;
+    }
+
+    function read(){
+        return counter;
+    }
+
+    return {inc: inc, dec: dec, read: read};
+}
+```
+
+... что во многом схоже с обычным объектом сделанным через **функцию-конструктор**:
+
+```javascript
+function Person(name, surname){
+    this.name    = name
+    this.surname = surname
+
+    var age      = 0
+
+    function checkNewAge(){
+        return (!isNaN(this.newAge) && Number.isInteger(this.newAge) && (this.newAge > 0) && (this.newAge < 100))
+    }
+
+    this.setAge = function(newAge){
+        this.newAge = +newAge
+        if (checkNewAge()){
+            age = this.newAge;
+        }
+
+        return age
+    }
+
+    this.getAge = function(){
+        return age
+    }
+    
+    this.getOriginalName = function(){
+        return name;
+    }
+
+    this.getOriginalSurname = function(){
+        return surname;
+    }
+}
+
+var person        = new Person("Ivan", "Petroff")
+
+alert(person.setAge(50))
+alert(person.setAge(125))
+alert(person.setAge(25))
+
+person.name = "John";
+person.surname = "Doe";
+
+alert(person.getOriginalName());
+alert(person.getOriginalSurname());
+```