This commit is contained in:
Ilya Kantor 2014-11-16 01:40:20 +03:00
parent 962caebbb7
commit 87bf53d076
1825 changed files with 94929 additions and 0 deletions

View file

@ -0,0 +1,24 @@
Ответ: `1`.
```js
//+ run untrusted refresh
if ("a" in window) {
var a = 1;
}
alert(a);
```
Посмотрим, почему.
На стадии подготовки к выполнению, из `var a` создается `window.a`:
```js
// window = {a:undefined}
if ("a" in window) { // в if видно что window.a уже есть
var a = 1; // поэтому эта строка сработает
}
alert(a);
```
В результате `a` становится `1`.

View file

@ -0,0 +1,13 @@
# Window и переменная
[importance 5]
Каков будет результат кода?
```js
if ("a" in window) {
var a = 1;
}
alert(a);
```

View file

@ -0,0 +1,12 @@
Ответ: **ошибка**.
Переменной `a` нет, так что условие `"a" in window` не выполнится. В результате на последней строчке - обращение к неопределенной переменной.
```js
//+ run untrusted refresh
if ("a" in window) {
a = 1;
}
alert(a); // <-- error!
```

View file

@ -0,0 +1,13 @@
# Window и переменная 2
[importance 5]
Каков будет результат (перед `a` нет `var`)?
```js
if ("a" in window) {
a = 1;
}
alert(a);
```

View file

@ -0,0 +1,14 @@
Ответ: `1`.
Переменная `a` создается до начала выполнения кода, так что условие `"a" in window` выполнится и сработает `a = 1`.
```js
//+ run untrusted refresh
if ("a" in window) {
a = 1;
}
var a;
alert(a); // 1
```

View file

@ -0,0 +1,15 @@
# Window и переменная 3
[importance 5]
Каков будет результат (перед `a` нет `var`, а ниже есть)?
```js
if ("a" in window) {
a = 1;
}
var a;
alert(a);
```

View file

@ -0,0 +1,19 @@
Ответ: `5`.
```js
//+ run untrusted
var a = 5;
function a() { }
alert(a);
```
Чтобы понять, почему -- разберём внимательно как работает этот код.
<ol>
<li>До начала выполнения создаётся переменная `a` и функция `a`. Стандарт написан так, что функция создаётся первой и переменная ее не перезаписывает. То есть, функция имеет приоритет. Но в данном случае это совершенно неважно, потому что...
</li>
<li>...После инициализации, когда код начинает выполняться -- срабатывает присваивание `a = 5`, перезаписывая `a`, и уже не важно, что там лежало.</li>
<li>Объявление `Function Declaration` на стадии выполнения игнорируется (уже обработано).</li>
<li>В результате `alert(a)` выводит 5.</li>
</ol>

View file

@ -0,0 +1,15 @@
# Функция и переменная
[importance 3]
Каков будет результат кода? Почему?
```js
var a = 5;
function a() { }
alert(a);
```
P.S. Это задание -- учебное, на понимание процесса инициализации и выполнения. В реальной жизни мы, конечно же, не будем называть переменную и функцию одинаково.

View file

@ -0,0 +1,242 @@
# Глобальный объект
Механизм работы функций и переменных в JavaScript очень отличается от большинства языков.
Чтобы его понять, мы в этой главе рассмотрим переменные и функции в глобальной области. А в следующей -- пойдём дальше.
[cut]
## Глобальный объект
*Глобальными* называют переменные и функции, которые не находятся внутри какой-то функции. То есть, иными словами, если переменная или функция не находятся внутри конструкции `function`, то они -- "глобальные".
**В JavaScript все глобальные переменные и функции являются свойствами специального объекта, который называется *"глобальный объект"* (`global object`).**
В браузере этот объект явно доступен под именем `window`. Объект `window` одновременно является глобальным объектом и содержит ряд свойств и методов для работы с окном браузера, но нас здесь интересует только его роль как глобального объекта.
В других окружениях, например Node.JS, глобальный объект может быть недоступен в явном виде, но суть происходящего от этого не изменяется, поэтому далее для обозначения глобального объекта мы будем использовать `"window"`.
**Присваивая или читая глобальную переменную, мы, фактически, работаем со свойствами `window`.**
Например:
```js
//+ run untrusted refresh
var a = 5; // объявление var создаёт свойство window.a
alert(window.a); // 5
```
Создать переменную можно и явным присваиванием в `window`:
```js
//+ run untrusted refresh
window.a = 5;
alert(a); // 5
```
## Порядок инициализации
Выполнение скрипта происходит в две фазы:
<ol>
<li>На первой фазе происходит инициализация, подготовка к запуску.
Во время инициализации скрипт сканируется на предмет объявления функций вида [Function Declaration](/function-declaration-expression), а затем -- на предмет объявления переменных `var`. Каждое такое объявление добавляется в `window`.
**Функции, объявленные как Function Declaration, создаются сразу работающими, а переменные -- равными `undefined`.**
</li>
<li>На второй фазе -- собственно, выполнение.
Присваивание (`=`) значений переменных происходит на второй фазе, когда поток выполнения доходит до соответствующей строчки кода.
</li>
</ol>
В начале кода ниже указано содержание глобального объекта на момент окончания инициализации:
```js
// По окончании инициализации, до выполнения кода:
*!*
// window = { f: function, a: undefined, g: undefined }
*/!*
var a = 5; // при инициализации даёт: window.a=undefined
function f(arg) { /*...*/ } // при инициализации даёт: window.f = function
var g = function(arg) { /*...*/ }; // при инициализации даёт: window.g = undefined
```
Кстати, тот факт, что к началу выполнения кода переменные и функции *уже* содержатся в `window`, можно легко проверить:
```js
//+ run untrusted refresh
alert("a" in window); // *!*true*/!*, т.к. есть свойство window.a
alert(a); // равно *!*undefined*/!*, присваивание будет выполнено далее
alert(f); // *!*function ...*/!*, готовая к выполнению функция
alert(g); // *!*undefined*/!*, т.к. это переменная, а не Function Declaration
var a = 5;
function f() { /*...*/ }
var g = function() { /*...*/ };
```
[smart header="Присвоение переменной без объявления"]
В старом стандарте JavaScript переменную можно было создать и без объявления `var`:
```js
//+ run
a = 5;
alert(a); // 5
```
Такое присвоение, как и `var a = 5`, создает свойство `window.a = 5`. Отличие от `var a = 5` -- в том, что переменная будет создана не на этапе входа в область видимости, а в момент присвоения.
Сравните два кода ниже.
Первый выведет `undefined`, так как переменная была добавлена в `window` на фазе инициализации:
```js
//+ run untrusted refresh
*!*
alert(a); // undefined
*/!*
var a = 5;
```
Второй код выведет ошибку, так как переменной ещё не существует:
```js
//+ run untrusted refresh
*!*
alert(a); // error, a is not defined
*/!*
a = 5;
```
**Вообще, рекомендуется всегда объявлять переменные через `var`.**
В современном стандарте присваивание без `var` вызовет ошибку:
```js
//+ run
'use strict';
a = 5; // error, a is not defined
```
[/smart]
[smart header="Конструкции `for, if...` не влияют на видимость переменных"]
Фигурные скобки, которые используются в `for, while, if`, в отличие от объявлений функции, имеют "декоративный" характер.
В JavaScript нет разницы между объявлением вне блока:
```js
*!*var*/!* i;
{
i = 5;
}
```
...И внутри него:
```js
i = 5;
{
*!*var*/!* i;
}
```
**Также нет разницы между объявлением в цикле и вне его:**
```js
//+ run untrusted refresh
for (*!*var*/!* i=0; i<5; i++) { }
```
Идентичный по функциональности код:
```js
//+ run untrusted refresh
*!*var i;*/!*
for (i=0; i<5; i++) { }
```
В обоих случаях переменная будет создана до выполнения цикла, на стадии инициализации, и ее значение будет сохранено после окончания цикла.
[/smart]
[smart header="Не важно, где и сколько раз объявлена переменная"]
Объявлений `var` может быть сколько угодно:
```js
var i = 10;
for (var i=0; i<20; i++) {
...
}
var i = 5;
```
**Все `var` будут обработаны один раз, на фазе инициализации.**
На фазе исполнения объявления `var` будут проигнорированы: они уже были обработаны. Зато будут выполнены присваивания.
[/smart]
[warn header="Ошибки при работе с `window` в IE8-"]
В старых IE есть две забавные ошибки при работе с переменными в `window`:
<ol>
<li>Переопределение переменной, у которой такое же имя, как и `id` элемента, приведет к ошибке:
```html
<!--+ run -->
<div id="a">...</div>
<script>
a = 5; // ошибка в IE<9! Правильно будет "var a = 5"
alert(a); // никогда не сработает
</script>
```
А если сделать через `var`, то всё будет хорошо.
Это была реклама того, что надо везде ставить `var`.
</li>
<li>Ошибка при рекурсии через функцию-свойство `window`. Следующий код "умрет" в IE<9:
```html
<!--+ run height=0 -->
<script>
// рекурсия через функцию, явно записанную в window
window.recurse = function(times) {
if (times !== 0) recurse(times-1);
}
recurse(13);
</script>
```
Проблема здесь возникает из-за того, что функция напрямую присвоена в `window.recurse = ...`. Ее не будет при обычном объявлении функции.
**Этот пример выдаст ошибку только в настоящем IE8!** Не IE9 в режиме эмуляции. Вообще, режим эмуляции позволяет отлавливать где-то 95% несовместимостей и проблем, а для оставшихся 5% вам нужен будет настоящий IE8 в виртуальной машине.
</li>
</ol>
[/warn]
## Итого
В результате инициализации, к началу выполнения кода:
<ol>
<li>Функции, объявленные как `Function Declaration`, создаются полностью и готовы к использованию.</li>
<li>Переменные объявлены, но равны `undefined`. Присваивания выполнятся позже, когда выполнение дойдет до них.</li>
</ol>

