renovations
|
@ -37,7 +37,9 @@ log(a,b,c); // вызовется вторая функция
|
|||
|
||||
Это называется "полиморфизмом функций" или "перегрузкой функций". В JavaScript ничего подобного нет.
|
||||
|
||||
**Может быть только одна функция с именем `log`, которая вызывается с любыми аргументами.** А уже внутри она может посмотреть, с чем вызвана и по-разному отработать.
|
||||
**Может быть только одна функция с именем `log`, которая вызывается с любыми аргументами.**
|
||||
|
||||
А уже внутри она может посмотреть, с чем вызвана и по-разному отработать.
|
||||
|
||||
В примере выше второе объявление `log` просто переопределит первое.
|
||||
[/smart]
|
||||
|
@ -142,9 +144,7 @@ for(var i=0; i<arguments.length; i++) {
|
|||
|
||||
Такие объекты иногда называют *"коллекциями"* или *"псевдомассивами"*.
|
||||
|
||||
## Примеры работы с аргументами
|
||||
|
||||
### Копирование объектов copy(dst, src1, src2...) [#copy]
|
||||
## Пример: копирование свойств copy(dst, src1, src2...) [#copy]
|
||||
|
||||
Иногда встаёт задача -- скопировать в существующий объект свойства из одного или нескольких других.
|
||||
|
||||
|
@ -206,12 +206,14 @@ var userClone = copy({}, user);
|
|||
//+ autorun
|
||||
function copy() {
|
||||
var dst = arguments[0];
|
||||
|
||||
for (var i=1; i<arguments.length; i++) {
|
||||
var arg = arguments[i];
|
||||
for (var key in arg) {
|
||||
dst[key] = arg[key];
|
||||
}
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
```
|
||||
|
@ -229,6 +231,7 @@ function copy(dst) {
|
|||
dst[key] = arg[key];
|
||||
}
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
```
|
||||
|
@ -333,14 +336,17 @@ showWarning(opts); // вызвать с новым текстом, без коп
|
|||
|
||||
## Устаревшее свойство arguments.callee [#arguments-callee]
|
||||
|
||||
В старом стандарте JavaScript объект `arguments` не только хранил список аргументов, но и содержал в свойстве `arguments.callee` ссылку на функцию, которая выполняется в данный момент.
|
||||
|
||||
[warn header="Используйте NFE вместо `arguments.callee`"]
|
||||
Это свойство устарело, при `use strict` оно не работает, но ещё встречается в старом коде, поэтому о нём желательно знать.
|
||||
Это свойство устарело, при `use strict` оно не работает.
|
||||
|
||||
Единственная причина, по которой оно тут -- это то, что его можно встретить в старом коде, поэтому о нём желательно знать.
|
||||
|
||||
Современная спецификация рекомендует использовать ["именованные функциональные выражения (NFE)"](#functions-nfe).
|
||||
|
||||
[/warn]
|
||||
|
||||
В старом стандарте JavaScript объект `arguments` не только хранил список аргументов, но и содержал в свойстве `arguments.callee` ссылку на функцию, которая выполняется в данный момент.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
|
@ -366,7 +372,7 @@ var factorial = function(n) {
|
|||
};
|
||||
```
|
||||
|
||||
В учебнике мы его использовать не будем, оно приведено для общего ознакомления, так как пока ещё много старого кода использует его.
|
||||
В учебнике мы его использовать не будем, оно приведено для общего ознакомления.
|
||||
|
||||
### arguments.callee.caller
|
||||
|
||||
|
@ -375,7 +381,7 @@ var factorial = function(n) {
|
|||
[warn header="Это свойство тоже устарело"]
|
||||
Это свойство было в старом стандарте, при `use strict` оно не работает, как и `arguments.callee`.
|
||||
|
||||
Также ранее существовало похожее свойство `arguments.caller` (без `callee`). Но это уже вообще раритет, оно даже не кросс-браузерное. А вот свойство `arguments.callee.caller` поддерживается везде, если не использован `use strict`, поэтому в старом коде оно встречается.
|
||||
Также ранее существовало более короткое свойство `arguments.caller`. Но это уже раритет, оно даже не кросс-браузерное. А вот свойство `arguments.callee.caller` поддерживается везде, если не использован `use strict`, поэтому в старом коде оно встречается.
|
||||
[/warn]
|
||||
|
||||
Пример работы:
|
||||
|
|
|
@ -29,24 +29,29 @@ alert( Jan02_1970 );
|
|||
|
||||
</dd>
|
||||
<dt>`new Date(datestring)`</dt>
|
||||
<dd>Если единственный аргумент - строка, используется вызов `Date.parse` для ее разбора.</dd>
|
||||
<dd>Если единственный аргумент - строка, используется вызов `Date.parse` (см. далее) для чтения даты из неё.</dd>
|
||||
<dt>`new Date(year, month, date, hours, minutes, seconds, ms)`</dt>
|
||||
<dd>Дату можно создать, используя компоненты в местной временной зоне. Для этого формата обязательны только первые два аргумента. Отсутствующие параметры, начиная с `hours` считаются равными нулю, а `date` -- единице.
|
||||
|
||||
**Заметим, что год `year` должен быть из 4 цифр, а отсчет месяцев `month` начинается с нуля 0.** Например:
|
||||
Заметим:
|
||||
<ul>
|
||||
<li>Год `year` должен быть из 4 цифр.</li>
|
||||
<li>Отсчет месяцев `month` начинается с нуля 0.</li>
|
||||
</ul>
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
new Date(2011, 0, 1) // 1 января 2011, 00:00:00 в местной временной зоне
|
||||
new Date(2011, 0) // то же самое, date по умолчанию равно 1
|
||||
new Date(2011, 0, 1, 0, 0, 0, 0); // то же самое
|
||||
new Date(2011, 0, 1, 0, 0, 0, 0); // // 1 января 2011, 00:00:00
|
||||
new Date(2011, 0, 1); // то же самое, часы/секунды по умолчанию равны 0
|
||||
```
|
||||
|
||||
Дата задана с точностью до миллисекунд:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var d = new Date(2011, 0, 1, 2, 3, 4, 567);
|
||||
alert(d); // 1.01.2011, 02:03:04.567
|
||||
var date = new Date(2011, 0, 1, 2, 3, 4, 567);
|
||||
alert(date); // 1.01.2011, 02:03:04.567
|
||||
```
|
||||
|
||||
</dd>
|
||||
|
@ -67,7 +72,7 @@ alert(d); // 1.01.2011, 02:03:04.567
|
|||
<dd>Получить соответствующие компоненты.</dd>
|
||||
</dl>
|
||||
|
||||
[warn header="Устаревший `getYear()`"]
|
||||
[warn header="Не `getYear()`, а `getFullYear()`"]
|
||||
Некоторые браузеры реализуют нестандартный метод `getYear()`. Где-то он возвращает только две цифры из года, где-то четыре. Так или иначе, этот метод отсутствует в стандарте JavaScript. Не используйте его. Для получения года есть `getFullYear()`.
|
||||
[/warn]
|
||||
|
||||
|
@ -85,17 +90,22 @@ alert(d); // 1.01.2011, 02:03:04.567
|
|||
|
||||
```js
|
||||
//+ run
|
||||
// текущая дата
|
||||
var date = new Date();
|
||||
|
||||
alert( date.getHours() ); // час в вашей зоне для даты date
|
||||
alert( date.getUTCHours() ); // час в зоне GMT+0 для даты date
|
||||
// час в текущей временной зоне
|
||||
alert( date.getHours() );
|
||||
|
||||
// сколько сейчас времени в Лондоне?
|
||||
// час в зоне GMT+0
|
||||
alert( date.getUTCHours() );
|
||||
```
|
||||
|
||||
Кроме описанных выше, существуют два специальных метода без UTC-варианта:
|
||||
|
||||
<dl>
|
||||
<dt>`getTime()`</dt>
|
||||
<dd>Возвращает число миллисекунд, прошедших с 01.01.1970 00:00:00 UTC. Это то же число, которое используется в конструкторе `new Date(milliseconds)`.</dd>
|
||||
<dd>Возвращает число миллисекунд, прошедших с 1 января 1970 года GMT+0, то есть того же вида, который используется в конструкторе `new Date(milliseconds)`.</dd>
|
||||
<dt>`getTimezoneOffset()`</dt>
|
||||
<dd>Возвращает разницу между местным и UTC-временем, в минутах.
|
||||
|
||||
|
@ -296,7 +306,9 @@ alert('Время walkLength: ' +timeLength + 'мс');
|
|||
```
|
||||
|
||||
[smart header="Более точное время с `performance.now()`"]
|
||||
В современных браузерах (кроме IE9-) вызов [performance.now()](https://developer.mozilla.org/en-US/docs/Web/API/performance.now) возвращает количество миллисекунд, прошедшее с начала загрузки страницы, а если точнее -- с момента выгрузки предыдущей страницы из памяти.
|
||||
В современных браузерах (кроме IE9-) вызов [performance.now()](https://developer.mozilla.org/en-US/docs/Web/API/performance.now) возвращает количество миллисекунд, прошедшее с начала загрузки страницы. Причём именно с самого начала, до того, как загрузился HTML-файл, если точнее -- с момента выгрузки предыдущей страницы из памяти.
|
||||
|
||||
Так что это время включает в себя всё, включая начальное обращение к серверу.
|
||||
|
||||
Его можно посмотреть в любом месте страницы, даже в `<head>`, чтобы узнать, сколько времени потребовалось браузеру, чтобы до него добраться, включая загрузку HTML.
|
||||
|
||||
|
@ -348,17 +360,17 @@ console.timeEnd("All Benchmarks");
|
|||
<li>Автоматически выносят инвариант, то есть постоянное в цикле значение типа `arr.length`, за пределы цикла.</li>
|
||||
<li>Стараются понять, значения какого типа хранит данная переменная или массив, какую структуру имеет объект и, исходя из этого, оптимизировать внутренние алгоритмы.</li>
|
||||
<li>Выполняют простейшие операции, например сложение явно заданных чисел и строк, на этапе компиляции.</li>
|
||||
<li>В теории, могут выкинуть код, который ни на что не влияет, например присваивание к неиспользуемой локальной переменной, хотя делают это редко.</li>
|
||||
<li>Могут обнаружить, что некий код, например присваивание к неиспользуемой локальной переменной, ни на что не влияет и вообще исключить его из выполнения, хотя делают это редко.</li>
|
||||
</ol>
|
||||
Они могут влиять на результаты тестов.
|
||||
Эти оптимизации могут влиять на результаты тестов, поэтому измерять скорость базовых операций JavaScript ("проводить миробенчмаркинг") до того, как вы изучите внутренности JavaScript-интерпретаторов и поймёте, что они реально делают на таком коде, не рекомендуется.
|
||||
[/warn]
|
||||
|
||||
|
||||
## Форматирование
|
||||
## Форматирование и вывод дат
|
||||
|
||||
Во всех браузерах, кроме IE10-, поддерживается новый стандарт [Ecma 402](http://www.ecma-international.org/publications/standards/Ecma-402.htm), который добавляет специальные методы для форматирования дат.
|
||||
|
||||
Это делается взыовом `date.toLocaleString(локаль, опции)`, у которого много настроек. Он позволяет указать, какие параметры даты нужно вывести, и ряд настроек вывода, после чего интерпретатор сам сформирует строку.
|
||||
Это делается вызовом `date.toLocaleString(локаль, опции)`, в котором можно задать много настроек. Он позволяет указать, какие параметры даты нужно вывести, и ряд настроек вывода, после чего интерпретатор сам сформирует строку.
|
||||
|
||||
Пример с почти всеми параметрами даты и русским, затем английским (США) форматированием:
|
||||
|
||||
|
@ -389,7 +401,7 @@ alert( date.toLocaleString("en-US", options) ); // Wednesday, December 31, 2014
|
|||
|
||||
<dl>
|
||||
<dt>`toString()`, `toDateString()`, `toTimeString()`</dt>
|
||||
<dd>Возвращают стандартное строчное представление, не указанное в стандарте, а зависящее от браузера. Единственное требование - читаемость человеком. Метод `toString` возвращает дату целиком, `toDateString()` и `toTimeString()` - только дату и время соответственно.
|
||||
<dd>Возвращают стандартное строчное представление, не заданное жёстко в стандарте, а зависящее от браузера. Единственное требование к нему -- читаемость человеком. Метод `toString` возвращает дату целиком, `toDateString()` и `toTimeString()` -- только дату и время соответственно.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -413,7 +425,7 @@ alert( d.toISOString() ); // вывод, похожий на '2011-01-26T13:51:5
|
|||
|
||||
</dd></dl>
|
||||
|
||||
**Если хочется иметь большую гибкость и кросс-браузерность, то также можно воспользоваться специальной библиотекой, например [Moment.JS](http://momentjs.com/) или написать свою функцию.**
|
||||
Если хочется иметь большую гибкость и кросс-браузерность, то также можно воспользоваться специальной библиотекой, например [Moment.JS](http://momentjs.com/) или написать свою функцию форматирования.
|
||||
|
||||
|
||||
|
||||
|
@ -421,31 +433,29 @@ alert( d.toISOString() ); // вывод, похожий на '2011-01-26T13:51:5
|
|||
|
||||
Все современные браузеры, включая IE9+, понимают даты в упрощённом формате ISO 8601 Extended.
|
||||
|
||||
Этот формат выглядит так: `YYYY-MM-DDTHH:mm:ss.sssZ`. Для разделения даты и времени в нем используется символ `'T'`. Часть `'Z'` обозначает (необязательную) временную зону -- она может отсутствовать, тогда зона UTC, либо может быть символ `z` -- тоже UTC, или зона в формате `+-hh:mm`.
|
||||
Этот формат выглядит так: `YYYY-MM-DDTHH:mm:ss.sssZ`, где:
|
||||
|
||||
Также возможны упрощенные варианты, к примеру:
|
||||
<ul>
|
||||
<li>`YYYY-MM-DD` -- дата в формате год-месяц-день.</li>
|
||||
<li>Обычный символ `T` используется как разделитель.</li>
|
||||
<li>`HH:mm:ss.sss` -- время: часы-минуты-секунды-миллисекунды.</li>
|
||||
<li>Часть `'Z'` обозначает временную зону -- в формате `+-hh:mm`, либо символ `Z`, обозначающий UTC. По стандарту её можно не указывать, тогда UTC, но в Safari с этим ошибка, так что лучше указывать всегда.</li>
|
||||
</ul>
|
||||
|
||||
```js
|
||||
YYYY
|
||||
YYYY-MM
|
||||
YYYY-MM-DD
|
||||
```
|
||||
Также возможны укороченные варианты, например `YYYY-MM-DD` или `YYYY-MM` или даже только `YYYY`.
|
||||
|
||||
Метод `Date.parse(str)` разбирает строку `str` в таком формате и возвращает соответствующее ей количество миллисекунд. Если это невозможно, `Date.parse` возвращает `NaN`.
|
||||
|
||||
На момент написания некоторые браузеры (Safari) воспринимали формат без `'Z'` как дату в локальной таймзоне (по стандарту UTC), поэтому пример ниже в них работает некорректно:
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var msNoZone = Date.parse('2012-01-26T13:51:50.417'); // без зоны, значит UTC
|
||||
var msUTC = Date.parse('2012-01-26T13:51:50.417Z'); // зона UTC
|
||||
|
||||
alert(msNoZone); // 1327571510417 (число миллисекунд)
|
||||
|
||||
var msZ = Date.parse('2012-01-26T13:51:50.417z'); // зона z означает UTC
|
||||
alert(msZ == msNoZone); // true, если браузер правильный
|
||||
alert(msUTC); // 1327571510417 (число миллисекунд)
|
||||
```
|
||||
|
||||
С таймзоной `-07:00 GMT` в конце все современные браузеры работают правильно:
|
||||
С таймзоной `-07:00 GMT`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -454,6 +464,7 @@ var ms = Date.parse('2012-01-26T13:51:50.417-07:00');
|
|||
alert(ms); // 1327611110417 (число миллисекунд)
|
||||
```
|
||||
|
||||
|
||||
[smart header="Формат дат для IE8-"]
|
||||
До появления спецификации EcmaScript 5 формат не был стандартизован, и браузеры, включая IE8-, имели свои собственные форматы дат. Частично, эти форматы пересекаются.
|
||||
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
<dd>Строки, такие как `"Мяу"` или пустая строка `""`.</dd>
|
||||
</dl>
|
||||
|
||||
Все остальные значения являются **объектами**, включая функции и массивы.
|
||||
Все остальные значения, включая даты и массивы, являются объектами.
|
||||
|
||||
## Оператор typeof [#type-typeof]
|
||||
|
||||
Оператор `typeof` возвращает тип аргумента. У него есть два синтаксиса:
|
||||
Оператор `typeof` возвращает тип аргумента. У него есть два синтаксиса: со скобками и без:
|
||||
<ol>
|
||||
<li>Синтаксис оператора: `typeof x`.</li>
|
||||
<li>Синтаксис функции: `typeof(x)`.</li>
|
||||
|
@ -91,14 +91,14 @@ alert( typeof new Date ); // 'object'
|
|||
|
||||
Смысл утиной типизации -- в проверке необходимых методов и свойств.
|
||||
|
||||
Например, у нас функция работает с массивами. Мы можем проверить, что объект -- массив, уточнив наличие метода `splice`:
|
||||
Например, у нас функция работает с массивами. Мы можем проверить, что объект -- массив, уточнив наличие метода `splice`, который, как известно, есть у всех массивов:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var something = [1,2,3];
|
||||
var something = [1, 2, 3];
|
||||
|
||||
if (something.splice) {
|
||||
alert('Массив!');
|
||||
alert('Это утка! То есть, массив!');
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -121,16 +121,14 @@ if (x.getTime) {
|
|||
|
||||
## Полиморфизм
|
||||
|
||||
Используем проверку типов для того, чтобы создать полиморфную функцию `sayHi(who)`, которая говорит "Привет" своему аргументу.
|
||||
|
||||
При этом, если передали массив, она должна вызвать себя для каждого подэлемента.
|
||||
Пример полимофрной функции -- `sayHi(who)`, которая будет говорить "Привет" своему аргументу, причём если передан массив -- то "Привет" каждому:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function sayHi(who) {
|
||||
|
||||
if (who.splice) { // проверка на массив (или что-то похожее)
|
||||
for(var i=0; i<who.length; i++) sayHi(who[i]);
|
||||
if (who.forEach) { // проверка на массив (или что-то похожее)
|
||||
who.forEach(sayHi);
|
||||
} else {
|
||||
alert('Привет, ' + who);
|
||||
}
|
||||
|
@ -141,10 +139,14 @@ sayHi("Вася"); // Привет, Вася
|
|||
|
||||
// Вызов с массивом
|
||||
sayHi( ["Саша", "Петя"] ); // Привет, Саша... Петя
|
||||
|
||||
// Вызов с вложенными массивами
|
||||
sayHi( ["Саша", "Петя", ["Маша", "Юля"] ] ); // Привет Саша..Петя..Маша..Юля
|
||||
```
|
||||
|
||||
Обратите внимание, получилась даже поддержка вложенных массивов :). Да здравствует рекурсия!
|
||||
Здесь вместо `splice` проверяется наличие `forEach`. Так надёжнее, поскольку именно его мы собираемся использовать.
|
||||
|
||||
Обратите внимание, получилась даже поддержка вложенных массивов. Да здравствует рекурсия!
|
||||
|
||||
## Итого
|
||||
|
||||
|
@ -158,5 +160,5 @@ sayHi( ["Саша", "Петя", ["Маша", "Юля"] ] ); // Привет Са
|
|||
<li>Для функций он возвращает `function`, по стандарту функция не считается базовым типом, но на практике это удобно и полезно.</li>
|
||||
</ol>
|
||||
|
||||
Там, где нужно различать объекты, обычно используется утиная типизация, то есть мы смотрим, есть ли в объекте нужный метод, желательно -- тот, который мы собираемся исползовать, но это не обязательно.
|
||||
Там, где нужно различать объекты, обычно используется утиная типизация, то есть мы смотрим, есть ли в объекте нужный метод, желательно -- тот, который мы собираемся использовать.
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ alert( unique(strings) ); // кришна, харе, 8-()
|
|||
<li>Для второго элемента -- это обойдётся в `1` операцию доступа к элементам `result`.</li>
|
||||
<li>Для третьего элемента -- это обойдётся в `2` операции доступа к элементам `result`.</li>
|
||||
<li>...Для n-го элемента -- это обойдётся в `n-1` операций доступа к элементам `result`.</li>
|
||||
</ul>
|
||||
</ol>
|
||||
|
||||
Всего <code>0 + 1 + 2 + ... + n-1 = (n-1)*n/2 = n<sup>2</sup>/2 - n/2</code> (как сумма арифметической прогрессии), то есть количество операций растёт примерно как квадрат от `n`.
|
||||
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
|
||||
Метод ["arr.forEach(callback[, thisArg])"](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach) используется для перебора массива.
|
||||
|
||||
Он позволяет перебрать массив при помощи функции `callback`, что зачастую гораздо элегантнее, нежели цикл `for`.
|
||||
Он позволяет для каждого элемента массива вызывает функцию `callback`.
|
||||
|
||||
Функция `callback` вызывается для каждого элемента с тремя параметрами `callback(item, i, arr)`:
|
||||
Этой функции он передаёт три параметра `callback(item, i, arr)`:
|
||||
|
||||
<ul>
|
||||
<li>`item` -- очередной элемент массива.</li>
|
||||
|
@ -24,16 +24,14 @@
|
|||
//+ run
|
||||
var arr = ["Яблоко", "Апельсин", "Груша"];
|
||||
|
||||
function show(item, i, arr) {
|
||||
arr.forEach(function(item, i, arr) {
|
||||
alert(i + ": " + item + " (массив:" + arr + ")");
|
||||
}
|
||||
|
||||
arr.forEach(show);
|
||||
});
|
||||
```
|
||||
|
||||
Второй, необязательный аргумент `forEach` позволяет указать контекст `this` для `callback`. Мы обсудим его в деталях чуть позже, сейчас он нам не важен.
|
||||
|
||||
Метод `forEach` ничего не возвращает, его используют только для перебора.
|
||||
Метод `forEach` ничего не возвращает, его используют только для перебора, как более "элегантный" вариант, чем обычный цикл `for`.
|
||||
|
||||
## filter
|
||||
|
||||
|
@ -47,12 +45,10 @@ arr.forEach(show);
|
|||
//+ run
|
||||
var arr = [1, -1, 2, -2, 3];
|
||||
|
||||
function isPositive(number) {
|
||||
return number > 0;
|
||||
}
|
||||
|
||||
*!*
|
||||
var positiveArr = arr.filter(isPositive);
|
||||
var positiveArr = arr.filter(function(number) {
|
||||
return number > 0;
|
||||
});
|
||||
*/!*
|
||||
|
||||
alert(positiveArr); // 1,2,3
|
||||
|
@ -68,27 +64,26 @@ alert(positiveArr); // 1,2,3
|
|||
|
||||
```js
|
||||
//+ run
|
||||
var arr = [1, 2, 3, 4];
|
||||
|
||||
function square(number) {
|
||||
return number * number;
|
||||
}
|
||||
var pages = ['a.html', 'b.html', 'c.html'];
|
||||
|
||||
*!*
|
||||
var squaredArr = arr.map(square);
|
||||
var urls = pages.map(function(page) {
|
||||
return 'http://site.com/' + page;
|
||||
});
|
||||
*/!*
|
||||
|
||||
alert(squaredArr); // получили массив квадратов чисел: 1, 4, 9, 16
|
||||
// к каждой строке был прибавлен префикс
|
||||
alert(urls); // http://site.com/a.html, http://site.com/b.html...
|
||||
```
|
||||
|
||||
## every/some
|
||||
|
||||
Эти методы используется для проверки массива.
|
||||
|
||||
Метод ["arr.every(callback[, thisArg])"](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every) возвращает `true`, если вызов `callback` вернёт `true` для *каждого* элемента `arr`.
|
||||
|
||||
|
||||
Метод ["arr.some(callback[, thisArg])"](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some) возвращает `true`, если вызов `callback` вернёт `true` для *какого-нибудь* элемента `arr`.
|
||||
<ul>
|
||||
<li>Метод ["arr.every(callback[, thisArg])"](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every) возвращает `true`, если вызов `callback` вернёт `true` для *каждого* элемента `arr`.</li>
|
||||
<li>Метод ["arr.some(callback[, thisArg])"](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some) возвращает `true`, если вызов `callback` вернёт `true` для *какого-нибудь* элемента `arr`.</li>
|
||||
</ul>
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -117,15 +112,17 @@ alert( arr.some(isPositive) ); // true, есть хоть одно положи
|
|||
Аргументы функции `callback(previousValue, currentItem, index, arr)`:
|
||||
|
||||
<ul>
|
||||
<li>`previousValue` -- последний результат вызова функции, он же "промежуточный результат". Значение `previousValue` при первом вызове равно `initialValue` (второй аргумент `reduce`) или, если у `reduce` нет второго аргумента, то оно равно первому элементу массива, а перебор начинается со второго.</li>
|
||||
<li>`previousValue` -- последний результат вызова функции, он же "промежуточный результат".</li>
|
||||
<li>`currentItem` -- текущий элемент массива, элементы перебираются по очереди слева-направо. </li>
|
||||
<li>`index` -- номер текущего элемента.</li>
|
||||
<li>`arr` -- обрабатываемый массив.</li>
|
||||
</ul>
|
||||
|
||||
Разберём работу метода `reduce` на примере.
|
||||
Кроме `callback`, методу можно передать "начальное значение" -- аргумент `initialValue`. Если он есть, то на первом вызове значение `previousValue` будет равно `initialValue`, а если у `reduce` нет второго аргумента, то оно равно первому элементу массива, а перебор начинается со второго.
|
||||
|
||||
Пусть мы хотим вычислить сумму всех элементов массива. Можно сделать это при помощи цикла, но это как раз подходящий повод познакомиться с `reduce`.
|
||||
Проще всего понять работу метода `reduce` на примере.
|
||||
|
||||
Например, в качестве "свёртки" мы хотим получить сумму всех элементов массива.
|
||||
|
||||
Вот решение в одну строку:
|
||||
|
||||
|
@ -133,23 +130,30 @@ alert( arr.some(isPositive) ); // true, есть хоть одно положи
|
|||
//+ run
|
||||
var arr = [1, 2, 3, 4, 5]
|
||||
|
||||
var result = arr.reduce(function(prev, current) { return prev + current }, 0);
|
||||
// для каждого элемента массива запустить функцию,
|
||||
// промежуточный результат передавать первым аргументом далее
|
||||
var result = arr.reduce(function(sum, current) { return sum + current; }, 0);
|
||||
|
||||
alert(result); // 15
|
||||
```
|
||||
|
||||
Разберём, что в нём происходит.
|
||||
|
||||
Здесь начальное значение, с которого начинаются вычисления, равно нулю (второй аргумент `reduce`).
|
||||
При первом запуске `sum` -- исходное значение, с которого начинаются вычисления, равно нулю (второй аргумент `reduce`).
|
||||
|
||||
Сначала анонимная функция вызывается с этим начальным значением и первым элементом массива, результат запоминается и передаётся в следующий вызов, уже со вторым аргументом массива, затем новое значение участвует в вычислениях с третьим аргументом и так далее.
|
||||
|
||||
Таблица вычислений получается такая:
|
||||
Поток вычислений получается такой
|
||||
|
||||
<img src="reduce.svg">
|
||||
|
||||
В виде таблицы где каждая строка -- вызов функции на очередном элементе массива:
|
||||
|
||||
<table class="bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>`prev`</th>
|
||||
<th>`sum`</th>
|
||||
<th>`current`</th>
|
||||
<th>результат</th>
|
||||
</tr>
|
||||
|
@ -188,9 +192,9 @@ alert(result); // 15
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
Функция-аргумент `reduce` могла бы также использовать параметры `i` и `array`, но здесь в них нет нужды.
|
||||
Как видно, результат предыдущего вызова передаётся в первый аргумент следующего.
|
||||
|
||||
**Можно сделать ещё короче!**
|
||||
Кстати, полный набор аргументов функции для `reduce` включает в себя `function(sum, current, i, array)`, то есть номер текущего вызова `i` и весь массив `arr`, но здесь в них нет нужды.
|
||||
|
||||
Посмотрим, что будет, если не указать `initialValue` в вызове `arr.reduce`:
|
||||
|
||||
|
@ -199,7 +203,7 @@ alert(result); // 15
|
|||
var arr = [1, 2, 3, 4, 5]
|
||||
|
||||
// убрали 0 в конце
|
||||
var result = arr.reduce(function(prev, current) { return prev + current });
|
||||
var result = arr.reduce(function(sum, current) { return sum + current });
|
||||
|
||||
alert(result); // 15
|
||||
```
|
||||
|
|
64
1-js/4-data-structures/9-array-iteration/reduce.svg
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="600px" height="105px" viewBox="0 0 600 105" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Slice 1</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="reduce" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<rect id="Rectangle-1" stroke="#979797" sketch:type="MSShapeGroup" x="0" y="65" width="80" height="40"></rect>
|
||||
<text id="1" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="36" y="90">1</tspan>
|
||||
</text>
|
||||
<text id="sum" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="7" y="8">sum</tspan>
|
||||
<tspan x="7" y="24">0</tspan>
|
||||
<tspan x="7" y="40">current</tspan>
|
||||
<tspan x="7" y="56">1</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-2" stroke="#979797" sketch:type="MSShapeGroup" x="79" y="65" width="80" height="40"></rect>
|
||||
<text id="2" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="115" y="90">2</tspan>
|
||||
</text>
|
||||
<text id="sum" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="86" y="8">sum</tspan>
|
||||
<tspan x="86" y="24">0+1</tspan>
|
||||
<tspan x="86" y="40">current</tspan>
|
||||
<tspan x="86" y="56">2</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-3" stroke="#979797" sketch:type="MSShapeGroup" x="158" y="65" width="80" height="40"></rect>
|
||||
<text id="3" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="194" y="90">3</tspan>
|
||||
</text>
|
||||
<text id="sum" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="165" y="8">sum</tspan>
|
||||
<tspan x="165" y="24">0+1+2</tspan>
|
||||
<tspan x="165" y="40">current</tspan>
|
||||
<tspan x="165" y="56">3</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-5" stroke="#979797" sketch:type="MSShapeGroup" x="237" y="65" width="80" height="40"></rect>
|
||||
<text id="4" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="273" y="90">4</tspan>
|
||||
</text>
|
||||
<text id="sum" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="244" y="8">sum</tspan>
|
||||
<tspan x="244" y="24">0+1+2+3</tspan>
|
||||
<tspan x="244" y="40">current</tspan>
|
||||
<tspan x="244" y="56">4</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-4" stroke="#979797" sketch:type="MSShapeGroup" x="316" y="65" width="80" height="40"></rect>
|
||||
<text id="5" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="352" y="90">5</tspan>
|
||||
</text>
|
||||
<text id="sum" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="323" y="8">sum</tspan>
|
||||
<tspan x="323" y="24">0+1+2+3+4</tspan>
|
||||
<tspan x="323" y="40">current</tspan>
|
||||
<tspan x="323" y="56">5</tspan>
|
||||
</text>
|
||||
<path d="M412.5,83 L462.5,83" id="Line" stroke="#979797" stroke-width="2" stroke-linecap="square" fill="#979797" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-decoration-1" d="M462.5,83 C458.72,81.95 455.48,81.05 451.7,80 C451.7,82.1 451.7,83.9 451.7,86 C455.48,84.95 458.72,84.05 462.5,83 C462.5,83 462.5,83 462.5,83 Z" stroke="#979797" stroke-width="2" stroke-linecap="square" fill="#979797"></path>
|
||||
<text id="0+1+2+3+4+5-=-15" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="477" y="87">0+1+2+3+4+5 = 15</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.2 KiB |
|
@ -46,26 +46,27 @@ alert(a); // 5
|
|||
</li>
|
||||
<li>На второй фазе -- собственно, выполнение.
|
||||
|
||||
Присваивание (`=`) значений переменных происходит на второй фазе, когда поток выполнения доходит до соответствующей строчки кода.
|
||||
Присваивание (`=`) значений переменных происходит, когда поток выполнения доходит до соответствующей строчки кода, до этого они `undefined`.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
В начале кода ниже указано содержание глобального объекта на момент окончания инициализации:
|
||||
В коде ниже указано содержание глобального объекта на момент инициализации и далее последовательно по коду:
|
||||
|
||||
```js
|
||||
// По окончании инициализации, до выполнения кода:
|
||||
*!*
|
||||
// На момент инициализации, до выполнения кода:
|
||||
// window = { f: function, a: undefined, g: undefined }
|
||||
*/!*
|
||||
|
||||
var a = 5; // при инициализации даёт: window.a=undefined
|
||||
var a = 5;
|
||||
// window = { f: function, *!*a: 5*/!*, g: undefined }
|
||||
|
||||
function f(arg) { /*...*/ } // при инициализации даёт: window.f = function
|
||||
function f(arg) { /*...*/ }
|
||||
// window = { f: function, a: 5, g: undefined } без изменений, f обработана ранее
|
||||
|
||||
var g = function(arg) { /*...*/ }; // при инициализации даёт: window.g = undefined
|
||||
var g = function(arg) { /*...*/ };
|
||||
// window = { f: function, a: 5, g: *!*function*/!* }
|
||||
```
|
||||
|
||||
Кстати, тот факт, что к началу выполнения кода переменные и функции *уже* содержатся в `window`, можно легко проверить:
|
||||
Кстати, тот факт, что к началу выполнения кода переменные и функции *уже* содержатся в `window`, можно легко проверить, выведя их:
|
||||
|
||||
```js
|
||||
//+ run untrusted refresh
|
||||
|
@ -115,17 +116,7 @@ alert(a); // error, a is not defined
|
|||
|
||||
a = 5;
|
||||
```
|
||||
|
||||
**Вообще, рекомендуется всегда объявлять переменные через `var`.**
|
||||
|
||||
В современном стандарте присваивание без `var` вызовет ошибку:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
'use strict';
|
||||
a = 5; // error, a is not defined
|
||||
```
|
||||
|
||||
Это, конечно, для общего понимания, мы всегда объявляем переменные через `var`.
|
||||
[/smart]
|
||||
|
||||
[smart header="Конструкции `for, if...` не влияют на видимость переменных"]
|
||||
|
|
|
@ -80,10 +80,10 @@ sayHi('Вася');
|
|||
Из функции мы можем обратиться не только к локальной переменной, но и к внешней:
|
||||
|
||||
```js
|
||||
var a = 5;
|
||||
var userName = "Вася";
|
||||
|
||||
function f() {
|
||||
alert(a); // 5
|
||||
function sayHi() {
|
||||
alert(userName); // "Вася"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -91,54 +91,33 @@ function f() {
|
|||
|
||||
Такой порядок поиска возможен благодаря тому, что ссылка на внешний объект переменных хранится в специальном внутреннем свойстве функции, которое называется `[[Scope]]`. Это свойство закрыто от прямого доступа, но знание о нём очень важно для понимания того, как работает JavaScript.
|
||||
|
||||
Рассмотрим, как `[[Scope]]` создаётся и используется:
|
||||
**При создании функция получает скрытое свойство `[[Scope]]`, которое ссылается на лексическое окружение, в котором она была создана.**
|
||||
|
||||
<ol>
|
||||
<li>Всё начинается с момента создания функции. Функция создается не в вакууме, а в некотором лексическом окружении.
|
||||
В примере выше таким окружением является `window`, так что создаётся свойство:
|
||||
```js
|
||||
sayHi.[[Scope]] = window
|
||||
```
|
||||
|
||||
В случае выше функция создается в глобальном лексическом окружении `window`:
|
||||
Это свойство никогда не меняется. Оно всюду следует за функцией, привязывая её, таким образом, к месту своего рождения.
|
||||
|
||||
<img src="1.png">
|
||||
При запуске функции её объект переменных `LexicalEnvironment` получает ссылку на "внешнее лексическое окружение" со значением из `[[Scope]]`.
|
||||
|
||||
**Для того, чтобы функция могла в будущем обратиться к внешним переменным, в момент создания она получает скрытое свойство `[[Scope]]`, которое ссылается на лексическое окружение, в котором она была создана:**
|
||||
Если переменная не найдена в функции -- она будет искаться в снаружи.
|
||||
|
||||
<img src="2.png">
|
||||
|
||||
Эта ссылка появляется одновременно с функцией и умирает вместе с ней. Программист не может как-либо получить или изменить её.
|
||||
</li>
|
||||
<li>Позже, приходит время и функция запускается.
|
||||
|
||||
Интерпретатор вспоминает, что у неё есть свойство `f.[[Scope]]`:
|
||||
|
||||
<img src="3.png">
|
||||
|
||||
...И использует его при создании объекта переменных для функции.
|
||||
|
||||
**Новый объект `LexicalEnvironment` получает ссылку на "внешнее лексическое окружение" со значением из `[[Scope]]`. Эта ссылка используется для поиска переменных, которых нет в текущей функции.**
|
||||
|
||||
<img src="4.png">
|
||||
Например, `alert(a)` сначала ищет в текущем объекте переменных: он пустой. А потом, как показано зеленой стрелкой на рисунке ниже -- по ссылке, во внешнем окружении.
|
||||
|
||||
<img src="5.png">
|
||||
|
||||
|
||||
На уровне кода это выглядит как поиск во внешней области видимости, вне функции:
|
||||
|
||||
<img src="6.png">
|
||||
|
||||
</li>
|
||||
</ol>
|
||||
Именно благодаря этой механике в примере выше `alert(userName)` выводит внешнюю переменную. На уровне кода это выглядит как поиск во внешней области видимости, вне функции.
|
||||
|
||||
Если обобщить:
|
||||
<ul>
|
||||
<li>Каждая функция при создании получает ссылку `[[Scope]]` на объект с переменными, в контексте которого была создана.</li>
|
||||
<li>При запуске функции создаётся новый объект с переменными `LexicalEnvironment`. Он получает ссылку на внешний объект переменных из `[[Scope]]`.</li>
|
||||
<li>При поиске переменных он осуществляется сначала в текущем объекте переменных, а потом -- по этой ссылке. Благодаря этому в функции доступны внешние переменные.</li>
|
||||
<li>При поиске переменных он осуществляется сначала в текущем объекте переменных, а потом -- по этой ссылке.</li>
|
||||
</ul>
|
||||
|
||||
### Значение переменных -- всегда текущее
|
||||
Выглядит настолько просто, что непонятно -- зачем вообще говорить об этом `[[Scope]]`, об объектах переменных. Сказали бы: "Функция читает переменные снаружи" -- и всё. Но знание этих деталей позволит нам легко объяснить и понять более сложные ситуации, с которыми мы столкнёмся далее.
|
||||
|
||||
Значение переменной из внешней области берётся всегда текущее, на момент запуска, а не то, которое было на момент создания функции.
|
||||
## Всегда текущее значение
|
||||
|
||||
Значение переменной из внешней области берётся всегда текущее. Оно может быть уже не то, что было на момент создания функции.
|
||||
|
||||
Например, в коде ниже функция `sayHi` берёт `phrase` из внешней области:
|
||||
|
||||
|
@ -148,25 +127,23 @@ function f() {
|
|||
var phrase = 'Привет';
|
||||
|
||||
function say(name) {
|
||||
alert(name + ", " + phrase);
|
||||
alert(phrase + ', ' + name);
|
||||
}
|
||||
|
||||
*!*
|
||||
say('Вася'); // Вася, Привет (*)
|
||||
say('Вася'); // Привет, Вася (*)
|
||||
*/!*
|
||||
|
||||
phrase = 'Пока';
|
||||
|
||||
*!*
|
||||
say('Вася'); // Вася, Пока (**)
|
||||
say('Вася'); // Пока, Вася (**)
|
||||
*/!*
|
||||
```
|
||||
|
||||
На момент выполнения строки `(*)`, переменная `phrase` имела значение `'Привет'`, а потом изменила его на `'Пока'`. Функция всегда берёт то внешнее значение, которое есть сейчас.
|
||||
|
||||
Это естественно, ведь для доступа к внешним переменным функция использует ссылку на внешний объект с ними -- на внешний объект целиком! А не на каждое его свойство (переменную) по отдельности. Если переменная меняется, то при новом доступе функция всегда получит текущее, последнее её значение.
|
||||
|
||||
На момент первого запуска `(*)`, переменная `phrase` имела значение `'Привет'`, а ко второму `(**)` изменила его на `'Пока'`.
|
||||
|
||||
Это естественно, ведь для доступа к внешним переменным функция хранит ссылку `[[Scope]]` на весь внешний объект с ними, а не на каждое его свойство (переменную) по отдельности.
|
||||
|
||||
|
||||
## Вложенные функции
|
||||
|
@ -177,9 +154,10 @@ say('Вася'); // Вася, Пока (**)
|
|||
|
||||
```js
|
||||
//+ run
|
||||
function sayHi(firstName, lastName) {
|
||||
function sayHiBye(firstName, lastName) {
|
||||
|
||||
alert( "Привет, " + getFullName() );
|
||||
alert( "Пока, " + getFullName() );
|
||||
|
||||
*!*
|
||||
function getFullName() {
|
||||
|
@ -189,57 +167,52 @@ function sayHi(firstName, lastName) {
|
|||
|
||||
}
|
||||
|
||||
sayHi("Вася", "Пупкин"); // Привет, Вася Пупкин
|
||||
sayHiBye("Вася", "Пупкин"); // Привет, Вася Пупкин ; Пока, Вася Пупкин
|
||||
```
|
||||
|
||||
Здесь, для наглядности, для вычислений создана функция `getFullName()`.
|
||||
Здесь, для удобства, создана вспомогательная функция `getFullName()`.
|
||||
|
||||
**Вложенные функции обрабатываются в точности так же, как и глобальные. Единственная разница -- они создаются в объекте переменных внешней функции, а не в `window`.**
|
||||
|
||||
При запуске функции `sayHi("Вася", "Пупкин")`:
|
||||
<ul>
|
||||
<li>Интерпретатор создаст объект для переменных.</li>
|
||||
<li>Заполнит его аргументами текущего вызова и локальными переменными:
|
||||
Вложенные функции получают `[[Scope]]` так же, как и глобальные. В нашем случае:
|
||||
|
||||
```js
|
||||
function sayHi(firstName, lastName) {
|
||||
*!*
|
||||
// LexicalEnvironment = {
|
||||
// firstName: "Вася",
|
||||
// lastName: "Пупкин",
|
||||
// getFullName: function
|
||||
// }
|
||||
*/!*
|
||||
|
||||
alert( "Привет, " + getFullName() ); // (*)
|
||||
|
||||
function getFullName() {
|
||||
return firstName + " " + lastName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sayHi("Вася", "Пупкин"); // Привет, Вася Пупкин
|
||||
getFullName.[[Scope]] = объект переменных текущего запуска sayHiBye
|
||||
```
|
||||
|
||||
</li>
|
||||
<li>Далее, в строке `(*)`, при вызове `getFullName()` -- она получит ссылку на внешний объект переменных, достанет оттуда `firstName` и `lastName`.</li>
|
||||
</ul>
|
||||
Благодаря этому `getFullName()` получает снаружи `firstName` и `lastName`.
|
||||
|
||||
Заметим, что если переменная не найдена во внешнем объекте переменных, то она ищется ещё более внешнем (через `[[Scope]]` внешней функции), то есть, такой пример тоже будет работать:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var phrase = 'Привет';
|
||||
|
||||
function say() {
|
||||
|
||||
function go() {
|
||||
alert(phrase); // найдёт переменную снаружи
|
||||
}
|
||||
|
||||
go();
|
||||
}
|
||||
```
|
||||
|
||||
## Возврат функции
|
||||
|
||||
Рассмотрим более "продвинутый" вариант, при котором внутри одной функции создаётся другая и возвращается в качестве результата.
|
||||
|
||||
Здесь мы будем создавать функцию-счётчик. Это, конечно, учебный пример, дальше будут задачи посложнее, поближе к реальности, ну а при изучении интерфейсов создавать и передавать туда-сюда функцию будет вообще стандартным приёмом разработки.
|
||||
В разработке интерфейсов это совершенно стандартный приём, функция затем может назначаться как обработчик действий посетителя.
|
||||
|
||||
В примере ниже `makeCounter` создает функцию, которая считает свои вызовы:
|
||||
Здесь мы будем создавать функцию-счётчик, которая считает свои вызовы и возвращает их текущее число.
|
||||
|
||||
В примере ниже `makeCounter` создает такую функцию:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function makeCounter() {
|
||||
*!*
|
||||
var currentCount = 1;
|
||||
|
||||
*/!*
|
||||
|
||||
return function() { // (**)
|
||||
return currentCount++;
|
||||
};
|
||||
|
@ -257,11 +230,11 @@ var counter2 = makeCounter();
|
|||
alert( counter2() ); // 1
|
||||
```
|
||||
|
||||
Хранение текущего числа вызовов осуществляется в переменной `currentCount` внешней функции.
|
||||
|
||||
Что здесь, вообще, происходит?
|
||||
Как видно, мы получили два независимых счётчика `counter` и `counter2`, каждый из которых незаметным снаружи образом сохраняет текущее количество вызовов.
|
||||
|
||||
**Первый этап -- вызов `makeCounter()`:**
|
||||
Где? Конечно, во внешней переменной `currentCount`, которая у каждого счётчика своя.
|
||||
|
||||
Если подробнее описать происходящее:
|
||||
|
||||
<ol>
|
||||
<li>В строке `(*)` запускается `makeCounter()`. При этом создаётся `LexicalEnvironment` для переменных текущего вызова. В функции есть одна переменная `var currentCount`, которая станет свойством этого объекта. Она изначально инициализуется в `undefined`, затем, в процессе выполнения, получит значение `1`:
|
||||
|
@ -269,13 +242,13 @@ alert( counter2() ); // 1
|
|||
```js
|
||||
function makeCounter() {
|
||||
*!*
|
||||
// LexicalEnvironment = { currentCount: undefined } -> window
|
||||
// LexicalEnvironment = { currentCount: undefined }
|
||||
*/!*
|
||||
|
||||
var currentCount = 1;
|
||||
|
||||
*!*
|
||||
// LexicalEnvironment = { currentCount: 1 } -> window
|
||||
// LexicalEnvironment = { currentCount: 1 }
|
||||
*/!*
|
||||
|
||||
return function() { // [[Scope]] -> LexicalEnvironment (**)
|
||||
|
@ -291,36 +264,25 @@ var counter = makeCounter(); // (*)
|
|||
<li>Далее вызов `makeCounter()` завершается и функция `(**)` возвращается и сохраняется во внешней переменной `counter` `(*)`.</li>
|
||||
</ol>
|
||||
|
||||
**На этом первый этап можно считать завершённым.**
|
||||
На этом создание "счётчика" завершено.
|
||||
|
||||
В результате вызова `makeCounter` в переменную `counter` была записана функция:
|
||||
Итоговым значением, записанным в переменную `counter`, является функция:
|
||||
|
||||
```js
|
||||
var counter = function() { // [[Scope]] -> {currentCount: 1} -> window
|
||||
function() { // [[Scope]] -> {currentCount: 1}
|
||||
return currentCount++;
|
||||
};
|
||||
```
|
||||
|
||||
**Возвращённая из `makeCounter()` функция `counter` отличается от "просто функции" тем, что она помнит (через `[[Scope]]`) о том, в каком окружении была создана.**
|
||||
Возвращённая из `makeCounter()` функция `counter` помнит (через `[[Scope]]`) о том, в каком окружении была создана.
|
||||
|
||||
Скорее всего, когда-нибудь функция `counter` будет вызвана. Мы не знаем, когда это произойдёт. Может быть, прямо сейчас, но, вообще говоря, совсем не факт. Этот вызов может быть сильно отделён по времени, поэтому назовём происходящее "вторым этапом".
|
||||
Это и используется для хранения текщуего значения счётчика.
|
||||
|
||||
**Второй этап -- вызов `counter`:**
|
||||
Далее, когда-нибудь, функция `counter` будет вызвана. Мы не знаем, когда это произойдёт. Может быть, прямо сейчас, но, вообще говоря, совсем не факт.
|
||||
|
||||
<ol>
|
||||
<li>Эта функция состоит из одной строки: `return currentCount++`, ни переменных ни параметров в ней нет, поэтому её собственный объект переменных, для краткости назовём его `LE` -- будет пуст.
|
||||
Эта функция состоит из одной строки: `return currentCount++`, ни переменных ни параметров в ней нет, поэтому её собственный объект переменных, для краткости назовём его `LE` -- будет пуст.
|
||||
|
||||
Единственное, что у него есть -- так это ссылка на внешний `LexicalEnvironment`, которую он получит из `[[Scope]]`:
|
||||
|
||||
```js
|
||||
var counter = function() {
|
||||
//в процессе запуска LE = {} -> {currentCount: 1} -> window
|
||||
return currentCount++;
|
||||
};
|
||||
```
|
||||
|
||||
</li>
|
||||
<li>Чтобы увеличить и вернуть `currentCount`, интерпретатор ищет в текущем объекте переменных `LE`, но он пуст, затем идёт во внешний объект, там находит, изменяет и возвращает новое значение:
|
||||
Однако, у неё есть свойство `[[Scope]]`, которое указывает на внешнее окружение. Чтобы увеличить и вернуть `currentCount`, интерпретатор ищет в текущем объекте переменных `LE`, не находит, затем идёт во внешний объект, там находит, изменяет и возвращает новое значение:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -332,17 +294,17 @@ function makeCounter() {
|
|||
};
|
||||
}
|
||||
|
||||
var counter = makeCounter(); // [[Scope]] -> {currentCount: 1} -> window
|
||||
var counter = makeCounter(); // [[Scope]] -> {currentCount: 1}
|
||||
|
||||
alert( counter() ); // 1, [[Scope]] -> {currentCount: 1} -> window
|
||||
alert( counter() ); // 2, [[Scope]] -> {currentCount: 2} -> window
|
||||
alert( counter() ); // 3, [[Scope]] -> {currentCount: 3} -> window
|
||||
alert( counter() ); // 1, [[Scope]] -> {currentCount: 1}
|
||||
alert( counter() ); // 2, [[Scope]] -> {currentCount: 2}
|
||||
alert( counter() ); // 3, [[Scope]] -> {currentCount: 3}
|
||||
```
|
||||
|
||||
</li>
|
||||
</ol>
|
||||
**Переменную во внешней области видимости можно не только читать, но и изменять.**
|
||||
|
||||
Можно создать несколько счётчиков. Все они будут взаимно независимы:
|
||||
|
||||
В примере выше было создано несколько счётчиков. Все они взаимно независимы:
|
||||
|
||||
```js
|
||||
var counter = makeCounter();
|
||||
|
@ -356,37 +318,50 @@ alert( counter() ); // 3
|
|||
alert( counter2() ); // 1, *!*счётчики независимы*/!*
|
||||
```
|
||||
|
||||
Они независимы, потому что при каждом запуске `makeCounter` создаётся свой `LexicalEnvironment`, на который имеет ссылку соответствующий счётчик.
|
||||
Они независимы, потому что при каждом запуске `makeCounter` создаётся свой объект переменных `LexicalEnvironment`, со своим свойством `currentCount`, на который новый счётчик получит ссылку `[[Scope]]`.
|
||||
|
||||
|
||||
## Альтернатива -- свойство функции
|
||||
## Свойства функции
|
||||
|
||||
Функция в JavaScript является объектом, поэтому можно присваивать свойства прямо к ней.
|
||||
|
||||
Перепишем пример со счётчиком, используя запись в функцию:
|
||||
Функция в JavaScript является объектом, поэтому можно присваивать свойства прямо к ней, вот так:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function f() {}
|
||||
|
||||
f.test = 5;
|
||||
alert(f.test);
|
||||
```
|
||||
|
||||
Свойства функции не стоит путать с переменными и параметрами. Они совершенно никак не связаны. Переменные доступны только внутри функции, они создаются в процессе её выполнения. Это -- использование функции "как функции".
|
||||
|
||||
А свойство у функции -- доступно отовсюду и всегда. Это -- использование функции "как объекта".
|
||||
|
||||
Если хочется привязать значение к функции, то можно им воспользоваться вместо внешних переменных.
|
||||
|
||||
В качестве демонстрации, перепишем пример со счётчиком:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
*!*
|
||||
function makeCounter() {
|
||||
*!*
|
||||
function counter() {
|
||||
return counter.currentCount++;
|
||||
};
|
||||
counter.currentCount = 1;
|
||||
*/!*
|
||||
|
||||
return counter;
|
||||
}
|
||||
*/!*
|
||||
|
||||
var counter = makeCounter();
|
||||
alert( counter() ); // 1
|
||||
alert( counter() ); // 2
|
||||
alert (counter() ); // 3
|
||||
```
|
||||
|
||||
Как видно, с виду пример работает также. Но внутри всё по-другому.
|
||||
При запуске пример работает также.
|
||||
|
||||
**Свойство функции, в отличие от переменной из замыкания -- общедоступно. К нему имеет доступ любой, у кого есть объект функции.**
|
||||
Принципиальная разница -- во внутренней механике и в том, что свойство функции, в отличие от переменной из замыкания -- общедоступно, к нему имеет доступ любой, у кого есть объект функции.
|
||||
|
||||
Например, можно взять и поменять счётчик из внешнего кода:
|
||||
|
||||
|
@ -404,7 +379,7 @@ alert( counter() ); // 5
|
|||
[smart header="Статические переменные"]
|
||||
Иногда свойства, привязанные к функции, называют "статическими переменными".
|
||||
|
||||
В некоторых языках программирования можно объявлять переменную, которая сохраняет значение между вызовами функции. В JavaScript ближайший аналог -- это свойство функции.
|
||||
В некоторых языках программирования можно объявлять переменную, которая сохраняет значение между вызовами функции. В JavaScript ближайший аналог -- такое вот свойство функции.
|
||||
[/smart]
|
||||
|
||||
|
||||
|
@ -418,18 +393,18 @@ alert( counter() ); // 5
|
|||
|
||||
**Обычно, говоря "замыкание функции", подразумевают не саму эту функцию, а именно внешние переменные.**
|
||||
|
||||
**Иногда говорят "переменная берётся из замыкания". Это означает -- из внешнего объекта переменных.**
|
||||
Иногда говорят "переменная берётся из замыкания". Это означает -- из внешнего объекта переменных.
|
||||
|
||||
|
||||
[smart header="Что это такое -- \"понимать замыкания?\""]
|
||||
Иногда говорят "Вася крут, Вася понимает замыкания!". Что это такое -- "понимать замыкания", какой смысл обычно вкладывают в эти слова?
|
||||
Иногда говорят "Вася молодец, понимает замыкания!". Что это такое -- "понимать замыкания", какой смысл обычно вкладывают в эти слова?
|
||||
|
||||
"Понимать замыкания" в JavaScript означает понимать следующие вещи:
|
||||
<ol>
|
||||
<li>Все переменные и параметры функций являются свойствами объекта переменных `LexicalEnvironment`. Каждый запуск функции создает новый такой объект. На верхнем уровне роль `LexicalEnvironment` играет "глобальный объект", в браузере это `window`.</li>
|
||||
<li>При создании функция получает системное свойство `[[Scope]]`, которое ссылается на `LexicalEnvironment`, в котором она была создана (кроме `new Function`).</li>
|
||||
<li>Свойство `[[Scope]]` создаётся вместе с функцией и далее не меняется. Когда бы ни была вызвана функция, куда бы её ни передали в коде -- она будет искать переменные сначала у себя, а затем во внешних `LexicalEnvironment` с места своего создания.</li>
|
||||
<li>Все переменные и параметры функций являются свойствами объекта переменных `LexicalEnvironment`. Каждый запуск функции создает новый такой объект. На верхнем уровне им является "глобальный объект", в браузере -- `window`.</li>
|
||||
<li>При создании функция получает системное свойство `[[Scope]]`, которое ссылается на `LexicalEnvironment`, в котором она была создана.</li>
|
||||
<li>При вызове функции, куда бы её ни передали в коде -- она будет искать переменные сначала у себя, а затем во внешних `LexicalEnvironment` с места своего "рождения".</li>
|
||||
</ol>
|
||||
|
||||
В следующих главах мы углубим и расширим это понимание дополнительными примерами, а также рассмотрим, что происходит с памятью.
|
||||
В следующих главах мы углубим это понимание дополнительными примерами, а также рассмотрим, что происходит с памятью.
|
||||
[/smart]
|
||||
|
|
|
@ -5,35 +5,16 @@
|
|||
|
||||
Есть одно исключение из общего правила присвоения `[[Scope]]`, которое мы рассматривали в предыдущей главе.
|
||||
|
||||
**При создании функции с использованием `new Function`, её свойство `[[Scope]]` ссылается не на текущий `LexicalEnvironment`, а на `window`.**
|
||||
При создании функции с использованием `new Function`, её свойство `[[Scope]]` ссылается не на текущий `LexicalEnvironment`, а на `window`.
|
||||
|
||||
## Пример
|
||||
|
||||
Следующий пример демонстрирует как функция, созданная `new Function`, игнорирует внешнюю переменную `a` и выводит глобальную вместо нее.
|
||||
|
||||
Сначала обычное поведение:
|
||||
Следующий пример демонстрирует как функция, созданная `new Function`, игнорирует внешнюю переменную `a` и выводит глобальную вместо неё:
|
||||
|
||||
```js
|
||||
//+ run untrusted refresh
|
||||
var a = 1;
|
||||
function getFunc() {
|
||||
var a = 2;
|
||||
|
||||
*!*
|
||||
var func = function() { alert(a); };
|
||||
*/!*
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
getFunc()(); // *!*2*/!*, из LexicalEnvironment функции getFunc
|
||||
```
|
||||
|
||||
А теперь -- для функции, созданной через `new Function`:
|
||||
|
||||
```js
|
||||
//+ run untrusted refresh
|
||||
var a = 1;
|
||||
function getFunc() {
|
||||
var a = 2;
|
||||
|
||||
|
@ -47,19 +28,41 @@ function getFunc() {
|
|||
getFunc()(); // *!*1*/!*, из window
|
||||
```
|
||||
|
||||
Сравним с обычным поведением:
|
||||
|
||||
```js
|
||||
//+ run untrusted refresh
|
||||
var a = 1;
|
||||
|
||||
function getFunc() {
|
||||
var a = 2;
|
||||
|
||||
*!*
|
||||
var func = function() { alert(a); };
|
||||
*/!*
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
getFunc()(); // *!*2*/!*, из LexicalEnvironment функции getFunc
|
||||
```
|
||||
|
||||
|
||||
## Почему так сделано?
|
||||
|
||||
[warn header="Продвинутые знания"]
|
||||
Содержимое этой секции содержит информацию теоретического характера, которая прямо сейчас не обязательна для дальнейшего изучения JavaScript.
|
||||
Содержимое этой секции содержит продвинутую информацию теоретического характера, которая прямо сейчас не обязательна для дальнейшего изучения JavaScript.
|
||||
[/warn]
|
||||
|
||||
**Эта особенность `new Function`, хоть и выглядит странно, на самом деле весьма полезна.**
|
||||
Эта особенность `new Function`, хоть и выглядит странно, на самом деле весьма полезна.
|
||||
|
||||
Представьте себе, что нам действительно нужно создать функцию из строки кода. Наверняка код этой функции неизвестен на момент написания скрипта (иначе зачем `new Function`), но станет известен позже, например получен с сервера или из других источников данных.
|
||||
Представьте себе, что нам действительно нужно создать функцию из строки кода. Текст кода этой функции неизвестен на момент написания скрипта (иначе зачем `new Function`), но станет известен позже, например получен с сервера или из других источников данных.
|
||||
|
||||
При выполнении кода на боевом сервере он наверняка сжат минификатором -- специальной программой, которая уменьшает размер кода, убирая из него лишние комментарии, пробелы, что очень важно -- переименовывает локальные переменные на более короткие.
|
||||
Предположим, что этому коду надо будет взаимодействовать с внешними переменными основного скрипта.
|
||||
|
||||
То есть, обычно если внутри функции есть `var userName`, то минификатор заменит её на `var u` (или другую букву, чтобы не было конфликта), предполагая, что так как переменная видна только внутри функции, то этого всё равно никто не заметит, а код станет короче. И обычно проблем нет.
|
||||
Но проблема в том, что JavaScript при выкладывании на "боевой сервер" предварительно сжимается минификатором -- специальной программой, которая уменьшает размер кода, убирая из него лишние комментарии, пробелы, что очень важно -- переименовывает локальные переменные на более короткие.
|
||||
|
||||
То есть, если внутри функции есть `var userName`, то минификатор заменит её на `var a` (или другую букву, чтобы не было конфликта), предполагая, что так как переменная видна только внутри функции, то этого всё равно никто не заметит, а код станет короче. И обычно проблем нет.
|
||||
|
||||
...Но если бы `new Function` могла обращаться к внешним переменным, то при попытке доступа к `userName` в сжатом коде была бы ошибка, так как минификатор переименовал её.
|
||||
|
||||
|
@ -67,7 +70,7 @@ getFunc()(); // *!*1*/!*, из window
|
|||
|
||||
Описанная особенность `new Function` просто-таки спасает нас от ошибок.
|
||||
|
||||
Если внутри функции, создаваемой через `new Function`, всё же нужно использовать локальные переменные -- нужно всего лишь предусмотреть соответствующие параметры и передавать их явным образом, например так:
|
||||
Ну а если внутри функции, создаваемой через `new Function`, всё же нужно использовать какие-то данные -- без проблем, нужно всего лишь предусмотреть соответствующие параметры и передавать их явным образом, например так:
|
||||
|
||||
```js
|
||||
//+ run untrusted refresh
|
||||
|
|
|
@ -1,304 +0,0 @@
|
|||
# Модули через замыкания
|
||||
|
||||
Приём программирования "модуль" имеет громадное количество вариаций.
|
||||
|
||||
Его цель -- скрыть внутренние детали реализации скрипта. В том числе: временные переменные, константы, вспомогательные мини-функции и т.п.
|
||||
|
||||
## Зачем нужен модуль?
|
||||
|
||||
Допустим, мы хотим разработать скрипт, который делает что-то полезное.
|
||||
|
||||
В браузере скрипты могут делать много чего -- если бы мы умели работать со страницей, то могли бы сделать так, чтобы все блоки кода красиво расцвечивались, так сделано на этом сайте.
|
||||
|
||||
Но, так как пока мы со страницей работать не умеем (скоро научимся), то пусть скрипт просто выводит сообщение:
|
||||
|
||||
Файл `highlight.js`
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// глобальная переменная нашего скрипта
|
||||
var message = "Привет";
|
||||
|
||||
// функция для вывода этой переменной
|
||||
function showMessage() {
|
||||
alert(message);
|
||||
}
|
||||
|
||||
// выводим сообщение
|
||||
showMessage();
|
||||
```
|
||||
|
||||
**У этого скрипта есть свои внутренние переменные и функции.**
|
||||
|
||||
В данном случае это `message` и `showMessage`.
|
||||
|
||||
**Если подключить подобный скрипт к странице "как есть", то возможен конфликт с переменными, которые она использует.**
|
||||
|
||||
То есть, при подключении к такой странице он её "сломает":
|
||||
|
||||
```html
|
||||
<script>
|
||||
var message = "Пожалуйста, нажмите на кнопку";
|
||||
</script>
|
||||
<script src="highlight.js"></script>
|
||||
|
||||
<button>Кнопка</button>
|
||||
<script>
|
||||
alert(message);
|
||||
</script>
|
||||
```
|
||||
|
||||
Будет выведено два раза слово "Привет".
|
||||
|
||||
[edit src="highlight-conflict"/]
|
||||
|
||||
Если же убрать скрипт `highlight.js`, то страница будет выводить правильное сообщение.
|
||||
|
||||
**Проблема возникла потому, что переменная `message` из скрипта `highlight.js` перезаписала объявленную на странице.**
|
||||
|
||||
## Приём проектирования "Модуль"
|
||||
|
||||
Чтобы проблемы не было, нам всего-то нужно, чтобы у скрипта была *своя собственная область видимости*, чтобы его переменные не попали на страницу.
|
||||
|
||||
Для этого мы завернём всё его содержимое в функцию, которую тут же запустим.
|
||||
|
||||
Файл `highlight.js`, оформленный как модуль:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
(function() {
|
||||
|
||||
// глобальная переменная нашего скрипта
|
||||
var message = "Привет";
|
||||
|
||||
// функция для вывода этой переменной
|
||||
function showMessage() {
|
||||
alert(message);
|
||||
}
|
||||
|
||||
// выводим сообщение
|
||||
showMessage();
|
||||
|
||||
})();
|
||||
```
|
||||
|
||||
Этот скрипт при подключении к той же странице будет работать корректно.
|
||||
|
||||
Будет выводиться "Привет", а затем "Пожалуйста, нажмите на кнопку".
|
||||
|
||||
[edit src="highlight-module"/]
|
||||
|
||||
### Зачем скобки вокруг функции?
|
||||
|
||||
В примере выше объявление модуля выглядит так:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
(function() {
|
||||
|
||||
alert("объявляем локальные переменные, функции, работаем");
|
||||
// ...
|
||||
|
||||
}());
|
||||
```
|
||||
|
||||
**В начале и в конце стоят скобки, так как иначе была бы ошибка.**
|
||||
|
||||
Вот неверный вариант:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function() {
|
||||
// будет ошибка
|
||||
}();
|
||||
```
|
||||
|
||||
Ошибка при его запуске произойдет потому, что браузер, видя ключевое слово `function` в основном потоке кода, попытается прочитать `Function Declaration`, а здесь имени нет.
|
||||
|
||||
Впрочем, даже если имя поставить, то работать тоже не будет:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function work() {
|
||||
// ...
|
||||
}(); // syntax error
|
||||
```
|
||||
|
||||
**Дело в том, что "на месте" разрешено вызывать *только* `Function Expression`.**
|
||||
|
||||
Общее правило таково:
|
||||
|
||||
<ul>
|
||||
<li>**Если браузер видит `function` в основном потоке кода -- он считает, что это `Function Declaration`.**</li>
|
||||
<li>**Если же `function` идёт в составе более сложного выражения, то он считает, что это `Function Expression`.**</li>
|
||||
</ul>
|
||||
|
||||
Для этого и нужны скобки -- показать, что у нас `Function Expression`, который по правилам JavaScript можно вызвать "на месте".
|
||||
|
||||
Можно показать это другим способом, например поставив перед функцией оператор:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
+function() {
|
||||
alert('Вызов на месте');
|
||||
}();
|
||||
|
||||
!function() {
|
||||
alert('Так тоже будет работать');
|
||||
}();
|
||||
```
|
||||
|
||||
## Библиотека
|
||||
|
||||
Приём "модуль" используется почти во всех современных библиотеках.
|
||||
|
||||
Ведь что такое библиотека? Это полезные функции, ради которых её подключают, плюс временные переменные и вспомогательные функции, которые библиотека использует внутри себя.
|
||||
|
||||
Посмотрим, к примеру, на библиотеку [Lodash](http://lodash.com/), хотя могли бы и [jQuery](http://jquery.com/), там почти то же самое.
|
||||
|
||||
Если её подключить, то появится функция `lodash` (она же `_`), в которую можно обернуть любой объект, так что `lodash(obj)` -- это обёртка, добавляющая к объекту функциональность.
|
||||
|
||||
Кроме того, `lodash` имеет ряд полезных свойств-функций, например [lodash.defaults(object, source)](http://lodash.com/docs#defaults) для удобного добавления в объект `object` значений свойств "по умолчанию", описанных в `source`.
|
||||
|
||||
Выдержка из файла [lodash.js](https://github.com/lodash/lodash/blob/master/dist/lodash.js) для демонстрации того, как организована библиотека:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
;(function() {
|
||||
|
||||
*!*
|
||||
// порядок не важен, но сначала объявим то, что нужно только внутри библиотеки
|
||||
// version, objectTypes, assignDefaults
|
||||
*/!*
|
||||
var version = '2.4.1';
|
||||
|
||||
var objectTypes = {
|
||||
'function': true,
|
||||
'object': true
|
||||
};
|
||||
|
||||
function assignDefaults(objectValue, sourceValue) {
|
||||
return typeof objectValue == 'undefined' ? sourceValue : objectValue;
|
||||
}
|
||||
|
||||
*!*
|
||||
// а это функция, которая станет lodash.defaults
|
||||
*/!*
|
||||
function defaults(object) {
|
||||
if (!object || arguments.length < 2) {
|
||||
return object;
|
||||
}
|
||||
var args = slice(arguments);
|
||||
args.push(assignDefaults);
|
||||
return assign.apply(null, args);
|
||||
}
|
||||
|
||||
*!*
|
||||
// lodash - основная функция для библиотеки, единственное, что пойдёт наружу
|
||||
*/!*
|
||||
function lodash(value) {
|
||||
// ...
|
||||
}
|
||||
|
||||
*!*
|
||||
// присвоим ей defaults и другие функции, которые нужно вынести из модуля
|
||||
*/!*
|
||||
lodash.defaults = defaults;
|
||||
// lodash... = ...
|
||||
|
||||
*!*
|
||||
// root - это window в браузере
|
||||
// в других окружениях, где window нет, root = this
|
||||
*/!*
|
||||
var root = (objectTypes[typeof window] && window) || this;
|
||||
|
||||
root.lodash = lodash; // в браузере будет window.lodash = lodash
|
||||
|
||||
}.call(this)); // this = window в браузере, в Node.JS - по-другому
|
||||
```
|
||||
|
||||
**Внутри внешней функции:**
|
||||
<ol>
|
||||
<li>**Происходит что угодно, объявляются свои локальные переменные, функции.**</li>
|
||||
<li>**В `window` выносится то, что нужно снаружи.**</li>
|
||||
</ol>
|
||||
|
||||
Технически, мы могли бы вынести в `window` не только `lodash`, но и вообще все объекты и функции. На практике, обычно модуль -- это один объект, глобальную область во избежание конфликтов хранят максимально чистой.
|
||||
|
||||
[smart header="Зачем точка с запятой в начале?"]
|
||||
Если получится, что несколько JS-файлы объединены в один (и, скорее всего, сжаты минификатором, но это не важно), то без точки с запятой будет ошибка:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// a.js, в конце забыта точка с запятой!
|
||||
*!*
|
||||
var a = 5
|
||||
*/!*
|
||||
|
||||
// lib.js, библиотека
|
||||
(function() {
|
||||
// ...
|
||||
})();
|
||||
```
|
||||
|
||||
Ошибка при запуске будет потому, что JavaScript интерпретирует код как `var a = 5(function ...)`, то есть пытается вызвать число `5` как функцию.
|
||||
|
||||
Таковы правила языка, и поэтому рекомендуется явно ставить точку с запятой. В данном случае автор Lodash ставит `;` перед функцией, чтобы предупредить эту ошибку.
|
||||
[/smart]
|
||||
|
||||
Использование:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<p>Подключим библиотеку</p>
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.js"></script>
|
||||
|
||||
<p>Используем <code>_.defaults()</code>.</p>
|
||||
<script>
|
||||
var user = { name: 'Вася' };
|
||||
|
||||
*!*
|
||||
_.defaults(user, { name: 'Не указано', employer: 'Не указан' });
|
||||
*/!*
|
||||
|
||||
alert(user.name); // Вася
|
||||
alert(user.employer); // Не указан
|
||||
</script>
|
||||
```
|
||||
|
||||
## Экспортирование через return
|
||||
|
||||
Можно оформить модуль и чуть по-другому, например передать значение через `return`:
|
||||
|
||||
```js
|
||||
var lodash = (function() {
|
||||
|
||||
var version;
|
||||
function assignDefaults() { ... }
|
||||
|
||||
return {
|
||||
defaults: function() { }
|
||||
}
|
||||
|
||||
})();
|
||||
```
|
||||
|
||||
Здесь, кстати, скобки вокруг внешней `function() { ... }` не обязательны, ведь функция и так объявлена внутри выражения присваивания, а значит -- является Function Expression.
|
||||
|
||||
Тем не менее, лучше их ставить, для улучшения читаемости кода, чтобы было сразу видно, что это не простое присвоение функции.
|
||||
|
||||
## Итого
|
||||
|
||||
Модуль при помощи замыканий -- это оборачивание пакета функционала в единую внешнюю функцию, которая тут же выполняется.
|
||||
|
||||
**Все функции модуля будут иметь доступ к другим переменным и внутренним функциям этого же модуля через замыкание.**
|
||||
|
||||
Например, `defaults` из примера выше имеет доступ к `assignDefaults`.
|
||||
|
||||
**Но снаружи программист, использующий модуль, может обращаться напрямую только к тем, которые экспортированы.**
|
||||
|
||||
Благодаря этому будут скрыты внутренние аспекты реализации, которые нужны только разработчику модуля.
|
||||
|
||||
Можно придумать и много других вариаций такого подхода. В конце концов, "модуль" -- это всего лишь функция-обёртка для скрытия переменных.
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Использование замыканий
|
||||
# Локальные переменные для объекта
|
||||
|
||||
Замыкания можно использовать сотнями способов. Иногда люди сами не замечают, что использовали замыкания -- настолько это просто и естественно.
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
|||
|
||||
[cut]
|
||||
|
||||
## Локальные переменные для объекта
|
||||
## Счётчик-объект
|
||||
|
||||
Ранее мы сделали счётчик.
|
||||
|
316
1-js/5-functions-closures/5-closures-module/article.md
Normal file
|
@ -0,0 +1,316 @@
|
|||
# Модули через замыкания
|
||||
|
||||
Приём программирования "модуль" имеет громадное количество вариаций. Он похож на счётчик, который мы рассматривали ранее, но переносит использованный приём его на уровень выше.
|
||||
|
||||
Его цель -- скрыть внутренние детали реализации скрипта. В том числе: временные переменные, константы, вспомогательные мини-функции и т.п.
|
||||
|
||||
## Зачем нужен модуль?
|
||||
|
||||
Допустим, мы хотим разработать скрипт, который делает что-то полезное на странице.
|
||||
|
||||
Умея работать со страницей, мы могли бы сделать много чего, но так как пока этого не было (скоро научимся), то пусть скрипт просто выводит сообщение:
|
||||
|
||||
Файл `hello.js`
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// глобальная переменная нашего скрипта
|
||||
var message = "Привет";
|
||||
|
||||
// функция для вывода этой переменной
|
||||
function showMessage() {
|
||||
alert(message);
|
||||
}
|
||||
|
||||
// выводим сообщение
|
||||
showMessage();
|
||||
```
|
||||
|
||||
У этого скрипта есть свои внутренние переменные и функции.
|
||||
|
||||
В данном случае это `message` и `showMessage`.
|
||||
|
||||
Предположим, что мы хотели бы распространять этот скрипт в виде библиотеки. Каждый, кто хочет, чтобы посетителям выдавалось "Привет" -- может просто подключить этот скрипт. Достаточно скачать и подключить, например, как внешний файл `hello.js` -- и готово.
|
||||
|
||||
**Если подключить подобный скрипт к странице "как есть", то возможен конфликт с переменными, которые она использует.**
|
||||
|
||||
То есть, при подключении к такой странице он её "сломает":
|
||||
|
||||
```html
|
||||
<script>
|
||||
var message = "Пожалуйста, нажмите на кнопку";
|
||||
</script>
|
||||
<script src="hello.js"></script>
|
||||
|
||||
<button>Кнопка</button>
|
||||
<script>
|
||||
// ожидается сообщение из переменной выше...
|
||||
alert(message); // но на самом деле будет введено "Привет"
|
||||
</script>
|
||||
```
|
||||
|
||||
[edit src="hello-conflict"/]
|
||||
|
||||
Автор страницы ожидает, что библиотека `"hello.js"` просто отработает, без побочных эффектов. А она вместе с этим переопределила `message` в `"Привет"`.
|
||||
|
||||
Если же убрать скрипт `hello.js`, то страница будет выводить правильное сообщение.
|
||||
|
||||
Зная внутреннее устройство `hello.js` нам, конечно, понятно, что проблема возникла потому, что переменная `message` из скрипта `hello.js` перезаписала объявленную на странице.
|
||||
|
||||
## Приём проектирования "Модуль"
|
||||
|
||||
Чтобы проблемы не было, всего-то нужно, чтобы у скрипта была *своя собственная область видимости*, чтобы его переменные не попали на страницу.
|
||||
|
||||
Для этого мы завернём всё его содержимое в функцию, которую тут же запустим.
|
||||
|
||||
Файл `hello.js`, оформленный как модуль:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
(function() {
|
||||
|
||||
// глобальная переменная нашего скрипта
|
||||
var message = "Привет";
|
||||
|
||||
// функция для вывода этой переменной
|
||||
function showMessage() {
|
||||
alert(message);
|
||||
}
|
||||
|
||||
// выводим сообщение
|
||||
showMessage();
|
||||
|
||||
})();
|
||||
```
|
||||
|
||||
[edit src="hello-module"/]
|
||||
|
||||
Этот скрипт при подключении к той же странице будет работать корректно.
|
||||
|
||||
Будет выводиться "Привет", а затем "Пожалуйста, нажмите на кнопку".
|
||||
|
||||
|
||||
### Зачем скобки вокруг функции?
|
||||
|
||||
В примере выше объявление модуля выглядит так:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
(function() {
|
||||
|
||||
alert("объявляем локальные переменные, функции, работаем");
|
||||
// ...
|
||||
|
||||
}());
|
||||
```
|
||||
|
||||
В начале и в конце стоят скобки, так как иначе была бы ошибка.
|
||||
|
||||
Вот, для сравнения, неверный вариант:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function() {
|
||||
// будет ошибка
|
||||
}();
|
||||
```
|
||||
|
||||
Ошибка при его запуске произойдет потому, что браузер, видя ключевое слово `function` в основном потоке кода, попытается прочитать `Function Declaration`, а здесь имени нет.
|
||||
|
||||
Впрочем, даже если имя поставить, то работать тоже не будет:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function work() {
|
||||
// ...
|
||||
}(); // syntax error
|
||||
```
|
||||
|
||||
**Дело в том, что "на месте" разрешено вызывать *только* `Function Expression`.**
|
||||
|
||||
Общее правило таково:
|
||||
|
||||
<ul>
|
||||
<li>Если браузер видит `function` в основном потоке кода -- он считает, что это `Function Declaration`.</li>
|
||||
<li>Если же `function` идёт в составе более сложного выражения, то он считает, что это `Function Expression`.</li>
|
||||
</ul>
|
||||
|
||||
Для этого и нужны скобки -- показать, что у нас `Function Expression`, который по правилам JavaScript можно вызвать "на месте".
|
||||
|
||||
Можно показать это другим способом, например поставив перед функцией оператор:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
+function() {
|
||||
alert('Вызов на месте');
|
||||
}();
|
||||
|
||||
!function() {
|
||||
alert('Так тоже будет работать');
|
||||
}();
|
||||
```
|
||||
|
||||
## Экспорт значения
|
||||
|
||||
Приём "модуль" используется почти во всех современных библиотеках.
|
||||
|
||||
Ведь что такое библиотека? Это полезные функции, ради которых её подключают, плюс временные переменные и вспомогательные функции, которые библиотека использует внутри себя.
|
||||
|
||||
Посмотрим, к примеру, на библиотеку [Lodash](http://lodash.com/), хотя могли бы и [jQuery](http://jquery.com/), там почти то же самое.
|
||||
|
||||
Если её подключить, то появится специальная переменная `lodash` (короткое имя `_`), которую можно использовать как функцию, и кроме того в неё записаны различные полезных свойства, например:
|
||||
|
||||
<ul>
|
||||
<li>`_.defaults(src, dst1, dst2...)` -- копирует в объект `src` те свойства из объектов `dst1`, `dst2` и других, которых там нет.</li>
|
||||
<li>`_.cloneDeep(obj)` -- делает глубокое копирование объекта `obj`, создавая полностью независимый клон.</li>
|
||||
<li>`_.size(obj)` -- возвращает количество свойств в объекте, полиморфная функция: можно передать массив или даже 1 значение.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
Есть и много других функций, подробнее описанных в [документации](https://lodash.com/docs).
|
||||
|
||||
Пример использования:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<p>Подключим библиотеку</p>
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.js"></script>
|
||||
|
||||
<p>Функция <code>_.defaults()</code> добавляет отсутствующие свойства.</p>
|
||||
<script>
|
||||
var user = { name: 'Вася' };
|
||||
|
||||
*!*
|
||||
_.defaults(user, { name: 'Не указано', employer: 'Не указан' });
|
||||
*/!*
|
||||
|
||||
alert(user.name); // Вася
|
||||
alert(user.employer); // Не указан
|
||||
alert(_.size(user)); // 2
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
Здесь нам не важно, какие, нас интересует именно как описана эта библиотека, как в ней применяется приём "модуль".
|
||||
|
||||
Вот выдержка из исходного файла:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
;(function() {
|
||||
|
||||
*!*
|
||||
// lodash - основная функция для библиотеки
|
||||
*/!*
|
||||
function lodash(value) {
|
||||
// ...
|
||||
}
|
||||
|
||||
*!*
|
||||
// вспомогательная переменная
|
||||
*/!*
|
||||
var version = '2.4.1';
|
||||
// ... другие вспомогательные переменные и функции
|
||||
|
||||
*!*
|
||||
// код функции size, пока что доступен только внутри
|
||||
*/!*
|
||||
function size(collection) {
|
||||
var length = collection ? collection.length : 0;
|
||||
return typeof length == 'number' ? length : Object.keys(collection).length;
|
||||
}
|
||||
|
||||
*!*
|
||||
// присвоим в lodash size и другие функции, которые нужно вынести из модуля
|
||||
*/!*
|
||||
lodash.size = size
|
||||
// lodash.defaults = ...
|
||||
// lodash.cloneDeep = ...
|
||||
|
||||
*!*
|
||||
// "экспортировать" lodash наружу из модуля
|
||||
*/!*
|
||||
window._ = lodash; // в оригинальном коде здесь сложнее, но смысл тот же
|
||||
|
||||
}());
|
||||
```
|
||||
|
||||
Внутри внешней функции:
|
||||
<ol>
|
||||
<li>Происходит что угодно, объявляются свои локальные переменные, функции.</li>
|
||||
<li>В `window` выносится то, что нужно снаружи.</li>
|
||||
</ol>
|
||||
|
||||
Технически, мы могли бы вынести в `window` не только `lodash`, но и вообще все объекты и функции. На практике, как раз наоборот, всё прячут внутри модуля, глобальную область во избежание конфликтов хранят максимально чистой.
|
||||
|
||||
[smart header="Зачем точка с запятой в начале?"]
|
||||
В начале кода выше находится точка с запятой `;` -- это не опечатка, а особая "защита от дураков".
|
||||
|
||||
Если получится, что несколько JS-файлы объединены в один (и, скорее всего, сжаты минификатором, но это не важно), и программист забыл поставить точку с запятой, то будет ошибка.
|
||||
|
||||
Например, первый файл `a.js`:
|
||||
```js
|
||||
var a = 5
|
||||
```
|
||||
|
||||
Второй файл `lib.js`:
|
||||
```js
|
||||
(function() {
|
||||
// без точки с запятой в начале
|
||||
})()
|
||||
```
|
||||
|
||||
После объединения в один файл:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
*!*
|
||||
var a = 5
|
||||
*/!*
|
||||
|
||||
// библиотека
|
||||
(function() {
|
||||
// ...
|
||||
})();
|
||||
```
|
||||
|
||||
При запуске будет ошибка, потому что интерпретатор перед скобкой сам не вставит точку с запятой. Он просто поймёт код как `var a = 5(function ...)`, то есть пытается вызвать число `5` как функцию.
|
||||
|
||||
Таковы правила языка, и поэтому рекомендуется явно ставить точку с запятой. В данном случае автор lodash ставит `;` перед функцией, чтобы предупредить эту ошибку.
|
||||
[/smart]
|
||||
|
||||
|
||||
## Экспортирование через return
|
||||
|
||||
Можно оформить модуль и чуть по-другому, например передать значение через `return`:
|
||||
|
||||
```js
|
||||
var lodash = (function() {
|
||||
|
||||
var version;
|
||||
function assignDefaults() { ... }
|
||||
|
||||
return {
|
||||
defaults: function() { }
|
||||
}
|
||||
|
||||
})();
|
||||
```
|
||||
|
||||
Здесь, кстати, скобки вокруг внешней `function() { ... }` не обязательны, ведь функция и так объявлена внутри выражения присваивания, а значит -- является Function Expression.
|
||||
|
||||
Тем не менее, лучше их ставить, для улучшения читаемости кода, чтобы было сразу видно, что это не простое присвоение функции.
|
||||
|
||||
## Итого
|
||||
|
||||
Модуль при помощи замыканий -- это оборачивание пакета функционала в единую внешнюю функцию, которая тут же выполняется.
|
||||
|
||||
Все функции модуля будут иметь доступ к другим переменным и внутренним функциям этого же модуля через замыкание.
|
||||
|
||||
Например, `defaults` из примера выше имеет доступ к `assignDefaults`.
|
||||
|
||||
Но снаружи программист, использующий модуль, может обращаться напрямую только к тем, которые экспортированы. Благодаря этому будут скрыты внутренние аспекты реализации, которые нужны только разработчику модуля.
|
||||
|
||||
Можно придумать и много других вариаций такого подхода. В конце концов, "модуль" -- это всего лишь функция-обёртка для скрытия переменных.
|
||||
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
<script>
|
||||
var message = 'Пожалуйста, нажмите на кнопку';
|
||||
</script>
|
||||
<script src="highlight.js"></script>
|
||||
<script src="hello.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
<script>
|
||||
var message = 'Пожалуйста, нажмите на кнопку';
|
||||
</script>
|
||||
<script src="highlight.js"></script>
|
||||
<script src="hello.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Управление памятью в JavaScript
|
||||
|
||||
Управление памятью обычно незаметно. Мы создаём примитивы, объекты, функции.. Всё это занимает память.
|
||||
Управление памятью в JavaScript обычно происходит незаметно. Мы создаём примитивы, объекты, функции... Всё это занимает память.
|
||||
|
||||
Что происходит с объектом, когда он становится "не нужен"? Возможно ли "переполнение" памяти? Для ответа на эти вопросы -- залезем "под капот" интерпретатора.
|
||||
|
||||
|
@ -23,14 +23,20 @@
|
|||
|
||||
Для очистки памяти от недостижимых значений в браузерах используется автоматический <a href="http://en.wikipedia.org/wiki/Garbage_collection_(computer_science)">Сборщик мусора</a> (англ. Garbage collection, GC), встроенный в интерпретатор, который наблюдает за объектами и время от времени удаляет недостижимые.
|
||||
|
||||
Самая простая ситуация здесь с примитивами. При присвоении они копируются целиком, ссылок на них не создаётся, так что если в переменной была одна строка, а её заменили на другую, то предыдущую можно смело выбросить.
|
||||
|
||||
Именно объекты требуют специального "сборщика мусора", который наблюдает за ссылками, так как на один объект может быть много ссылок из разных переменных и, при перезаписи одной из них, объект может быть всё ещё доступен из другой.
|
||||
|
||||
Далее мы посмотрим ряд примеров, которые помогут в этом разобраться.
|
||||
|
||||
### Достижимость и наличие ссылок
|
||||
|
||||
Можно сказать просто: "значение остаётся в памяти, пока на него есть ссылка". Но такое упрощение будет не совсем верным.
|
||||
Есть одно упрощение для работы с памятью: "значение остаётся в памяти, пока на него есть ссылка".
|
||||
|
||||
Но такое упрощение будет верным лишь в одну сторону.
|
||||
|
||||
<ul>
|
||||
<li>**Верно -- в том плане, что если на значение не остаётся ссылок, то память из-под него очищается.**
|
||||
<li>**Верно -- в том плане, что если ссылок на значение нет, то память из-под него очищается.**
|
||||
|
||||
Например, была создана ссылка в переменной, и эту переменную тут же перезаписали:
|
||||
|
||||
|
@ -41,9 +47,9 @@ user = null;
|
|||
|
||||
Теперь объект `{ name: "Вася" }` более недоступен. Память будет освобождена.
|
||||
</li>
|
||||
<li>**Неверно -- может быть так, что ссылка есть, но при этом значение недостижимо и должно быть удалено из памяти.**
|
||||
<li>**Неверно -- в другую сторону: наличие ссылки не гарантирует, что значение останется в памяти.**
|
||||
|
||||
Такая ситуация возникает с объектами, при наличии ссылок друг на друга:
|
||||
Такая ситуация возникает с объектами, которые ссылаются друг на друга:
|
||||
|
||||
```js
|
||||
var vasya = {};
|
||||
|
@ -58,276 +64,96 @@ vasya = petya = null;
|
|||
|
||||
Поэтому они будут удалены из памяти.
|
||||
|
||||
Чтобы отследить такие сложные случаи, придуман [сборщик мусора](http://ru.wikipedia.org/wiki/%D0%A1%D0%B1%D0%BE%D1%80%D0%BA%D0%B0_%D0%BC%D1%83%D1%81%D0%BE%D1%80%D0%B0), который время от времени перебирает объекты и ищет недоступные, с использованием хитрых алгоритмов и оптимизаций, чтобы это было быстро и незаметно.
|
||||
Здесь как раз и играет роль "достижимость" -- оба этих объекта становятся недостижимы из корней, в первую очередь, из глобальной области, стека.
|
||||
|
||||
[Сборщик мусора](http://ru.wikipedia.org/wiki/%D0%A1%D0%B1%D0%BE%D1%80%D0%BA%D0%B0_%D0%BC%D1%83%D1%81%D0%BE%D1%80%D0%B0) отслеживает такие ситуации и очищает память.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
## Управление памятью в картинках
|
||||
## Алгоритм сборки мусора
|
||||
|
||||
Рассмотрим пример объекта "семья":
|
||||
Сборщик мусора идёт от корня по ссылкам и запоминает все найденные объекты. По окончанию -- он смотрит, какие объекты в нём отсутствуют и удаляет их.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Код</th>
|
||||
<th>Структура в памяти</th>
|
||||
<tr>
|
||||
<td>
|
||||
Например, рассмотрим пример объекта "семья":
|
||||
|
||||
```js
|
||||
var family = { };
|
||||
function marry(man, woman) {
|
||||
woman.husband = man;
|
||||
man.wife = woman;
|
||||
|
||||
family.father = {
|
||||
name: "Вася"
|
||||
};
|
||||
return {
|
||||
father: man,
|
||||
mother: woman
|
||||
}
|
||||
}
|
||||
|
||||
family.mother = {
|
||||
name: "Маша"
|
||||
};
|
||||
var family = marry({ name: "Василий" }, { name: "Мария"});
|
||||
```
|
||||
|
||||
</td>
|
||||
<td>
|
||||
Функция `marry` принимает два объекта, даёт им ссылки друг на друга и возвращает третий, содержащий ссылки на оба.
|
||||
|
||||
<img src="family.png">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
Этот код создаёт объект `family` и два дополнительных объекта, доступных по ссылкам `family.father` и `family.mother`.
|
||||
Получившийся объект `family` можно изобразить так:
|
||||
|
||||
### Недостижимый объект
|
||||
<img src="family.svg">
|
||||
|
||||
Теперь посмотрим, что будет, если удалить ссылку `family.father` при помощи `delete`:
|
||||
Здесь стрелочками показаны ссылки, а вот свойство `name` ссылкой не является, там хранится примитив, поэтому оно внутри самого объекта.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Код</th>
|
||||
<th>Структура в памяти</th>
|
||||
<tr>
|
||||
<td>
|
||||
Чтобы запустить сборщик мусора, удалим две ссылки:
|
||||
|
||||
```js
|
||||
var family = { };
|
||||
|
||||
family.father = {
|
||||
name: "Вася"
|
||||
};
|
||||
|
||||
family.mother = {
|
||||
name: "Маша"
|
||||
};
|
||||
|
||||
*!*
|
||||
```
|
||||
delete family.father;
|
||||
*/!*
|
||||
delete family.wife.husband;
|
||||
```
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<img src="family-nofatherlink.png">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
Обратим внимание, удаление только одной из этих ссылок ни к чему бы не привело. Пока до объекта можно добраться из корня `window`, объект остаётся жив.
|
||||
|
||||
### Пришёл сборщик мусора
|
||||
А если две, то получается, что от бывшего `family.father` ссылки выходят, но в него -- ни одна не идёт:
|
||||
|
||||
Сборщик мусора ищет недоступные объекты. Базовый алгоритм поиска -- это идти от корня (`window`) по ссылкам и помечать все объекты, которые встретит. Тогда после окончания обхода непомеченными останутся как раз недостижимые объекты.
|
||||
<img src="family-no-father.svg">
|
||||
|
||||
В нашем случае таким объектом будет бывший `family.father`. Он стал недостижимым и будет удалён вместе со своим "поддеревом", которое также более недоступно из программы.
|
||||
**Совершенно неважно, что из объекта выходят какие-то ссылки, они не влияют на достижимость этого объекта.**
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Код</th>
|
||||
<th>Структура в памяти</th>
|
||||
<tr>
|
||||
<td>
|
||||
Бывший `family.father` стал недостижимым и будет удалён вместе со своми данными, которые также более недоступны из программы.
|
||||
|
||||
<img src="family-no-father-2.svg">
|
||||
|
||||
А теперь -- рассмотрим более сложный случай. Что будет, если удалить главную ссылку `family`?
|
||||
|
||||
Исходный объект -- тот же, что и в начале, а затем:
|
||||
|
||||
```js
|
||||
var family = {
|
||||
father: {
|
||||
name: "Вася"
|
||||
},
|
||||
|
||||
mother: {
|
||||
name: "Маша"
|
||||
}
|
||||
};
|
||||
|
||||
*!*
|
||||
delete family.father;
|
||||
*/!*
|
||||
window.family = null;
|
||||
```
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<img src="family-nofatherlink-junk.png">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
Результат:
|
||||
|
||||
### После сборщика
|
||||
<img src="family-no-family.svg">
|
||||
|
||||
После того, как сработает сборщик мусора, картина в памяти будет такой:
|
||||
Как видим, объекты в конструкции всё ещё связаны между собой. Однако, поиск от корня их не находит, они не достижимы, и значит сборщик мусора удалит их из памяти.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Код</th>
|
||||
<th>Структура в памяти</th>
|
||||
<tr>
|
||||
<td>
|
||||
[smart header="Оптимизации"]
|
||||
Проблема описанного алгоритма -- в больших задержках. Если объектов много, то на поиск всех достижимых уйдёт довольно много времени. А ведь выполнение скрипта при этом должно быть остановлено, уже просканированные объекты не должны поменяться до окончания процесса. Получатся небольшие, но неприятные паузы-зависания в работе скрипта.
|
||||
|
||||
Поэтому современные интерпретаторы применяют различные оптимизации.
|
||||
|
||||
Самая частая -- это деление объектов на два вида "старые" и "новые". Для каждого типа выделяется своя область памяти. Каждый объект создаётся в "новой" области и, если прожил достаточно долго, мигрирует в старую. "Новая" область обычно небольшая. Она очищается часто. "Старая" -- редко.
|
||||
|
||||
На практике получается эффективно, обычно большинство объектов создаются и умирают почти сразу, к примеру, служа локальными переменными функции:
|
||||
```js
|
||||
var family = {
|
||||
father: {
|
||||
name: "Вася"
|
||||
},
|
||||
|
||||
mother: {
|
||||
name: "Маша"
|
||||
}
|
||||
};
|
||||
|
||||
*!*
|
||||
delete family.father;
|
||||
*/!*
|
||||
function showTime() {
|
||||
alert( new Date() ); // этот объект будет создан и умрёт сразу
|
||||
}
|
||||
```
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<img src="family-nofatherlink-junk-cleanup.png">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
Если вы знаете низкоуровневые языки программирования, то более подробно об организации сборки мусора в V8 можно почитать, например, в статье [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection).
|
||||
|
||||
|
||||
### Достижимость -- только по входящим ссылкам
|
||||
|
||||
Вернёмся к исходному коду.
|
||||
|
||||
**Пусть внутренние объекты ссылаются друг на друга:**
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Код</th>
|
||||
<th>Структура в памяти</th>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```js
|
||||
var family = {
|
||||
father: {
|
||||
name: "Вася"
|
||||
},
|
||||
|
||||
mother: {
|
||||
name: "Маша"
|
||||
}
|
||||
};
|
||||
|
||||
// добавим перекрёстных ссылок
|
||||
*!*
|
||||
family.father.wife = family.mother;
|
||||
family.mother.husband = family.father;
|
||||
family.father.we = family;
|
||||
family.mother.we = family;
|
||||
*/!*
|
||||
```
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<img src="family-ext.png">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Получилась сложная структура, с круговыми ссылками.
|
||||
|
||||
**Если удалить ссылки `family.father` и `family.mother.husband` (см. иллюстрацию ниже), то получится объект, который имеет исходящие ссылки, но не имеет входящих:**
|
||||
<table>
|
||||
<tr>
|
||||
<th>Код</th>
|
||||
<th>Структура в памяти</th>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```js
|
||||
var family = {
|
||||
father: {
|
||||
name: "Вася"
|
||||
},
|
||||
|
||||
mother: {
|
||||
name: "Маша"
|
||||
}
|
||||
};
|
||||
|
||||
family.father.wife = family.mother;
|
||||
family.mother.husband = family.father;
|
||||
family.father.we = family;
|
||||
family.mother.we = family;
|
||||
|
||||
*!*
|
||||
delete family.father;
|
||||
delete family.mother.husband;
|
||||
*/!*
|
||||
```
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<img src="family-ext-nofatherlink-nohusband.png">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
При стандартном алгоритме очистки памяти, сборщик мусора пойдёт от корня и не сможет достичь объект, помеченный серым. Поэтому он будет удалён.
|
||||
|
||||
**И совершенно неважно, что из объекта выходят какие-то ссылки `wife`, `we`, они не влияют на достижимость этого объекта.**
|
||||
|
||||
### Недостижимый остров
|
||||
|
||||
Всё "семейство" объектов, которое мы рассматривали выше, достижимо исключительно через глобальную переменную `family` или, иными словами, через свойство `window.family`.
|
||||
|
||||
Если записать в `window.family` что-то ещё, то все они, вместе со своими внутренними ссылками станут "недостижимым островом" и будут удалены:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Код</th>
|
||||
<th>Структура в памяти</th>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```js
|
||||
var family = {
|
||||
father: {
|
||||
name: "Вася"
|
||||
},
|
||||
|
||||
mother: {
|
||||
name: "Маша"
|
||||
}
|
||||
};
|
||||
|
||||
family.father.wife = family.mother;
|
||||
family.mother.husband = family.father;
|
||||
family.father.we = family;
|
||||
family.mother.we = family;
|
||||
|
||||
*!*
|
||||
family = null;
|
||||
*/!*
|
||||
```
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<img src="family-ext-nolink.png">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
[/smart]
|
||||
|
||||
## Замыкания
|
||||
|
||||
Замыкания следуют тем же правилам, что и обычные объекты.
|
||||
Объекты переменных, о которых шла речь ранее, в главе про замыкания, также подвержены сборке мусора. Они следуют тем же правилам, что и обычные объекты.
|
||||
|
||||
**Объект переменных внешней функции существует в памяти до тех пор, пока существует хоть одна внутренняя функция, ссылающаяся на него через свойство `[[Scope]]`.**
|
||||
Объект переменных внешней функции существует в памяти до тех пор, пока существует хоть одна внутренняя функция, ссылающаяся на него через свойство `[[Scope]]`.
|
||||
|
||||
Например:
|
||||
|
||||
|
@ -336,7 +162,7 @@ family = null;
|
|||
|
||||
```js
|
||||
function f() {
|
||||
var value = Math.random();
|
||||
var value = 123;
|
||||
|
||||
function g() { } // g видна только изнутри
|
||||
}
|
||||
|
@ -344,13 +170,13 @@ function f() {
|
|||
f();
|
||||
```
|
||||
|
||||
В коде выше внутренняя функция объявлена, но она осталась внутри. После окончания работы `f()` она станет недоступной для вызовов, так что будет убрана из памяти вместе с остальными локальными переменными.
|
||||
В коде выше `value` и `g` являются свойствами объекта переменных. Во время выполнения `f()` её объект переменных находится в текущем стеке выполнения, поэтому жив. По окончанию, он станет недостижимым и будет убран из памяти вместе с остальными локальными переменными.
|
||||
</li>
|
||||
<li>...А вот в этом случае лексическое окружение, включая переменную `value`, будет сохранено:
|
||||
|
||||
```js
|
||||
function f() {
|
||||
var value = Math.random();
|
||||
var value = 123;
|
||||
|
||||
function g() { }
|
||||
|
||||
|
@ -362,7 +188,7 @@ function f() {
|
|||
var g = f(); // функция g будет жить и сохранит ссылку на объект переменных
|
||||
```
|
||||
|
||||
Причина сохранения проста: в скрытом свойстве `g.[[Scope]]` находится ссылка на объект переменных, в котором была создана `g`.
|
||||
В скрытом свойстве `g.[[Scope]]` находится ссылка на объект переменных, в котором была создана `g`. Поэтому этот объект переменных останется в памяти, а в нём -- и `value`.
|
||||
</li>
|
||||
<li>
|
||||
Если `f()` будет вызываться много раз, а полученные функции будут сохраняться, например, складываться в массив, то будут сохраняться и объекты `LexicalEnvironment` с соответствующими значениями `value`:
|
||||
|
@ -375,17 +201,16 @@ function f() {
|
|||
}
|
||||
|
||||
// 3 функции, каждая ссылается на свой объект переменных,
|
||||
// со своим значением value
|
||||
// каждый со своим значением value
|
||||
var arr = [f(), f(), f()];
|
||||
```
|
||||
|
||||
При этом совершенно не важно, имеет ли вложенная функция имя или нет.
|
||||
</li>
|
||||
<li>Объект `LexicalEnvironment` живёт ровно до тех пор, пока на него существуют ссылки. В коде ниже замыкание сначала сохраняется в памяти, а после удаления ссылки на `g` умирает:
|
||||
<li>Объект `LexicalEnvironment` живёт ровно до тех пор, пока на него существуют ссылки. В коде ниже после удаления ссылки на `g` умирает:
|
||||
|
||||
```js
|
||||
function f() {
|
||||
var value = Math.random();
|
||||
var value = 123;
|
||||
|
||||
function g() { }
|
||||
|
||||
|
@ -393,7 +218,7 @@ function f() {
|
|||
}
|
||||
|
||||
var g = f(); // функция g жива
|
||||
// а значит в памяти остается соответствующий объект переменных
|
||||
// а значит в памяти остается соответствующий объект переменных f()
|
||||
|
||||
g = null; // ..а вот теперь память будет очищена
|
||||
```
|
||||
|
@ -427,6 +252,8 @@ var g = f();
|
|||
g();
|
||||
```
|
||||
|
||||
Как вы могли увидеть -- нет такой переменной! Недоступна она изнутри `g`. Интерпретатор решил, что она нам не понадобится и удалил.
|
||||
|
||||
Это может привести к забавным казусам при отладке, вплоть до того что вместо этой переменной будет другая, внешняя:
|
||||
|
||||
```js
|
||||
|
@ -434,7 +261,7 @@ g();
|
|||
var value = "Сюрприз";
|
||||
|
||||
function f() {
|
||||
var value = "...";
|
||||
var value = "самое близкое значение";
|
||||
|
||||
function g() {
|
||||
debugger; // выполните в консоли alert(value); Сюрприз!
|
||||
|
@ -488,4 +315,6 @@ alert("Разница в " + ( timeRecursion / timeLoop ) + " раз");
|
|||
|
||||
Различие в скорости на таком примере может составлять, в зависимости от интерпретатора, 2-10 раз.
|
||||
|
||||
В большинстве ситуаций оптимизация по количеству создаваемых объектов несущественна, просто потому что "JavaScript и так достаточно быстр". Но она может быть важной для "узких мест" кода, а также при написании компьютерной графики и сложных вычислений на JS.
|
||||
Вообще, этот пример -- не показателен. Ещё раз обращаю ваше внимание на то, что такие искусственные "микротесты" часто врут. Правильно их делать -- отдельная наука, которая выходит за рамки этой главы. Но и на практике ускорение в 2-10 раз оптимизацией по количеству объектв (и вообще, любых значений) -- отнюдь не миф, а вполне достижимо.
|
||||
|
||||
В реальной жизни в большинстве ситуаций такая оптимизация несущественна, просто потому что "JavaScript и так достаточно быстр". Но она может быть эффективной для "узких мест" кода.
|
||||
|
|
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="120px" height="203px" viewBox="0 0 120 203" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>family-no-father-2</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="family-no-father-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<rect id="Rectangle-1" stroke="#979797" sketch:type="MSShapeGroup" x="18" y="18" width="80" height="30"></rect>
|
||||
<text id="window" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="18" y="11">window</tspan>
|
||||
</text>
|
||||
<text id="Корень" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="35" y="37">Корень</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-2" stroke="#979797" fill="#E8E8E8" sketch:type="MSShapeGroup" x="18" y="85" width="80" height="28"></rect>
|
||||
<text id="Object" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="35" y="102">Object</tspan>
|
||||
</text>
|
||||
<text id="family" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="3" y="69">family</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-4" stroke="#979797" fill="#E8E8E8" sketch:type="MSShapeGroup" x="0" y="153" width="120" height="50"></rect>
|
||||
<text id="name:-"Мария"" sketch:type="MSTextLayer" font-family="Consolas" font-size="12" font-weight="normal" fill="#000000">
|
||||
<tspan x="23" y="191">name: "Мария"</tspan>
|
||||
</text>
|
||||
<text id="mother" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="5" y="136">mother</tspan>
|
||||
</text>
|
||||
<text id="Object-3" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="37" y="171">Object</tspan>
|
||||
</text>
|
||||
<path d="M59.5,50.5 L59.5,81.5" id="Line" stroke="#979797" stroke-linecap="square" fill="#979797" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-decoration-1" d="M59.5,81.5 C60.55,77.72 61.45,74.48 62.5,70.7 C60.4,70.7 58.6,70.7 56.5,70.7 C57.55,74.48 58.45,77.72 59.5,81.5 C59.5,81.5 59.5,81.5 59.5,81.5 Z" stroke="#979797" stroke-linecap="square" fill="#979797"></path>
|
||||
<path d="M58.5,117.5 L58.5,149.5" id="Line" stroke="#979797" stroke-linecap="square" fill="#979797" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-decoration-1" d="M58.5,149.5 C59.55,145.72 60.45,142.48 61.5,138.7 C59.4,138.7 57.6,138.7 55.5,138.7 C56.55,142.48 57.45,145.72 58.5,149.5 C58.5,149.5 58.5,149.5 58.5,149.5 Z" stroke="#979797" stroke-linecap="square" fill="#979797"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 10 KiB |
59
1-js/5-functions-closures/6-memory-management/family.svg
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="329px" height="203px" viewBox="0 0 329 203" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>family</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="family" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<path d="M188.5,196.5 L129.5,196.5" id="Line" stroke="#979797" stroke-linecap="square" fill="#979797" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-decoration-1" d="M129.5,196.5 C133.28,197.55 136.52,198.45 140.3,199.5 C140.3,197.4 140.3,195.6 140.3,193.5 C136.52,194.55 133.28,195.45 129.5,196.5 C129.5,196.5 129.5,196.5 129.5,196.5 Z" stroke="#979797" stroke-linecap="square" fill="#979797"></path>
|
||||
<rect id="Rectangle-1" stroke="#979797" sketch:type="MSShapeGroup" x="118" y="18" width="80" height="30"></rect>
|
||||
<text id="window" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="118" y="11">window</tspan>
|
||||
</text>
|
||||
<text id="Корень" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="135" y="37">Корень</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-2" stroke="#979797" fill="#E8E8E8" sketch:type="MSShapeGroup" x="118" y="85" width="80" height="28"></rect>
|
||||
<text id="Object" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="135" y="102">Object</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-3" stroke="#979797" fill="#E8E8E8" sketch:type="MSShapeGroup" x="0" y="153" width="120" height="50"></rect>
|
||||
<text id="Object-2" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="35" y="171">Object</tspan>
|
||||
</text>
|
||||
<text id="father" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="31" y="133">father</tspan>
|
||||
</text>
|
||||
<text id="wife" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="143" y="154">wife</tspan>
|
||||
</text>
|
||||
<text sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="103" y="69">family</tspan>
|
||||
</text>
|
||||
<text id="name:-"Василий"" sketch:type="MSTextLayer" font-family="Consolas" font-size="12" font-weight="normal" fill="#000000">
|
||||
<tspan x="15" y="191">name: "Василий"</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-4" stroke="#979797" fill="#E8E8E8" sketch:type="MSShapeGroup" x="209" y="153" width="120" height="50"></rect>
|
||||
<text id="name:-"Мария"" sketch:type="MSTextLayer" font-family="Consolas" font-size="12" font-weight="normal" fill="#000000">
|
||||
<tspan x="226" y="191">name: "Мария"</tspan>
|
||||
</text>
|
||||
<text id="mother" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="238" y="133">mother</tspan>
|
||||
</text>
|
||||
<text id="Object-3" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="240" y="171">Object</tspan>
|
||||
</text>
|
||||
<path d="M159.5,50.5 L159.5,81.5" id="Line" stroke="#979797" stroke-linecap="square" fill="#979797" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-decoration-1" d="M159.5,81.5 C160.55,77.72 161.45,74.48 162.5,70.7 C160.4,70.7 158.6,70.7 156.5,70.7 C157.55,74.48 158.45,77.72 159.5,81.5 C159.5,81.5 159.5,81.5 159.5,81.5 Z" stroke="#979797" stroke-linecap="square" fill="#979797"></path>
|
||||
<path d="M110.5,118.5 L64.5,150.5" id="Line-2" stroke="#979797" stroke-linecap="square" fill="#979797" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-2-decoration-1" d="M64.5293087,150.479611 C68.2319481,149.182938 71.405639,148.071504 75.1082783,146.77483 C73.9090429,145.05093 72.8811268,143.5733 71.6818914,141.849399 C69.1784875,144.869973 67.0327127,147.459037 64.5293087,150.479611 C64.5293087,150.479611 64.5293087,150.479611 64.5293087,150.479611 Z" stroke="#979797" stroke-linecap="square" fill="#979797"></path>
|
||||
<path d="M129.5,162.5 L190.5,162.5" id="Line" stroke="#979797" stroke-linecap="square" fill="#9B9B9B" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-decoration-1" d="M190.5,162.5 C186.72,161.45 183.48,160.55 179.7,159.5 C179.7,161.6 179.7,163.4 179.7,165.5 C183.48,164.45 186.72,163.55 190.5,162.5 C190.5,162.5 190.5,162.5 190.5,162.5 Z" stroke="#979797" stroke-linecap="square" fill="#9B9B9B"></path>
|
||||
<text id="husband" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
|
||||
<tspan x="132" y="187">husband</tspan>
|
||||
</text>
|
||||
<path d="M204.5,118.5 L251.5,150.5" id="Line" stroke="#979797" stroke-linecap="square" fill="#979797" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-decoration-1" d="M250.789558,150.016295 C248.255943,147.021016 246.084274,144.453634 243.550659,141.458354 C242.368798,143.194213 241.355774,144.682091 240.173913,146.41795 C243.889389,147.677371 247.074082,148.756874 250.789558,150.016295 C250.789558,150.016295 250.789558,150.016295 250.789558,150.016295 Z" stroke="#979797" stroke-linecap="square" fill="#979797"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.7 KiB |
|
@ -13,7 +13,7 @@ with(obj) {
|
|||
}
|
||||
```
|
||||
|
||||
**Любое обращение к переменной внутри `with` сначала ищет её среди свойств `obj`, а только потом -- вне `with`.**
|
||||
Любое обращение к переменной внутри `with` сначала ищет её среди свойств `obj`, а только потом -- вне `with`.
|
||||
|
||||
## Пример
|
||||
|
||||
|
@ -70,9 +70,8 @@ with(obj) {
|
|||
}
|
||||
```
|
||||
|
||||
Свойства из разных объектов используются как обычные переменные... Магия! Порядок поиска переменных в выделенном коде: `size => obj => window`
|
||||
Свойства из разных объектов используются как обычные переменные... Магия! Порядок поиска переменных в выделенном коде: `size => obj => window`.
|
||||
|
||||
<img src="with_obj_size.png">
|
||||
|
||||
## Изменения переменной
|
||||
|
||||
|
@ -97,7 +96,7 @@ alert(obj.a); // 20, переменная была изменена в объе
|
|||
Есть несколько причин.
|
||||
|
||||
<ol>
|
||||
<li>В современном стандарте `JavaScript` отказались от `with`, потому что **конструкция `with` подвержена ошибкам и непрозрачна.**
|
||||
<li>В современном стандарте `JavaScript` отказались от `with`, потому что конструкция `with` подвержена ошибкам и непрозрачна.
|
||||
|
||||
Проблемы возникают в том случае, когда в `with(obj)` присваивается переменная, которая по замыслу должна быть в свойствах `obj`, но ее там нет.
|
||||
|
||||
|
@ -120,8 +119,10 @@ alert(window.size);
|
|||
|
||||
Такие ошибки редки, но очень сложны в отладке, особенно если `size` изменилась не в `window`, а где-нибудь во внешнем `LexicalEnvironment`.
|
||||
</li>
|
||||
<li>Еще одна причина -- **алгоритмы сжатия JavaScript не любят `with`**. Перед выкладкой на сервер JavaScript сжимают. Для этого есть много инструментов, например [Closure Compiler](http://code.google.com/intl/ru-RU/closure/compiler/) и [UglifyJS](https://github.com/mishoo/UglifyJS). Если вкратце -- они либо сжимают код с `with` с ошибками, либо оставляют его частично несжатым.</li>
|
||||
<li>Ну и, наконец, **производительность -- усложнение поиска переменной из-за `with` влечет дополнительные накладные расходы**. Современные движки применяют много внутренних оптимизаций, ряд которых не могут быть применены к коду, в котором есть `with`.
|
||||
<li>Еще одна причина -- алгоритмы сжатия JavaScript не любят `with`. Перед выкладкой на сервер JavaScript сжимают. Для этого есть много инструментов, например [Closure Compiler](http://code.google.com/intl/ru-RU/closure/compiler/) и [UglifyJS](https://github.com/mishoo/UglifyJS). Обычно они переименовывают локальные переменные в более короткие имена, но не свойства объектов. С конструкцией `with` до запуска кода непонятно -- откуда будет взята переменная. Поэтому выходит, что, на всякий случай (если это свойство), лучше её не переименовывать. Таким образом, качество сжатия кода страдает.</li>
|
||||
<li>Ну и, наконец, производительность -- усложнение поиска переменной из-за `with` влечет дополнительные накладные расходы.
|
||||
|
||||
Современные движки применяют много внутренних оптимизаций, ряд которых не могут быть применены к коду, в котором есть `with`.
|
||||
|
||||
Вот, к примеру, запустите этот код в современном браузере. Производительность функции `fast` существенно отличается `slow` с пустым(!) `with`. И дело тут именно в `with`, т.к. наличие этой конструкции препятствует оптимизации.
|
||||
|
||||
|
@ -139,14 +140,14 @@ function slow() {
|
|||
}
|
||||
|
||||
|
||||
var time = new Date();
|
||||
var time = performance.now();
|
||||
while(i < 1000000) fast();
|
||||
alert(new Date - time);
|
||||
alert("Без with: " + (performance.now() - time));
|
||||
|
||||
var time = new Date();
|
||||
var time = performance.now();
|
||||
i=0;
|
||||
while(i < 1000000) slow();
|
||||
alert(new Date - time);
|
||||
alert("С with: " + (performance.now() - time));
|
||||
```
|
||||
|
||||
</li>
|
||||
|
|
Before Width: | Height: | Size: 9.4 KiB |
|
@ -96,9 +96,9 @@ user = null;
|
|||
admin.sayHi(); // упс! внутри sayHi обращение по старому имени, ошибка!
|
||||
```
|
||||
|
||||
**Использование `this` гарантирует, что функция работает именно с тем объектом, в контексте которого вызвана!**
|
||||
Использование `this` гарантирует, что функция работает именно с тем объектом, в контексте которого вызвана.
|
||||
|
||||
Через `this` метод может обратиться к любому свойству объекта, а, при желании, и передать объект куда-либо:
|
||||
Через `this` метод может не только обратиться к любому свойству объекта, но и передать куда-то ссылку на сам объект целиком:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -112,8 +112,8 @@ var user = {
|
|||
*/!*
|
||||
};
|
||||
|
||||
function showName(obj) {
|
||||
alert( obj.name );
|
||||
function showName(namedObj) {
|
||||
alert( namedObj.name );
|
||||
}
|
||||
|
||||
user.sayHi(); // Василий
|
||||
|
@ -157,7 +157,7 @@ admin['g'](); // Админ (не важно, доступ к объекту ч
|
|||
*/!*
|
||||
```
|
||||
|
||||
**Значение `this` не зависит от того, как функция была создана, оно определяется исключительно в момент вызова.**
|
||||
Итак, значение `this` не зависит от того, как функция была создана, оно определяется исключительно в момент вызова.
|
||||
|
||||
## Значение this при вызове без контекста
|
||||
|
||||
|
@ -176,7 +176,9 @@ function func() {
|
|||
func();
|
||||
```
|
||||
|
||||
В современном стандарте языка это поведение изменено, вместо глобального объекта `this` будет `undefined`.
|
||||
Таково поведение в старом стандарте.
|
||||
|
||||
А в режиме `use strict` вместо глобального объекта `this` будет `undefined`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -188,12 +190,13 @@ function func() {
|
|||
func();
|
||||
```
|
||||
|
||||
Это стоит иметь в виду для общего развития, но обычно если в функции используется `this`, то она, всё же, проектируется для вызова в контексте объекта.
|
||||
Обычно если в функции используется `this`, то она, всё же, служит для вызова в контексте объекта, так что такая ситуация -- скорее исключение.
|
||||
|
||||
[warn header="`this` теряется при операциях с методом"]
|
||||
Ещё раз обратим внимание: контекст `this` никак не привязан к функции, даже если она создана в объявлении объекта.
|
||||
## Ссылочный тип
|
||||
|
||||
Чтобы `this` передался правильно, нужно вызвать функцию именно через точку (или квадратные скобки). Любой более хитрый вызов приведёт к потере контекста, например:
|
||||
Контекст `this` никак не привязан к функции, даже если она создана в объявлении объекта. Чтобы `this` передался, нужно вызвать функцию именно через точку (или квадратные скобки).
|
||||
|
||||
Любой более хитрый вызов приведёт к потере контекста, например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -204,25 +207,38 @@ var user = {
|
|||
};
|
||||
|
||||
user.hi(); // Вася (простой вызов работает)
|
||||
|
||||
*!*
|
||||
// а теперь вызовем user.hi или user.bye в зависимости от имени
|
||||
(user.name == "Вася" ? user.hi : user.bye)(); // undefined
|
||||
*/!*
|
||||
```
|
||||
|
||||
В последней строке примера метод получен в результате выполнения тернарного оператора и тут же вызван. При этом `this` теряется.
|
||||
В последней строке примера метод получен в результате выполнения тернарного оператора и тут же вызван. Но `this` при этом теряется.
|
||||
|
||||
Иначе говоря, такой вызов эквивалентен двум строкам:
|
||||
Если хочется понять, почему, то причина кроется в деталях работы вызова `obj.method()`.
|
||||
|
||||
```js
|
||||
var method = (user.name == "Вася" ? user.hi : user.bye);
|
||||
method(); // без this
|
||||
```
|
||||
Он ведь, на самом деле, состоит из двух независимых операций: точка `.` -- получение свойства и скобки `()` -- его вызов (предполагается, что это функция).
|
||||
|
||||
[/warn]
|
||||
|
||||
## Задачи
|
||||
Функция, как мы говорили раньше, сама по себе не запоминает контекст. Чтобы "донести его" до скобок, JavaScript применяет "финт ушами" -- точка возвращает не функцию, а значение специального "ссылочного" типа [Reference Type](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-reference-specification-type).
|
||||
|
||||
Этот тип представляет собой связку "base-name-strict", где:
|
||||
<ul>
|
||||
<li>*base* -- как раз объект,</li>
|
||||
<li>*name* -- имя свойства,</li>
|
||||
<li>*strict* -- вспомогательный флаг для передачи `use strict`.</li>
|
||||
</ul>
|
||||
|
||||
То есть, ссылочный тип (Reference Type) -- это своеобразное "три-в-одном". Он существует исключительно для целей спецификации, мы его не видим, поскольку любой оператор тут же от него избавляется:
|
||||
|
||||
<ul>
|
||||
<li>Скобки `()` получают из `base` значение свойства `name` и вызывают в контексте base.</li>
|
||||
<li>Другие операторы получают из `base` значение свойства `name` и используют, а остальные компоненты игнорируют.</li>
|
||||
</ul>
|
||||
|
||||
Поэтому любая операция над результатом операции получения свойства, кроме вызова, приводит к потере контекста.
|
||||
|
||||
Аналогично работает и получение свойства через квадратные скобки `obj[method]`.
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ var user = {
|
|||
alert(user); // [object Object]
|
||||
```
|
||||
|
||||
**Как видно, содержимое объекта не вывелось. Это потому, что стандартным строковым представлением пользовательского объекта является строка `"[object Object]"`**.
|
||||
Как видно, содержимое объекта не вывелось. Это потому, что стандартным строковым представлением пользовательского объекта является строка `"[object Object]"`.
|
||||
|
||||
Такой вывод объекта не содержит интересной информации. Поэтому имеет смысл его поменять на что-то более полезное.
|
||||
|
||||
|
@ -109,7 +109,7 @@ alert( +room ); // 777, *!*вызвался toString*/!*
|
|||
Метод `valueOf` обязан возвращать примитивное значение, иначе его результат будет проигнорирован. При этом -- не обязательно числовое.
|
||||
|
||||
[smart header="У большинства объектов нет `valueOf`"]
|
||||
**У большинства встроенных объектов такого `valueOf` нет, поэтому численное и строковое преобразования для них работают одинаково.**
|
||||
У большинства встроенных объектов такого `valueOf` нет, поэтому численное и строковое преобразования для них работают одинаково.
|
||||
|
||||
Исключением является объект `Date`, который поддерживает оба типа преобразований:
|
||||
|
||||
|
@ -127,7 +127,8 @@ alert( +new Date() ); // valueOf: кол-во миллисекунд, проше
|
|||
|
||||
## Две стадии преобразования
|
||||
|
||||
Если необходимо, полученный из объекта примитив может быть преобразован дальше.
|
||||
Итак, объект преобразован в примитив при помощи `toString` или `valueOf`. Далее, вполне возможно,
|
||||
Если необходимо, что полученный из объекта примитив будет преобразован дальше, уже по правилам для примитивов.
|
||||
|
||||
Например, рассмотрим применение к объекту операции `==`:
|
||||
|
||||
|
@ -170,20 +171,18 @@ alert(a - b); // "1" - "2" = -1
|
|||
```
|
||||
|
||||
[warn header="Исключение: `Date`"]
|
||||
Обычно арифметические операторы и сравнения используют численное преобразование, но есть исключение: объект `Date`.
|
||||
Объект `Date`, по историческим причинам, является исключением.
|
||||
|
||||
Он преобразуется в примитив, используя строковое преобразование. С этим можно столкнуться в операторе `"+"`:
|
||||
Бинарный оператор плюс `+` обычно использует числовое преобразование, но в случае с `Date` -- строковое:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// бинарный вариант, преобразование к примитиву
|
||||
// бинарный вариант, строчное преобразование
|
||||
alert( new Date + "" ); // "строка даты"
|
||||
|
||||
// унарный вариант, наравне с - * / и другими приводит к числу
|
||||
// унарный вариант, как и - * /, приводит к числу
|
||||
alert( +new Date ); // число миллисекунд
|
||||
```
|
||||
|
||||
Это исключение явно прописано в стандарте и является единственным в своём роде.
|
||||
[/warn]
|
||||
|
||||
[warn header="Как испугать Java-разработчика"]
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Обычный синтаксис `{...}` позволяет создать один объект. Но зачастую нужно создать много однотипных объектов.
|
||||
|
||||
Для этого используют функции, запуская их при помощи специального оператора `new`.
|
||||
Для этого используют "функции-конструкторы", запуская их при помощи специального оператора `new`.
|
||||
[cut]
|
||||
## Конструктор
|
||||
|
||||
|
@ -21,9 +21,9 @@ var animal = new Animal("ёжик");
|
|||
*/!*
|
||||
```
|
||||
|
||||
Технически, никаких ограничений нет -- любую функцию можно вызвать при помощи `new`. Но при этом она работает несколько иным образом, чем обычно, поэтому функции, предназначенные к вызову через `new`, называют с большой буквы.
|
||||
Технически, любую функцию можно вызвать при помощи `new`. Но при этом она работает несколько иным образом, чем обычно, поэтому функции, предназначенные к вызову через `new`, называют с большой буквы.
|
||||
|
||||
**Алгоритм работы оператора `new`:**
|
||||
**Алгоритм работы функции, запущенной через `new`:**
|
||||
|
||||
<ol>
|
||||
<li>Автоматически создается новый пустой объект.</li>
|
||||
|
@ -33,7 +33,7 @@ var animal = new Animal("ёжик");
|
|||
</ol>
|
||||
|
||||
|
||||
В результате вызова `new Animal("ёжик");` получаем объект:
|
||||
В результате вызова `new Animal("ёжик");` получаем такой объект:
|
||||
|
||||
```js
|
||||
animal = {
|
||||
|
@ -42,15 +42,18 @@ animal = {
|
|||
}
|
||||
```
|
||||
|
||||
Иными словами, при вызове `new Animal` происходит что-то в таком духе (комментарии -- это то, что делает интерпретатор):
|
||||
Иными словами, при вызове `new Animal` происходит что-то в таком духе (первая и последняя строка -- это то, что делает интерпретатор):
|
||||
|
||||
```js
|
||||
function Animal(name) {
|
||||
*!*
|
||||
// this = {}
|
||||
*/!*
|
||||
|
||||
// в this пишем свойства, методы
|
||||
this.name = name;
|
||||
this.canWalk = true;
|
||||
|
||||
*!*
|
||||
// return this
|
||||
*/!*
|
||||
|
@ -67,7 +70,7 @@ function Animal(name) {
|
|||
<li>При вызове `return` с примитивным значением, оно будет отброшено.</li>
|
||||
</ul>
|
||||
|
||||
Иными словами, вызов `return` с объектом вернёт объект, а с чем угодно, кроме объекта -- прекратит выполнение функции и возвратит `this`.
|
||||
Иными словами, вызов `return` с объектом вернёт объект, а с чем угодно, кроме объекта -- возвратит, как обычно, `this`.
|
||||
|
||||
Например, возврат объекта:
|
||||
|
||||
|
@ -97,9 +100,9 @@ function BigAnimal() {
|
|||
alert( new BigAnimal().name ); // Мышь, получили this (а Годзилла пропал)
|
||||
```
|
||||
|
||||
Эта особенность работы `new` прописана в стандарте, знать о ней полезно, но используется она весьма редко.
|
||||
Эта особенность работы `new` прописана в стандарте, но используется она весьма редко.
|
||||
|
||||
[smart]
|
||||
[smart header="Можно без скобок"]
|
||||
Кстати, при вызове `new` без аргументов скобки можно не ставить:
|
||||
|
||||
```js
|
||||
|
@ -108,11 +111,14 @@ var animal = new BigAnimal; // <-- без скобок
|
|||
var animal = new BigAnimal();
|
||||
```
|
||||
|
||||
Не сказать, что выбрасывание скобок -- "хороший стиль", но такой синтаксис допустим стандартом.
|
||||
[/smart]
|
||||
|
||||
## Создание методов в конструкторе
|
||||
|
||||
Использование функций для создания объекта дает большую гибкость. Можно передавать конструктору параметры, определяющие как его создавать.
|
||||
Использование функций для создания объекта дает большую гибкость. Можно передавать конструктору параметры, определяющие как его создавать, и он будет "клепать" объекты заданным образом.
|
||||
|
||||
Добавим в создаваемый объект ещё и метод.
|
||||
|
||||
Например, `new User(name)` создает объект с заданным значением свойства `name` и методом `sayHi`:
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
|
||||
```js
|
||||
//+ run
|
||||
|
||||
function User(fullName) {
|
||||
this.fullName = fullName;
|
||||
|
||||
Object.defineProperties(this, {
|
||||
|
||||
firstName: {
|
||||
|
||||
get: function() {
|
||||
return this.fullName.split(' ')[0];
|
||||
},
|
||||
|
||||
set: function(newFirstName) {
|
||||
this.fullName = newFirstName + ' ' + this.lastName;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
lastName: {
|
||||
|
||||
get: function() {
|
||||
return this.fullName.split(' ')[1];
|
||||
},
|
||||
|
||||
set: function(newLastName) {
|
||||
this.fullName = this.firstName + ' ' + newLastName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
var vasya = new User("Василий Попкин");
|
||||
|
||||
// чтение firstName/lastName
|
||||
alert(vasya.firstName); // Василий
|
||||
alert(vasya.lastName); // Попкин
|
||||
|
||||
// запись в lastName
|
||||
vasya.lastName = 'Сидоров';
|
||||
|
||||
alert(vasya.fullName); // Василий Сидоров
|
||||
```
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# Добавить get/set-свойства
|
||||
|
||||
[importance 5]
|
||||
|
||||
Вам попал в руки код объекта `User`, который хранит имя и фамилию в свойстве `this.fullName`:
|
||||
|
||||
```js
|
||||
function User(fullName) {
|
||||
this.fullName = fullName;
|
||||
}
|
||||
|
||||
var vasya = new User("Василий Попкин");
|
||||
```
|
||||
|
||||
Имя и фамилия всегда разделяются пробелом.
|
||||
|
||||
Сделайте, чтобы были доступны свойства `firstName` и `lastName`, причём не только на чтение, но и на запись, вот так:
|
||||
|
||||
```js
|
||||
var vasya = new User("Василий Попкин");
|
||||
|
||||
// чтение firstName/lastName
|
||||
alert(vasya.firstName); // Василий
|
||||
alert(vasya.lastName); // Попкин
|
||||
|
||||
// запись в lastName
|
||||
vasya.lastName = 'Сидоров';
|
||||
|
||||
alert(vasya.fullName); // Василий Сидоров
|
||||
```
|
||||
|
||||
Важно: не рекомендуется дублировать одни и те же данные в различных свойствах. Поэтому в этой задаче `fullName` должно остаться свойством, а `firstName/lastName` -- реализованы через `get/set`.
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
В этой главе мы рассмотрим возможности, которые позволяют очень гибко и мощно управлять всеми свойствами объекта, включая их аспекты -- изменяемость, видимость в цикле `for..in` и даже "невидимые" геттеры-сеттеры.
|
||||
|
||||
Они поддерживаются всеми современными браузерами, но не IE8-. Точнее говоря, они поддерживаются даже в IE8, но не для всех объектов, а только для DOM-объектов (они используются при работе со страницей, это сейчас вне нашего рассмотрения).
|
||||
|
||||
Большая часть этих методов, в частности, работа с дескрипторами, не задействуется в других главах учебника для обеспечения совместимости с IE8-, но во вспомогательных скриптах -- библиотеках для тестирования, сборки, а также для сервера Node.JS они используются достаточно активно.
|
||||
Они поддерживаются всеми современными браузерами, но не IE8-. Точнее говоря, они поддерживаются даже в IE8, но не для всех объектов, а только для DOM-объектов (используются при работе со страницей, это сейчас вне нашего рассмотрения).
|
||||
|
||||
[cut]
|
||||
## Дескрипторы в примерах
|
||||
|
@ -28,28 +26,20 @@ Object.defineProperty(obj, prop, descriptor)
|
|||
<dt>`descriptor`</dt>
|
||||
<dd>Дескриптор -- объект, который описывает поведение свойства. В нём могут быть следующие поля:
|
||||
|
||||
<dl>
|
||||
<dt>`value`</dt>
|
||||
<dd>Значение свойства, по умолчанию `undefined`</dd>
|
||||
<dt>`writable`</dt>
|
||||
<dd>Значение свойства можно менять, если `true`. По умолчанию `false`.</dd>
|
||||
<dt>`configurable`</dt>
|
||||
<dd>Если `true`, то свойство можно удалять, а также менять его в дальнейшем при помощи `defineProperty`. По умолчанию `false`.</dd>
|
||||
<dt>`enumerable`</dt>
|
||||
<dd>Если `true`, то свойство будет участвовать в переборе `for..in`. По умолчанию `false`.</dd>
|
||||
<dt>`get`</dt>
|
||||
<dd>Функция, которая возвращает значение свойства. По умолчанию `undefined`.</dd>
|
||||
<dt>`set`</dt>
|
||||
<dd>Функция, которая записывает значение свойства. По умолчанию `undefined`.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
<ul>
|
||||
<li>`value` -- значение свойства, по умолчанию `undefined`</li>
|
||||
<li>`writable` -- значение свойства можно менять, если `true`. По умолчанию `false`.</li>
|
||||
<li>`configurable` -- если `true`, то свойство можно удалять, а также менять его в дальнейшем при помощи новых вызовов `defineProperty`. По умолчанию `false`.</li>
|
||||
<li>`enumerable` -- если `true`, то свойство будет участвовать в переборе `for..in`. По умолчанию `false`.</li>
|
||||
<li>`get` -- функция, которая возвращает значение свойства. По умолчанию `undefined`.</li>
|
||||
<li>`set` -- функция, которая записывает значение свойства. По умолчанию `undefined`.</li>
|
||||
</ul>
|
||||
|
||||
Чтобы избежать конфликта, запрещено одновременно указывать значение `value` и функции `get/set`. Либо значение, либо функции для его чтения-записи, одно из двух. Также запрещено и не имеет смысла указывать `writable` при наличии `get/set`-функций.
|
||||
|
||||
Далее мы подробно разберём эти свойства на примерах.
|
||||
|
||||
### Пример: обычное свойство
|
||||
## Обычное свойство
|
||||
|
||||
Обычное свойство добавить очень просто.
|
||||
|
||||
|
@ -65,7 +55,7 @@ user.name = "Вася";
|
|||
Object.defineProperty(user, "name", { value: "Вася" });
|
||||
```
|
||||
|
||||
### Пример: свойство-константа
|
||||
## Свойство-константа
|
||||
|
||||
Для того, чтобы сделать свойство неизменяемым, добавим ему флаги `writable` и `configurable`:
|
||||
|
||||
|
@ -91,11 +81,9 @@ user.name = "Петя";
|
|||
*/!*
|
||||
```
|
||||
|
||||
**Заметим, что ошибки при попытке изменения такого свойства произойдут только при `use strict`.**
|
||||
Заметим, что без `use strict` операция записи "молча" не сработает, а при `use strict` дополнительно генерируется ошибка.
|
||||
|
||||
Без `use strict` операция записи "молча" не сработает.
|
||||
|
||||
### Пример: свойство, скрытое для for..in
|
||||
## Свойство, скрытое для for..in
|
||||
|
||||
Встроенный метод `toString`, как и большинство встроенных методов, не участвует в цикле `for..in`. Это удобно, так как обычно такое свойство является "служебным".
|
||||
|
||||
|
@ -113,7 +101,9 @@ for(var key in user) alert(key); // name, toString
|
|||
*/!*
|
||||
```
|
||||
|
||||
`Object.defineProperty` может помочь исключить `toString` из списка итерации. Достаточно поставить ему флаг `enumerable: false`:
|
||||
Мы бы хотели, чтобы поведение нашего метода `toString` было таким же, как и стандартного.
|
||||
|
||||
`Object.defineProperty` может исключить `toString` из списка итерации, поставив ему флаг `enumerable: false`. По стандарту, у встроенного `toString` этот флаг уже стоит.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -123,6 +113,7 @@ var user = {
|
|||
};
|
||||
|
||||
*!*
|
||||
// помечаем toString как не подлежащий перебору в for..in
|
||||
Object.defineProperty(user, "toString", {enumerable: false});
|
||||
|
||||
for(var key in user) alert(key); // name
|
||||
|
@ -131,7 +122,7 @@ for(var key in user) alert(key); // name
|
|||
|
||||
Обратим внимание, вызов `defineProperty` не перезаписал свойство, а просто модифицировал настройки у существующего `toString`.
|
||||
|
||||
### Пример: свойство как функция-геттер
|
||||
## Свойство-функция
|
||||
|
||||
Дескриптор позволяет задать свойство, которое на самом деле работает как функция. Для этого в нём нужно указать эту функцию в `get`.
|
||||
|
||||
|
@ -157,11 +148,7 @@ alert(user.fullName); // Вася Петров
|
|||
*/!*
|
||||
```
|
||||
|
||||
**Обратим внимание, снаружи это обычное свойство `user.fullName`.**
|
||||
|
||||
Лишь в описании указывается, что на самом деле его значение возвращается функцией.
|
||||
|
||||
### Пример: свойство геттер-сеттер
|
||||
Обратим внимание, снаружи `fullName` -- это обычное свойство `user.fullName`. Но дескриптор указывает, что на самом деле его значение возвращается функцией.
|
||||
|
||||
Также можно указать функцию, которая используется для записи значения, при помощи дескриптора `set`.
|
||||
|
||||
|
@ -196,9 +183,9 @@ alert(user.firstName); // Петя
|
|||
alert(user.surname); // Иванов
|
||||
```
|
||||
|
||||
## Геттеры и сеттеры в литералах
|
||||
## Указание get/set в литералах
|
||||
|
||||
Если мы создаём объект при помощи синтаксиса `{ ... }`, то задать геттеры/сеттеры можно прямо в его определении.
|
||||
Если мы создаём объект при помощи синтаксиса `{ ... }`, то задать свойства-функции можно прямо в его определении.
|
||||
|
||||
Для этого используется особый синтаксис: `get свойство` или `set свойство`.
|
||||
|
||||
|
@ -234,13 +221,13 @@ alert(user.surname); // Иванов (поставил сеттер)
|
|||
*/!*
|
||||
```
|
||||
|
||||
## Да здравствуют геттеры и сеттеры!
|
||||
## Да здравствуют get/set!
|
||||
|
||||
Казалось бы, зачем нам назначать геттеры и сеттеры через всякие хитрые вызовы? Можно же сделать функции `getFullName`, `setFullName`...
|
||||
Казалось бы, зачем нам назначать get/set для свойства через всякие хитрые вызовы, когда можно сделать просто функции с самого начала? Например, `getFullName`, `setFullName`...
|
||||
|
||||
**Основной бонус -- возможность получить контроль над свойством в любой момент!**
|
||||
Конечно, в ряде случаев свойства выглядят короче, такое решение просто может быть красивым. Но основной бонус -- это гибкость, возможность получить контроль над свойством в любой момент!
|
||||
|
||||
В начале разработки мы можем использовать обычные свойства, например у `User` будет имя `name` и возраст `age`:
|
||||
Например, в начале разработки мы используем обычные свойства, например у `User` будет имя `name` и возраст `age`:
|
||||
|
||||
```js
|
||||
function User(name, age) {
|
||||
|
@ -253,11 +240,11 @@ var pete = new User("Петя", 25);
|
|||
alert(pete.age); // 25
|
||||
```
|
||||
|
||||
**С обычными свойствами в коде меньше букв, они удобны.**
|
||||
С обычными свойствами в коде меньше букв, они удобны, причины использовать функции пока нет.
|
||||
|
||||
...Но рано или поздно может наступить расплата!
|
||||
...Но рано или поздно может произойти что-то, что потребует более сложной логики.
|
||||
|
||||
Например, когда написано много кода, который использует эти свойства, формат данных изменился и теперь вместо возраста `age` хранится дата рождения `birthday`:
|
||||
Например, формат данных изменился и теперь вместо возраста `age` хранится дата рождения `birthday`:
|
||||
|
||||
```js
|
||||
function User(name, birthday) {
|
||||
|
@ -272,9 +259,7 @@ var pete = new User("Петя", new Date(1987, 6, 1));
|
|||
|
||||
Можно, конечно, найти все места и поправить их, но это долго, а иногда и невозможно, скажем, если вы взаимодействуете со сторонней библиотекой, код в которой -- чужой и влезать в него нежелательно.
|
||||
|
||||
Геттеры позволяют обойти проблему легко и непринуждённо.
|
||||
|
||||
Просто добавляем геттер `age`:
|
||||
Добавление `get`-функции `age` позволяет обойти проблему легко и непринуждённо:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -297,7 +282,7 @@ var pete = new User("Петя", new Date(1987, 6, 1));
|
|||
alert(pete.age); // получает возраст из даты рождения
|
||||
```
|
||||
|
||||
**Таким образом, `defineProperty` позволяет нам использовать обычные свойства и, при необходимости, в любой момент заменить их на функции, сохраняя совместимость внешнего интерфейса.**
|
||||
Таким образом, `defineProperty` позволяет нам использовать обычные свойства и, при необходимости, в любой момент заменить их на функции, сохраняя полную совместимость.
|
||||
|
||||
## Другие методы работы со свойствами
|
||||
|
||||
|
@ -338,7 +323,9 @@ alert( user.fullName ); // Петя Иванов
|
|||
<dt>[Object.keys(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys), [Object.getOwnPropertyNames(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames)</dt>
|
||||
<dd>Возвращают массив -- список свойств объекта.
|
||||
|
||||
При этом `Object.keys` возвращает только `enumerable`-свойства, а `Object.getOwnPropertyNames` -- все:
|
||||
`Object.keys` возвращает только `enumerable`-свойства.
|
||||
|
||||
`Object.getOwnPropertyNames` -- возвращает все:
|
||||
|
||||
```js
|
||||
//+ run
|
|
@ -50,7 +50,7 @@ Article.showCount(); // (2)
|
|||
|
||||
Здесь `Article.count` -- статическое свойство, а `Article.showCount` -- статический метод.
|
||||
|
||||
**Обратите внимание на контекст `this`. Несмотря на то, что переменная и метод -- статические, он всё ещё полезен. В строке `(1)` он равен `Article`!**
|
||||
Обратим внимание на использование `this` в примере выше. Несмотря на то, что переменная и метод -- статические, он всё ещё полезен. В строке `(1)` он равен `Article`.
|
||||
|
||||
## Пример: сравнение объектов
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Явное указание this: "call", "apply"
|
||||
|
||||
Итак, мы знаем, что в `this` -- это текущий объект при вызове "через точку" и новый объект при конструировании через `new`.
|
||||
Итак, мы знаем, что `this` -- это текущий объект при вызове "через точку" и новый объект при конструировании через `new`.
|
||||
|
||||
В этой главе наша цель получить окончательное и полное понимание `this` в JavaScript. Для этого не хватает всего одного элемента: способа явно указать `this` при помощи методов `call` и `apply`.
|
||||
|
||||
|
@ -26,7 +26,7 @@ function showFullName() {
|
|||
}
|
||||
```
|
||||
|
||||
**Обратите внимание, JavaScript позволяет использовать `this` везде. Любая функция может в своём коде упомянуть `this`, каким будет это значение -- выяснится в момент запуска.**
|
||||
Пока объекта нет, но это нормально, ведь JavaScript позволяет использовать `this` везде. Любая функция может в своём коде упомянуть `this`, каким будет это значение -- выяснится в момент запуска.
|
||||
|
||||
Вызов `showFullName.call(user)` запустит функцию, установив `this = user`, вот так:
|
||||
|
||||
|
@ -138,13 +138,13 @@ alert( obj.join(';') ); // "A;Б;В"
|
|||
|
||||
[/smart]
|
||||
|
||||
**...Однако, копирование метода из одного объекта в другой не всегда приемлемо!**
|
||||
...Однако, копирование метода из одного объекта в другой не всегда приемлемо!
|
||||
|
||||
Представим на минуту, что вместо `arguments` у нас -- произвольный объект. У него тоже есть числовые индексы, `length` и мы хотим вызвать в его контексте метод `[].join`. То есть, ситуация похожа на `arguments`, но (!) вполне возможно, что у объекта есть *свой* метод `join`.
|
||||
|
||||
Поэтому копировать `[].join`, как сделано выше, нельзя: если он перезапишет собственный `join` объекта, то будет страшный бардак и путаница.
|
||||
|
||||
**Безопасно вызвать метод нам поможет `call`:**
|
||||
Безопасно вызвать метод нам поможет `call`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -208,7 +208,16 @@ showFullName.call(user, 'firstName', 'surname');
|
|||
showFullName.apply(user, ['firstName', 'surname']);
|
||||
```
|
||||
|
||||
Преимущество `apply` перед `call` отчётливо видно в следующем примере, когда мы формируем массив аргументов динамически:
|
||||
Преимущество `apply` перед `call` отчётливо видно, когда мы формируем массив аргументов динамически.
|
||||
|
||||
Например, в JavaScript есть встроенная функция `Math.max(a, b, c...)`, которая возвращает максимальное значение из аргументов:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( Math.max(1, 5, 2) ); // 5
|
||||
```
|
||||
|
||||
При помощи `apply` мы могли бы найти максимум в произвольном массиве, вот так:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -221,57 +230,46 @@ arr.push(2);
|
|||
alert( Math.max.apply(null, arr) ); // 5
|
||||
```
|
||||
|
||||
Обратим внимание, в примере выше вызывается метод `Math.max`. Его стандартное применение -- это выбор максимального аргумента из переданных, которых может быть сколько угодно:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( Math.max(1, 5, 2) ); // 5
|
||||
alert( Math.max(1, 5, 2, 8) ); // 8
|
||||
```
|
||||
|
||||
В примере выше мы передали аргументы через массив -- второй параметр `apply`... Но вы, наверное, заметили небольшую странность? В качестве контекста `this` был передан `null`.
|
||||
|
||||
Строго говоря, полным эквивалентом вызову `Math.max(1,2,3)` был бы вызов `Math.max.apply(Math, [1,2,3])`. В обоих этих вызовах контекстом будет объект `Math`.
|
||||
|
||||
Но в данном случае в качестве контекста можно передавать что угодно, поскольку в своей внутренней реализации метод `Math.max`не использует `this`. Действительно, зачем `this`, если нужно всего лишь выбрать максимальный из аргументов?
|
||||
|
||||
**Вот так, при помощи `apply` мы получили короткий и элегантный способ вычислить максимальное значение в массиве!**
|
||||
Но в данном случае в качестве контекста можно передавать что угодно, поскольку в своей внутренней реализации метод `Math.max` не использует `this`. Действительно, зачем `this`, если нужно всего лишь выбрать максимальный из аргументов? Вот так, при помощи `apply` мы получили короткий и элегантный способ вычислить максимальное значение в массиве!
|
||||
|
||||
[smart header="Вызов `call/apply` с `null` или `undefined`"]
|
||||
|
||||
В старом стандарте при указании первого аргумента `null` или `undefined` в `call/apply`, функция получала `this = window`, например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function f() {
|
||||
alert(this);
|
||||
}
|
||||
|
||||
f.call(null); // window
|
||||
```
|
||||
|
||||
Это поведение исправлено в современном стандарте ([15.3](http://es5.github.com/x15.3.html#x15.3.4.3)).
|
||||
|
||||
Если функция работает в строгом режиме, то `this` передаётся "как есть":
|
||||
В современном стандарте `call/apply` передают `this` "как есть". А в старом, без `use strict`, при указании первого аргумента `null` или `undefined` в `call/apply`, функция получает `this = window`, например:
|
||||
|
||||
Современный стандарт:
|
||||
```js
|
||||
//+ run
|
||||
function f() {
|
||||
"use strict";
|
||||
*!*
|
||||
alert(this); // null, а не window
|
||||
alert(this); // null
|
||||
*/!*
|
||||
}
|
||||
|
||||
f.call(null);
|
||||
```
|
||||
|
||||
Без `use strict`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function f() {
|
||||
alert(this); // window
|
||||
}
|
||||
|
||||
f.call(null);
|
||||
```
|
||||
|
||||
[/smart]
|
||||
|
||||
## Итого про this
|
||||
|
||||
|
||||
Значение `this` устанавливается в зависимости от того, как вызвана функция:
|
||||
|
||||
<dl>
|
||||
<dt>При вызове функции как метода</dt>
|
||||
<dd>
|