В двух словах, 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 печатает
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 печатает
hello
undefined
Давайте посмотрим на строку console.log(foo);
. На данный момент мы находимся в области видимости g
и пытаемся получить доступ к переменной foo
, объявленной в области видимости f
. Но, как указывалось ранее, мы можем получить доступ к любой переменной, объявленной в лексической родительской функции, которая здесь имеет место; g
является лексическим родителем f
. Поэтому hello
печатается.
Давайте теперь посмотрим на строку console.log(bar);
. На данный момент мы находимся в области видимости f
и пытаемся получить доступ к переменной bar
, объявленной в области видимости g
. bar
не объявлен в текущей области видимости, и функция g
не является родителем f
, поэтому bar
не определена
На самом деле мы также можем получить доступ к переменным, объявленным в области действия лексической функции «прародителя». Поэтому, если бы была функция, 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
объекта.
Поэтому существует ровно два способа объявления переменной 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.