Замыкание
Замыкание обеспечивает доступ к переменным в своей лексической области; включая переменные родителей, которые были удалены из стека вызовов, путём определения, какие именно переменные понадобятся дочерним функциям, путём сохранения их в памяти.
Другими словами, замыкание даёт нам доступ к области видимости внешней функции из внутренней функции. В JavaScript замыкания создаются каждый раз, когда во время создания функции, внутри неё создаётся ещё одна функция.
Инкапсуляция
Инкапсуляция позволяет нам скрывать/показывать свойства функций и объектов.
Замыкания обычно используются для обеспечения конфиденциальности данных объектов. Конфиденциальность данных - это важное свойство, которое помогает нам программировать интерфейс, а не реализацию. Дання концепция важна тем, что помогает нам создавать более надежное программное обеспечение.
В JavaScript, замыкания являются основным механизмом, обеспечивающим конфиденциальность данных. Когда мы используем замыкания для конфиденциальности данных, вложенные переменные находятся только в области действия, внутри содержащей (внешней) функции. Мы не можем получить данные из внешней области, кроме как через привилегированные функции. В JavaScript любая функция, определенная в области замыкания, является привилегированной.
Замыкания подобны объектам в том смысле, что они представляют собой механизм для хранения состояния:
Например, в приведенном ниже примере, мы не хотим показывать функцию launch
для её вызова, а также не даём доступ к timeWithoutDesctruction
:
const makeNuclearButton = () => {
// Переменные, опредёленные в области действия фабрики или конструктора
// являются приватным для этой функции.
// К ним нет доступа, если только мы не вернем их в качестве свойств объекта.
let timeWithoutDesctruction = 0;
const passTime = () => timeWithoutDesctruction++;
// totalPeaceTime является привилегированной, так как она определена в области замыкания -
// поэтому у неё есть доступ к timeWithoutDesctruction
const totalPeaceTime = () => timeWithoutDesctruction;
const launch = () => {
timeWithoutDesctruction = -1;
return '💥'
}
setInterval(passTime, 1000);
return {totalPeaceTime};
}
const button = makeNuclearButton();
button // {totalPeaceTime: ƒ}
button.totalPeaceTime(); // 1
Таким образом, мы скрываем данные объекта, которые не должны быть напрямую доступны. Вместо прямого доступа к данным нужно вызывать методы.
Эффективное использование памяти
Замыкания эффективны с точки зрения памяти. Используя замыкания, мы можем создать переменные, которые будут храниться в памяти и использоваться в будущем.
const closureTest1 = function() {
const bigArray = new Array(7000).fill('1');
console.log('created');
return function(index) {
return bigArray[index];
}
}
const closureTest1Fn = closureTest1();
closureTest1Fn(500);
closureTest1Fn(300);
closureTest1Fn(100);
created // from console.log('created');
// returned "1"
Или
// IIFE (Immediately Invoked Function Expression)
const closureTest2 = (function() {
const bigArray = new Array(7000).fill('1');
console.log('created');
return function(index) {
return bigArray[index];
}
})();
closureTest2(500);
closureTest2(300);
closureTest2(100);
created // from console.log('created');
// returned "1"
Мы вызываем функцию closureTest1Fn
и closureTest2
3 раза, но console.log
выводится только один раз. Это происходит потому, что мы, благодаря замыканию, сохраняем в памяти значения bigArray
и console.log
.
Типичный случай без замыкания приведет к неэффективному использованию памяти: переменная bigArray
будет создаваться и сохраняться каждый раз.
function closureTest(index) {
const bigArray = new Array(7000).fill('1');
console.log('created');
return bigArray[index];
};
closureTest(500);
closureTest(300);
closureTest(100);
created // from console.log('created');
created // from console.log('created');
created // from console.log('created');
// returned "1"
В функциональном программировании замыкания часто используются для частичного применения и каррирования.