Вступление
Замыкания - это несколько абстрактная концепция языка JavaScript, которая проникает в программирование на стороне компилятора. Однако понимание того, как JavaScript интерпретирует функции, вложенные функции, области видимости и лексические среды, необходимо для использования всего его потенциала.
В этой статье мы попытаемся демистифицировать упомянутые концепции и предоставить простое руководство по закрытию JavaScript .
Что такое закрытие?
Сначала давайте взглянем на официальное определение закрытия в MDN:
Замыкание - это комбинация функции, объединенной вместе (заключенной) со ссылками на ее окружающее состояние (лексическое окружение). Другими словами, замыкание дает вам доступ к области внешней функции из внутренней функции.
Проще говоря, замыкание - это функция, которая имеет доступ к области внешней функции. Чтобы понять это, давайте посмотрим, как области видимости работают в JavaScript.
Область видимости в JavaScript
Область видимости определяет, какие переменные видны или на которые можно ссылаться в данном контексте. Область действия в целом делится на два типа - глобальная область действия и локальная область действия :
-
Global Scope - переменные, определенные вне функции. К переменным в этой области можно получить доступ и изменить из любой точки программы, отсюда и название «глобальные».
-
Local Scope - переменные, определенные внутри функции. Эти переменные относятся к той функции, в которой они определены, поэтому они называются «локальными».
Давайте посмотрим на глобальную и локальную переменные в JavaScript:
let name = "Joe";
function hello(){
let message = "Hello";
console.log(message + " " +name);
}
В приведенном выше примере область действия name
глобальна, т. Е. К
нему можно получить доступ откуда угодно. С другой стороны, message
определяется внутри функции, его область действия является локальной для
функции hello()
.
Когда дело доходит до областей функций, в JavaScript используется лексическая область видимости. Это означает, что область видимости переменной определяется положением ее определения в исходном коде. Это позволяет нам ссылаться на глобальные переменные в меньших масштабах. Локальная переменная может использовать глобальную переменную, но наоборот невозможно.
На
function outer(){
let x = 10;
function inner() {
let y = 20;
console.log(x);
}
inner();
console.log(y)
}
outer();
Этот код приводит к:
10
error: Uncaught ReferenceError: y is not defined
Функция inner()
может ссылаться на x
поскольку она определена в
функции outer()
. Однако console.log(y)
в функции outer()
не может
ссылаться на y
поскольку она определена в области видимости функции
inner()
Кроме того, в этом сценарии:
let x = 10;
function func1(){
console.log(x);
}
function func2() {
let x = 20;
func1();
}
func2();
Результатом будет:
10
Когда мы вызываем func1()
из func2()
, мы получаем переменную x
с
локальной областью видимости. Однако эта переменная совершенно не имеет
отношения к func1()
поскольку она недоступна в func1()
.
Таким образом, func1()
проверяет, доступна ли глобальная переменная с
этим идентификатором, и использует ее, в результате чего получается
значение 10
.
Закрытие под капотом
Замыкание - это функция, которая имеет доступ к своим родительским переменным даже после того, как внешняя функция вернулась. Другими словами, у закрытия есть три области действия:
- Локальная область видимости - доступ к переменным в ее собственной области видимости.
- Область видимости родительской функции - доступ к переменным внутри ее родительской функции
- Global Scope - доступ к глобальным переменным
Давайте посмотрим на закрытие в действии, создав функцию, которая возвращает другую функцию:
function outer() {
let x = 3
return function inner(y) {
return x*y
}
}
let multiplyByThree = outer();
console.log(multiplyByThree(2));
Это приводит к:
6
Если мы сделаем:
console.log(multiplyByThree);
Нас встречают:
function inner(y) { return x * y; }
Давайте рассмотрим код шаг за шагом, чтобы увидеть, что происходит под капотом:
- Функция
outer()
определена в глобальной области видимости. outer()
вызывается, и он возвращает функцию, которая назначенаmultiplyByThree
.outer()
создается новый контекст выполнения.- Переменная
x
установлена на 3.
- Переменная
- Возвращает функцию с именем
inner()
. - Ссылка на
inner()
присваиваетсяmultiplyByThree
. - Когда внешняя функция завершает выполнение, все переменные в ее области удаляются.
- Результат вызова функции
multiplyByThree(2)
записывается в консоль.inner()
вызывается с2
в качестве аргумента. Итак,y
установлено на2
.- Поскольку
inner()
сохраняет цепочку областей видимости своей родительской функции, во время выполнения она все еще будет иметь доступ к значениюx
. - Он возвращает
6
что записывается в консоль.
В заключение, даже после того, как внешняя функция перестает существовать, внутренняя функция имеет доступ к переменным, определенным в области действия внешней функции.
Визуализация замыканий
Замыкания можно визуализировать через консоль разработчика:
function outer() {
let x = 3
return function inner(y) {
return x*y
}
}
let multiplyByThree = outside();
console.dir(multiplyByThree);
Выполнив приведенный выше код в консоли разработчика, мы видим, что у
нас есть доступ к контексту inner(y)
. При ближайшем рассмотрении мы
видим, что часть его контекста представляет собой [[Scopes]]
, который
содержит все три области, о которых мы говорили.
И вот, массив областей видимости содержит область видимости родительской
функции, которая содержит x = 3
:
{.ezlazyload}
Общие варианты использования
Замыкания полезны, потому что они помогают нам кластеризовать данные с функциями, которые работают с этими данными. Это может стать сигналом для некоторых из вас, кто знаком с объектно-ориентированным программированием (ООП). В результате мы можем использовать замыкания везде, где можем использовать объект.
Другой важный вариант использования замыканий - это когда нам нужно, чтобы наши переменные были закрытыми , поскольку переменные, определенные в области действия замыкания, не допускаются для функций вне его. В то же время замыкания имеют доступ к переменным в своей цепочке областей видимости.
Давайте посмотрим на следующий пример, чтобы лучше понять это:
const balance = (function() {
let privateBalance = 0;
return {
increment: function(value){
privateBalance += value;
return privateBalance;
},
decrement: function(value){
privateBalance -= value;
return privateBalance;
},
show: function(){
return privateBalance;
}
}
})()
console.log(balance.show()); // 0
console.log(balance.increment(500)); // 500
console.log(balance.decrement(200)); // 300
В этом примере мы определили постоянную переменную balance
и
установили ее как возвращаемое значение нашей анонимной функции.
Обратите внимание, что privateBalance
можно изменить только путем
вызова методов balance
.
Заключение
Хотя замыкания - довольно нишевая концепция в JavaScript, они являются важным инструментом в наборе инструментов хорошего разработчика JavaScript. Их можно использовать для элегантной реализации решений, которые в противном случае были бы сложной задачей.
В этой статье мы сначала узнали немного о областях и о том, как они реализованы в JavaScript. Затем мы использовали эти знания, чтобы понять, как замыкания работают под капотом и как их использовать.