Главная->Уроки по js->Что такое замыкание в javascript
Что такое замыкание в javascript
Всем привет! В этой статье мы рассмотрим, что такое замыкание в 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()); //