Коротше кажучи, закриття 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)
})();
}
тому це буде надруковано undefined
10 разів у 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);
}
тому це буде надруковано undefined
10 разів у JavaScript без закриття.
Обидва рішення використовують закриття.
Редагувати: Передбачається, що ці 3 фрагменти коду не визначені в глобальній області. В іншому випадку змінні foo
та i
будуть прив’язані до window
об'єкта і, отже, доступні через window
об'єкт як у JavaScript, так і в JavaScript-не-закритті.