View file

@ -0,0 +1,17 @@
Ошибки не будет, выведет `"Вася, undefined"`.
```js
//+ run
*!*
say('Вася'); // Что выведет? Не будет ли ошибки?
*/!*
var phrase = 'Привет';
function say(name) {
alert(name + ", " + phrase);
}
```
Переменная как таковая существует, вот только на момент запуска функции она равна `undefined`.

View file

@ -0,0 +1,19 @@
# Что выведет say в начале кода?
[importance 5]
Что будет, если вызов `sayHi('Вася');` стоит в самом-самом начале, в первой строке кода?
```js
*!*
say('Вася'); // Что выведет? Не будет ли ошибки?
*/!*
var phrase = 'Привет';
function say(name) {
alert(name + ", " + phrase);
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -0,0 +1,9 @@
**Результатом будет `true`**, т.к. `var` обработается и переменная будет создана до выполнения кода.
Соответственно, присвоение `value=true` сработает на локальной переменной, и `alert` выведет `true`.
**Внешняя переменная не изменится.**
P.S. Если `var` нет, то в функции переменная не будет найдена. Интерпретатор обратится за ней в `window` и изменит её там.
**Так что без `var` результат будет также `true`, но внешняя переменная изменится.**

View file

@ -0,0 +1,25 @@
# В какую переменную будет присвоено значение?
[importance 5]
Каков будет результат выполнения этого кода?
```js
var value = 0;
function f() {
if (1) {
value = true;
} else {
var value = false;
}
alert(value);
}
f();
```
Изменится ли внешняя переменная `value` ?
P.S. Какими будут ответы, если из строки `var value = false` убрать `var`?

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View file

@ -0,0 +1,29 @@
Результатом будет `undefined`, затем `5`.
```js
//+ run
function test() {
alert(window);
var window = 5;
alert(window);
}
test();
```
Такой результат получился потом, что `window` -- это глобальная переменная, но ничто не мешает объявить такую же локальную.
Директива `var window` обработается до начала выполнения кода функции и будет создана локальная переменная, т.е. свойство `LexicalEnvironment.window`:
```js
LexicalEnvironment = {
window: undefined
}
```
Когда выполнение кода начнется и сработает `alert`, он выведет уже локальную переменную, которая на тот момент равна `undefined`.
Затем сработает присваивание, и второй `alert` выведет уже `5`.

View file

@ -0,0 +1,19 @@
# var window
[importance 5]
Каков будет результат выполнения этого кода? Почему?
```js
function test() {
alert(window);
var window = 5;
alert(window);
}
test();
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -0,0 +1,37 @@
Результат - **ошибка**. Попробуйте:
```js
//+ run
var a = 5
(function() {
alert(a)
})()
```
Дело в том, что после `var a = 5` нет точки с запятой.
JavaScript воспринимает этот код как если бы перевода строки не было:
```js
//+ run
var a = 5(function() {
alert(a)
})()
```
То есть, он пытается вызвать *функцию* `5`, что и приводит к ошибке.
Если точку с запятой поставить, все будет хорошо:
```js
//+ run
var a = 5;
(function() {
alert(a)
})()
```
Это один из наиболее частых и опасных подводных камней, приводящих к ошибкам тех, кто *не* ставит точки с запятой.

View file

@ -0,0 +1,16 @@
# Вызов "на месте"
[importance 4]
Каков будет результат выполнения кода? Почему?
```js
var a = 5
(function() {
alert(a)
})()
```
P.S. *Подумайте хорошо! Здесь все ошибаются!*
P.P.S. *Внимание, здесь подводный камень! Ок, вы предупреждены.*

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1,3 @@
Нет, нельзя.
Локальная переменная полностью перекрывает внешнюю.

View file

@ -0,0 +1,17 @@
# Перекрытие переменной
[importance 4]
Если во внутренней функции есть своя переменная с именем `currentCount` -- можно ли в ней получить `currentCount` из внешней функции?
```js
function makeCounter() {
var currentCount = 1;
return function() {
var currentCount;
// можно ли здесь вывести currentCount из внешней функции (равный 1)?
};
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1,30 @@
Выведут **1,2,3,4.**
Здесь внутренняя функция будет искать -- и находить `currentCount` каждый раз в самом внешнем объекте переменных: глобальном объекте `window`.
В результате все счётчики будут разделять единое, глобальное текущее значение.
```js
//+ run
var currentCount = 1;
function makeCounter() {
return function() {
return currentCount++;
};
}
var counter = makeCounter();
var counter2 = makeCounter();
*!*
alert( counter() ); // ?
alert( counter() ); // ?
*/!*
*!*
alert( counter2() ); // ?
alert( counter2() ); // ?
*/!*
```

View file

@ -0,0 +1,29 @@
# Глобальный счётчик
[importance 5]
Что выведут эти вызовы, если переменная `currentCount` находится вне `makeCounter`?
```js
var currentCount = 1;
function makeCounter() {
return function() {
return currentCount++;
};
}
var counter = makeCounter();
var counter2 = makeCounter();
*!*
alert( counter() ); // ?
alert( counter() ); // ?
*/!*
*!*
alert( counter2() ); // ?
alert( counter2() ); // ?
*/!*
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View file

