Мой друг и я в настоящее время обсуждаем, что является закрытием в JS, а что нет. Мы просто хотим убедиться, что мы действительно понимаем это правильно.
Давайте возьмем этот пример. У нас есть цикл подсчета, и мы хотим напечатать переменную counter на консоли с задержкой. Поэтому мы используем setTimeout
и закрытия для захвата значения переменной счетчика, чтобы убедиться, что она не будет печатать N раз значение N.
Неправильное решение без замыканий или чего-то близкого к замыканиям было бы:
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
который, конечно, будет в 10 раз превышать значение i
после цикла, а именно 10.
Итак, его попытка была:
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2);
}, 1000)
})();
}
печать от 0 до 9, как и ожидалось.
Я сказал ему, что он не использует закрытие для захвата i
, но он настаивает, что он есть. Я доказал, что он не использует замыкания , помещая тело цикла for в другое setTimeout
(передавая свою анонимную функцию setTimeout
), печатая снова 10 раз 10. То же самое относится , если хранить его функцию в var
и выполнить его после цикла, а также печать в 10 раз 10. Так что мой аргумент, что он на самом деле не захватить значениеi
, что делает его версию не укупорочное.
Моя попытка была:
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2);
}
})(i), 1000);
}
Итак, я фиксирую i
(назван i2
в замыкании), но теперь я возвращаю другую функцию и передаю ее. В моем случае функция, переданная в setTimeout, действительно захватывает i
.
Теперь, кто использует замыкания, а кто нет?
Обратите внимание, что оба решения печатают от 0 до 9 на консоли с задержкой, поэтому они решают исходную проблему, но мы хотим понять, какое из этих двух решений использует замыкания для достижения этой цели.
источник
Ответы:
Примечание редактора: все функции в JavaScript являются замыканиями, как описано в этом посте . Однако нас интересует только определение подмножества этих функций, которые интересны с теоретической точки зрения. Отныне любая ссылка на слово замыкание будет ссылаться на это подмножество функций, если не указано иное.
Простое объяснение замыканий:
Теперь давайте используем это, чтобы выяснить, кто использует замыкания, а кто нет (для пояснения я назвал функции):
Случай 1: Программа вашего друга
В вышеуказанной программе есть две функции:
f
иg
. Давайте посмотрим, являются ли они замыканиями:Для
f
:i2
является локальным переменной.i
это бесплатно переменной.setTimeout
это бесплатно переменной.g
является локальным переменной.console
является свободной переменной.i
будет связан с глобальной области.setTimeout
будет связан с глобальной области.console
будет связан с глобальной области.i
не сомкнулись наf
.setTimeout
не сомкнулись наf
.console
не сомкнулись наf
.Таким образом, функция
f
не является закрытием.Для
g
:console
это бесплатно переменной.i2
является свободной переменной.console
это связано с глобальной области.i2
будет связан с областьюf
.setTimeout
.console
не сомкнулись наg
.i2
будет закрыта в течение отg
.Таким образом, функция
g
является закрытием для свободной переменнойi2
(которая является повышением значенияg
), когда на нее ссылаются изнутриsetTimeout
.Плохо для вас: ваш друг использует закрытие. Внутренняя функция - это замыкание.
Случай 2: Ваша программа
В вышеуказанной программе есть две функции:
f
иg
. Давайте посмотрим, являются ли они замыканиями:Для
f
:i2
является локальной переменной.g
является локальной переменной.console
является свободной переменной.console
будет связан с глобальной области.console
не сомкнулись наf
.Таким образом, функция
f
не является закрытием.Для
g
:console
является свободной переменной.i2
является свободной переменной.console
будет связан с глобальной области.i2
будет связан с областьюf
.setTimeout
.console
не сомкнулись наg
.i2
будет закрыта в течение отg
.Таким образом, функция
g
является закрытием для свободной переменнойi2
(которая является повышением значенияg
), когда на нее ссылаются изнутриsetTimeout
.Повезло тебе: вы используете закрытие. Внутренняя функция - это замыкание.
Так что и вы, и ваш друг используете замыкания. Перестать спорить. Я надеюсь, что прояснил концепцию замыканий и как определить их для вас обоих.
Редактировать: простое объяснение, почему все функции закрываются (кредиты @Peter):
Сначала давайте рассмотрим следующую программу (это элемент управления ):
lexicalScope
иregularFunction
не являются замыканиями из приведенного выше определения .message
предупреждение, потому чтоregularFunction
это не закрытие (то есть она имеет доступ ко всем переменным в родительской области, включаяmessage
).message
это действительно насторожило.Далее давайте рассмотрим следующую программу (это альтернатива ):
closureFunction
замыкание из приведенного выше определения .message
не будем предупреждены, потому чтоclosureFunction
это закрытие (то есть она имеет доступ ко всем своим нелокальным переменным во время создания функции ( см. Этот ответ ) - это не включаетmessage
).message
на самом деле это предупреждение.Что мы выводим из этого?
источник
g
работает в области действияsetTimeout
, но в случае 2 вы говорите, что онf
работает в глобальной области действия. Они оба находятся внутри setTimeout, так в чем же разница?Согласно
closure
определению:Вы используете,
closure
если вы определяете функцию, которая использует переменную, которая определена вне функции. (мы называем переменную свободной переменной ).Все они используют
closure
(даже в 1-м примере).источник
i2
которая определена снаружи.В двух словах, Javascript Closures позволяет функции получить доступ к переменной , объявленной в лексической родительской функции .
Давайте посмотрим более подробное объяснение. Чтобы понять замыкания, важно понять, как JavaScript определяет переменные.
Области применения
В JavaScript области видимости определяются с помощью функций. Каждая функция определяет новую область.
Рассмотрим следующий пример;
зов f печатает
Давайте теперь рассмотрим случай, когда у нас есть функция,
g
определенная в другой функцииf
.Мы будем называть
f
в лексический родитель изg
. Как объяснялось ранее, теперь у нас есть 2 области действия; сфераf
и объемg
.Но одна область видимости находится «в пределах» другой области, так является ли область дочерней функции частью области родительской функции? Что происходит с переменными, объявленными в области родительской функции; смогу ли я получить к ним доступ из области дочерней функции? Это именно то, где замыкания вступают.
Затворы
В JavaScript функция
g
может не только обращаться к любым переменным, объявленным в области видимости,g
но также и к любым переменным, объявленным в области видимости родительской функцииf
.Подумайте о следующем;
зов f печатает
Давайте посмотрим на строку
console.log(foo);
. На данный момент мы находимся в области видимостиg
и пытаемся получить доступ к переменнойfoo
, объявленной в области видимостиf
. Но, как указывалось ранее, мы можем получить доступ к любой переменной, объявленной в лексической родительской функции, которая здесь имеет место;g
является лексическим родителемf
. Поэтомуhello
печатается.Давайте теперь посмотрим на строку
console.log(bar);
. На данный момент мы находимся в области видимостиf
и пытаемся получить доступ к переменнойbar
, объявленной в области видимостиg
.bar
не объявлен в текущей области видимости, и функцияg
не является родителемf
, поэтомуbar
не определенаНа самом деле мы также можем получить доступ к переменным, объявленным в области действия лексической функции «прародителя». Поэтому, если бы была функция,
h
определенная в функцииg
то
h
сможет получить доступ ко всем переменным , объявленным в области видимости функцииh
,g
иf
. Это сделано с замыканиями . В JavaScript замыкания позволяют нам получить доступ к любой переменной, объявленной в лексической родительской функции, в лексической родительской функции, в лексической родительской функции и т. Д. Это можно рассматривать как цепочку областей видимости ;scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ...
до последней родительской функции, у которой нет лексического родителя.Объект окна
На самом деле цепочка не останавливается на последней родительской функции. Есть еще одна особая область; глобальный масштаб . Каждая переменная, не объявленная в функции, считается объявленной в глобальной области видимости. Глобальная сфера имеет две особенности;
window
объекта.Поэтому существует ровно два способа объявления переменной
foo
в глобальной области видимости; либо не объявив его в функции, либо установив свойствоfoo
объекта окна.Обе попытки используют замыкания
Теперь, когда вы прочитали более подробное объяснение, теперь может быть очевидно, что оба решения используют замыкания. Но чтобы быть уверенным, давайте сделаем доказательство.
Давайте создадим новый язык программирования; JavaScript-No-Closure. Как следует из названия, JavaScript-No-Closure идентичен JavaScript, за исключением того, что он не поддерживает Closures.
Другими словами;
Хорошо, давайте посмотрим, что происходит с первым решением с JavaScript-No-Closure;
поэтому это будет печатать
undefined
10 раз в JavaScript-No-Closure.Следовательно, первое решение использует замыкание.
Давайте посмотрим на второе решение;
поэтому это будет печатать
undefined
10 раз в JavaScript-No-Closure.Оба решения используют замыкания.
Редактировать: предполагается, что эти 3 фрагмента кода не определены в глобальной области видимости. В противном случае переменные
foo
иi
будут привязаны кwindow
объекту и поэтому доступны черезwindow
объект как в JavaScript, так и в JavaScript-No-Closure.источник
i
быть неопределенным? Вы просто ссылаетесь на родительскую область, которая все еще действует, если не было замыканий.i2
вi
то время , когда вы определяете функцию. Это делаетi
НЕ свободной переменной. Тем не менее, мы считаем вашу функцию закрытой, но без какой-либо свободной переменной, в этом все дело.Я никогда не был доволен тем, как кто-то объясняет это.
Ключом к пониманию замыканий является понимание того, как будет выглядеть JS без замыканий.
Без замыканий это приведет к ошибке
После того, как externalFunc вернется в воображаемой версии JavaScript с отключенным замыканием, ссылка на externalVar будет собирать мусор и исчезнет, оставляя для внутреннего функционала ничего, на что можно было бы ссылаться.
Замыкания - это, по сути, особые правила, которые вступают в действие и позволяют существовать этим переменным, когда внутренняя функция ссылается на переменные внешней функции. В случае замыканий ссылочные переменные сохраняются даже после выполнения внешней функции или «замыкания», если это помогает вам запомнить точку.
Даже с замыканиями жизненный цикл локальных переменных в функции без внутренних функций, которые ссылаются на ее локальные функции, работает так же, как и в версии без замыканий. Когда функция заканчивается, местные жители получают мусор.
Если у вас есть ссылка во внутренней функции на внешнюю переменную, это похоже на то, что дверной косяк становится препятствием для сборки мусора для этих ссылочных переменных.
Возможно, более точный способ взглянуть на замыкания - это то, что внутренняя функция в основном использует внутреннюю область видимости в качестве своей собственной области видимости.
Но упомянутый контекст на самом деле является постоянным, а не снимком. Повторное срабатывание возвращенной внутренней функции, которая продолжает увеличивать и регистрировать локальную переменную внешней функции, будет продолжать оповещать о более высоких значениях.
источник
Вы оба используете замыкания.
Я собираюсь с определением Википедии здесь:
Попытка вашего друга явно использует переменную
i
, которая не является локальной, беря ее значение и делая копию для хранения в локальнойi2
.Ваша собственная попытка передается
i
(которая на сайте вызова находится в области видимости) анонимной функции в качестве аргумента. Пока это не закрытие, но тогда эта функция возвращает другую функцию, которая ссылается на то же самоеi2
. Поскольку внутренняя анонимная функцияi2
не является локальной, это создает замыкание.источник
i
вi2
, а затем определяет некоторую логику и выполняет эту функцию. Если бы я не выполнил это немедленно, но сохранил бы это в переменной и выполнил бы это после цикла, это напечатало бы 10, не так ли? Так что это не захватило меня.i
просто отлично. Поведение, которое вы описываете, не является результатом замыкания против незакрытия; это результат изменения закрытой переменной за это время. Вы делаете то же самое, используя другой синтаксис, немедленно вызывая функцию и передавая ееi
в качестве аргумента (которая сразу копирует ее текущее значение). Если вы положите свой собственныйsetTimeout
в другой,setTimeout
то же самое произойдет.Вы и ваш друг используете замыкания:
В коде вашего друга функция
function(){ console.log(i2); }
определена внутри замыкания анонимной функцииfunction(){ var i2 = i; ...
и может читать / записывать локальные переменныеi2
.В вашем коде функция
function(){ console.log(i2); }
определена внутри замыкания функцииfunction(i2){ return ...
и может считывать / записывать локальные значенияi2
(объявленные в данном случае в качестве параметра).В обоих случаях функция
function(){ console.log(i2); }
затем перешла вsetTimeout
.Другой эквивалент (но с меньшим использованием памяти):
источник
закрытие
Замыкание - это не функция и не выражение. Его следует рассматривать как своего рода «снимок» из используемых переменных вне функциональной области и используемых внутри функции. Грамматически следует сказать: «возьмите замыкание переменных».
Опять же, другими словами: замыкание - это копия соответствующего контекста переменных, от которых зависит функция.
Еще раз (naïf): замыкание имеет доступ к переменным, которые не передаются в качестве параметра.
Имейте в виду, что эти функциональные концепции сильно зависят от языка программирования / среды, которую вы используете. В JavaScript закрытие зависит от лексической области видимости (что верно для большинства c-языков).
Таким образом, возвращение функции в основном возвращает анонимную / неназванную функцию. Когда функция обращается к переменным, не переданным в качестве параметра и находящимся в пределах (лексической) области видимости, выполняется закрытие.
Итак, что касается ваших примеров:
Все используют замыкания. Не путайте точку исполнения с замыканиями. Если «моментальный снимок» замыканий сделан в неправильный момент, значения могут быть неожиданными, но, безусловно, замыкание берется!
источник
Давайте посмотрим на оба пути:
Объявляет и немедленно выполняет анонимную функцию, которая выполняется
setTimeout()
в своем собственном контексте. Текущее значениеi
сохраняется путем копирования вi2
первую очередь; это работает из-за немедленного исполнения.Объявляет контекст выполнения для внутренней функции, посредством чего текущее значение
i
сохраняется вi2
; этот подход также использует немедленное выполнение для сохранения значения.Важный
Следует отметить, что семантика выполнения НЕ одинакова для обоих подходов; Ваша внутренняя функция передается,
setTimeout()
тогда как его внутренняя функция вызываетsetTimeout()
себя.Заключение обоих кодов в другой
setTimeout()
не доказывает, что только во втором подходе используются замыкания, для начала просто не то же самое.Вывод
Оба метода используют замыкания, поэтому все сводится к личному вкусу; второй подход легче «передвигать» или обобщать.
источник
setTimeout()
?i
может быть изменена, не влияя на то, что функция должна вывести, независимо от того, где и когда мы ее выполняем.()
, передав таким образом функцию, и вы увидите 10 раз результат10
.()
это именно то, что заставляет его код работать так же, как ваш(i)
; вы не просто обернули его код, вы внесли в него изменения ... поэтому вы больше не можете сделать правильное сравнение.Я написал это некоторое время назад, чтобы напомнить себе, что такое замыкание и как оно работает в JS.
Закрытие - это функция, которая при вызове использует область, в которой она была объявлена, а не область, в которой она была вызвана. В javaScript все функции ведут себя так. Значения переменных в области действия сохраняются до тех пор, пока существует функция, которая все еще указывает на них. Исключением из правила является «this», которое относится к объекту, внутри которого находится функция, когда она вызывается.
источник
После тщательного осмотра похоже, что вы оба используете закрытие.
В случае с вашими друзьями
i
доступ осуществляется внутри анонимной функции 1 иi2
в анонимной функции 2, гдеconsole.log
присутствует.В вашем случае вы обращаетесь
i2
внутрь анонимной функции, гдеconsole.log
присутствует. Добавьтеdebugger;
оператор доconsole.log
и в инструментах разработчика Chrome в разделе «Переменные области видимости», он скажет, в какой области видна переменная.источник
Учтите следующее. Это создает и воссоздает функцию,
f
которая закрываетсяi
, но разные!в то время как следующее закрывает саму «функцию» «
(сами! фрагмент после этого использует один референт
f
)или чтобы быть более явным:
NB. последнее определение
f
IS ,function(){ console.log(9) }
прежде чем0
будет напечатано.Предостережение! Концепция замыкания может отвлекать нас от сути элементарного программирования:
x-refs .:
Как работают JavaScript-замыкания?
Javascript Closures Объяснение
Требует ли (JS) замыкания функция внутри функции
Как понимать замыкания в Javascript?
Путаница в локальной и глобальной переменной Javascript
источник
Run' only was desired - not sure how to remove the
Копировать`Я хотел бы поделиться своим примером и объяснением закрытия. Я сделал пример с Python и две фигуры для демонстрации состояния стека.
Вывод этого кода будет следующим:
Вот два рисунка, на которых показаны стеки и замыкание, прикрепленные к объекту функции.
когда функция возвращается от создателя
когда функция вызывается позже
Когда функция вызывается через параметр или нелокальную переменную, коду нужны привязки локальной переменной, такие как margin_top, padding, а также a, b, n. Для того, чтобы код функции работал, должен быть доступен стековый фрейм функции maker, который давно отсутствовал, который поддерживается в замыкании, которое мы можем найти вместе с объектом сообщения функции.
источник
delete
ссылку под ответом.