Простое руководство, которое поможет вам понять замыкания в JavaScript.
Автор: admin
Дата: 13.09.2020 02:08
Что такое закрытие? Замыкание - это функция в JavaScript, при которой внутренняя функция имеет доступ к переменным внешней (включающей) функции - цепочке областей видимости. Замыкание имеет три цепочки областей видимости: доступ к своей собственной области видимости - переменные, определенные в фигурных скобках; доступ к переменным внешней функции и к глобальным переменным. Итак, что же такое закрытие? Проще понять это на примере:
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
Здесь у нас есть две функции: внешняя функция outer, которая имеет переменную b и возвращает внутреннюю функцию внутренняя функция inner, которая имеет свою переменную, называемую a, и обращается к внешней переменной b в теле функции Область действия переменной b ограничена внешней функцией, а область действия переменной a ограничена внутренней функцией. Давайте теперь вызовем функцию outer() и сохраним результат в переменной X. Давайте затем вызовем функцию outer() второй раз и сохраним ее в переменной Y.
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
var X = outer(); //outer() вызывается в первый раз
var Y = outer(); //outer() вызывается второй раз
Что происходит при первом вызове функции outer() шаг за шагом: Создана переменная b, ее область действия ограничена функцией outer() и значение b равно 10. Следующая строка - это объявление функции, поэтому выполнять нечего. В последней строке return inner ищет переменную с именем inner, обнаруживает, что эта переменная inner на самом деле является функцией, и поэтому возвращает все тело функции inner. Оператор return не выполняет внутреннюю функцию - функция выполняется, только если за ней следует () -, а оператор return возвращает все тело функции. Содержимое, возвращаемое оператором return, сохраняется в сертификате X. Таким образом, X будет хранить следующее:
function inner () {
var a = 20;
console.log (а + б);
}
*Функция outer() завершает выполнение, и все переменные в области outer() больше не существуют.
Эту последнюю часть важно понять. Как только функция завершает свое выполнение, любые переменные, которые были определены внутри области действия функции, перестают существовать. Продолжительность жизни переменной, определенной внутри функции, - это продолжительность жизни выполнения функции.
Это означает, что в console.log(a + b) переменная b существует только во время выполнения функции inner(). Как только внешняя функция завершила выполнение, переменная b больше не существует.
Когда функция выполняется во второй раз, переменные функции создаются снова и действуют только до тех пор, пока функция не завершит выполнение.
Таким образом, когда outer() вызывается второй раз: Создается новая переменная b, ее область действия ограничена функцией outer(), а ее значение установлено на 10. Следующая строка - это объявление функции, поэтому выполнять нечего. return inner возвращает все тело функции inner. Содержимое, возвращаемое оператором return, сохраняется в Y. Функция outer() завершает выполнение, и все переменные в области outer() больше не существуют.
Важным моментом здесь является то, что при повторном вызове функции outer() переменная b создается заново. Кроме того, когда функция outer() завершает выполнение во второй раз, эта новая переменная b снова перестает существовать. Это самый важный момент, который нужно осознать. Переменные внутри функций возникают только тогда, когда функция работает, и перестают существовать, когда функции завершают выполнение.
Теперь давайте вернемся к нашему примеру кода и посмотрим на X и Y. Поскольку функция outer() при выполнении возвращает функцию, переменные X и Y являются функциями. В этом легко убедиться, добавив в код JavaScript следующее:
console.log(typeof(X)); //X функция
console.log(typeof(Y)); //Y функция
Поскольку переменные X и Y являются функциями, мы можем их выполнять. В JavaScript функцию можно выполнить, добавив () после имени функции, например X() и Y().
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
var X = outer();
var Y = outer();
//конец outer() выполнение функций
X(); // X() вызывается в первый раз
X(); // X() второй раз
X(); // X() вызвал в третий раз
Y(); // Y() вызывается в первый раз
Когда мы выполняем X() и Y(), мы, по сути, выполняем внутреннюю функцию. Давайте рассмотрим шаг за шагом, что происходит, когда X() выполняется в первый раз: Создана переменная a, для которой установлено значение 20. JavaScript теперь пытается выполнить a + b. Здесь все становится интересно. JavaScript знает, что существует объект a, поскольку он только что его создал. Однако переменной b больше не существует. Поскольку b является частью внешней функции, b будет существовать только во время выполнения функции outer(). Поскольку функция outer() завершила выполнение задолго до того, как мы вызвали X(), любые переменные в области видимости внешней функции перестают существовать, и, следовательно, переменная b больше не существует.
Внутренняя функция может получать доступ к переменным внешней функции из-за замыканий в JavaScript. Другими словами, внутренняя функция сохраняет цепочку областей видимости внешней функции во время выполнения закрывающей функции и, таким образом, может получить доступ к переменным внешней функции. В нашем примере внутренняя функция сохранила значение b = 10 при выполнении функции outer() и продолжила сохранять (закрывать) его. Теперь он обращается к своей цепочке областей видимости и замечает, что у нее действительно есть значение переменной b в ее цепочке областей видимости, поскольку она заключила значение b в замыкание в момент, когда была выполнена внешняя функция. Таким образом, JavaScript знает a = 20 и b = 10 и может вычислить a + b. В этом можно убедиться, добавив следующую строку кода в приведенный выше пример:
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
var X = outer();
console.dir(X); //используйте console.dir() вместо console.log()
Откройте элемент Inspect в Google Chrome и перейдите в Консоль. Вы можете развернуть элемент, чтобы на самом деле увидеть элемент Closure (показанный в предпоследней строке ниже). Обратите внимание, что значение b = 10 сохраняется в Closure даже после того, как функция external () завершит свое выполнение.
Давайте теперь вернемся к определению замыканий, которое мы видели в начале, и посмотрим, имеет ли оно теперь больше смысла. Итак, внутренняя функция имеет три цепочки областей видимости: доступ к собственной области видимости - переменная a доступ к переменным внешней функции - переменной b, которую она заключила доступ к любым глобальным переменным, которые могут быть определены
Давайте дополним пример, добавив три строки кода:
function outer() {
var b = 10;
var c = 100;
function inner() {
var a = 20;
console.log("a= " + a + " b= " + b);
a++;
b++;
}
return inner;
}
var X = outer(); // outer() вызывается в первый раз
var Y = outer(); // outer() вызывается в первый раз
//конец outer() выполнение функций
X(); // X() вызывается в первый раз
X(); // X() второй раз
X(); // X() третий раз
Y(); // Y() вызывается в первый раз
Когда вы запустите этот код, вы увидите следующий вывод в console.log:
a=20 b=10
a=20 b=11 //третий раз
a=20 b=12
a=20 b=10
Давайте изучим этот код шаг за шагом, чтобы увидеть, что именно происходит, и увидеть замыкания в действии!
var X = outer(); // outer() вызывается в первый раз
Функция outer() вызывается в первый раз. Происходят следующие шаги: Создана переменная b, ей присвоено значение 10. Создана переменная c, ей присвоено значение 100. Назовем это b (first_time) и c (first_time) для нашей справки. Возвращается внутренняя функция и присваивается X На этом этапе переменная b заключена во внутреннюю цепочку области видимости функции в виде замыкания с b = 10, поскольку inner использует переменную b. Внешняя функция завершает выполнение, и все ее переменные перестают существовать. Переменная c больше не существует, хотя переменная b существует как закрытие внутри inner.
var Y= outer(); // outer() invoked the second time
Переменная b создается заново и устанавливается на 10. Переменная c создается заново. Обратите внимание, что даже несмотря на то, что outer() был выполнен один раз до того, как переменные b и c перестали существовать, после завершения выполнения функции они создаются как совершенно новые переменные. Назовем их b (second_time) и c (second_time) для нашей справки. Внутренняя функция возвращается и присваивается Y На этом этапе переменная b заключена в цепочку внутренней области видимости функции в виде замыкания с b (second_time) = 10, поскольку inner использует переменную b. Внешняя функция завершает выполнение, и все ее переменные перестают существовать. Переменная c (second_time) больше не существует, хотя переменная b (second_time) существует как закрытие внутри inner. Теперь давайте посмотрим, что происходит, когда выполняются следующие строки кода:
X(); // X() вызывается первый раз
X(); // X() вызывается второй раз
X(); // X() вызывается третий раз
Y(); // Y() вызывается в первый раз
Когда X() вызывается в первый раз, переменная a создается и устанавливается на 20 значение a = 20, значение b от значения закрытия. b (first_time), поэтому b = 10 переменные a и b увеличиваются на 1 X() завершает выполнение, и все его внутренние переменные - переменная a - перестают существовать. Однако b (first_time) был сохранен как закрытие, поэтому b (first_time) продолжает существовать.
Когда X() вызывается второй раз, переменная a создается заново и устанавливается на 20 Любое предыдущее значение переменной a больше не существует, так как оно перестало существовать, когда X() завершил выполнение в первый раз. значение a = 20 значение b берется из значения закрытия - b (first_time) Также обратите внимание, что мы увеличили значение b на 1 по сравнению с предыдущим выполнением, поэтому b = 11 переменные a и b снова увеличиваются на 1 X() завершает выполнение, и все его внутренние переменные - переменная a - перестают существовать Однако b (first_time) сохраняется, поскольку закрытие продолжает существовать. Когда X() вызывается в третий раз, переменная a создается заново и устанавливается на 20 Любое предыдущее значение переменной a больше не существует, поскольку оно перестало существовать, когда X() завершил выполнение в первый раз. значение a = 20, значение b от значения закрытия - b (first_time) Также обратите внимание, что мы увеличили значение b на 1 в предыдущем выполнении, поэтому b = 12 переменные a и b снова увеличиваются на 1 X() завершает выполнение, и все его внутренние переменные - переменная a - перестают существовать Однако b (first_time) сохраняется, поскольку закрытие продолжает существовать Когда Y() вызывается в первый раз, переменная a создается заново и устанавливается на 20 значение a = 20, значение b из значения закрытия - b(second_time), поэтому b = 10 переменные a и b увеличиваются на 1 Y() завершает выполнение, и все его внутренние переменные - переменная a - перестают существовать Однако b(second_time) был сохранен как закрытие, поэтому b(second_time) продолжает существовать. Заключительные замечания Замыкания - одна из тех тонких концепций в JavaScript, которые поначалу трудно понять. Но как только вы их понимаете, вы понимаете, что иначе и не могло быть. Надеюсь, эти пошаговые объяснения помогли вам действительно понять концепцию замыканий в JavaScript!