@ -0,0 +1,435 @@
# Замыкания, функции изнутри
В этой главе мы продолжим рассматривать, как работают переменные, и, как следствие, познакомимся с замыканиями. От глобального объекта мы переходим к работе внутри функций.
[cut]
## Лексическое окружение
Все переменные внутри функции -- это свойства специального внутреннего объекта `LexicalEnvironment`, который создаётся при её запуске.
Мы будем называть этот объект "лексическое окружение" или просто "объект переменных".
При запуске функция создает объект `LexicalEnvironment`, записывает туда аргументы, функции и переменные. Процесс инициализации выполняется в том же порядке, что и для глобального объекта, который, вообще говоря, является частным случаем лексического окружения.
В отличие от `window`, объект `LexicalEnvironment` является внутренним, он скрыт от прямого доступа.
### Пример
Посмотрим пример, чтобы лучше понимать, как это работает:
```js
function sayHi(name) {
var phrase = "Привет, " + name;
alert(phrase);
}
sayHi('Вася');
```
При вызове функции:
<ol>
<li>До выполнения первой строчки её кода, на стадии инициализации, интерпретатор создает пустой объект `LexicalEnvironment` и заполняет его.
В данном случае туда попадает аргумент `name` и единственная переменная `phrase`:
```js
function sayHi(name) {
*!*
// LexicalEnvironment = { name: 'Вася', phrase: undefined }
*/!*
var phrase = "Привет, " + name;
alert(phrase);
}
sayHi('Вася');
```
</li>
<li>Функция выполняется.
Во время выполнения происходит присвоение локальной переменной `phrase`, то есть, другими словами, присвоение свойству `LexicalEnvironment.phrase` нового значения:
```js
function sayHi(name) {
// LexicalEnvironment = { name: 'Вася', phrase: undefined }
var phrase = "Привет, " + name;
*!*
// LexicalEnvironment = { name: 'Вася', phrase: 'Привет, Вася'}
*/!*
alert(phrase);
}
sayHi('Вася');
```
</li>
<li>В конце выполнения функции объект с переменными обычно выбрасывается и память очищается.</li>
</ol>
[smart header="Тонкости спецификации"]
Если почитать спецификацию ECMA-262, то мы увидим, что речь идёт о двух объектах: `VariableEnvironment` и `LexicalEnvironment`.
Но там же замечено, что в реализациях эти два объекта могут быть объединены. Так что мы избегаем лишних деталей и используем везде термин `LexicalEnvironment`, это достаточно точно позволяет описать происходящее.
Более формальное описание находится в спецификации ECMA-262, секции 10.2-10.5 и 13.
[/smart]
## Доступ ко внешним переменным
Из функции мы можем обратиться не только к локальной переменной, но и к внешней:
```js
var a = 5;
function f() {
alert(a); // 5
}
```
**Интерпретатор, при доступе к переменной, сначала пытается найти переменную в текущем `LexicalEnvironment`, а затем, если её нет -- ищет во внешнем объекте переменных. В данном случае им является `window`.**
Такой порядок поиска возможен благодаря тому, что ссылка на внешний объект переменных хранится в специальном внутреннем свойстве функции, которое называется `[[Scope]]`. Это свойство закрыто от прямого доступа, но знание о нём очень важно для понимания того, как работает JavaScript.
Рассмотрим, как `[[Scope]]` создаётся и используется:
<ol>
<li>Всё начинается с момента создания функции. Функция создается не в вакууме, а в некотором лексическом окружении.
В случае выше функция создается в глобальном лексическом окружении `window`:
<img src="1.png">
**Для того, чтобы функция могла в будущем обратиться к внешним переменным, в момент создания она получает скрытое свойство `[[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>
Если обобщить:
<ul>
<li>Каждая функция при создании получает ссылку `[[Scope]]` на объект с переменными, в контексте которого была создана.</li>
<li>При запуске функции создаётся новый объект с переменными `LexicalEnvironment`. Он получает ссылку на внешний объект переменных из `[[Scope]]`.</li>
<li>При поиске переменных он осуществляется сначала в текущем объекте переменных, а потом -- по этой ссылке. Благодаря этому в функции доступны внешние переменные.</li>
</ul>
### Значение переменных -- всегда текущее
Значение переменной из внешней области берётся всегда текущее, на момент запуска, а не то, которое было на момент создания функции.
Например, в коде ниже функция `sayHi` берёт `phrase` из внешней области:
```js
//+ run
var phrase = 'Привет';
function say(name) {
alert(name + ", " + phrase);
}
*!*
say('Вася'); // Вася, Привет (*)
*/!*
phrase = 'Пока';
*!*
say('Вася'); // Вася, Пока (**)
*/!*
```
На момент выполнения строки `(*)`, переменная `phrase` имела значение `'Привет'`, а потом изменила его на `'Пока'`. Функция всегда берёт то внешнее значение, которое есть сейчас.
Это естественно, ведь для доступа к внешним переменным функция использует ссылку на внешний объект с ними -- на внешний объект целиком! А не на каждое его свойство (переменную) по отдельности. Если переменная меняется, то при новом доступе функция всегда получит текущее, последнее её значение.
## Вложенные функции
Внутри функции можно объявлять не только локальные переменные, но и другие функции.
К примеру, вложенная функция может помочь лучше организовать код:
```js
//+ run
function sayHi(firstName, lastName) {
alert( "Привет, " + getFullName() );
*!*
function getFullName() {
return firstName + " " + lastName;
}
*/!*
}
sayHi("Вася", "Пупкин"); // Привет, Вася Пупкин
```
Здесь, для наглядности, для вычислений создана функция `getFullName()`.
**Вложенные функции обрабатываются в точности так же, как и глобальные. Единственная разница -- они создаются в объекте переменных внешней функции, а не в `window`.**
При запуске функции `sayHi("Вася", "Пупкин")`:
<ul>
<li>Интерпретатор создаст объект для переменных.</li>
<li>Заполнит его аргументами текущего вызова и локальными переменными:
```js
function sayHi(firstName, lastName) {
*!*
// LexicalEnvironment = {
// firstName: "Вася",
// lastName: "Пупкин",
// getFullName: function
// }
*/!*
alert( "Привет, " + getFullName() ); // (*)
function getFullName() {
return firstName + " " + lastName;
}
}
sayHi("Вася", "Пупкин"); // Привет, Вася Пупкин
```
</li>
<li>Далее, в строке `(*)`, при вызове `getFullName()` -- она получит ссылку на внешний объект переменных, достанет оттуда `firstName` и `lastName`.</li>
</ul>
## Возврат функции
Рассмотрим более "продвинутый" вариант, при котором внутри одной функции создаётся другая и возвращается в качестве результата.
Здесь мы будем создавать функцию-счётчик. Это, конечно, учебный пример, дальше будут задачи посложнее, поближе к реальности, ну а при изучении интерфейсов создавать и передавать туда-сюда функцию будет вообще стандартным приёмом разработки.
В примере ниже `makeCounter` создает функцию, которая считает свои вызовы:
```js
//+ run
function makeCounter() {
var currentCount = 1;
return function() { // (**)
return currentCount++;
};
}
var counter = makeCounter(); // (*)
// каждый вызов увеличивает счётчик и возвращает результат
alert( counter() ); // 1
alert( counter() ); // 2
alert( counter() ); // 3
// создать другой счётчик, он будет независим от первого
var counter2 = makeCounter();
alert( counter2() ); // 1
```
Хранение текущего числа вызовов осуществляется в переменной `currentCount` внешней функции.
Что здесь, вообще, происходит?
**Первый этап -- вызов `makeCounter()`:**
<ol>
<li>В строке `(*)` запускается `makeCounter()`. При этом создаётся `LexicalEnvironment` для переменных текущего вызова. В функции есть одна переменная `var currentCount`, которая станет свойством этого объекта. Она изначально инициализуется в `undefined`, затем, в процессе выполнения, получит значение `1`:
```js
function makeCounter() {
*!*
// LexicalEnvironment = { currentCount: undefined } -> window
*/!*
var currentCount = 1;
*!*
// LexicalEnvironment = { currentCount: 1 } -> window
*/!*
return function() { // [[Scope]] -> LexicalEnvironment (**)
return currentCount++;
};
}
var counter = makeCounter(); // (*)
```
</li>
<li>В процессе выполнения `makeCounter()` создаёт функцию в строке `(**)`. При создании эта функция получает внутреннее свойство `[[Scope]]` со ссылкой на текущий `LexicalEnvironment`.</li>
<li>Далее вызов `makeCounter()` завершается и функция `(**)` возвращается и сохраняется во внешней переменной `counter` `(*)`.</li>
</ol>
**На этом первый этап можно считать завершённым.**
В результате вызова `makeCounter` в переменную `counter` была записана функция:
```js
var counter = function() { // [[Scope]] -> {currentCount: 1} -> window
return currentCount++;
};
```
**Возвращённая из `makeCounter()` функция `counter` отличается от "просто функции" тем, что она помнит (через `[[Scope]]`) о том, в каком окружении была создана.**
Скорее всего, когда-нибудь функция `counter` будет вызвана. Мы не знаем, когда это произойдёт. Может быть, прямо сейчас, но, вообще говоря, совсем не факт. Этот вызов может быть сильно отделён по времени, поэтому назовём происходящее "вторым этапом".
**Второй этап -- вызов `counter`:**
<ol>
<li>Эта функция состоит из одной строки: `return currentCount++`, ни переменных ни параметров в ней нет, поэтому её собственный объект переменных, для краткости назовём его `LE` -- будет пуст.
Единственное, что у него есть -- так это ссылка на внешний `LexicalEnvironment`, которую он получит из `[[Scope]]`:
```js
var counter = function() {
//в процессе запуска LE = {} -> {currentCount: 1} -> window
return currentCount++;
};
```
</li>
<li>Чтобы увеличить и вернуть `currentCount`, интерпретатор ищет в текущем объекте переменных `LE`, но он пуст, затем идёт во внешний объект, там находит, изменяет и возвращает новое значение:
```js
//+ run
function makeCounter() {
var currentCount = 1;
return function() {
return currentCount++;
};
}
var counter = makeCounter(); // [[Scope]] -> {currentCount: 1} -> window
alert( counter() ); // 1, [[Scope]] -> {currentCount: 1} -> window
alert( counter() ); // 2, [[Scope]] -> {currentCount: 2} -> window
alert( counter() ); // 3, [[Scope]] -> {currentCount: 3} -> window
```
</li>
</ol>
Можно создать несколько счётчиков. Все они будут взаимно независимы:
```js
var counter = makeCounter();
var counter2 = makeCounter();
alert( counter() ); // 1
alert( counter() ); // 2
alert( counter() ); // 3
alert( counter2() ); // 1, *!*счётчики независимы*/!*
```
Они независимы, потому что при каждом запуске `makeCounter` создаётся свой `LexicalEnvironment`, на который имеет ссылку соответствующий счётчик.
## Альтернатива -- свойство функции
Функция в JavaScript является объектом, поэтому можно присваивать свойства прямо к ней.
Перепишем пример со счётчиком, используя запись в функцию:
```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
```
Как видно, с виду пример работает также. Но внутри всё по-другому.
**Свойство функции, в отличие от переменной из замыкания -- общедоступно. К нему имеет доступ любой, у кого есть объект функции.**
Например, можно взять и поменять счётчик из внешнего кода:
```js
var counter = makeCounter();
alert( counter() ); // 1
*!*
counter.currentCount = 5;
*/!*
alert( counter() ); // 5
```
[smart header="Статические переменные"]
Иногда свойства, привязанные к функции, называют "статическими переменными".
В некоторых языках программирования можно объявлять переменную, которая сохраняет значение между вызовами функции. В JavaScript ближайший аналог -- это свойство функции.
[/smart]
## Итого: замыкания
[Замыкание](http://en.wikipedia.org/wiki/Closure_(computer_science&#041;) -- это функция вместе со всеми внешними переменными, которые ей доступны.
Таково стандартное определение, которое есть в Wikipedia и большинстве серьёзных источников по программированию. То есть, замыкание -- это функция + внешние переменные.
Тем не менее, в JavaScript есть небольшая терминологическая особенность.
**Обычно, говоря "замыкание функции", подразумевают не саму эту функцию, а именно внешние переменные.**
**Иногда говорят "переменная берётся из замыкания". Это означает -- из внешнего объекта переменных.**
[smart header="Что это такое -- \"понимать замыкания?\""]
Иногда говорят "Вася крут, Вася понимает замыкания!". Что это такое -- "понимать замыкания", какой смысл обычно вкладывают в эти слова?
"Понимать замыкания" в JavaScript означает понимать следующие вещи:
<ol>
<li>Все переменные и параметры функций являются свойствами объекта переменных `LexicalEnvironment`. Каждый запуск функции создает новый такой объект. На верхнем уровне роль `LexicalEnvironment` играет "глобальный объект", в браузере это `window`.</li>
<li>При создании функция получает системное свойство `[[Scope]]`, которое ссылается на `LexicalEnvironment`, в котором она была создана (кроме `new Function`).</li>
<li>Свойство `[[Scope]]` создаётся вместе с функцией и далее не меняется. Когда бы ни была вызвана функция, куда бы её ни передали в коде -- она будет искать переменные сначала у себя, а затем во внешних `LexicalEnvironment` с места своего создания.</li>
</ol>
В следующих главах мы углубим и расширим это понимание дополнительными примерами, а также рассмотрим, что происходит с памятью.
[/smart]

View file

@ -0,0 +1,90 @@
# [[Scope]] для new Function
## Присвоение [[Scope]] для new Function [#scope-Function]
Есть одно исключение из общего правила присвоения `[[Scope]]`, которое мы рассматривали в предыдущей главе.
**При создании функции с использованием `new Function`, её свойство `[[Scope]]` ссылается не на текущий `LexicalEnvironment`, а на `window`.**
## Пример
Следующий пример демонстрирует как функция, созданная `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;
*!*
var func = new Function('', 'alert(a)');
*/!*
return func;
}
getFunc()(); // *!*1*/!*, из window
```
## Почему так сделано?
[warn header="Продвинутые знания"]
Содержимое этой секции содержит информацию теоретического характера, которая прямо сейчас не обязательна для дальнейшего изучения JavaScript.
[/warn]
**Эта особенность `new Function`, хоть и выглядит странно, на самом деле весьма полезна.**
Представьте себе, что нам действительно нужно создать функцию из строки кода. Наверняка код этой функции неизвестен на момент написания скрипта (иначе зачем `new Function`), но станет известен позже, например получен с сервера или из других источников данных.
При выполнении кода на боевом сервере он наверняка сжат минификатором -- специальной программой, которая уменьшает размер кода, убирая из него лишние комментарии, пробелы, что очень важно -- переименовывает локальные переменные на более короткие.
То есть, обычно если внутри функции есть `var userName`, то минификатор заменит её на `var u` (или другую букву, чтобы не было конфликта), предполагая, что так как переменная видна только внутри функции, то этого всё равно никто не заметит, а код станет короче. И обычно проблем нет.
...Но если бы `new Function` могла обращаться к внешним переменным, то при попытке доступа к `userName` в сжатом коде была бы ошибка, так как минификатор переименовал её.
**Получается, что даже если бы мы захотели использовать локальные переменные в `new Function`, то после сжатия были бы проблемы, так как минификатор переименовывает локальные переменные.**
Описанная особенность `new Function` просто-таки спасает нас от ошибок.
Если внутри функции, создаваемой через `new Function`, всё же нужно использовать локальные переменные -- нужно всего лишь предусмотреть соответствующие параметры и передавать их явным образом, например так:
```js
//+ run untrusted refresh
*!*
var sum = new Function('a, b', ' return a + b; ');
*/!*
var a = 1, b = 2;
*!*
alert( sum(a, b) ); // 3
*/!*
```
## Итого
<ul>
<li>Функции, создаваемые через `new Function`, имеют значением `[[Scope]]` не внешний объект переменных, а `window`.</li>
<li>Следствие -- такие функции не могут использовать замыкание. Но это хорошо, так как бережёт от ошибок проектирования, да и при сжатии JavaScript проблем не будет. Если же внешние переменные реально нужны -- их можно передать в качестве параметров.</li>
</ul>

View file

@ -0,0 +1,304 @@
# Модули через замыкания
Приём программирования "модуль" имеет громадное количество вариаций.
Его цель -- скрыть внутренние детали реализации скрипта. В том числе: временные переменные, константы, вспомогательные мини-функции и т.п.
## Зачем нужен модуль?
Допустим, мы хотим разработать скрипт, который делает что-то полезное.
В браузере скрипты могут делать много чего -- если бы мы умели работать со страницей, то могли бы сделать так, чтобы все блоки кода красиво расцвечивались, так сделано на этом сайте.
Но, так как пока мы со страницей работать не умеем (скоро научимся), то пусть скрипт просто выводит сообщение:
Файл `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`.
**Но снаружи программист, использующий модуль, может обращаться напрямую только к тем, которые экспортированы.**
Благодаря этому будут скрыты внутренние аспекты реализации, которые нужны только разработчику модуля.
Можно придумать и много других вариаций такого подхода. В конце концов, "модуль" -- это всего лишь функция-обёртка для скрытия переменных.

View file

@ -0,0 +1,10 @@
// глобальная переменная нашего скрипта
var message = "Привет";
// функция для вывода этой переменной
function showMessage() {
alert(message);
}
// выводим сообщение
showMessage();

View file

@ -0,0 +1,20 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<script>
var message = 'Пожалуйста, нажмите на кнопку';
</script>
<script src="highlight.js"></script>
</head>
<body>
<button>Кнопка</button>
<script>
alert(message);
</script>
</body>
</html>

View file

@ -0,0 +1,14 @@
(function() {
// глобальная переменная нашего скрипта
var message = "Привет";
// функция для вывода этой переменной
function showMessage() {
alert(message);
}
// выводим сообщение
showMessage();
})();

View file

@ -0,0 +1,20 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<script>
var message = 'Пожалуйста, нажмите на кнопку';
</script>
<script src="highlight.js"></script>
</head>
<body>
<button>Кнопка</button>
<script>
alert(message);
</script>
</body>
</html>

View file

@ -0,0 +1,18 @@
Чтобы вторые скобки в вызове работали - первые должны возвращать функцию.
Эта функция должна знать про `a` и уметь прибавлять `a` к `b`. Вот так:
```js
//+ run
function sum(a) {
return function(b) {
return a + b; // возьмет a из внешнего LexicalEnvironment
};
}
alert( sum(1)(2) );
alert( sum(5)(-1) );
```

View file

@ -0,0 +1,13 @@
# Сумма через замыкание
[importance 4]
Напишите функцию `sum`, которая работает так: `sum(a)(b) = a+b`.
Да, именно так, через двойные скобки (это не опечатка). Например:
```js
sum(1)(2) = 3
sum(5)(-1) = 4
```

View file

@ -0,0 +1,10 @@
function makeBuffer() {
var text = '';
return function(piece) {
if (arguments.length == 0) { // вызов без аргументов
return text;
}
text += piece;
};
};

View file

@ -0,0 +1,22 @@
var buffer;
beforeEach(function() {
buffer = makeBuffer();
});
it("возвращает пустую строку по умолчанию", function() {
assert.strictEqual( buffer(), "");
});
it("добавляет аргументы в буффер", function() {
buffer('Замыкания');
buffer(' Использовать');
buffer(' Нужно!');
assert.equal( buffer(), 'Замыкания Использовать Нужно!');
});
it("приводит всё к строке", function() {
buffer(null);
buffer(false);
assert.equal( buffer(), "nullfalse");
});

View file

@ -0,0 +1,30 @@
Текущее значение текста удобно хранить в замыкании, в локальной переменной `makeBuffer`:
```js
//+ run
function makeBuffer() {
var text = '';
return function(piece) {
if (arguments.length == 0) { // вызов без аргументов
return text;
}
text += piece;
};
};
var buffer = makeBuffer();
// добавить значения к буферу
buffer('Замыкания');
buffer(' Использовать');
buffer(' Нужно!');
alert( buffer() ); // 'Замыкания Использовать Нужно!'
var buffer2 = makeBuffer();
buffer2(0); buffer2(1); buffer2(0);
alert( buffer2() ); // '010'
```
Начальное значение `text = ''` -- пустая строка. Поэтому операция `text += piece` прибавляет `piece` к строке, автоматически преобразуя его к строковому типу, как и требовалось в условии.

View file

@ -0,0 +1,43 @@
# Функция - строковый буфер
[importance 5]
В некоторых языках программирования существует объект "строковый буфер", который аккумулирует внутри себя значения. Его функционал состоит из двух возможностей:
<ol>
<li>Добавить значение в буфер.</li>
<li>Получить текущее содержимое.</li>
</ol>
**Задача -- реализовать строковый буфер на функциях в JavaScript, со следующим синтаксисом:**
<ul>
<li>Создание объекта: `var buffer = makeBuffer();`.</li>
<li>Вызов `makeBuffer` должен возвращать такую функцию `buffer`, которая при вызове `buffer(value)` добавляет значение в некоторое внутреннее хранилище, а при вызове без аргументов `buffer()` -- возвращает его.</li>
</ul>
Вот пример работы:
```js
function makeBuffer() { /* ваш код */ }
var buffer = makeBuffer();
// добавить значения к буферу
buffer('Замыкания');
buffer(' Использовать');
buffer(' Нужно!');
// получить текущее значение
alert( buffer() ); // Замыкания Использовать Нужно!
```
Буфер должен преобразовывать все данные к строковому типу:
```js
var buffer = makeBuffer();
buffer(0); buffer(1); buffer(0);
alert( buffer() ); // '010'
```
Решение не должно использовать глобальные переменные.

View file

@ -0,0 +1,16 @@
function makeBuffer() {
var text = '';
function buffer(piece) {
if (arguments.length == 0) { // вызов без аргументов
return text;
}
text += piece;
};
buffer.clear = function() {
text = "";
}
return buffer;
};

View file

@ -0,0 +1,30 @@
var buffer;
beforeEach(function() {
buffer = makeBuffer();
});
it("возвращает пустую строку по умолчанию", function() {
assert.strictEqual( buffer(), "");
});
it("добавляет аргументы в буффер", function() {
buffer('Замыкания');
buffer(' Использовать');
buffer(' Нужно!');
assert.equal( buffer(), 'Замыкания Использовать Нужно!');
});
it("приводит всё к строке", function() {
buffer(null);
buffer(false);
assert.equal( buffer(), "nullfalse");
});
it("очищает буфер вызовом clear", function() {
buffer("test");
buffer.clear();
buffer("первый");
buffer("второй");
assert.equal( buffer(), "первыйвторой");
});

View file

@ -0,0 +1,34 @@
```js
//+ run
function makeBuffer() {
var text = '';
function buffer(piece) {
if (arguments.length == 0) { // вызов без аргументов
return text;
}
text += piece;
};
buffer.clear = function() {
text = "";
}
return buffer;
};
var buffer = makeBuffer();
buffer("Тест");
buffer(" тебя не съест ");
alert( buffer() ); // Тест тебя не съест
*!*
buffer.clear();
*/!*
alert( buffer() ); // ""
```

View file

@ -0,0 +1,24 @@
# Строковый буфер с очисткой
[importance 5]
Добавьте буферу из решения задачи [](/task/stringbuffer) метод `buffer.clear()`, который будет очищать текущее содержимое буфера:
```js
function makeBuffer() {
...ваш код...
}
var buffer = makeBuffer();
buffer("Тест");
buffer(" тебя не съест ");
alert( buffer() ); // Тест тебя не съест
*!*
buffer.clear();
*/!*
alert( buffer() ); // ""
```

View file

@ -0,0 +1,25 @@
```js
//+ run
var users = [
{ name: "Вася", surname: 'Иванов', age: 20 },
{ name: "Петя", surname: 'Чапаев', age: 25 },
{ name: "Маша", surname: 'Медведева', age: 18 }
];
*!*
function byField(field) {
return function(a, b) {
return a[field] > b[field] ? 1: -1;
}
}
*/!*
users.sort(byField('name'));
users.forEach(function(user) { alert(user.name); });
users.sort(byField('age'));
users.forEach(function(user) { alert(user.name); });
```

View file

@ -0,0 +1,41 @@
# Сортировка
[importance 5]
У нас есть массив объектов:
```js
var users = [
{ name: "Вася", surname: 'Иванов', age: 20 },
{ name: "Петя", surname: 'Чапаев', age: 25 },
{ name: "Маша", surname: 'Медведева', age: 18 }
];
```
Обычно сортировка по нужному полю происходит так:
```js
// по полю name (Вася, Маша, Петя)
users.sort(function(a, b) {
return a.name > b.name ? 1 : -1;
});
// по полю age (Маша, Вася, Петя)
users.sort(function(a, b) {
return a.age > b.age ? 1 : -1;
});
```
Мы хотели бы упростить синтаксис до одной строки, вот так:
```js
users.sort(byField('name'));
users.forEach(function(user) { alert(user.name); }); // Вася, Маша, Петя
users.sort(byField('age'));
users.forEach(function(user) { alert(user.name); }); // Маша, Вася, Петя
```
То есть, вместо того, чтобы каждый раз писать в `sort` `function...` -- будем использовать `byField(...)`
Напишите функцию `byField(field)`, которую можно использовать в `sort` для сравнения объектов по полю `field`, чтобы пример выше заработал.

View file

@ -0,0 +1,24 @@
function filter(arr, func) {
var result = [];
for(var i=0; i<arr.length; i++) {
var val = arr[i];
if (func(val)) {
result.push(val);
}
}
return result;
}
function inArray(arr) {
return function(x) {
return arr.indexOf(x) != -1;
};
}
function inBetween(a, b) {
return function(x) {
return x >=a && x <= b;
};
}

View file

@ -0,0 +1,12 @@
function filter(arr, fuc) {
// ...ваш код...
}
function inBetween(a, b) {
// ...ваш код...
}
function inArray(arr) {
// ...ваш код...
}

View file

@ -0,0 +1,54 @@
var arr;
before(function() {
arr = [1, 2, 3, 4, 5, 6, 7];
});
describe("inArray", function() {
var checkInArr;
before(function() {
checkInArr = inArray(arr);
});
it("возвращает фильтр для значений в массиве", function() {
assert.isTrue( checkInArr(5) );
assert.isFalse( checkInArr(0) );
});
});
describe("inBetween", function() {
var checkBetween36;
before(function() {
checkBetween36 = inBetween(3, 6);
});
it("возвращает фильтрa для значений в промежутке", function() {
assert.isTrue( checkBetween36(5) );
assert.isFalse( checkBetween36(0) );
});
});
describe("filter", function() {
it("фильтрует через func", function() {
assert.deepEqual( filter(arr, function(a) { return a % 2 == 0; }), [2,4,6] );
});
it("не модифицирует исходный массив", function() {
filter(arr, function(a) { return a % 2 == 0; });
assert.deepEqual( arr, [1, 2, 3, 4, 5, 6, 7] );
});
it("поддерживает фильтр inBetween", function() {
assert.deepEqual( filter(arr,inBetween(3,6)), [3,4,5,6]);
});
it("поддерживает фильтр inArray", function() {
assert.deepEqual( filter(arr, inArray([1,2,3])), [1,2,3]);
});
});

View file

@ -0,0 +1,80 @@
# Функция фильтрации
```js
//+ run
function filter(arr, func) {
var result = [];
for(var i=0; i<arr.length; i++) {
var val = arr[i];
if (func(val)) {
result.push(val);
}
}
return result;
}
var arr = [1, 2, 3, 4, 5, 6, 7];
alert( filter(arr, function(a) { return a % 2 == 0; }) ); // 2, 4, 6
```
# Фильтр inBetween
```js
//+ run
function filter(arr, func) {
var result = [];
for(var i=0; i<arr.length; i++) {
var val = arr[i];
if (func(val)) {
result.push(val);
}
}
return result;
}
*!*
function inBetween(a, b) {
return function(x) {
return x >=a && x <= b;
};
}
*/!*
var arr = [1, 2, 3, 4, 5, 6, 7];
alert( filter(arr, inBetween(3,6)) ); // 3,4,5,6
```
# Фильтр inArray
```js
//+ run
function filter(arr, func) {
var result = [];
for(var i=0; i<arr.length; i++) {
var val = arr[i];
if (func(val)) {
result.push(val);
}
}
return result;
}
*!*
function inArray(arr) {
return function(x) {
return arr.indexOf(x) != -1;
};
}
*/!*
var arr = [1, 2, 3, 4, 5, 6, 7];
alert( filter(arr, inArray([1,2,10])) ); // 1,2
```

View file

@ -0,0 +1,27 @@
# Фильтрация через функцию
[importance 5]
<ol>
<li>Создайте функцию `filter(arr, func)`, которая получает массив `arr` и возвращает новый, в который входят только те элементы `arr`, для которых `func` возвращает `true`.</li>
<li>Создайте набор "готовых фильтров": `inBetween(a,b)` -- "между a,b", `inArray([...])` -- "в массиве `[...]`".
Использование должно быть таким:
<ul>
<li>`filter(arr, inBetween(3,6))` -- выберет только числа от 3 до 6,</li>
<li>`filter(arr, inArray([1,2,3]))` -- выберет только элементы, совпадающие с одним из значений массива.</li>
</ul>
</li>
</ol>
Пример, как это должно работать:
```js
/* .. ваш код для filter, inBetween, inArray */
var arr = [1, 2, 3, 4, 5, 6, 7];
alert( filter(arr, function(a) { return a % 2 == 0 }) ); // 2,4,6
alert( filter(arr, inBetween(3,6)) ); // 3,4,5,6
alert( filter(arr, inArray([1,2,10])) ); // 1,2
```

View file

@ -0,0 +1,19 @@
function makeArmy() {
var shooters = [];
for(var i=0; i<10; i++) {
var shooter = (function(x) {
return function() {
alert( x );
};
})(i);
shooters.push(shooter);
}
return shooters;
}

View file

@ -0,0 +1,13 @@
function makeArmy() {
var shooters = [];
for(var i=0; i<10; i++) {
var shooter = function() { // функция-стрелок
alert(i); // выводит свой номер
};
shooters.push(shooter);
}
return shooters;
}

View file

@ -0,0 +1,20 @@
var army;
before(function() {
army = makeArmy();
window.alert = sinon.stub(window, "alert");
});
it("army[0] выводит 0", function() {
army[0]();
assert(alert.calledWith(0));
});
it("army[5] функция выводит 5", function() {
army[5]();
assert(alert.calledWith(5));
});
after(function() {
window.alert.restore();
});

View file

@ -0,0 +1,217 @@
# Что происходит в этом коде
Функция `makeArmy` делает следующее:
<ol>
<li>Создаёт пустой массив `shooter`:
```js
var shooters = [];
```
</li>
<li>В цикле заполняет массив элементами через `shooter.push`.
При этом каждый элемент массива -- это функция, так что в итоге после цикла массив будет таким:
```js
shooters = [
function () { alert(i); },
function () { alert(i); },
function () { alert(i); },
function () { alert(i); },
function () { alert(i); },
function () { alert(i); },
function () { alert(i); },
function () { alert(i); },
function () { alert(i); },
function () { alert(i); }
];
```
Этот массив возвращается из функции.
</li>
<li>Вызов `army[5]()` -- это получение элемента массива (им будет функция), и тут же -- её запуск.</li>
</ol>
# Почему ошибка
Вначале разберемся, почему все стрелки выводят одно и то же значение.
В функциях-стрелках `shooter` отсутствует переменная `i`. Когда такая функция вызывается, то `i` она берет из внешнего `LexicalEnvironment`.
Чему же будет равно это значение `i`?
К моменту вызова `army[0]()`, функция `makeArmy` уже закончила работу. Цикл завершился, последнее значение было `i=10`.
В результате все функции `shooter` получают из внешнего лексического кружения это, одно и то же, последнее, значение `i=10`.
Попробуйте исправить проблему самостоятельно.
# Исправление (3 варианта)
Есть несколько способов исправить ситуацию.
<ol>
<li>**Первый способ исправить код - это привязать значение непосредственно к функции-стрелку:**
```js
//+ run
function makeArmy() {
var shooters = [];
for(var i=0; i<10; i++) {
*!*
var shooter = function me() {
alert( me.i );
};
shooter.i = i;
*/!*
shooters.push(shooter);
}
return shooters;
}
var army = makeArmy();
army[0](); // 0
army[1](); // 1
```
В этом случае каждая функция хранит в себе свой собственный номер.
Кстати, обратите внимание на использование Named Function Expression, вот в этом участке:
```js
...
var shooter = function me() {
alert( me.i );
};
...
```
Если убрать имя `me` и оставить обращение через `shooter`, то работать не будет:
```js
for(var i=0; i<10; i++) {
var shooter = function() {
*!*
alert(shooter.i); // вывести свой номер (не работает!)
// потому что откуда функция возьмёт переменную shooter?
// ..правильно, из внешнего объекта, а там она одна на всех
*/!*
};
shooter.i = i;
shooters.push(shooter);
}
```
Вызов `alert(shooter.i)` при вызове будет искать переменную `shooter`, а эта переменная меняет значение по ходу цикла, и к моменту вызову она равна последней функции, созданной в цикле.
Если использовать Named Function Expression, то имя жёстко привязывается к конкретной функции, и поэтому в коде выше `me.i` возвращает правильный `i`.
</li>
<li>**Другое, более продвинутое решение --- использовать дополнительную функцию для того, чтобы "поймать" текущее значение `i`**:
```js
//+ run
function makeArmy() {
var shooters = [];
for(var i=0; i<10; i++) {
*!*
var shooter = (function(x) {
return function() {
alert( x );
};
})(i);
*/!*
shooters.push(shooter);
}
return shooters;
}
var army = makeArmy();
army[0](); // 0
army[1](); // 1
```
Посмотрим выделенный фрагмент более внимательно, чтобы понять, что происходит:
```js
var shooter = (function(x) {
return function() {
alert( x );
};
})(i);
```
Функция `shooter` создана как результат вызова промежуточного функционального выражения `function(x)`, которое объявляется -- и тут же выполняется, получая `x = i`.
Так как `function(x)` тут же завершается, то значение `x` больше не меняется. Оно и будет использовано в возвращаемой функции-стрелке.
Для красоты можно изменить название переменной `x` на `i`, суть происходящего при этом не изменится:
```js
var shooter = (function(i) {
return function() {
alert( i );
};
})(i);
```
**Кстати, обратите внимание -- скобки вокруг `function(i)` не нужны**, можно и так:
```js
var shooter = function(i) { // *!*без скобок вокруг function(i)*/!*
return function() {
alert( i );
};
}(i);
```
Скобки добавлены в код для лучшей читаемости, чтобы человек, который просматривает его, не подумал, что `var shooter = function`, а понял что это вызов "на месте", и присваивается его результат.
</li>
<li>**Еще один забавный способ - обернуть весь цикл во временную функцию**:
```js
//+ run
function makeArmy() {
var shooters = [];
*!*
for(var i=0; i<10; i++) (function(i) {
var shooter = function() {
alert( i );
};
shooters.push(shooter);
})(i);
*/!*
return shooters;
}
var army = makeArmy();
army[0](); // 0
army[1](); // 1
```
Вызов `(function(i) { ... })` обернут в скобки, чтобы интерпретатор понял, что это `Function Expression`.
Плюс этого способа - в большей читаемости. Фактически, мы не меняем создание `shooter`, а просто обертываем итерацию в функцию.
</li>
</ol>

View file

@ -0,0 +1,31 @@
# Армия функций
[importance 5]
Следующий код создает массив функций-стрелков `shooters`. По замыслу, каждый стрелок должен выводить свой номер:
```js
//+ run
function makeArmy() {
var shooters = [];
for(var i=0; i<10; i++) {
var shooter = function() { // функция-стрелок
alert(i); // выводит свой номер
};
shooters.push(shooter);
}
return shooters;
}
var army = makeArmy();
army[0](); // стрелок выводит 10, а должен 0
army[5](); // стрелок выводит 10...
// .. все стрелки выводят 10 вместо 0,1,2...9
```
Почему все стрелки́ выводят одно и то же? Поправьте код, чтобы стрелки работали как задумано. Предложите несколько вариантов исправления.

View file

@ -0,0 +1,126 @@
# Использование замыканий
Замыкания можно использовать сотнями способов. Иногда люди сами не замечают, что использовали замыкания -- настолько это просто и естественно.
В этой главе мы рассмотрим дополнительные примеры использования замыканий и задачи на эту тему.
[cut]
## Локальные переменные для объекта
Ранее мы сделали счётчик.
Напомню, как он выглядел:
```js
//+ run
function makeCounter() {
var currentCount = 1;
return function() {
return currentCount++;
};
}
var counter = makeCounter();
// каждый вызов увеличивает счётчик и возвращает результат
alert( counter() ); // 1
alert( counter() ); // 2
alert( counter() ); // 3
```
Счётчик получился вполне рабочий, но вот только возможностей ему не хватает. Хорошо бы, чтобы можно было сбрасывать значение счётчика или начинать отсчёт с другого значения вместо `1` или... Да много чего можно захотеть от простого счётчика и, тем более, в более сложных проектах.
**Чтобы добавить счётчику возможностей -- перейдём с функции на полноценный объект:**
```js
//+ run
function makeCounter() {
var currentCount = 1;
return { // возвратим объект вместо функции
getNext: function() {
return currentCount++;
},
set: function(value) {
currentCount = value;
},
reset: function() {
currentCount = 0;
}
};
}
var counter = makeCounter();
alert( counter.getNext() ); // 1
alert( counter.getNext() ); // 2
counter.set(5);
alert( counter.getNext() ); // 5
```
Теперь функция `makeCounter` возвращает не одну функцию, а объект с несколькими методами:
<ul>
<li>`getNext()` -- получить следующее значение, то, что раньше делал вызов `counter()`.</li>
<li>`set(value)` -- поставить значение.</li>
<li>`reset()` -- обнулить счётчик.</li>
</ul>
Все они получают ссылку `[[Scope]]` на текущий (внешний) объект переменных. Поэтому вызов любого из этих методов будет получать или модифицировать одно и то же внешнее значение `currentCount`.
## Объект счётчика + функция
Изначально, счётчик делался функцией во многом ради красивого вызова: `counter()`, который увеличивал значение и возвращал результат.
К сожалению, при переходе на объект короткий вызов пропал, вместо него теперь `counter.getNext()`. Но он ведь был таким простым и удобным...
Поэтому давайте вернём его!
```js
//+ run
function makeCounter() {
var currentCount = 1;
*!*
// возвращаемся к функции
function counter() {
return currentCount++;
}
*/!*
// ...и добавляем ей методы!
counter.set = function(value) {
currentCount = value;
};
counter.reset = function() {
currentCount = 0;
};
return counter;
}
var counter = makeCounter();
*!*
alert( counter() ); // 1
alert( counter() ); // 2
counter.set(5);
alert( counter() ); // 5
*/!*
```
Красиво, не правда ли? Получился полноценный объект, который можно вдобавок ещё и вызывать.
Этот трюк часто используется при разработке JavaScript-библиотек. Например, популярная библиотека [jQuery](http://jquery.com) предоставляет глобальную переменную с именем [jQuery](http://api.jquery.com/jQuery/) (доступна также под коротким именем `$`), которая с одной стороны является функцией и может вызываться как `jQuery(...)`, а с другой -- у неё есть различные методы, например `jQuery.type(123)` возвращает тип аргумента.
## Задачи на понимание замыканий

View file

@ -0,0 +1,491 @@
# Управление памятью в JavaScript
Управление памятью обычно незаметно. Мы создаём примитивы, объекты, функции.. Всё это занимает память.
Что происходит с объектом, когда он становится "не нужен"? Возможно ли "переполнение" памяти? Для ответа на эти вопросы -- залезем "под капот" интерпретатора.
[cut]
## Управление памятью в JavaScript
Главной концепцией управления памятью в JavaScript является принцип *достижимости* (англ. reachability).
<ol>
<li>Определённое множество значений считается достижимым изначально, в частности:
<ul>
<li>Значения, ссылки на которые содержатся в стеке вызова, то есть -- все локальные переменные и параметры функций, которые в настоящий момент выполняются или находятся в ожидании окончания вложенного вызова.</li>
<li>Все глобальные переменные.</li>
</ul>
Эти значения гарантированно хранятся в памяти. Мы будем называть их *корнями*.
</li>
<li>**Любое другое значение сохраняется в памяти лишь до тех пор, пока доступно из корня по ссылке или цепочке ссылок.**</li>
</ol>
Для очистки памяти от недостижимых значений в браузерах используется автоматический <a href="http://en.wikipedia.org/wiki/Garbage_collection_(computer_science)">Сборщик мусора</a> (англ. Garbage collection, GC), встроенный в интерпретатор, который наблюдает за объектами и время от времени удаляет недостижимые.
Далее мы посмотрим ряд примеров, которые помогут в этом разобраться.
### Достижимость и наличие ссылок
Можно сказать просто: "значение остаётся в памяти, пока на него есть ссылка". Но такое упрощение будет не совсем верным.
<ul>
<li>**Верно -- в том плане, что если на значение не остаётся ссылок, то память из-под него очищается.**
Например, была создана ссылка в переменной, и эту переменную тут же перезаписали:
```js
var user = { name: "Вася" };
user = null;
```
Теперь объект `{ name: "Вася" }` более недоступен. Память будет освобождена.
</li>
<li>**Неверно -- может быть так, что ссылка есть, но при этом значение недостижимо и должно быть удалено из памяти.**
Такая ситуация возникает с объектами, при наличии ссылок друг на друга:
```js
var vasya = {};
var petya = {};
vasya.friend = petya;
petya.friend = vasya;
vasya = petya = null;
```
Несмотря на то, что на объекты `vasya`, `petya` ссылаются друг на друга через ссылку `friend`, то есть можно сказать, что на каждый из них есть ссылка, последняя строка делает эти объекты в совокупности недостижимыми.
Поэтому они будут удалены из памяти.
Чтобы отследить такие сложные случаи, придуман [сборщик мусора](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 = { };
family.father = {
name: "Вася"
};
family.mother = {
name: "Маша"
};
```
</td>
<td>
<img src="family.png">
</td>
</tr>
</table>
Этот код создаёт объект `family` и два дополнительных объекта, доступных по ссылкам `family.father` и `family.mother`.
### Недостижимый объект
Теперь посмотрим, что будет, если удалить ссылку `family.father` при помощи `delete`:
<table>
<tr>
<th>Код</th>
<th>Структура в памяти</th>
<tr>
<td>
```js
var family = { };
family.father = {
name: "Вася"
};
family.mother = {
name: "Маша"
};
*!*
delete family.father;
*/!*
```
</td>
<td>
<img src="family-nofatherlink.png">
</td>
</tr>
</table>
### Пришёл сборщик мусора
Сборщик мусора ищет недоступные объекты. Базовый алгоритм поиска -- это идти от корня (`window`) по ссылкам и помечать все объекты, которые встретит. Тогда после окончания обхода непомеченными останутся как раз недостижимые объекты.
В нашем случае таким объектом будет бывший `family.father`. Он стал недостижимым и будет удалён вместе со своим "поддеревом", которое также более недоступно из программы.
<table>
<tr>
<th>Код</th>
<th>Структура в памяти</th>
<tr>
<td>
```js
var family = {
father: {
name: "Вася"
},
mother: {
name: "Маша"
}
};
*!*
delete family.father;
*/!*
```
</td>
<td>
<img src="family-nofatherlink-junk.png">
</td>
</tr>
</table>
### После сборщика
После того, как сработает сборщик мусора, картина в памяти будет такой:
<table>
<tr>
<th>Код</th>
<th>Структура в памяти</th>
<tr>
<td>
```js
var family = {
father: {
name: "Вася"
},
mother: {
name: "Маша"
}
};
*!*
delete family.father;
*/!*
```
</td>
<td>
<img src="family-nofatherlink-junk-cleanup.png">
</td>
</tr>
</table>
### Достижимость -- только по входящим ссылкам
Вернёмся к исходному коду.
**Пусть внутренние объекты ссылаются друг на друга:**
<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>
## Замыкания
Замыкания следуют тем же правилам, что и обычные объекты.
**Объект переменных внешней функции существует в памяти до тех пор, пока существует хоть одна внутренняя функция, ссылающаяся на него через свойство `[[Scope]]`.**
Например:
<ul>
<li>Обычно объект переменных удаляется по завершении работы функции. Даже если в нём есть объявление внутренней функции:
```js
function f() {
var value = Math.random();
function g() { } // g видна только изнутри
}
f();
```
В коде выше внутренняя функция объявлена, но она осталась внутри. После окончания работы `f()` она станет недоступной для вызовов, так что будет убрана из памяти вместе с остальными локальными переменными.
</li>
<li>...А вот в этом случае лексическое окружение, включая переменную `value`, будет сохранено:
```js
function f() {
var value = Math.random();
function g() { }
*!*
return g;
*/!*
}
var g = f(); // функция g будет жить и сохранит ссылку на объект переменных
```
Причина сохранения проста: в скрытом свойстве `g.[[Scope]]` находится ссылка на объект переменных, в котором была создана `g`.
</li>
<li>
Если `f()` будет вызываться много раз, а полученные функции будут сохраняться, например, складываться в массив, то будут сохраняться и объекты `LexicalEnvironment` с соответствующими значениями `value`:
```js
function f() {
var value = Math.random();
return function() { };
}
// 3 функции, каждая ссылается на свой объект переменных,
// со своим значением value
var arr = [f(), f(), f()];
```
При этом совершенно не важно, имеет ли вложенная функция имя или нет.
</li>
<li>Объект `LexicalEnvironment` живёт ровно до тех пор, пока на него существуют ссылки. В коде ниже замыкание сначала сохраняется в памяти, а после удаления ссылки на `g` умирает:
```js
function f() {
var value = Math.random();
function g() { }
return g;
}
var g = f(); // функция g жива
// а значит в памяти остается соответствующий объект переменных
g = null; // ..а вот теперь память будет очищена
```
</li>
</ul>
### Оптимизация в V8 и её последствия
Современные JS-движки делают оптимизации замыканий по памяти. Они анализируют использование переменных и в случае, когда переменная из замыкания абсолютно точно не используется, удаляют её.
В коде выше переменная `value` никак не используется. Поэтому она будет удалена из памяти.
**Важный побочный эффект в V8 (Chrome, Opera) состоит в том, что удалённая переменная станет недоступна и при отладке!**
Попробуйте запустить пример ниже с открытой консолью Chrome. Когда он остановится, в консоли наберите `alert(value)`.
```js
//+ run
function f() {
var value = Math.random();
function g() {
debugger; // выполните в консоли alert(value); Нет такой переменной!
}
return g;
}
var g = f();
g();
```
Это может привести к забавным казусам при отладке, вплоть до того что вместо этой переменной будет другая, внешняя:
```js
//+ run
var value = "Сюрприз";
function f() {
var value = "...";
function g() {
debugger; // выполните в консоли alert(value); Сюрприз!
}
return g;
}
var g = f();
g();
```
[warn header="Ещё увидимся"]
Об этой особенности важно знать. Если вы отлаживаете под Chrome/Opera, то наверняка рано или поздно с ней встретитесь!
Это не глюк отладчика, а особенность работы V8, которая, возможно, будет когда-нибудь изменена. Вы всегда сможете проверить, не изменилось ли чего, запустив примеры на этой странице.
[/warn]
## Влияние управления памятью на скорость
На создание новых объектов и их удаление тратится время. Это важно иметь в виду в случае, когда важна производительность.
В качестве примера рассмотрим рекурсию. При вложенных вызовах каждый раз создаётся новый объект с переменными и помещается в стек. Потом память из-под него нужно очистить. Поэтому рекурсивный код будет всегда медленнее использующего цикл, но насколько?
Пример ниже тестирует сложение чисел до данного через рекурсию по сравнению с обычным циклом:
```js
//+ run
function sumTo(n) { // обычный цикл 1+2+...+n
var result = 0;
for (var i=1; i<=n; i++) {
result += i;
}
return result;
}
function sumToRec(n) { // рекурсия sumToRec(n) = n+SumToRec(n-1)
return n == 1 ? 1 : n + sumToRec(n-1);
}
var timeLoop = performance.now();
for (var i=1;i<1000;i++) sumTo(1000); // цикл
timeLoop = performance.now() - timeLoop;
var timeRecursion = performance.now();
for (var i=1;i<1000;i++) sumToRec(1000); // рекурсия
timeRecursion = performance.now() - timeRecursion;
alert("Разница в " + ( timeRecursion / timeLoop ) + " раз");
```
Различие в скорости на таком примере может составлять, в зависимости от интерпретатора, 2-10 раз.
В большинстве ситуаций оптимизация по количеству создаваемых объектов несущественна, просто потому что "JavaScript и так достаточно быстр". Но она может быть важной для "узких мест" кода, а также при написании компьютерной графики и сложных вычислений на JS.

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1,17 @@
Вторая (`2`), т.к. при обращении к любой переменной внутри `with` -- она ищется прежде всего в объекте.
Соответственно, будет выведено `2`:
```js
//+ run
function f() { alert(1) }
var obj = {
f: function() { alert(2) }
};
with(obj) {
f();
}
```

