зачем нужны замыкания в javascript
Форум
Справочник
Замыкания
Действительно, замыкания могут приводить к проблемам. Но на самом деле они очень удобны, просто нужно понимать, что реально происходит.
Простое описание
Наиболее часто замыкания применяются для назначения функций-обработчиков событий:
Эта функция принимает два ID элементов HTML и ставит первому элементу обработчик onclick, который прячет второй элемент.
Здесь динамически созданный обработчик события handler использует targetId из внешней функции для доступа к элементу.
Подробное описание
..На самом деле происходящее в интерпретаторе Javascript гораздо сложнее и содержит куда больше деталей, чем здесь описано.
..Но чтобы понять и использовать замыкания, достаточно понять внутренний механизм работы функций, хотя бы и в таком, местами упрощенном виде.
[[scope]]
Каждое выполнение функции хранит все переменные в специальном объекте с кодовым именем [[scope]], который нельзя получить в явном виде, но он есть .
Общий поток выполнения выглядит так:
Область видимости вложенной функции
Когда одна функция создается внутри другой, то ей передается ссылка на объект с локальными переменными [[scope]] внешней функции.
Внутренняя функция может обратиться к нему в любой момент и получить переменную внешней функции.
Например, разберем работу функции, которая устанавливает обработчики событий:
При запуске функции все происходит стандартно:
Вместо этого он просто оставляет весь [[scope]] внешней функции в живых.
Пример на понимание
В этом примере внешняя функция makeShout () создает внутреннюю shout ().
То есть, внутренняя функция получает последнее значение внешних переменных.
Забавный пример
Замыкание позволяет создать функцию суммирования, которая работает вот так:
А вот и сама функция sum :
Пример ошибочного использования
С вопроса «Почему это не работает?» люди обычно начинают изучение замыканий.
Для тестового примера сделаем 10 разноцветных нумерованных div ‘ов с разными цветами:
Кнопка ниже создаст 10 дивов и вызовет для них addEvents
Для присваивания div.onclick запускается временная функция function(x) <..>, принимающая аргумент x и возвращающая обработчик, который берет x из [[scope]] этой временной функции.
Вообще, javascript очень удобный в этом смысле язык. Допускает любые конструкции, например, вместо последовательных вызовов:
можно в одну строчку создать и тут же вызвать функцию и тут же получить 2й элемент массива:
По идее, этих примеров должно хватать для понимания и практического использования замыканий.
Конечно, разобрать происходящее во всех деталях позволит стандарт языка ECMA-262.
На моем браузере вывод «makeDivs + addEvents» и «makeDivs + новая функция» отображаются одинаково.
Да, makeDivs должны отображаться одинаково. Но работают по-разному.
Расскажите плз. про последниий пример поподробнее, я ничего не понял
>>alert( function a()< return [5] >()[0] ) // => выведет 5
и чуть выше тоже
Ок, спасибо. Сделал эту часть подробнее.
Отличная статья. спасибо
Спасибо за статью.
Возник вопрос по работе с локальными переменными в таких случаях в отладчике Firebug.
var xx = 20;
(function() <
var xx = 10;
func = function() <
alert(xx);
>
>)();
func();
отладчик Firebug при вызове функции func()
показывает что xx = 20, хотя alert() выводит правильное значение 10.
Спасибо за столь подробное объянение
Отличная статья, спасибо огромное!
Молодцы спасибо за статью!
Спс. очень признателен!
Не лишне было бы упомянуть, что замыкания можно организовать и с использованием неанонимных внутренних функций.
You must to visit our web platform for your own sexy chat pleasure in United Kingdom sexchat
Очень похоже, что [[scope]].[[prototype]] вложенной функции == [[scope]] внешней. Это действительно так, или я ошибаюсь?
Речь идет просто об иерархии, в чем-то аналогичной наследованию через прототипы.
Спасибо. Очень интересно и понятно!
Спасибо за столь подробное объянение
хорошая статья) первый раз вижу такое на русском языке)
Спасибо! На русском аналогов не видел, по моему даже на википедии нет!
Скажите, пожалуйста, а как оградить переменные во внешней фунции от внутренней? Сталкнулся с этой проблемой, когда писал рекурсивную функцию. Заранее спасибо.
Подозреваю, что вам поможет слово var.
Спасибо! очень толково и понятно освящен такой сложный для меня вопрос)
знаете, всё это, конечно, интересно и всё такое, но я бы попросил цвет скобок из зелёного сделать каким нибудь другим. например, синим или оранжевым, ибо зелёный на белом видно плохо и, чтоб отличить фигурную скобку от круглой, приходится глаза ломать. спасибо.
з.ы. ещё их можно обозначить полужирным шрифтом.
Спасибо за очень хорошую статью.
Добрый день. У меня возник следующий вопрос. Что и в каких scope происходит со свойством x в данном примере:
Почему в результате я получаю undefined?
Вот это работает как надо, вот только почему не работает при var x=20; мне непонятно. Как вариант, некорректная запись условия. Надо копать мануал.
Соответственно, она не существует, пока не будет инициализирована
не совсем так, вернее вот так:
При входе в контекст исполнения создаётся свойство VO с именем переменной, и значением undefined
Поэтому Ваши слова в виде:
больше отражают суть.
По всей вероятности, интерпретатор добавил к данному объекту вызова свойство «x» со значением «undefined», всему виной инструкция:
Скорее всего из-за того, что var x=20; интерпретатор создаёт раньше чем происходит сравнение. Интерпретатору пофигу в какой части функции вы объявите переменную scope всё равно приклеивает к этой функции.
shemales glasgow is great web platform for lonley guys to make chat contacts in UK
Ответ через 3 года )
Когда интерпретатор начинает выполнять функцию f он находит все записи var и создает соответствующие переменные но присваивает в них значение undefined.
Далее, когда он перейдет уже непосредственно к выполнению кода значения установятся. Но поскольку x=undefined условие x==10 не выполнится, и значение не установится.
потому что интерпретатор выносит имена перемененных и функций в самый верх,
а только потом начинает выполнять код. Соответственно при сравнении (x == 10) значение берется из переменной х которая еще не инициализирована.
Как вы отнесетесь, если я сделаю перевод вашей статьи на английский язык с указанием оригинала, т.е. вашей статьи?
Вопрос про конструкцию:
Что происходит «внутри» когда используются круглые скобки ( … )?
Иллюстрация:
Форум
Справочник
Замыкания
Действительно, замыкания могут приводить к проблемам. Но на самом деле они очень удобны, просто нужно понимать, что реально происходит.
Простое описание
Наиболее часто замыкания применяются для назначения функций-обработчиков событий:
Эта функция принимает два ID элементов HTML и ставит первому элементу обработчик onclick, который прячет второй элемент.
Здесь динамически созданный обработчик события handler использует targetId из внешней функции для доступа к элементу.
Подробное описание
..На самом деле происходящее в интерпретаторе Javascript гораздо сложнее и содержит куда больше деталей, чем здесь описано.
..Но чтобы понять и использовать замыкания, достаточно понять внутренний механизм работы функций, хотя бы и в таком, местами упрощенном виде.
[[scope]]
Каждое выполнение функции хранит все переменные в специальном объекте с кодовым именем [[scope]], который нельзя получить в явном виде, но он есть .
Общий поток выполнения выглядит так:
Область видимости вложенной функции
Когда одна функция создается внутри другой, то ей передается ссылка на объект с локальными переменными [[scope]] внешней функции.
Внутренняя функция может обратиться к нему в любой момент и получить переменную внешней функции.
Например, разберем работу функции, которая устанавливает обработчики событий:
При запуске функции все происходит стандартно:
Вместо этого он просто оставляет весь [[scope]] внешней функции в живых.
Пример на понимание
В этом примере внешняя функция makeShout () создает внутреннюю shout ().
То есть, внутренняя функция получает последнее значение внешних переменных.
Забавный пример
Замыкание позволяет создать функцию суммирования, которая работает вот так:
А вот и сама функция sum :
Пример ошибочного использования
С вопроса «Почему это не работает?» люди обычно начинают изучение замыканий.
Для тестового примера сделаем 10 разноцветных нумерованных div ‘ов с разными цветами:
Кнопка ниже создаст 10 дивов и вызовет для них addEvents
Для присваивания div.onclick запускается временная функция function(x) <..>, принимающая аргумент x и возвращающая обработчик, который берет x из [[scope]] этой временной функции.
Вообще, javascript очень удобный в этом смысле язык. Допускает любые конструкции, например, вместо последовательных вызовов:
можно в одну строчку создать и тут же вызвать функцию и тут же получить 2й элемент массива:
По идее, этих примеров должно хватать для понимания и практического использования замыканий.
Конечно, разобрать происходящее во всех деталях позволит стандарт языка ECMA-262.
На моем браузере вывод «makeDivs + addEvents» и «makeDivs + новая функция» отображаются одинаково.
Да, makeDivs должны отображаться одинаково. Но работают по-разному.
Расскажите плз. про последниий пример поподробнее, я ничего не понял
>>alert( function a()< return [5] >()[0] ) // => выведет 5
и чуть выше тоже
Ок, спасибо. Сделал эту часть подробнее.
Отличная статья. спасибо
Спасибо за статью.
Возник вопрос по работе с локальными переменными в таких случаях в отладчике Firebug.
var xx = 20;
(function() <
var xx = 10;
func = function() <
alert(xx);
>
>)();
func();
отладчик Firebug при вызове функции func()
показывает что xx = 20, хотя alert() выводит правильное значение 10.
Спасибо за столь подробное объянение
Отличная статья, спасибо огромное!
Молодцы спасибо за статью!
Спс. очень признателен!
Не лишне было бы упомянуть, что замыкания можно организовать и с использованием неанонимных внутренних функций.
You must to visit our web platform for your own sexy chat pleasure in United Kingdom sexchat
Очень похоже, что [[scope]].[[prototype]] вложенной функции == [[scope]] внешней. Это действительно так, или я ошибаюсь?
Речь идет просто об иерархии, в чем-то аналогичной наследованию через прототипы.
Спасибо. Очень интересно и понятно!
Спасибо за столь подробное объянение
хорошая статья) первый раз вижу такое на русском языке)
Спасибо! На русском аналогов не видел, по моему даже на википедии нет!
Скажите, пожалуйста, а как оградить переменные во внешней фунции от внутренней? Сталкнулся с этой проблемой, когда писал рекурсивную функцию. Заранее спасибо.
Подозреваю, что вам поможет слово var.
Спасибо! очень толково и понятно освящен такой сложный для меня вопрос)
знаете, всё это, конечно, интересно и всё такое, но я бы попросил цвет скобок из зелёного сделать каким нибудь другим. например, синим или оранжевым, ибо зелёный на белом видно плохо и, чтоб отличить фигурную скобку от круглой, приходится глаза ломать. спасибо.
з.ы. ещё их можно обозначить полужирным шрифтом.
Спасибо за очень хорошую статью.
Добрый день. У меня возник следующий вопрос. Что и в каких scope происходит со свойством x в данном примере:
Почему в результате я получаю undefined?
Вот это работает как надо, вот только почему не работает при var x=20; мне непонятно. Как вариант, некорректная запись условия. Надо копать мануал.
Соответственно, она не существует, пока не будет инициализирована
не совсем так, вернее вот так:
При входе в контекст исполнения создаётся свойство VO с именем переменной, и значением undefined
Поэтому Ваши слова в виде:
больше отражают суть.
По всей вероятности, интерпретатор добавил к данному объекту вызова свойство «x» со значением «undefined», всему виной инструкция:
Скорее всего из-за того, что var x=20; интерпретатор создаёт раньше чем происходит сравнение. Интерпретатору пофигу в какой части функции вы объявите переменную scope всё равно приклеивает к этой функции.
shemales glasgow is great web platform for lonley guys to make chat contacts in UK
Ответ через 3 года )
Когда интерпретатор начинает выполнять функцию f он находит все записи var и создает соответствующие переменные но присваивает в них значение undefined.
Далее, когда он перейдет уже непосредственно к выполнению кода значения установятся. Но поскольку x=undefined условие x==10 не выполнится, и значение не установится.
потому что интерпретатор выносит имена перемененных и функций в самый верх,
а только потом начинает выполнять код. Соответственно при сравнении (x == 10) значение берется из переменной х которая еще не инициализирована.
Как вы отнесетесь, если я сделаю перевод вашей статьи на английский язык с указанием оригинала, т.е. вашей статьи?
Вопрос про конструкцию:
Что происходит «внутри» когда используются круглые скобки ( … )?
Иллюстрация:
Замыкания в Javascript (что такое замыкание в JS + 3 примера)
В этой статье мы с вами разберемся как что такое, и как работают замыкания (closure) в Javascript. Эта тема однозначно будет всплывать в большинстве ваших интервью. Поэтому, нужно обязательно в ней разбираться и быть готовым объяснить ее суть.
На самом деле, в замыканиях нет ничего сложного. Суть замыкания в JS, заключается в возможности одной функции (назовем ее 1-я функция), которая возвращается из родительской функции (2-я функция), получать доступ к переменным, которые находятся в области видимости родительской, то есть 1-й функции.
Тонкий момент заключается в том, что это происходит даже после того, как родительская, то есть 1-я функция завершила свое выполнение.
Замыкания JS (пример №1)
Для примера, давайте создадим 2 функции:
Теперь давайте запустим функцию external:
Таким образом, автоматически запускается наша внутренняя функция internal, и мы получаем в консоле 2 лога, которые содержат значения переменных externalVar и internalVal:
Так где же здесь замыкание?
Мы получим замыкание, если будем запускать нашу функцию internal не внутри родительской функции external, а за ее пределами.
Как это сделать?
Для этого нам нужно сделать 2 вещи:
Если мы сейчас запустим нашу функцию external, то мы получим «определение» внутренней функции «internal».
Если мы выведем в лог значение нашей новой переменной internalFn, то мы также получим определение нашей внутренней функции internal.
Так где же здесь замыкание?
То есть замыкание в JS можно представить себе, как некое «магическое» пространство между внешней и внутренней функциями, которое остается доступным даже после завершения выполнения внешней функции external.
Замыкания Javascript (пример №2)
Для второго примера давайте создадим следующую функцию:
Теперь давайте создадим 2 новые переменные, в которых используем функцию createAddress с 2-мя типами обращений:
Внутри обеих новых переменных мы получаем доступ к внутренней безымянной функции, которую можем использовать за пределами внешней функции createAddress.
Замыкание JS (пример №3)
Для последнего примера, давайте создадим функцию, которая будет вести подсчет очков для нескольких игроков одновременно.
Ниже, давайте создадим переменные для каждого игрока.
Теперь, каждый раз, когда мы будем вызывать какую-либо из наших новых функций, в ответ будем получать обновленное количество баллов для каждого конкретного игрока.
Это происходит, потому что при создании переменных playerOne и playerTwo мы создали замыкание со своим собственным значением переменной score.
Поднятие в JS (Hoisting в Javascript + 3 примера)
Как понять замыкание в JavaScript: детальный разбор на примерах
Позвольте пропустить дежурные фразы о популярности JavaScript, о его мультипарадигменности и гибкости, о растущем количестве нововведений в стандарте ECMAScript (нужное подчеркнуть). Да и о том, что замыкания — это «мощный инструмент», который не все умеют эффективно использовать, тоже писать не будем. Итак, меньше слов — больше кода:
Теперь внесем небольшие изменения в код:
Здесь мы стали свидетелями так называемого возврата функции из функции (более подробно речь об этом пойдет ниже). Более того, в отличие от предыдущего примера, вызов этой функции (речь о say() ) происходит только в последней строке (с помощью myFunc() ).
Тем не менее, этот код выдает тот же результат, что и в предыдущем примере.
Пример работает корректно: программа выдает строку:
Возможно, кому-то этот код покажется немного запутанным. Но в JS это работает. И виновато в этом не искривление пространства и времени, а более предсказуемое «явление» — замыкание.
Замыкание — механизм, позволяющий внутренним функциям использовать и изменять переменные внешней функции (даже после окончания ее работы).
Получается, что после завершения работы внешней функции makeFunc() ее переменная name продолжает храниться в каком-то другом месте. И функция say() из второго примера способна в любой момент получить туда доступ, прочитать значение этой переменной и даже изменить его.
Давайте разберемся, как и почему это работает, рассмотрим больше примеров, поговорим о том, как и где можно использовать замыкания. Ну и, конечно, в конце усвоим народную мудрость про сборку мусора.
Как и почему это работает
Лексическое окружение
Лексическое окружение — это объект, в котором хранятся:
Скрипт — это блок кода для глобальных переменных:
Переменную можно считать одним из свойств объекта лексического окружения. При изменении переменной меняется и соответствующее свойство объекта.
Вот что произойдет с нашим объектом лексического окружения при запуске кода:
Пока все очень просто.
Объявление функции
Пример с функцией say(name)
Это принципиально отличается от того, как объект лексического окружения обрабатывает переменные. Запомните эту интересную особенность хранения информации о функциях. Она нам еще пригодится.
Внутреннее и внешнее лексическое окружение
Внутреннее и внешнее лексическое окружение
В момент вызова функции в игру вступает ее тело. Это блок кода, имеющий свое собственное (внутреннее) лексическое окружение. Поэтому, пока функция (а точнее — тело) не завершит работу, мы будем иметь дело с двумя лексическими окружениями — внутренним и внешним.
Так и работает алгоритм поиска переменных:
1. ищет в текущем окружении (в нашем примере оно внутреннее — это тело функции say );
2. если находит:
а) прекращает искать;
б) использует переменную;
3. если не находит:
4. Если ничего не находит ни в одном окружении, то в зависимости от режима — либо выдает ошибку ( в режиме strict), либо создает новую глобальную переменную (в обычном режиме).
Возврат функции из функции
Мы уже использовали это выражение выше. Настало время объяснить подробнее. Взгляните на код и схему:
Два лексических окружения
Вооружившись этой информацией, перерисуем схему:
Объект для хранения окружений
Это специальный объект, созданный специально для хранения окружений. В нашем примере [[Environment]] нашей безымянной функции почти пуст ( ). У него есть лишь ссылка на ее внешнее лексическое окружение.
Теперь, вызывая нашу функцию в последней строке, изменим значение переменной count :
Меняем значение переменной count
Вызывая функцию counter() и многократно выполняя count++ внутри нее, мы каждый раз будем получать новое значение — 1, 2, 3, 4 и так далее.
Вложенные функции
Скорее всего, вам уже и так понятно, что «вложенными» бывают не только лексические окружения, но и функции.
Пример выше показывает, что внутри одной функции можно реализовать несколько вложенных функций.
В следующем примере мы повысили вложенность:
Результат, который у нас получится:
Если код все-таки выполнится корректно, то подумайте, пожалуйста, самостоятельно: есть ли в этом коде замыкания?
Более сложные примеры замыканий
Использование тройного замыкания и «приватная» функция
В этом примере можно наблюдать интересный эффект: попытка выполнить counter.changeBy(2) приводит к ошибке.
Обработка Ajax-запроса
В JS через замыкания можно реализовать так называемые callback-функции. Такие функции должны быть запущены как раз после того, как отработает некоторая внешняя функция. При этом callback-функция должна быть определена внутри этой внешней функции и иметь доступ к ее переменным после завершения работы. Пока все сходится.
Здесь sendAjax() — та самая внешняя функция. Внутри нее определена функция, которая запускается, если процесс отправки Ajax-запроса завершился успешно (success). Эта callback-функция использует внешнюю переменную outerVar:
Что и требовалось доказать.
Работа с DOM
При обработке событий, связанных с действиями пользователя (например, клик мышью или нажатие кнопки клавиатуры), мы часто меняем свойства элементов DOM. В этом нам также помогают замыкания.
Допустим, нужно устанавливать новый размер шрифта по нажатию кнопок, расположенных на HTML-странице:
Если хотите поиграться с кодом и программой, заходите сюда.
Вместо заключения: убираем за собой
В JS, как и во многих других языках, очисткой памяти занимается Сборщик мусора. Как и когда удаляются из памяти переменные внешней функции в нашей ситуации?
Ну что же, тогда мы будем самостоятельно следить за распределением и очисткой памяти в подобных ситуациях. Когда нам становится не нужен объект g.[[Environment]] и в частности все переменные внешнего лексического окружения, скажем об этом прямо:
В принципе, все. Только не забудьте сделать так, чтобы эта очистка памяти происходила в нужном месте и в нужное время.
Простой метод измерения реальной скорости загрузки страниц у посетителей сайта
Как можно закэшировать данные и выиграть в производительности
Как работает Server-Sent API с примерами
Примеры применения Javascript в Nginx’e
Как просто сделать удобный дебаг и не лазить в код или как бородатые хакеры перехватывают ajax-запросы, нарушая вашу безопасность.
В своем блоге индийский разработчик Шашват Верма (Shashwat Verma) рассказал, как преобразовать веб-сайт или веб-страницу в прогрессивное веб-приложение (PWA).