Что такое замыкание в javascript.
![Что такое замыкание в javascript.](/images/articles/javascript-closures.png)
Всем привет! В этой статье мы рассмотрим, что такое замыкание в javascript.
Это довольно простая тема, но она требует понимания. Для начала давайте рассмотрим, что происходит внутри функции.
function greeting(name) {
// LexicalEnvironment = {name: 'Николай', text: undefined}
var text = 'Здравствуйте, ' + name;
// LexicalEnvironment = {name: 'Николай', text: 'Здравствуйте, Николай'}
alert(text);
}
greeting('Николай');
Что здесь происходит и что такое LexicalEnvironment? Давайте разберемся.
Когда функция вызывается, у нее создается объект LexicalEnvironment, в который записываются все локальные переменные и функции, а также ссылка на внешнюю область видимости(об этом позже). В нашем случае у нас есть локальная переменная name, у которой сразу есть значение(то, которое мы передаем) и это "Николай". В одной из статей я уже писал, однако напомню, что интерпретатор все знает про все переменные заранее. Именно по этому у нас в самом начале функции уже есть переменная text, интерпретатор знает про нее, но так как мы еще не дошли по присваивания этой переменной какого-то значения, то она равна undefined. Теперь мы присваиваем переменной значение, и наш объект LexicalEnvironment меняется. Его свойство text становится равным тому, что мы записали("Здравствуйте, Николай" в нашем случае). После того, как функция отработала, объект LexicalEnvironment уничтожается. При последующих вызовах функции он будет создан снова и т.д.
Теперь перейдем к следующему примеру. Скажите, что будет выведено в этом случае?
var b = 2;
function x(a) {
alert(a + b);
}
x(1);
Подумали? Думаю, большинство ответило, что будет выведено число 3, и это правильный ответ, однако можете вы рассказать, как интерпретатор узнал о переменной b? Ведь ее нет в теле функции. Если нет, давайте разбираться.
На самом деле в javascript есть скрытое свойство, которое называется [[Scope]]. Когда функция объявляется, то она всегда объявляется где-то. Эта функция может быть в другой функции, может быть в глобальном объекте и т.д. В нашем случае функция объявлена в глобальном объекте window, поэтому свойство x.[[Scope]] = window.
Дальше будем рассматривать на примере кода с комментариями.
var b = 2;
function x(a) { // x.[[Scope]] = window
// LexicalEnvironment = {a: 1} -> window
alert(a + b);
}
x(1);
Эта стрелочка у объекта LexicalEnvironment - это ссылка на внешнюю область видимости, и эта ссылка устанавливается по свойству [[Scope]]. Таким образом в объекте LexicalEnvironment у нас будет ссылка на внешний объект window. Когда интерпретатор ищет переменную, то он сначала ищет ее в объекте LexicalEnvironment, затем, если он не нашел переменную, то он смотрим в ссылку, переходит во внешнюю область видимости и ищет ее там и так до конца. Если он нигде этой переменной не нашел, то будет ошибка. В нашем случае переменную a интерпретатор возьмет из объекта LexicalEnvironment, а переменную b из объекта window. Конечно, если у нас будет локальная переменная b с каким-то значением, то она запишется в объект LexicalEnvironment и впоследствии будет взята оттуда, а не из внешней области видимости.
ВАЖНО! Запомните, что свойство [[Scope]] устанавливается по тому месту, где функция была объявлена, а не вызвана, именно поэтому код ниже выведет число 3, а не 5, как некоторые могли подумать.
bar b = 2;
function x(a) {
alert(a + b);
}
function y() {
var b = 4;
x(1);
}
y();
Это все была прелюдия только для того, чтобы вы поняли, как это все работает, и вам было легче понять, как работают замыкания. А теперь перейдем непосредственно к теме статьи.
Как я уже говорил, объект LexicalEnvironment уничтожается каждый раз после выполнения функции и создается снова при повторном вызове. Однако что, если мы хотим сохранить эти данные? Т.е. мы хотим, чтобы все, что записано в LexicalEnvironment сейчас, сохранилось и было использовано при следующих вызовах? Именно для этого и существуют замыкания.
function greeting(name) {
// LexicalEnvironment = {name: 'Николай'}
return function() { // [[Scope]] = LexicalEnvironment
alert(name);
};
}
var func = greeting('Николай');
greeting = null;
func();
Давайте посмотрим, что мы сделали. Сначала мы создаем функцию greeting, в которую передается имя. В функции создается объект LexicalEnvironment, где создается свойство(наша локальная переменная) name и ей присваивается имя "Николай". А теперь важно: мы возвращаем из функции другую функцию, внутри которой выводим через alert переменную name. Дальше мы присваиваем переменной func значение, возвращенное из функции greeting, а это значение - наша функция, которая выводит имя. Теперь мы greeting присваиваем null, т.е. мы просто уничтожаем нашу функцию greeting, однако, когда мы вызовем func, то увидим значение переменной name("Николай") функции greeting. Как такое возможно, скажете вы? А очень просто. Все дело в том, что наша возвращаемая функция также имеет свойство [[Scope]], которое ссылается на внешнюю область видимости, а эта внешняя область видимости в нашем случае - объект LexicalEnvironment нашей функции greeting. Поэтому, несмотря на то, что мы удалили нашу функцию greeting, объект LexicalEnvironment не удалился и остался в памяти, и он будет оставаться в памяти до тех пор, пока на него будет хотя бы одна ссылка. У нас эта ссылка - наша возвращаемая функция, которая использует переменную name этого объекта LexicalEnvironment.
Итак, давайте теперь дадим определение тому, что такое замыкание.
Замыкание - функция вместе со всеми переменными, которые ей доступны.
Что же, статья получилась довольно объемная, но это только потому, что я попытался как можно подробнее описать весь процесс работы замыкания. На закрепление хочу привести простой пример - счетчик с использованием только что изученной темы. Пожалуйста, разберитесь с кодом и напишите в комментариях, как и почему он работает. Если вы чего-то не поняли, вы также можете задать вопрос. Спасибо за внимание!
function makeCounter() {
var currentCount = 0;
return function() {
currentCount++;
return currentCount;
};
}
var counter = makeCounter();
counter();
counter();
alert(counter()); // 3
-
-
Михаил Русаков
Комментарии (5):
А какое прикладное применение у этих замыканий? Что они упрощают или какие преимущества дают?
Ответить
Замыкание - это то, благодаря чему, по сути, язык программирования javascript вообще существует. Любое использование функций порождает замыкание. Основной их плюс в том, что вы можете хранить значения переменных на длительное время, а не сразу уничтожать их, как только функция закончит свою работу, и то, что вы можете из функции обратиться к переменной, которая находится в области видимости на уровень выше. Если научиться это правильно использовать, то это открывает большие возможности. Если вы не совсем понимаете этой темы, то советую сначала оставить ее, изучить другие основы javascript, а потом прочитать книжку "JavaScript. Шаблоны" от автора Стоян Стефанов. Там вы сможете не только изучить шаблоны, но также увидите, как и где применять замыкания.
Ответить
Только начал знакомиться с js, рано мне еще такие темы изучать, спасибо.
Ответить
Цитата из статьи : " ВАЖНО! Запомните, что свойство [[Scope]] устанавливается по тому месту, где функция была объявлена, а не вызвана, именно поэтому код ниже выведет число 3, а не 5, как некоторые могли подумать." Переставим запуск функции сразу после объявления переменной b и получим результат NaN. Может всё-таки место запуска функции тоже влияет на [SCOPE]? Вот код - y(); var b = 2; function x(a) { alert(a + b); } function y() { var b = 4; x(1); } В статье код надо подправить - bar на var поменять.
Ответить
Это лучшее объяснение замыкания, что я встречала на просторах интернета!
Ответить
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.