|
@@ -0,0 +1,840 @@
|
|
|
+# Javascript для Jav-истов
|
|
|
+
|
|
|
+# JAVA !== JAVASCRIPT && JAVA != JAVASCRIPT
|
|
|
+
|
|
|
+## Отладка
|
|
|
+
|
|
|
+Вместо IDE есть **Developer Tools** В браузере.
|
|
|
+
|
|
|
+
|
|
|
+**Developer Tools** содержит в себе множество средств отладки кода. Для хорошего понимания управляющих конструкций (**условий** и **циклов**) полезно
|
|
|
+разобраться с **пошаговой отладкой** - выполнением кода по шагам. Это позволяет увидеть, какие блоки кода выполняются или не выполняются в условных
|
|
|
+операторах и каковы значения переменных в каждый момент времени выполнения кода.
|
|
|
+
|
|
|
+Для начала пошаговой отладки устанавливается **точка останова** - строка кода, с которой обычный режим выполнения прерывается и начинается отладка по шагам.
|
|
|
+В **Developer Tools** найдите вкладку *Source*, в ней свой файл c кодом, и кликните на номере нужной строки слева от текста кода.
|
|
|
+
|
|
|
+Если вы используете [repl.it](http://repl.it), другие **онлайн-IDE** или **console**, то у вас будут определенные сложности с нахождением
|
|
|
+вашего кода и строки в нём. Поэтому вы можете вставить ключевое слово `debugger` в ваш код - это работает так же, как **точка останова** на
|
|
|
+строке в Developer Tools.
|
|
|
+
|
|
|
+### Отладка по шагам.
|
|
|
+
|
|
|
+**Пошаговая отладка** позволяет детально заглянуть в процесс выполнения вашего кода - вы можете узнать всё ли работает так, как нужно, в любой строке и таким
|
|
|
+образом упростить поиск логических ошибок в коде. Основные операции:
|
|
|
+- **Step over next function call** (**F10**) - следующий шаг/оператор в вашем коде. После выполнения каждой команды вы можете ознакомится со значениями
|
|
|
+переменных, наведя на них курсор мыши, написав их в консоли, или же используя вкладку Watch
|
|
|
+- **Resume script execution** (**F8**) - переход из режима отладки по шагам в обычный режим выполнения кода. Таким способом вы пропускаете хорошо отлаженные
|
|
|
+части кода. Этот режим может быть прерван следующей **точкой останова** или ключевым словом `debugger` в коде.
|
|
|
+- **Step into next function call** (**F11**) - сделать следующий шаг *в функцию*, "зайти" в неё.
|
|
|
+- **Step out of current function** (**Shift-F11**) - следующий шаг *из* функции, выйти из функции на уровень, который функцию вызвал.
|
|
|
+
|
|
|
+### Значение переменных и выражений
|
|
|
+
|
|
|
+- Вы можете узнать значение переменных, наведя на них курсор мыши
|
|
|
+- Вы можете узнать значение синтаксически верного выражения выделив его и наведя на него курсор мыши. Учтите, что при этом может быть вызвана
|
|
|
+ та или иная функция и могут возникнуть побочные эффекты.
|
|
|
+
|
|
|
+
|
|
|
+### Пошаговая отладка и консоль.
|
|
|
+
|
|
|
+Очень удобно использовать консоль и пошаговую отладку одновременно. Консоль предоставляет все переменные и их текущие значения, которые сейчас есть в
|
|
|
+отлаживаемом коде, таким образом вы можете поэкспериментировать с этими значениями и, например, запустить из консоли следующий проблемный оператор в коде
|
|
|
+или его часть для нахождения логической ошибки.
|
|
|
+
|
|
|
+### Отладочный вывод
|
|
|
+
|
|
|
+Вы всегда можете добавить `console.log` в место, где хотите получить информацию о состоянии программы. Этот способ хорош как дополнение к остальным.
|
|
|
+Так же вы можете написать определенное условие для отладки, вставить в него `console.log` и поставить **точку останова**.
|
|
|
+
|
|
|
+### Примеры ниже
|
|
|
+...могут быть запущены в отладочном режиме по красной кнопке :-), если вы откроете **Developer Tools**. Без **Developer Tools** они тоже работают.
|
|
|
+
|
|
|
+## Подключение
|
|
|
+
|
|
|
+"Корнем" **HTML**-документа является файл **html**, в котором указываются остальные части страницы - картинки, css, js файлы и код.
|
|
|
+
|
|
|
+### Подключение отдельных файлов и библиотек
|
|
|
+```html
|
|
|
+<script src="/path/to/script.js"></script>
|
|
|
+```
|
|
|
+
|
|
|
+### Подключение кода inline.
|
|
|
+
|
|
|
+```html
|
|
|
+<script>
|
|
|
+alert("AAaa");
|
|
|
+</script>
|
|
|
+```
|
|
|
+
|
|
|
+## `use strict`
|
|
|
+
|
|
|
+Javascript-интерпретатор в браузере может работать в двух режимах: "обычном" и в "строгом":
|
|
|
+- **Обычный** режим предназначен для совместимости.
|
|
|
+- **Строгий** режим включает *современный* стандарт **Javascript** (ECMAScript 5 или новее)
|
|
|
+
|
|
|
+В зависимости от режима поведение интерпретатора может
|
|
|
+меняться. Далее это будет упоминаться в тех или иных моментах. По умолчанию интерпретатор работает в обычном режиме, для включение строгого режима
|
|
|
+используется *строка* `'use strict'` в начале **Javascript**-кода.
|
|
|
+
|
|
|
+```javascript
|
|
|
+"use strict";
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+или
|
|
|
+```javascript
|
|
|
+'use strict';
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+## Семантика и её отличия от **Java**.
|
|
|
+
|
|
|
+- В **Java** код может исполняться многопоточно (однако каждый поток работает изолированно) и **синхронно**, т. е. код выполняется в том порядке, в котором он написан;
|
|
|
+ в коде никаких фоновых и
|
|
|
+ непоследовательных операций обычно не предусмотрено, ввод-вывод (запись и чтение файлов, сетевые запросы, запросы к СУБД) происходят в **блокирующем**
|
|
|
+ режиме, т. е. Java-код останавливает выполнение и ожидает отправки или приема той или иной порции данных.
|
|
|
+- В **JS** код исполняется однопоточно (за редким исключением), и **асинхронно**, т. е. код представляет из себя не единую последовательность действий,
|
|
|
+ а набор функций, которые могут исполнятся в произвольном порядке в качестве обработчиков событий на странице, событий, связанных с временными задержками
|
|
|
+ или сетевых событий. Однако надо учитывать, что код, расположенный в функции, обычно работает **синхронно** и **однопоточно**, *никакой другой обработчик*
|
|
|
+ события не может работать, пока работает другой код. **Асинхронными являются только вызовы обработчиков событий из движка браузера**, когда же вы вызываете
|
|
|
+ функцию в своем коде, весь ваш код работает *синхронно*. Ввод-вывод так же происходит в асинхронной манере - вместо ожидания данных, интерпретатор
|
|
|
+ освобождается для обработки других событий; по окончанию операции ввода-вывода вызывается соответствующая функция, обрабатывающая результаты.
|
|
|
+
|
|
|
+Данные отличия приводят к сложности написания, отладки и понимания **JS** кода. Стандартные ошибки:
|
|
|
+- Путание определения функции и её выполнения.
|
|
|
+- Ожидание работы кода в последовательности написания, а не в последовательности событий, которые запускают те или иные части кода.
|
|
|
+
|
|
|
+### Семантические последствия и особенности **JS**.
|
|
|
+
|
|
|
+- Язык унаследовал некоторые черты функциональных языков и языков высокого уровня абстракции (`Scheme`, `Common Lisp`)
|
|
|
+- Функциональный подход превалирует над "обычным" ООП-подходом, привычным после **PHP**/**C++**/**Java**.
|
|
|
+- Функции являются объектами первого класса, т. е. могут быть сохранены как переменные и переданы/возвращены в/из других функций.
|
|
|
+- Объект и ассоциативный массив - это одно и то же.
|
|
|
+- В ассоциативных массивах наряду с другими типами данных можно хранить функции, что превращает массивы в объекты. (**инкапсуляция**)
|
|
|
+- Функции можно заносить в объекты, запускать как методы, или как отдельные функции. `this` обычно равен объекту, указанному до `.`, однако
|
|
|
+ есть возможность передать функции в качестве `this` любой объект. По факту `this` - еще один специфический параметр функции.
|
|
|
+- Можно менять и переопределять структуру *почти* всех объектов, включая стандартные, что позволяет кардинально менять их поведение.(**полиморфизм**)
|
|
|
+ Таким образом можно сделать аналог примесей, интерфейсов и множественного наследования.
|
|
|
+- В **JS** реализовано так называемое прототипное ООП: вместо указания предка и обработки наследования на уровне интерпретатора, в **JS** в объекте
|
|
|
+ есть ключ `prototype`, который является ссылкой на другой объект-прототип. **В случае, когда тот или иной ключ (имя метода или поля) в объекте не найден,
|
|
|
+ JS ищет ключ в объекте-прототипе**, и далее по цепочке прототипов. Таким образом реализуется **наследование**.
|
|
|
+- `private`, `protected` и `public` нет.
|
|
|
+- Функция исполняется одновременно в *динамическом* (значение параметров при вызове) и *лексическом* (переменные из областей видимости *декларации* функции)
|
|
|
+ контекстах. Таким образом реализуются **замыкания**: переменные области видимости *завершенной* функции сохраняются для использования функциями,
|
|
|
+ определенными внутри, *в случае* если внутренние функции были переданы "наружу" как результат работы функции или другими способами остались существовать
|
|
|
+ после окончания внешней функции. Таким образом реализуются аналог `private` полей объекта, а так же сохраняется контекст для отложенных операций или
|
|
|
+ обработчиков событий. **Замыкания** являются *объектами* в общем смысле этого слова, так как инкапсулируют код и данные.
|
|
|
+- Синтаксис схож с **Java**, но это обманчивое сходство.
|
|
|
+- Язык с **нестрогой динамической типизацией**, т. е. переменные типа *не имеют*, однако тип имеют *значения* в этих переменных.
|
|
|
+- Структуры резиновые и могут быть изменены в любой момент
|
|
|
+
|
|
|
+### Типы данных
|
|
|
+
|
|
|
+- Целые и дробные числа представлены едиными типом `Number`
|
|
|
+- Под "массивом" в **JS** подразумевается массив с целочисленными ключами.
|
|
|
+- Ассоциативные массивы и объекты - это одно и то же;
|
|
|
+- Для переменных без значения и значений отсутствующих ключей в ассоциативных массивах вместо `NULL` используется аналогичный тип `undefined`
|
|
|
+- Так же существует тип `null`, который применяется программистами для задания "пустых" значений и/или в **DOM**.
|
|
|
+
|
|
|
+## `;`
|
|
|
+В отличие от **Java** и многих других языков с **C**-подобным синтаксисом, в **JS** точка с запятой *не является обязательной*, однако нужна
|
|
|
+в некоторых случаях, например при написании операторов в одну строку:
|
|
|
+
|
|
|
+```javascript
|
|
|
+var a = 5
|
|
|
+var b = 6
|
|
|
+var c = "string"
|
|
|
+var d
|
|
|
+
|
|
|
+a ++; b += a; d = c + a;
|
|
|
+```
|
|
|
+
|
|
|
+**Во избежание ошибок просто добавляйте `;` "как обычно", в конце строки**
|
|
|
+
|
|
|
+## Комментарии
|
|
|
+Как в Java.
|
|
|
+
|
|
|
+## Строки, переменные в них и конкатенация
|
|
|
+
|
|
|
+```javascript
|
|
|
+alert(`случайное число ${Math.random()}`)
|
|
|
+alert('ОДинарные кавычки ' + Math.random())
|
|
|
+alert("Двойные ничем не отличаются " + Math.random())
|
|
|
+```
|
|
|
+
|
|
|
+Строки иммутабельны, как в **Java** в них есть методы.
|
|
|
+
|
|
|
+
|
|
|
+## `+` и динамическая типизация.
|
|
|
+
|
|
|
+Так как в **JS** нет отдельного оператора конкатенации (в **PHP** это `.`), то `+` между числами в строках может вас удивить:
|
|
|
+
|
|
|
+
|
|
|
+Для того что бы избежать подобных ситуаций, приводите числа в строках перед использованием в математических операциях:
|
|
|
+- `+"123"`. Простой и краткий способ для приведения строки к числу
|
|
|
+- `parseInt("123")` или `parseFloat("123.45")` работает схожим образом, однако обладает другими странностями и возможностями (например есть возможность
|
|
|
+ задать систему счисления)
|
|
|
+
|
|
|
+## Операторы, условия, циклы.
|
|
|
+
|
|
|
+### Вызов функций
|
|
|
+
|
|
|
+Большинство кода состоит из тех или иных вызовов функций. В **JS** они выглядят почти так же:
|
|
|
+```javascript
|
|
|
+alert("as in Java");
|
|
|
+alert("as in JS, without semicolon")
|
|
|
+```
|
|
|
+Во второй строке примера нет `;`, в этом невеликое отличие.
|
|
|
+
|
|
|
+### Операторы
|
|
|
+
|
|
|
+В большинстве своем повторяют обычный набор **PHP**, **C**, **Java** и так далее (`+`, `-`, `*`, `/`, `%`, `++`, `--`, `+=` ...).
|
|
|
+
|
|
|
+### `&&` и `||`.
|
|
|
+
|
|
|
+В отличие от **Java**, в **JS** **И** и **ИЛИ** возвращают *оригинальное значение операнда*, а не булевский результат логического оператора.
|
|
|
+
|
|
|
+#### `||`
|
|
|
+Результат `||` становится очевиден после первого же значения, интерпретируемого как `true`. **ИЛИ** *слева направо* поочередно приводит операнды к Boolean
|
|
|
+и возвращает *первый* операнд, *интерпретируемый* как `true`. Если такого операнда нет, **ИЛИ** возвращает последний (правый) операнд. **Результатом**
|
|
|
+оператора является не приведенное к типу Boolean значение, а *оригинальное значение операнда*
|
|
|
+
|
|
|
+#### `&&`
|
|
|
+Результат `&&` становится очевиден после первого же значения, интепретируемого как `false`. **И** *слева направо* поочередно приводит операнды к Boolean
|
|
|
+и возвращает *первый* операнд, *интерпретируемый* как `false`. Если такого операнда нет, **И** возврщаает последний (правый) операнд. **Результатом**
|
|
|
+оператора является не приведенное к типу Boolean значение, а *оригинальное значение операнда*
|
|
|
+
|
|
|
+#### Приведение к типу `Boolean`
|
|
|
+
|
|
|
+Как `false` интерпретируются:
|
|
|
+- `false`
|
|
|
+- `0` // 0 как число
|
|
|
+- `""` //пустая строка
|
|
|
+- `null`
|
|
|
+- `undefined`
|
|
|
+- `NaN`
|
|
|
+
|
|
|
+Как `true` интерпретируются все остальные значения, в том числе:
|
|
|
+- `Infinity`
|
|
|
+- `-Infinity`
|
|
|
+- `"0"` //строка не пуста. однако +"0" уже 0 как число, а значит false
|
|
|
+- `{}` //пустой объект - всё равно `true`
|
|
|
+- `[]` //пустой массив `[] == false`, но в остальных случаях работает как true
|
|
|
+
|
|
|
+**Для проверки используйте** `!!`, то есть двойное отрицание: `!!null` равен `false`, таким образом мы можем почти всегда проверить как интерпретируется
|
|
|
+то или иное значение.
|
|
|
+
|
|
|
+В общем случае объект является `true`, за исключением `null` и `[] == false`
|
|
|
+
|
|
|
+**Примеры**
|
|
|
+```javascript
|
|
|
+2
|
|
|
+1+1
|
|
|
+2*1
|
|
|
+// bool type cast
|
|
|
+!!2
|
|
|
+!!0
|
|
|
+!!1
|
|
|
+// or
|
|
|
+2 || 1
|
|
|
+2 || 0
|
|
|
+//and
|
|
|
+2 && 1
|
|
|
+1 && 2
|
|
|
+0 && 2
|
|
|
+// or and and difference
|
|
|
+0 || 1 || 2
|
|
|
+0 && 1 && 2
|
|
|
+2 || 1 || 0
|
|
|
+2 && 1 && 0
|
|
|
+//null, undefined, so on
|
|
|
+null || 2
|
|
|
+undefined && 1
|
|
|
+//brackets and complex expressions
|
|
|
+(undefined || 2) && (2 || 0)
|
|
|
+(2 && 1) || (null && 0)
|
|
|
+(2 > 1) && "greater"
|
|
|
+(2 < 1) && null
|
|
|
+null && (2 < 1)
|
|
|
+// ternary operator
|
|
|
+1 ? "one" : "not one"
|
|
|
+0 ? "zero" : "not zero"
|
|
|
+"0" ? "\"zero\"" : "not `zero`"
|
|
|
+parseInt("0") ? 'true' : 'false'
|
|
|
+("" || 2) && (3 || "3.5") || (4 && 5)
|
|
|
+(-1 + 1) && "zero"
|
|
|
+"-1" + 1 && "oups"
|
|
|
+(typeof null === 'object') ? "null is object" : "null is null"
|
|
|
+// ternary && ||
|
|
|
+Math.random() < 0.5 && 'less' || 'more'
|
|
|
+(a = Math.random()) < 0.5 && 'less: '+a || 'more: '+a
|
|
|
+//in for array
|
|
|
+[2,3,5,7,11].indexOf(7) > -1 ? 'prime' : 'not found'
|
|
|
+'random' in Math
|
|
|
+'indexOf' in []
|
|
|
+var a = b = c = d = 5;
|
|
|
+```
|
|
|
+
|
|
|
+**Java** ведет себя по-другому:
|
|
|
+
|
|
|
+```php
|
|
|
+2 || "aaa";// bool(true)
|
|
|
+0 && "aaa";// bool(false)
|
|
|
+```
|
|
|
+
|
|
|
+### Условия `if` и `switch`.
|
|
|
+
|
|
|
+Работают аналогично **Java**.
|
|
|
+
|
|
|
+### Циклы
|
|
|
+
|
|
|
+#### `for ... in`
|
|
|
+
|
|
|
+```javascript
|
|
|
+var car = {brand: "Lada",
|
|
|
+ 'model': "2101"};
|
|
|
+
|
|
|
+for (var key in car){
|
|
|
+ alert(key + ": " + car[key]);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Как можно заметить, при этом в цикле нет переменной со значенинем, а только с ключем, по которому можно в цикле получить значение из итерируемого массива.
|
|
|
+
|
|
|
+#### `for`, `while` и `do-while`
|
|
|
+
|
|
|
+работают так же как в **Java**:
|
|
|
+
|
|
|
+```javascript
|
|
|
+for (var i=0;i<10;i++){
|
|
|
+ console.log(i);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## Типы данных и Объектная модель.
|
|
|
+
|
|
|
+### Обращение к полям и методам объектов **JS**
|
|
|
+
|
|
|
+Как и в **Java**, через точку `.`.
|
|
|
+
|
|
|
+### Всё является объектом
|
|
|
+
|
|
|
+Даже встроенные типы данных. Аналоги стандартных функций **PHP** (процедурный стиль) являются методами объектов в **JS**.
|
|
|
+
|
|
|
+### Числа.
|
|
|
+Кроме того, что целые и дробные числа объединены в единый тип `Number`, особых отличий с **Java** нет:
|
|
|
+
|
|
|
+```javascript
|
|
|
+console.log(1/0); //Infinity
|
|
|
+console.log(-1/0);//-Infinity
|
|
|
+console.log(1/"asdf");//NaN
|
|
|
+```
|
|
|
+
|
|
|
+Приведение к числу из строки:
|
|
|
+```javascript
|
|
|
+console.log(+"aaa100500"); //NaN
|
|
|
+console.log(+"100500"); //100500
|
|
|
+```
|
|
|
+
|
|
|
+Также **JS** не выбрасывает исключение "Деление на ноль", а просто возвращает бесконечность.
|
|
|
+
|
|
|
+#### Число как объект
|
|
|
+
|
|
|
+С числами можно работать как с объектами:
|
|
|
+
|
|
|
+```javascript
|
|
|
+console.log(5.123456.toFixed(2)); // "5.12"
|
|
|
+```
|
|
|
+
|
|
|
+Тем не менее, многие математические операции вынесены в глобальный объект-коллекцию `Math` (например, Math.round, Math.ceil, Math.random и другие)
|
|
|
+
|
|
|
+### Строки
|
|
|
+
|
|
|
+Большинство строковых операций являются методами объекта-строки:
|
|
|
+
|
|
|
+```javascript
|
|
|
+console.log("12345".length); //5
|
|
|
+console.log("aBcDe".indexOf("D")); // 3
|
|
|
+console.log("123string456".substr(3,6)); // "string"
|
|
|
+```
|
|
|
+
|
|
|
+### Boolean
|
|
|
+Работает как в **Java**
|
|
|
+
|
|
|
+### `undefined`
|
|
|
+
|
|
|
+Аналог `null` в **PHP**. В этом типе есть только одно значение - `undefined`
|
|
|
+
|
|
|
+### `null`
|
|
|
+
|
|
|
+`null` - это "`undefined` для программиста", а не для интерпретатора. Так же используется в **DOM** для пустых/незаданных значений.
|
|
|
+
|
|
|
+### Массивы
|
|
|
+
|
|
|
+Под массивом подразумевается нумерованный целочисленными индексами массив. Массив резиновый и нетипизированный.
|
|
|
+
|
|
|
+```javascript
|
|
|
+var a1 = [1,2,3]
|
|
|
+var a2 = new Array(4,5,6)
|
|
|
+
|
|
|
+alert(a1[1]) //
|
|
|
+a1.push(5)
|
|
|
+alert(a1.indexOf(3)) //
|
|
|
+alert(a2.length) //
|
|
|
+
|
|
|
+//Можно добавить именованное поле в обычный массив как и в любой другой объект 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
|
|
|
+```
|
|
|
+
|
|
|
+- конструкция `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** функциям отведена огромная роль:
|
|
|
+
|
|
|
+Они могут использоваться
|
|
|
+- Как обычные функции в процедурном стиле написания кода
|
|
|
+- Как *объекты первого класса*, т. е. как переменные. Это позволяет их передавать в другие функции, сохранять в переменных и так далее:
|
|
|
+ - Функциональный подход (`filter`, `map`, `reduce`, `sort`)
|
|
|
+ - Функции, сохраненные в ассоциативном массиве становятся методами
|
|
|
+ - Асинхронное программирование - обработчики событий и длительных операций ввода-вывода передаются в виде функций.
|
|
|
+ - Замыкания.
|
|
|
+
|
|
|
+**Java** тоже позволяет делать многое из этого, однако исторически такой подход редко используется в PHP-коде.
|
|
|
+
|
|
|
+### Основные синтаксические различия c **Java**
|
|
|
+
|
|
|
+Основные синтаксические различия касаются параметров функции:
|
|
|
+- В функциях в **JS** нет параметров по умолчанию (в ES6 - есть)
|
|
|
+- Набор параметров при вызове функции может отличаться от набора параметров при декларации, и это не вызывает ошибок
|
|
|
+- Вы всегда можете узнать актуальный набор параметров в форме псевдомассива `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: " + text);
|
|
|
+}
|
|
|
+
|
|
|
+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`, значения параметров) и *лексическом* (переменные из более высоких областей
|
|
|
+видимости в **месте определения функции**). *Динамический контекст* - это контекст **вызова** функции - значение параметров и окружение на момент вызова;
|
|
|
+*лексический контекст* - контекст **определения** функции, её вложенности в другие области видимости, доступ к которым функция имеет и после окончания
|
|
|
+выполнения функций-владельцев этих областей видимости.
|
|
|
+
|
|
|
+### [Let Over Lambda](https://letoverlambda.com/) (LOL)
|
|
|
+```javascript
|
|
|
+{
|
|
|
+ let randomValue = Math.random()
|
|
|
+ var getRandomValue = () => randomValue
|
|
|
+}
|
|
|
+alert(getRandomValue())
|
|
|
+```
|
|
|
+### 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());
|
|
|
+```
|
|
|
+
|