Понимание замыканий в JavaScript
Концепция закрытия - одна из тем в JavaScript, которая может быть сложной для понимания. Однако они могут быть чрезвычайно полезны, если вы их освоите! Они могут помочь вам добавить множество многоразовых функций в любое веб-приложение. Это помогает нам экономить время при компиляции и запуске приложения. Итак, что такое закрытие, вы спрашиваете, я собираюсь объяснить вам, сначала разбив его на более мелкие части и шаг за шагом, чтобы понять, что именно.
Что такое закрытие?
Объяснение от MDN Web Docs:
«Замыкание - это комбинация функции, объединенной (заключенной), со ссылками на ее окружающее состояние (лексическое окружение). Другими словами, замыкание дает вам доступ к области внешней функции из внутренней функции. В JavaScript замыкания создаются каждый раз, когда создается функция, во время создания функции ».
Поговорим об объемах
Области видимости определяют доступность (или видимость) переменных. И есть 2 вида:
- Локальные области
- Глобальные области
Локальный охват
Переменные, объявленные в функции JavaScript, относятся к ее ЛОКАЛЬНОЙ области видимости. Доступ к ним можно получить только в этой функции. Локальные переменные создаются при запуске функции и удаляются, когда функция завершается.
function myFunction() {
var count = 0;
console.log(count);
};
myFunction(); => 0
console.log(count); => ReferenceError: count is not defined count is declared within myFunction's scope, therefore it is not eligible outside of that function.
Вы даже можете повторно использовать имена общих переменных (count, index, current, value и т. Д.) В разных областях, не вызывая ошибок:
function myFunction() {
// 'myFunction" function scope
// This count variable belongs here, and it will log 0
let count = 0;
console.log(count);
};
function someFunction(){
// 'someFunction" function scope
// This count variable belongs here, and it will log 1
let count = 1;
console.log(count);
};
myFunction(); => 0
someFunction(); => 1
Глобальный масштаб
Переменные, объявленные вне функции, относятся к ГЛОБАЛЬНОЙ области видимости. После этого все скрипты и функции на веб-странице могут получить к ней доступ.
let carName = "Volvo";
console.log(carName); => "Volvo"
function myFunction() {
// Code written inside this function can also use carName,
because carName belongs to Global Scope
console.log(carName);
};
myFunction(); => "Volvo"
Это здорово, но с прицелами мы можем сделать гораздо больше!
Области вложенности
Давайте немного поэкспериментируем и поместим один прицел внутрь другого! Это называется вложением:
function outerFunction(){
// The outer scope
let outerVar = "Outer variable";
function innerFunction(){
// The inner scope
// The variables of the outer scope are accessible inside the inner scope
console.log(outerVar);
}
innerFunction();
}
outerFunction(); => "Outer variable"
Давайте посмотрим на приведенный выше код и пошагово разберемся, как он работает:
- Функция с именем
outerFunction()
определяется переменнойouterVar
и функциейinnerFunction()
в своем теле. innerFunction()
запускает консольный журнал нашегоouterVar
.- Поскольку в
innerFuction()
нет объявленияouterVar
, JavaScript будет рассматриватьouterVar
ту же переменную, объявленную вouterFunction()
. Однако, если мы создадим еще одинouterVar
в нашемinnerFunction()
, нашinnerFunction()
сначала будет полагаться на эту переменную.
Вот еще один пример:
let myGlobal = 0;
function myFunction() {
let myVar = 1;
console.log(myGlobal); // => It will log 0
function innerOfFunction() {
let myInnerVar = 2;
console.log(myVar, myGlobal); // => I will log 1 0
function innerOfInnerOfFunction() {
console.log(myInnerVar, myVar, myGlobal); // => It will log
2 1 0
}
innerOfInnerOfFunction();
}
innerOfFunction();
}
myFunction(); => 0
1 0
2 1 0
Давайте посмотрим на приведенный выше код построчно:
- Переменная
myGlobal
объявлена в Global Scope, и ее значение равно 0. - Объявлен следующий
myFunction()
. - В области видимости
myFunction()
переменнаяmyVar
объявлена со значением 1. - Внутри
myFunction()
объявлена другая функцияinnerOfFunction()
с переменнойmyInnerVar
, имеющей в области видимости значение 2. - Затем мы объявляем внутри
innerofFunction()
другую функцию, называемуюinnerOfinnerOfFunctoion()
(имена примеров сложны!), Консоль которой регистрирует переменные:myGlobal
,myVar
иmyInnerVar
. Поскольку он вложен как вmyFunction()
, так и вinnerOfFunction(),
, он может получить доступ ко всем переменным над ним. innerOfFunction()
затем завершается вызовомinnerOfInnerOfFunction()
.- Наконец,
myFunction()
закрывается путем вызоваinnerOfFunction()
, который запускает весь код, вложенный в него.
Внешние области не знают о переменных внутри внутренних областей, а внутренние области (если они вложены) знают о переменных во внешних областях.
Так что же на самом деле закрытие?
Лексическая среда позволяет нам получать статический доступ к переменным внешней области видимости. Мы можем продолжать вкладывать переменные и функции друг в друга, но мы еще не создали замыкание. До закрытия остался всего один шаг!
Взгляните на этот код:
function outerFunction(){ let counter = 0; function innerFunction(){ console.log(counter+=1); } innerFunction(); }
outerFunction(); // => 1 outerFunction(); // => 1 counter didn't change because everytime. outerFunction() is called, it sets the counter to 0, and adds 1 to it.
В настоящее время innerFunction()
вызывается в outerFunction()
. Давайте изменим это и вместо вызова функции просто вернем ее.
Добавим изменения:
function outerFunction(){
let counter = 0;
function innerFunction(){
console.log(counter+=1); // Let's increment the counter
}
return innerFunction; // Note that we are not invoking the
function, but just returning it.
}
Возвращая innerFunction()
, мы теперь можем сохранить внешнюю функцию в различных переменных, которые будут ее собственным экземпляром этой функции:
let invokeFirst = outerFunction() // => ƒ innerFunction(){
console.log(counter+=1); // Le…
// outerFunction() invoked the first time
let invokeSecond = outerFunction() // => ƒ innerFunction(){
console.log(counter+=1); // Le…
// outerFunction() invoked the first time
// By setting the invokeFirst and invokeSecond variables to
outerFunction, its value becomes result of innerFunction.
invokeFirst() // => 1 invokeFirst() invoked the first time
invokeFirst() // => 2 invokeFirst() invoked the second time
invokeFirst() // => 3 invokeFirst() invoked the third time
invokeSecond() // => 1 invokeSecond() invoked the first time
Давайте посмотрим, что происходит, когда invokeFirst()
выполняется в первый раз:
- Создается счетчик переменных, значение которого устанавливается на 0 внутри
outerFunction()
. outerFunction()
вызывается и запускается только один раз, когда он сохраняется в переменныхinvokeFirst()
иinvokeSecond()
.- JavaScript знает, что счетчик переменных больше не существует. Поскольку
counter
является частьюouterFunction()
,counter
будет существовать только тогда, когдаouterFunction()
находится в процессе выполнения. Поскольку выполнениеouterFunction()
завершилось задолго до того, как мы вызвалиinvokeFirst()
, любые переменные в области действия внешней функции перестают существовать, и, следовательно, переменнаяcounter
больше не существует.
Но как это работает на самом деле?
Внутренняя функция может получить доступ к переменным включающей функции из-за замыканий в JavaScript. Другими словами, внутренняя функция сохраняет цепочку областей видимости включающей функции во время выполнения включающей функции и, таким образом, может получить доступ к переменным включающей функции.
Все еще не понимаете? Проще говоря, когда мы сохраняем наш outerFunction()
в новую переменную, он запускается один раз, он устанавливает свои переменные и выполняет то, что находится внутри него, а затем возвращает innerFunction()
. Теперь, когда мы вызываем нашу новую переменную как функцию, она запускает только внутреннюю функцию. innerFunction()
запоминает все, что outerFunction()
ему передано, а затем выполняет свой собственный код. Все, что не было передано innerFunction()
, теперь будет за его пределами. Давайте посмотрим, что это значит:
function outerFunction(){
let counter = 0;
let anotherVar = "something" // This won't be remembered by
innerFunction(), because
outerFunction() returns the
function of innerFunction()
and innerFunction() doesn't
use anotherVar. Therefore,
it will be forgotten.
function innerFunction(){
console.log(counter+=1); // Let's increment the counter
}
return innerFunction;
}
Популярные варианты использования замыканий
- Обработка слушателей событий
- Частные методы
- Функциональное программирование
Обработка слушателей событий
const myButton = document.getElementById("myButton");
const button = document.getElementById("button");
const myfunc = () => {
let clicked = 0;
const inner = () => {
return clicked +=1;
};
return inner;
};
let callback = myfunc();
myButton.addEventListener("click", function handleClick() {
myText.innerText = `You clicked ${callback()} times`;
});
Чем он здесь полезен?
Здесь каждый раз, когда мы устанавливаем myFunc()
в новую переменную, он будет предоставлять нам новое значение clicked
, начиная с 0. Это делает его повторно используемым для множества различных прослушивателей событий. Представьте себе добавление лайков на страницу в социальных сетях или функцию аплодисментов.
Частные методы
Мы можем эмулировать частные методы с помощью замыканий. Частные методы предоставляют нам отличный способ управлять нашим глобальным пространством имен и ограничивать доступ к коду.
let counter = (function() {
let privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
};
})();
console.log(counter.value()); // 0.
counter.increment();
counter.increment();
console.log(counter.value()); // 2.
counter.decrement();
console.log(counter.value()); // 1.
В приведенном выше коде мы делаем несколько вещей. Сначала мы создаем частную переменную (privateCounter
со значением 0) и частную функцию (changeBy()
, которая добавляет значение к переменной privateCounter
). Оба они являются частными, потому что мы не передаем их нашему утверждению. Во-вторых, в нашем операторе возврата мы создаем анонимную оболочку, которая содержит три общедоступных объекта: increment
, decrement
и value
. Инкремент и декремент вызывают функцию changeBy()
, чтобы изменить значение privateCounter
на 1 или -1. Значение просто возвращает значение нашего privateCounter
. Затем к возвращаемым здесь объектам можно получить доступ, вызвав наш счетчик и вызвав одну из клавиш функции, которую мы хотим запустить (например: counter.value()
)
Функциональное программирование
Это действительно полезная концепция, которую мы можем использовать, чтобы не писать один и тот же код снова и снова. Мы можем создавать функциональное программирование, которое, в свою очередь, создает служебные функции, которые мы можем использовать в любое время, когда обнаруживаем, что постоянно пишем аналогичный код.
function division(a) {
return function runDivision(b) {
return b / a;
}
}
const divideByTwo= division(2);
divideByTwo(4); // => 2
divideByTwo(6); // => 3
const divideByThree = division(3);
tridivideByThree(3); // => 1
tridivideByThree(9); // => 3
Заключение
Если вы обнаружите, что повторно используете одни и те же блоки кода снова и снова, замыкания - отличный способ создать многоразовые функции, которые можно разместить там, где они вам нужны!