Закрытие JavaScript против анонимных функций
Друг мой и я в настоящее время обсуждаем, что такое закрытие в JS, а что нет. Мы просто хотим убедиться, что мы действительно понимаем это правильно.
Возьмем этот пример. У нас есть цикл подсчета и вы хотите напечатать переменную счетчика на консоли. Поэтому мы используем 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 loop в другой 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 на консоли, поэтому они решают исходную проблему, но мы хотим понять, какое из этих двух решений использует закрытие для выполнения этого.
Ответы
Ответ 1
Примечание редактора: Все функции в JavaScript - это блокировки, как описано в этом размещать. Однако нас интересует только определение подмножества этих функций, которые интересно с теоретической точки зрения. В дальнейшем любая ссылка на закрытие слова будет ссылаться на это подмножество функций, если не указано иное.
Простое объяснение замыкания:
- Возьмите функцию. Пусть это называется F.
- Список всех переменных F.
- Переменные могут быть двух типов:
- Локальные переменные (связанные переменные)
- Нелокальные переменные (свободные переменные)
- Если F не имеет свободных переменных, то это не может быть замыканием.
- Если F имеет любые свободные переменные (которые определены в a родительской области F), то:
- Должна быть только одна родительская область F, к которой привязана свободная переменная a.
- Если F является ссылкой из внешней , родительской области, то это становится закрытием для , свободной переменной.
- Свободная переменная называется upvalue замыкания F.
Теперь позвольте использовать это, чтобы выяснить, кто использует закрытие, а кто нет (для объяснения я назвал функции):
Случай 1: программа вашего друга
for (var i = 0; i < 10; i++) {
(function f() {
var i2 = i;
setTimeout(function g() {
console.log(i2);
}, 1000);
})();
}
В вышеуказанной программе есть две функции: 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
(которая является upvalue для g
) , когда она указана из setTimeout
.
Плохо для вас: ваш друг использует закрытие. Внутренняя функция - это замыкание.
Случай 2: Ваша программа
for (var i = 0; i < 10; i++) {
setTimeout((function f(i2) {
return function g() {
console.log(i2);
};
})(i), 1000);
}
В вышеуказанной программе есть две функции: f
и g
. Посмотрим, закрыты ли они:
Для f
:
- Перечислите переменные:
-
i2
является локальной переменной.
-
g
- это локальная переменная.
-
console
является переменной свободной.
- Найдите родительскую область, к которой привязана каждая свободная переменная:
-
console
привязан к глобальной области.
- В какой области действия находится функция , на которую ссылаются? Глобальная область .
- Следовательно,
console
не замкнуто над на f
.
Таким образом, функция f
не является замыканием.
Для g
:
- Перечислите переменные:
-
console
является переменной свободной.
-
i2
является переменной свободной.
- Найдите родительскую область, к которой привязана каждая свободная переменная:
-
console
привязан к глобальной области.
-
i2
привязан к области f
.
- В какой области действия находится функция , на которую ссылаются? область
setTimeout
.
- Следовательно,
console
не закрыто над на g
.
- Следовательно,
i2
закрыто над на g
.
Таким образом, функция g
является замыканием для свободной переменной i2
(которая является upvalue для g
) , когда она указана из setTimeout
.
Хорошо для вас: вы используете закрытие. Внутренняя функция - это замыкание.
Итак, вы и ваш друг используете закрытие. Перестать спорить. Надеюсь, я очистил концепцию закрытия и как идентифицировать их для вас обоих.
Изменить: Простое объяснение причин закрытия всех функций (кредиты @Peter):
Сначала рассмотрим следующую программу (это control):
lexicalScope();
function lexicalScope() {
var message = "This is the control. You should be able to see this message being alerted.";
regularFunction();
function regularFunction() {
alert(eval("message"));
}
}
Ответ 2
В соответствии с определением closure
:
"Закрытие" - это выражение (обычно функция), которое может иметь свободные переменные вместе с средой, которая связывает эти переменные (что "закрывает" выражение).
Вы используете closure
, если вы определяете функцию, которая использует переменную, которая определена вне функции. (мы называем переменную a свободной переменной).
Все они используют closure
(даже в первом примере).
Ответ 3
В двух словах Javascript Closures разрешает функции получать доступ к переменной, которая объявлена в лексико-родительской функции.
Посмотрите более подробное объяснение.
Чтобы понять замыкания, важно понять, как переменные области видимости JavaScript.
Прицелы
В JavaScript областях определены функции.
Каждая функция определяет новую область.
Рассмотрим следующий пример:
function f()
{//begin of scope f
var foo='hello'; //foo is declared in scope f
for(var i=0;i<2;i++){//i is declared in scope f
//the for loop is not a function, therefore we are still in scope f
var bar = 'Am I accessible?';//bar is declared in scope f
console.log(foo);
}
console.log(i);
console.log(bar);
}//end of scope f
вызов f prints
hello
hello
2
Am I Accessible?
Теперь рассмотрим случай, когда мы имеем функцию g
, определенную в рамках другой функции f
.
function f()
{//begin of scope f
function g()
{//being of scope g
/*...*/
}//end of scope g
/*...*/
}//end of scope f
Мы будем называть f
лексическим родителем g
.
Как объяснялось ранее, у нас теперь есть 2 области; область f
и область g
.
Но одна область видимости "внутри" другой области видимости, так же как и область дочерней функции в области родительской функции? Что происходит с переменными, объявленными в области родительской функции; я смогу получить к ним доступ из сферы действия дочерней функции?
То, где именно находятся затворы.
Затворы
В JavaScript функция g
может не только получить доступ к любым переменным, объявленным в области g
, но и получить доступ к любым переменным, объявленным в области родительской функции f
.
Рассмотрим следующее:
function f()//lexical parent function
{//begin of scope f
var foo='hello'; //foo declared in scope f
function g()
{//being of scope g
var bar='bla'; //bar declared in scope g
console.log(foo);
}//end of scope g
g();
console.log(bar);
}//end of scope f
вызов f prints
hello
undefined
Посмотрим на строку console.log(foo);
. На этом этапе мы находимся в области g
, и мы пытаемся получить доступ к переменной foo
, объявленной в области f
. Но, как указано выше, мы можем получить доступ к любой переменной, объявленной в лексической родительской функции, которая здесь имеет место; g
является лексическим родителем f
. Поэтому печатается hello
.
Теперь посмотрим на строку console.log(bar);
. На данный момент мы находимся в области f
, и мы пытаемся получить доступ к переменной bar
, объявленной в области g
. bar
не объявляется в текущей области, а функция g
не является родительским элементом f
, поэтому bar
- undefined
Фактически мы также можем получить доступ к переменным, объявленным в области лексической функции "великого родителя". Поэтому, если бы существовала функция h
, определенная внутри функции g
function f()
{//begin of scope f
function g()
{//being of scope g
function h()
{//being of scope h
/*...*/
}//end of scope h
/*...*/
}//end of scope g
/*...*/
}//end of scope f
то h
сможет получить доступ ко всем переменным, объявленным в области функций h
, g
и f
. Это делается при закрытии. В JavaScript закрытие позволяет нам получить доступ к любой переменной, объявленной в лексической родительской функции, в лексической великой родительской функции, в лексической величественной родительской функции и т.д.
Это можно рассматривать как цепочку цепей ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ...
до последней родительской функции, которая не имеет лексического родителя.
Объект window
На самом деле цепочка не останавливается на последней родительской функции. Есть еще один особый масштаб; глобальная область . Каждая переменная, не объявленная в функции, считается объявленной в глобальной области. Глобальный охват имеет две специальности:
- любая переменная, объявленная в глобальной области, доступна везде
- переменные, объявленные в глобальной области, соответствуют свойствам объекта
window
.
Следовательно, существует всего два способа объявления переменной foo
в глобальной области; либо не объявляя его в функции, либо устанавливая свойство foo
объекта окна.
Обе попытки используют блокировки
Теперь, когда вы прочли более подробное объяснение, теперь может показаться очевидным, что в обоих решениях используются блокировки.
Но, конечно, давайте сделаем доказательство.
Позвольте создать новый язык программирования; JavaScript-No-Closure.
Как следует из названия, JavaScript-No-Closure идентичен JavaScript, за исключением того, что он не поддерживает Closures.
Другими словами:
var foo = 'hello';
function f(){console.log(foo)};
f();
//JavaScript-No-Closure prints undefined
//JavaSript prints hello
Хорошо, посмотрим, что произойдет с первым решением с JavaScript-No-Closure;
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}, 1000)
})();
}
поэтому это будет печатать undefined
10 раз в JavaScript-No-Closure.
Следовательно, первое решение использует замыкание.
Посмотрим на второе решение:
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}
})(i), 1000);
}
поэтому это будет печатать undefined
10 раз в JavaScript-No-Closure.
Оба решения используют закрытие.
Изменить: предполагается, что эти 3 фрагмента кода не определены в глобальной области. В противном случае переменные foo
и i
будут привязаны к объекту window
и поэтому доступны через объект window
как в JavaScript, так и в JavaScript-No-Closure.
Ответ 4
Я никогда не был доволен тем, как кто-то объясняет это.
Ключом к пониманию закрытий является понимание того, что будет JS без закрытия.
Без закрытий это вызовет ошибку
function outerFunc(){
var outerVar = 'an outerFunc var';
return function(){
alert(outerVar);
}
}
outerFunc()(); //returns inner function and fires it
Как только внешнийFunc вернётся в воображаемой закрытой-отключенной версии JavaScript, ссылка на внешнийVar будет собирать мусор и не оставлять ничего там, где внутренняя функция ссылается.
Закрытие - это, по сути, специальные правила, которые пинают и позволяют, чтобы эти вары существовали, когда внутренняя функция ссылается на внешние переменные функции. С закрытием ссылки, обозначенные варами, поддерживаются даже после выполнения внешней функции или "закрыты", если это помогает вам запомнить эту точку.
Даже при закрытии жизненный цикл локальных варов в функции без внутренних функций, которые ссылаются на своих локальных жителей, работает так же, как и в закрытой версии. Когда функция закончена, локальные жители получают собранный мусор.
Как только у вас есть ссылка во внутреннем func внешнем var, однако это, как дверной косяк, помещается на пути сбора мусора для упомянутых vars.
Возможно, более точный способ взглянуть на замыкания состоит в том, что внутренняя функция в основном использует внутреннюю область видимости как свою собственную область видимости.
Но связанный с ним контекст на самом деле постоянный, а не как моментальный снимок. Повторное включение возвращенной внутренней функции, которая продолжает увеличивать и регистрировать внешнюю функцию, локальный var будет предупреждать более высокие значения.
function outerFunc(){
var incrementMe = 0;
return function(){ incrementMe++; console.log(incrementMe); }
}
var inc = outerFunc();
inc(); //logs 1
inc(); //logs 2
Ответ 5
Вы оба используете закрытие.
Я собираюсь с определение Википедии здесь:
В информатике закрытие (также лексическое замыкание или функция замыкание) является функцией или ссылкой на функцию вместе с справочная среда - таблица, хранящая ссылку на каждую из нелокальные переменные (также называемые свободными переменными) этой функции. Закрытие - в отличие от простого указателя функции - позволяет функции доступа эти нелокальные переменные, даже если они вызываются вне лексический охват.
В попытке вашего друга явно используется переменная i
, которая является нелокальной, принимая ее значение и делая копию для хранения в локальном i2
.
Ваша собственная попытка передает i
(которая на узле вызова в области) анонимной функции в качестве аргумента. Пока это не закрытие, но тогда эта функция возвращает другую функцию, которая ссылается на те же i2
. Поскольку внутри внутренней анонимной функции i2
не является локальным, это создает замыкание.
Ответ 6
Вы и ваш друг используете закрытие:
Закрытие - это особый вид объекта, который объединяет две вещи: функцию и среду, в которой эта функция была создана. Среда состоит из любых локальных переменных, которые были в области видимости в момент создания закрытия.
MDN: https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures
В вашей кодовой функции друга function(){ console.log(i2); }
, определенной внутри закрытия анонимной функции function(){ var i2 = i; ...
, и может читать/записывать локальную переменную i2
.
В вашей функции кода function(){ console.log(i2); }
, определенной внутри закрытия функции function(i2){ return ...
, и может читать/записывать локальный ценный i2
(объявленный в этом случае как параметр).
В обоих случаях функция function(){ console.log(i2); }
затем переходит в setTimeout
.
Другой эквивалент (но с меньшим использованием памяти):
function fGenerator(i2){
return function(){
console.log(i2);
}
}
for(var i = 0; i < 10; i++) {
setTimeout(fGenerator(i), 1000);
}
Ответ 7
Посмотрите на оба пути:
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2);
}, 1000)
})();
Объявляет и немедленно выполняет анонимную функцию, которая запускает setTimeout()
в своем собственном контексте. Текущее значение i
сохраняется, сначала сделав копию в i2
; он работает из-за немедленного выполнения.
setTimeout((function(i2){
return function() {
console.log(i2);
}
})(i), 1000);
Объявляет контекст выполнения для внутренней функции, при которой текущее значение i
сохраняется в i2
; этот подход также использует немедленное выполнение для сохранения значения.
Внимание!
Следует упомянуть, что семантика выполнения не совпадает с обеими подходами; ваша внутренняя функция переходит к setTimeout()
, тогда как его внутренняя функция вызывает setTimeout()
.
Обтекание обоих кодов внутри другого setTimeout()
не доказывает, что только второй подход использует закрытие, с самого начала не то же самое.
Заключение
Оба метода используют закрытие, поэтому оно сводится к личным вкусам; второй подход легче "перемещать" или обобщать.
Ответ 8
Закрытие
Закрытие не является функцией, а не выражением. Его следует рассматривать как своего рода "моментальный снимок" из используемых переменных вне функции и использовать внутри функции. Грамматически следует сказать: "возьмите замыкание переменных".
Иными словами: замыкание - это копия соответствующего контекста переменных, от которых зависит функция.
Еще раз (naïf): закрытие имеет доступ к переменным, которые не передаются в качестве параметра.
Помните, что эти функциональные концепции сильно зависят от языка программирования/среды, которую вы используете. В JavaScript закрытие зависит от лексического охвата (что истинно на большинстве c-языков).
Таким образом, возврат функции в основном возвращает анонимную/неназванную функцию. Когда переменная доступа к функции, не переданная как параметр, и внутри ее (лексической) области, было сделано закрытие.
Итак, в отношении ваших примеров:
// 1
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i); // closure, only when loop finishes within 1000 ms,
}, 1000); // i = 10 for all functions
}
// 2
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i; // closure of i (lexical scope: for-loop)
setTimeout(function(){
console.log(i2); // closure of i2 (lexical scope:outer function)
}, 1000)
})();
}
// 3
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2); // closure of i2 (outer scope)
}
})(i), 1000); // param access i (no closure)
}
Все используют замыкания. Не путайте точку исполнения с закрытием. Если "моментальный снимок" замыканий берется в неподходящий момент, значения могут быть неожиданными, но, конечно же, происходит закрытие!
Ответ 9
Я написал это некоторое время назад, чтобы напомнить себе, что такое закрытие и как оно работает в JS.
Закрытие - это функция, которая при вызове использует область, в которой она была объявлена, а не область, в которой она была вызвана. В javaScript все функции ведут себя так. Значения переменных в области действия сохраняются до тех пор, пока есть функция, которая все еще указывает на них. Исключением из правила является 'this', который относится к объекту, внутри которого функция находится, когда она вызывается.
var z = 1;
function x(){
var z = 2;
y(function(){
alert(z);
});
}
function y(f){
var z = 3;
f();
}
x(); //alerts '2'
Ответ 10
После тщательного осмотра, похоже, что вы используете закрытие.
В случае с друзьями, i
открывается внутри анонимной функции 1 и i2
открывается в анонимной функции 2, где присутствует console.log
.
В вашем случае вы получаете доступ к i2
внутри анонимной функции, где присутствует console.log
. Добавьте оператор debugger;
до console.log
и в инструментах разработчика Chrome в разделе "Scope variables" он скажет, в какой области видимости переменная.
Ответ 11
Рассмотрим следующее.
Это создает и воссоздает функцию f
, которая закрывается на i
, но отличается от нее::
i=100;
f=function(i){return function(){return ++i}}(0);
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));
f=function(i){return new Function('return ++i')}(0); /* function declarations ~= expressions! */
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));