Коротше кажучи, закриття Javascript дозволяє функції отримувати доступ до змінної, яка оголошена в лексичній батьківській функції .
Давайте подивимось більш детальне пояснення. Для розуміння закриттів важливо зрозуміти, як 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 дозволяє нам отримувати доступ до будь-якої змінної, оголошеної у лексичній батьківській функції, у лексичній грандіозній батьківській функції, у лексичній функції grand-grand parent тощо. Це може розглядатися як ланцюг діапазону ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ... до останньої батьківської функції, яка не має лексичного батьківського.
Об'єкт вікна
Насправді ланцюг не зупиняється на останній батьківській функції. Є ще одна спеціальна сфера; глобальний масштаб . Кожна змінна, не оголошена у функції, вважається оголошеною у глобальній області. Світова сфера має дві спеціальності;
- кожна змінна, оголошена в глобальному масштабі, доступна всюди
- змінні, оголошені в глобальній області, відповідають властивостям
windowоб'єкта.
Тому існують рівно два способи оголошення змінної fooу глобальному масштабі; або не оголосивши його у функції, або встановивши властивість fooоб’єкта вікна.
Обидві спроби використовують закриття
Тепер, коли ви прочитали більш детальне пояснення, зараз може бути очевидним, що обидва рішення використовують закриття. Але для впевненості давайте зробимо доказ.
Давайте створимо нову мову програмування; JavaScript-без закриття. Як випливає з назви, JavaScript-No-Closure є ідентичним JavaScript, за винятком того, що він не підтримує закриття.
Іншими словами;
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)
})();
}
тому це буде надруковано undefined10 разів у JavaScript без закриття.
Отже, перше рішення використовує закриття.
Давайте розглянемо друге рішення;
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}
})(i), 1000);
}
тому це буде надруковано undefined10 разів у JavaScript без закриття.
Обидва рішення використовують закриття.
Редагувати: Передбачається, що ці 3 фрагменти коду не визначені в глобальній області. В іншому випадку змінні fooта iбудуть прив’язані до windowоб'єкта і, отже, доступні через windowоб'єкт як у JavaScript, так і в JavaScript-не-закритті.