Если говорить просто, то замыкание - это внутренняя функция. Ведь javascript разрешает создавать функции по ходу выполнения скрипта. И эти функции имеют доступ к переменным внешней функции.
В этом примере создается внутренняя функция func, изнутри которой доступны как локальные переменные, так и переменные внешней функции outer:
function outer() {
var outerVar;
var func = function() {
var innerVar
...
x = innerVar + outerVar
}
return func
}
Когда заканчивает работать функция outer, внутренняя функция func остается жить, ее можно запускать в другом месте кода.
Получается, что при запуске func используется переменная уже отработавшей функции outer, т.е самим фактом своего существования, funcзамыкает на себя переменные внешней функции (а точнее - всех внешних функций).
Наиболее часто замыкания применяются для назначения функций-обработчиков событий:
function addHideHandler(sourceId, targetId) {
var sourceNode = document.getElementById(sourceId)
var handler = function() {
var targetNode = document.getElementById(targetId)
targetNode.style.display = ‘none’
}
sourceNode.onclick = handler
}
Эта функция принимает два ID элементов HTML и ставит первому элементу обработчик onclick, который прячет второй элемент.
Т.е,
// при клике на элемент с ID="clickToHide"
// будет спрятан элемент с ID="info"
addHideHandler("clickToHide", "info")
Здесь динамически созданный обработчик события handler использует targetId из внешней функции для доступа к элементу.
Каждое выполнение функции хранит все переменные в специальном объекте с кодовым именем [[scope]], который нельзя получить в явном виде, но он есть .
Каждый вызов var... - всего лишь создает новое свойство этого объекта, а любое упоминание переменной - первым делом ищется в свойствах этого объекта.
Такова внутренняя структура "области видимости" - это обыкновенный объект. Все изменения локальных переменных являются изменениями свойств этого неявного объекта.
Обычно после того, как функция закончила выполнение, ее область видимости [[scope]], т.е весь набор локальных переменных убивается.
Общий поток выполнения выглядит так:
// функция для примера
function sum(x,y) {
// неявно создался объект [[scope]]
...
// в [[scope]] записалось свойство z
var z
// нашли переменную в [[scope]], [[scope]].z = x+y
z = x+y
// нашли переменную в [[scope]], return [[scope]].z
return z
// функция закончилась,
// [[scope]] никому больше не нужен и умирает вместе с z
}
Кстати, для кода вне функции(и вообще глобальных переменных) роль объекта-контейнера [[scope]] выполняет объект window.
Когда одна функция создается внутри другой, то ей передается ссылка на объект с локальными переменными [[scope]] внешней функции.
Благодаря существованию этой ссылки, из внутренней функции можно получить переменные внешней функции - через ссылку на ее [[scope]]. Сначала ищем у себя, затем - во внешнем [[scope]] - и так далее по цепочке до самого объекта window.
Замыкание - это когда объект локальных переменных [[scope]] внешней функции остается жить после ее завершения.
Внутренняя функция может обратиться к нему в любой момент и получить переменную внешней функции.
Например, разберем работу функции, которая устанавливает обработчики событий:
function addHideHandler(sourceId, targetId) {
// создан объект [[scope]] со свойствами sourceId, targetId
// записать в [[scope]] свойство sourceNode
var sourceNode = document.getElementById(sourceId)
// записать в [[scope]] свойство handler
var handler = function() {
var targetNode = document.getElementById(targetId)
targetNode.style.display = ‘none’
}
sourceNode.onclick = handler
// функция закончила выполнение
// (***) и тут - самое интересное!
}
При запуске функции все происходит стандартно:
создается [[scope]]
туда записываются локальные переменные
внутренняя функция получает ссылку на [[scope]]
Но в самом конце - внутренняя функция присваивается sourceNode.onclick. Внешняя функция закончила свою работу, но внутренняя - может запуститься когда-нибудь потом.
Интерпретатор javascript не проводит анализ - понадобятся ли внутренней функции переменные из внешней, и какие переменные могут быть нужны.
Вместо этого он просто оставляет весь [[scope]] внешней функции в живых.
Чтобы когда внутренняя функция запустится, если она вдруг не найдет какую-либо переменную в своем [[scope]] - она могла обратиться к [[scope]] внешней функции и нашла бы ее там.
Если внешняя функция была создана внутри еще одной (еще более внешней) функции - то в цепочку добавляется еще один консервированный [[scope]] и так - до глобальной области window.
Функция addEvents принимает массив div'ов и ставит каждому вывод своего номера на onclick.
С вопроса "Почему это не работает?" люди обычно начинают изучение замыканий.
function addEvents(divs) {
for(var i=0; i<divs.length; i++) {
divs[i].innerHTML = i
divs[i].onclick = function() { alert(i) }
}
}
Для тестового примера сделаем 10 разноцветных нумерованных div'ов с разными цветами:
function makeDivs(parentId) {
for (var i=0;i<10;i++) {
var j = 9-i
var div = document.createElement('div')
div.style.backgroundColor = '#'+i+i+j+j+j+i
div.className="closure-div"
div.style.color = '#'+j+j+i+i+i+j
document.getElementById(parentId).appendChild(div)
}
}
Кнопка ниже создаст 10 дивов и вызовет для них addEvents
Если Вы покликаете на div'ы - они все выдают одинаковый alert.
Такой глюк возник из-за того, что все функции div[i].onclick получают значение i из одного на всех [[scope]] внешней функции. А это значение ([[scope]].i) на момент активации onclick-обработчика равно 10 (цикл завершился как только i==10).
Чтобы все было в порядке, в таких случаях применяют специальный прием - выделение [[scope]]. Следующая функция работает правильно. В ней все то же самое, кроме div.onclick.
Теперь все должно быть в порядке - каждый div дает alert на свой номер.
Для присваивания div.onclick запускается временная функция function(x) {..}, принимающая аргумент x и возвращающая обработчик, который берет x из [[scope]] этой временной функции.
Запись function(x) {..} используется для создания функции, и тут же (i) - для запуска с аргументом i.
Вообще, javascript очень удобный в этом смысле язык. Допускает любые конструкции, например, вместо последовательных вызовов:
var f = function(a) { return [0, a, 2*a] }
var t = f(1)
var result = t[2] // 2
можно в одну строчку создать и тут же вызвать функцию и тут же получить 2й элемент массива:
var result = function(a){ return [0,a,2*a] }(1)[2]
Временная функция function(x) {..} заканчивает работать тут же, оставляя в своем [[scope]] правильное значение x, равное текущей переменной i цикла.
Когда обработчик активизируется - alert возьмет из [[scope]] ближайшей внешней функциии правильное значение x.
По идее, этих примеров должно хватать для понимания и практического использования замыканий.
Для общего развития очень даже интересно.
Сам занимаюсь сбором информации по теме web-программирования.
Кстати, у меня есть тоже немало интересных на мой взгляд статей и примеров по javaScript
Все-таки без JavaScript вэб был бы очень скучным
Речь идет просто об иерархии, в чем-то аналогичной наследованию через прототипы.
Однако, насколько я понимаю - из стандарта (http://javascript.ru/ecma/part10#a-10.1.4) не следует, что у [[scope]] есть свойство [[prototype]]. Просто сказано, что [[scope]]'ы формируют иерархию..
Отличная статья, материал не то чтобы сложный, но везде он как-то через назад описан, а тут - ясно и четко.
Кстати, в варианте "Веселой функции"
sum(1)(3) = 4
я бы посоветовал вычитание
sub(7)(8) = -1
гораздо нагляднее, что в какую очередь вызывается.
ИМХО.
Скажите, пожалуйста, а как оградить переменные во внешней фунции от внутренней? Сталкнулся с этой проблемой, когда писал рекурсивную функцию. Заранее спасибо.
Честно сказать, у меня брызнули слезы из глаз.. Я так долго пыталась понять, как работает Javascript, а тут прочитала - и за 5 минут все улеглось по полочкам. Огромное спасибо!
знаете, всё это, конечно, интересно и всё такое, но я бы попросил цвет скобок из зелёного сделать каким нибудь другим. например, синим или оранжевым, ибо зелёный на белом видно плохо и, чтоб отличить фигурную скобку от круглой, приходится глаза ломать... спасибо.
з.ы. ещё их можно обозначить полужирным шрифтом.
Потому что используя var x Вы объявляете локальную переменную x. Соответственно, она не существует, пока не будет инициализирована. А для ее инициализации должно выполнится условие, т.е. ее значение должно быть = 10.
var x = 10;
var f = function() { // тут x - локальная, x = undefined
if (x == 10) { // тут x до сих пор undefined
var x = 20; // тут x стало бы 20, но эта ветвь никогда не выполнится
}
return alert(x);
}
f();
Я думаю, x undefined, потому-что в месте вызова alert(x) есть две переменные x и интерпретатор не может определить, к какой из них он должен обратиться.
WalterScott, вы думаете не правильно. Еще раз внимательно прочтите статью и все станет понятно. B@rmaley.e><e все правильно написал выше.
Не забывайте, что интерпретатор проходится по скрипту дважды:
1. сначала собирает данные о локальных переменных (ищет var) и засовывает их в [[scope]]
2. потом начинает исполнять.
Добрый день!
Спасибо за статью, очень понятно и доступно!
У меня есть вопрос, не омгут ли замыкания вызывать memory leaks?
Например при создании обработчиков событий, когда и сам элемент ДОМ (например див в вашем примере) попадает в [[SCOPE]] и обработчик его события также держит ссылку на тот же [[SCOPE]].
Что произойдет, если например ДИВ будет удален из ДОМ модели?
Здравствуйте.
function outer() {
02 var outerVar;
03
04 var func = function() {
05 var innerVar
06 ...
07 x = innerVar + outerVar
08 }
09 return func
10 }
Можно ли возвращать переменную func без ()? У меня работает только так return func().
Может, я что-то не учла? Тогда что?
я не очень понял, считал что свойствами window становятся только объявленные вне функции глобальные переменные (без var). из статьи же будто бы следует, что свойствами window становятся все переменные, объявленные вне функции
Вопросы по прочитанному. Именно по прочитанному, чтобы ответ на него помог другим разобраться в предмете статьи. Другие вопросы могут быть удалены. Для остальных вопросов и обсуждений есть форум.
P.S. Лучшее "спасибо" - не комментарий, как все здорово, а рекомендация или ссылка на статью.
Новости
Открылась регистрация на мастер-классы по профессиональному Javascript, AJAX/COMET, jQuery в городах:
На моем браузере вывод "makeDivs + addEvents" и "makeDivs + новая функция" отображаются одинаково.
Да, makeDivs должны отображаться одинаково. Но работают по-разному.
Для общего развития очень даже интересно.
Сам занимаюсь сбором информации по теме web-программирования.
Кстати, у меня есть тоже немало интересных на мой взгляд статей и примеров по javaScript
Все-таки без JavaScript вэб был бы очень скучным
Расскажите плз. про последниий пример поподробнее, я ничего не понял
>>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.
Как следить за переменной из [[scope]] функции func ??
В вашем коде нет явного вызова отладчика командой
debugger. Вставьте ее, чтобы понимать точное место вызова в коде.Спасибо за столь подробное объянение
Отличная статья, спасибо огромное!
Молодцы спасибо за статью!
Спс... очень признателен!
Не лишне было бы упомянуть, что замыкания можно организовать и с использованием неанонимных внутренних функций.
Очень похоже, что [[scope]].[[prototype]] вложенной функции == [[scope]] внешней. Это действительно так, или я ошибаюсь?
Речь идет просто об иерархии, в чем-то аналогичной наследованию через прототипы.
Однако, насколько я понимаю - из стандарта (http://javascript.ru/ecma/part10#a-10.1.4) не следует, что у [[scope]] есть свойство [[prototype]]. Просто сказано, что [[scope]]'ы формируют иерархию..
Спасибо. Очень интересно и понятно!
Спасибо за столь подробное объянение
Отлично!
хорошая статья) первый раз вижу такое на русском языке)
Спасибо! На русском аналогов не видел, по моему даже на википедии нет!
Отличная статья, материал не то чтобы сложный, но везде он как-то через назад описан, а тут - ясно и четко.
Кстати, в варианте "Веселой функции"
sum(1)(3) = 4
я бы посоветовал вычитание
sub(7)(8) = -1
гораздо нагляднее, что в какую очередь вызывается.
ИМХО.
Скажите, пожалуйста, а как оградить переменные во внешней фунции от внутренней? Сталкнулся с этой проблемой, когда писал рекурсивную функцию. Заранее спасибо.
Подозреваю, что вам поможет слово var.
Спасибо! очень толково и понятно освящен такой сложный для меня вопрос)
Честно сказать, у меня брызнули слезы из глаз.. Я так долго пыталась понять, как работает Javascript, а тут прочитала - и за 5 минут все улеглось по полочкам. Огромное спасибо!
знаете, всё это, конечно, интересно и всё такое, но я бы попросил цвет скобок из зелёного сделать каким нибудь другим. например, синим или оранжевым, ибо зелёный на белом видно плохо и, чтоб отличить фигурную скобку от круглой, приходится глаза ломать... спасибо.
з.ы. ещё их можно обозначить полужирным шрифтом.
Спасибо за очень хорошую статью.
Добрый день. У меня возник следующий вопрос. Что и в каких scope происходит со свойством x в данном примере:
var x = 10; var f = function() { if (x == 10) { var x = 20; } alert(x); } f();Почему в результате я получаю undefined?
var x = 10; var f = function() { if (x == 10) { x = 20; } return alert(x); } f();Вот это работает как надо, вот только почему не работает при var x=20; мне непонятно... Как вариант, некорректная запись условия. Надо копать мануал.
Потому что используя var x Вы объявляете локальную переменную x. Соответственно, она не существует, пока не будет инициализирована. А для ее инициализации должно выполнится условие, т.е. ее значение должно быть = 10.
var x = 10; var f = function() { // тут x - локальная, x = undefined if (x == 10) { // тут x до сих пор undefined var x = 20; // тут x стало бы 20, но эта ветвь никогда не выполнится } return alert(x); } f();Я думаю, x undefined, потому-что в месте вызова alert(x) есть две переменные x и интерпретатор не может определить, к какой из них он должен обратиться.
WalterScott, вы думаете не правильно. Еще раз внимательно прочтите статью и все станет понятно. B@rmaley.e><e все правильно написал выше.
Не забывайте, что интерпретатор проходится по скрипту дважды:
1. сначала собирает данные о локальных переменных (ищет var) и засовывает их в [[scope]]
2. потом начинает исполнять.
B@rmaley.e>e
не совсем так, вернее вот так:
http://javascript.ru/blog/Dmitry-A.-Soshnikov/Tonkosti-ECMA-262-3.-CHast-2.-Obekt-peremennyh.#obekt-peremennyh-v-kontekste-funkcii
Поэтому Ваши слова в виде:
больше отражают суть.
Как вы отнесетесь, если я сделаю перевод вашей статьи на английский язык с указанием оригинала, т.е. вашей статьи?
У меня выскачило окно и неуберается и просит пароль, а я его не знаю. Что делать?
Вопрос про конструкцию:
Что происходит "внутри" когда используются круглые скобки ( … )?
Иллюстрация:
(function a(b){alert(b)}(1)); //1 function a(b){alert(b)}(1); //ничего (function (){alert(1)}()); //1 function (){alert(1)}(); //syntax errorа эта запись о чем говорит fn(p)(p)
P = function(){} fn(p)(p) = function(){}Ни о чем, это вообще exception.
отличная статья!
предельно понятное изложение сути
спасибо большое)
все вроде понятно особенно со [[scope]], но вот "забавный" пример я вообще понять не могу! Не могли бы Вы его объяснить... спасибо
Добрый день!
Спасибо за статью, очень понятно и доступно!
У меня есть вопрос, не омгут ли замыкания вызывать memory leaks?
Например при создании обработчиков событий, когда и сам элемент ДОМ (например див в вашем примере) попадает в [[SCOPE]] и обработчик его события также держит ссылку на тот же [[SCOPE]].
Что произойдет, если например ДИВ будет удален из ДОМ модели?
Спасибо огромное! Уникальная статья на русском.
Обязательно рекомендую к прочтению!
Вопрос: значит "замыкание" - это closure. А что такое enclosure?
Здравствуйте.
function outer() {
02 var outerVar;
03
04 var func = function() {
05 var innerVar
06 ...
07 x = innerVar + outerVar
08 }
09 return func
10 }
Можно ли возвращать переменную func без ()? У меня работает только так return func().
Может, я что-то не учла? Тогда что?
Это в самом верху страницы
интересно вот что. почему замыкания не работают с функциями создаваемыми через new Function()?
var outer = function() {
var local = "works";
var inner = new Function("alert(local);");
return inner;
};
outer()();
ReferenceError: loc is not defined.
[[scope]] не передается? или все-таки поиск имени local не так просто ведется?
сорри, local в тексте ошибки вместо loc.
отвечу сам на свой вопрос:
"... [[Scope]] property of functions created via the Function constructor contains always only the global object".
http://dmitrysoshnikov.com/ecmascript/chapter-4-scope-chain/
я не очень понял, считал что свойствами window становятся только объявленные вне функции глобальные переменные (без var). из статьи же будто бы следует, что свойствами window становятся все переменные, объявленные вне функции
если я переменную объявляю вне функции, но с var, то она остается глобальной или нет?
становится, попробуй написать вне функции var p=12; а потом где-нибудь alert(window.p) или alert(window['p'])
Отправить комментарий
Приветствуются комментарии:- Полезные.
- Дополняющие прочитанное.
- Вопросы по прочитанному. Именно по прочитанному, чтобы ответ на него помог другим разобраться в предмете статьи. Другие вопросы могут быть удалены.
P.S. Лучшее "спасибо" - не комментарий, как все здорово, а рекомендация или ссылка на статью.Для остальных вопросов и обсуждений есть форум.