|
@@ -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());
|
|
|
+```
|