View file

@ -0,0 +1,18 @@
# With + функция
[importance 5]
Какая из функций будет вызвана?
```js
function f() { alert(1) }
var obj = {
f: function() { alert(2) }
};
with(obj) {
f();
}
```

View file

@ -0,0 +1,20 @@
Выведет `3`.
**Конструкция `with` не создаёт области видимости,** её создают только функции. Поэтому объявление `var b` внутри конструкции работает также, как если бы оно было вне её.
Код в задаче эквивалентен такому:
```js
//+ run
var a = 1;
*!*
var b;
*/!*
var obj = { b: 2 }
with(obj) {
alert( a + b );
}
```

View file

@ -0,0 +1,17 @@
# With + переменные
[importance 5]
Что выведет этот код?
```js
var a = 1;
var obj = { b: 2 };
with(obj) {
var b;
alert( a + b );
}
```

View file

@ -0,0 +1,181 @@
# Устаревшая конструкция "with"
Конструкция `with` позволяет использовать в качестве области видимости для переменных произвольный объект.
В современном JavaScript от этой конструкции отказались. С `use strict` она не работает, но её ещё можно найти в старом коде, так что стоит познакомиться с ней, чтобы если что -- понимать, о чём речь.
[cut]
Синтаксис:
```js
with(obj) {
... код ...
}
```
**Любое обращение к переменной внутри `with` сначала ищет её среди свойств `obj`, а только потом -- вне `with`.**
## Пример
В примере ниже переменная будет взята не из глобальной области, а из `obj`:
```js
//+ run
var a = 5;
var obj = { a : 10 };
*!*
with(obj) {
alert(a); // 10, из obj
}
*/!*
```
Попробуем получить переменную, которой в `obj` нет:
```js
//+ run
var b = 1;
var obj = { a : 10 };
*!*
with(obj) {
alert(b); // 1, из window
}
*/!*
```
Здесь интерпретатор сначала проверяет наличие `obj.b`, не находит и идет вне `with`.
Особенно забавно выглядит применение вложенных `with`:
```js
//+ run
var obj = {
weight: 10,
size: {
width: 5,
height: 7
}
};
with(obj) {
with(size) { // size будет взят из obj
*!*
alert( width*height / weight ); // width,height из size, weight из obj
*/!*
}
}
```
Свойства из разных объектов используются как обычные переменные... Магия! Порядок поиска переменных в выделенном коде: `size => obj => window`
<img src="with_obj_size.png">
## Изменения переменной
При использовании `with`, как и во вложенных функциях -- переменная изменяется в той области, где была найдена.
Например:
```js
//+ run
var obj = { a : 10 }
*!*
with(obj) {
a = 20;
}
*/!*
alert(obj.a); // 20, переменная была изменена в объекте
```
## Почему отказались от with?
Есть несколько причин.
<ol>
<li>В современном стандарте `JavaScript` отказались от `with`, потому что **конструкция `with` подвержена ошибкам и непрозрачна.**
Проблемы возникают в том случае, когда в `with(obj)` присваивается переменная, которая по замыслу должна быть в свойствах `obj`, но ее там нет.
Например:
```js
//+ run
var obj = { weight: 10 };
with(obj) {
weight = 20; // (1)
size = 35; // (2)
}
alert(obj.size);
alert(window.size);
```
В строке `(2)` присваивается свойство, отсутствующее в `obj`. В результате интерпретатор, не найдя его, создает новую глобальную переменную `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`.
Вот, к примеру, запустите этот код в современном браузере. Производительность функции `fast` существенно отличается `slow` с пустым(!) `with`. И дело тут именно в `with`, т.к. наличие этой конструкции препятствует оптимизации.
```js
//+ run
var i = 0;
function fast() {
i++;
}
function slow() {
with(i) {}
i++;
}
var time = new Date();
while(i < 1000000) fast();
alert(new Date - time);
var time = new Date();
i=0;
while(i < 1000000) slow();
alert(new Date - time);
```
</li>
</ol>
### Замена with
Вместо `with` рекомендуется использовать временную переменную, например:
```js
/* вместо
with(elem.style) {
top = '10px';
left = '20px';
}
*/
var s = elem.style;
s.top = '10px';
s.left = '0';
```
Это не так элегантно, но убирает лишний уровень вложенности и абсолютно точно понятно, что будет происходить и куда присвоятся свойства.
## Итого
<ul>
<li>Конструкция `with(obj) { ... }` использует `obj` как дополнительную область видимости. Все переменные, к которым идет обращение внутри блока, сначала ищутся в `obj`.</li>
<li>Конструкция `with` устарела и не рекомендуется по ряду причин. Избегайте её.</li>
</ul>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -0,0 +1,5 @@
# Замыкания, область видимости
Понимание "области видимости" и "замыканий" -- ключевое в изучении JavaScript, без них "каши не сваришь".
В этом разделе мы более глубоко изучаем переменные и функции -- и замыкания в том числе.