renovations
|
@ -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 |