Оригинальный адрес: Discover the power of closures in JavaScript
Оригинальный автор: Cristi Salcescu
Переводчик: wcflmy
Замыкание — это внутренняя функция, которая имеет доступ к внешней области видимости, даже если внешняя область действия завершила выполнение.
сфера
Область действия определяет время жизни этой переменной и ее видимость.
Когда мы создаем функцию или{}
блок, создается новая область. Следует отметить, что поvar
Создаваемые переменные имеют только область действия, в то время какlet
иconst
Переменные создаются как с функцией, так и с областью действия блока.
вложенная область
существуетJavascript
Функции могут быть вложены внутрь функции следующим образом:
(function autorun(){
let x = 1;
function log(){
console.log(x);
}
log();
})();
log()
является вложеннымautorun()
функции внутри функций. существуетlog()
Функции могут обращаться к переменным через внешние функцииx
. В настоящее время,log()
Функция — это замыкание.
Замыкания — это внутренние функции, мы можем сделать это, вызвав их внутри функции или{}
Внутри блока определена функция для создания замыкания.
область действия внешней функции
Внутренняя функция может обращаться к переменным, определенным во внешней функции, даже после завершения выполнения внешней функции. следующее:
(function autorun(){
let x = 1;
setTimeout(function log(){
console.log(x);
}, 10000);
})();
Более того, внутренняя функция также может получить доступ к формальным параметрам, определенным во внешней функции, следующим образом:
(function autorun(p){
let x = 1;
setTimeout(function log(){
console.log(x);//1
console.log(p);//10
}, 10000);
})(10);
область действия внешнего блока
Внутренняя функция может обращаться к переменным, определенным во внешнем блоке, даже после завершения выполнения внешнего блока следующим образом:
{
let x = 1;
setTimeout(function log(){
console.log(x);
}, 10000);
}
лексический охват
Лексическая область видимости означает, что внутренняя функция определяет свою внешнюю область видимости при ее определении.
СмотретьследующееКод:
(function autorun(){
let x = 1;
function log(){
console.log(x);
};
function run(fn){
let x = 100;
fn();
}
run(log);//1
})();
log()
Функция — это замыкание, и здесь она обращается кautorun()
в функцииx
переменная вместоrun
переменные в функции.
Внешняя область замыкания определяется при его определении, а не при его выполнении.
autorun()
Объем функцийlog()
Лексический объем функции.
цепочка прицелов
Каждая область имеет ссылку на свою родительскую область. Когда мы используем переменную,Javascript引擎
Он будет искаться в текущей области видимости по имени переменной. Если она не будет найдена, поиск продолжится вверх по цепочке областей видимости, покаglobal
глобальный масштаб.
Примерследующее:
let x0 = 0;
(function autorun1(){
let x1 = 1;
(function autorun2(){
let x2 = 2;
(function autorun3(){
let x3 = 3;
console.log(x0 + " " + x1 + " " + x2 + " " + x3);//0 1 2 3
})();
})();
})();
Мы видим, что,autorun3()
Эта внутренняя функция может обращаться к своим локальным переменным.x3
, вы также можете получить доступ к внешней областиx1
иx2
переменные, а в глобальной области видимостиx0
Переменная. То есть: замыкание имеет доступ ко всем переменным, определенным в его внешней (родительской) области видимости.
После выполнения внешней области
Замыкания действительно работают только тогда, когда внутренняя функция жива (все еще ссылается в другом месте) после завершения выполнения внешней области. Например, следующие ситуации:
- В асинхронных задачах, например.
timer
таймеры, обработка событий,Ajax
используется как обратный вызов в запросе - Возвращается внешней функцией в качестве возвращаемого результата, или внутренняя функция упоминается в возвращаемом объекте результата.
Рассмотрим следующееПример:
Timer
(function autorun(){
let x = 1;
setTimeout(function log(){
console.log(x);
}, 10000);
})();
Переменнаяx
будет жить до тех пор, пока не выполнится обратный вызов таймера илиclearTimeout()
называется.
Если используется здесьsetInterval()
, то переменнаяx
выживет, покаclearInterval()
называется.
Примечание переводчика: переменная в исходном текстеx
выжил, покаsetTimeout()
илиsetInterval()
называется неправильным.
Event
(function autorun(){
let x = 1;
$("#btn").on("click", function log(){
console.log(x);
});
})();
когда переменнаяx
При использовании в обработчике событий он будет жить до тех пор, пока обработчик событий не будет удален.
Ajax
(function autorun(){
let x = 1;
fetch("http://").then(function log(){
console.log(x);
});
})();
Переменнаяx
Будет жить до тех пор, пока бэкэнд не вернет результат и не будет выполнена функция обратного вызова.
В предыдущих примерах мы видим, чтоlog()
Функция сохраняется после выполнения родительской функции.log()
Функция — это замыкание.
Кромеtimer
таймеры, обработка событий,Ajax
Общие асинхронные задачи, такие как запросы и некоторые другие асинхронные задачиAPI
НапримерHTML5 Geolocation
,WebSockets
, requestAnimationFrame()
Эта особенность замыканий также будет использоваться.
Время жизни переменной зависит от времени жизни замыкания. Переменные во внешней области видимости, на которые ссылается замыкание, будут жить до тех пор, пока функция замыкания не будет уничтожена. Если на переменную ссылаются несколько замыканий, переменная не будет уничтожена до тех пор, пока все замыкания не будут удалены сборщиком мусора.
Замки и петли
Замыкания только хранят ссылки на внешние переменные, но не копируют значения этих внешних переменных. Посмотреть нижеПример:
function initEvents(){
for(var i=1; i<=3; i++){
$("#btn" + i).click(function showNumber(){
alert(i);//4
});
}
}
initEvents();
В этом примере мы создаем 3 замыкания, все ссылающиеся на одну и ту же переменную.i
, и эти три замыкания являются обработчиками событий. из-за переменнойi
По мере увеличения цикла окончательный результат будет тем же самым значением.
Самый простой способ исправить это -for
используется в блоках операторовlet
объявление переменной, которая будет использоваться в каждом цикле какfor
Блок операторов создает новую локальную переменную. следующее:
function initEvents(){
for(let i=1; i<=3; i++){
$("#btn" + i).click(function showNumber(){
alert(i);//1 2 3
});
}
}
initEvents();
Однако, если переменная объявлена вfor
Если он находится вне блока операторов, он используетсяlet
Объявление переменной, все замыкания по-прежнему будут ссылаться на одну и ту же переменную, и окончательный вывод будет иметь одно и то же значение.
Закрытие и инкапсуляция
Инкапсуляция означает сокрытие информации.
Функции и частное состояние
С замыканиями мы можем создавать функции с приватным состоянием, а замыкания инкапсулируют состояние.
Заводской шаблон и частные объекты-прототипы
Давайте сначала рассмотрим общий способ создания объектов с помощью прототипов, а именно:
let todoPrototype = {
toString : function() {
return this.id + " " + this.userName + ": " + this.title;
}
}
function Todo(todo){
let newTodo = Object.create(todoPrototype);
Object.assign(newTodo, todo);
return newTodo;
}
В этом примереtodoPrototype
Объект-прототип является глобальным объектом.
Мы можем использовать замыкания для создания объектов-прототипов только один раз, и они могут использоваться всеми.Todo
Вызовы функций являются общедоступными и гарантированно конфиденциальными.Примерследующее:
let Todo = (function createTodoFactory(){
let todoPrototype = {
toString : function() {
return this.id + " " + this.userName + ": " + this.title;
}
}
return function(todo){
let newTodo = Object.create(todoPrototype);
Object.assign(newTodo, todo);
return newTodo;
}
})();
let todo = Todo({id : 1, title: "This is a title", userName: "Cristi", completed: false });
здесь,Todo()
это функция с частным состоянием.
Заводской шаблон и частный конструктор
ПроверятьследующееКод:
let Todo = (function createTodoFactory(){
function Todo(spec){
Object.assign(this, spec);
}
return function(spec){
let todo = new Todo(spec);
return Object.freeze(todo);
}
})();
здесь,Todo()
Фабричная функция — это замыкание. передать его, использовать или не использоватьnew
, мы все можем создавать неизменяемые объекты, объект-прототип создается только один раз и является приватным.
let todo = Todo({title : "A description"});
todo.title = "Another description";
// Cannot assign to read only property 'title' of object
todo.toString = function() {};
//Cannot assign to read only property 'toString' of object
Кроме того, в моментальном снимке памяти мы можем идентифицировать эти примеры объектов по именам их конструкторов.
Функция перевода и приватная карта
С замыканиями мы можем создатьmap
, который используется во всех вызовах перевода и является закрытым.
Примерследующее:
let translate = (function(){
let translations = {};
translations["yes"] = "oui";
translations["no"] = "non";
return function(key){
return translations[key];
}
})();
translate("yes"); //oui
функция автоинкрементного генератора
С замыканиями мы можем создавать автоинкрементные генераторные функции. Кроме того, внутреннее состояние является частным.Примерследующее:
function createAGenerate(count, increment) {
return function(){
count += increment;
return count;
}
}
let generateNextNumber = createAGenerate(0, 1);
console.log(generateNextNumber()); //1
console.log(generateNextNumber()); //2
console.log(generateNextNumber()); //3
let generateMultipleOfTen = createAGenerate(0, 10);
console.log(generateMultipleOfTen()); //10
console.log(generateMultipleOfTen()); //20
console.log(generateMultipleOfTen()); //30
Примечание переводчика: неправильно выводить 0, 1, 2, 0, 10, 20 последовательно в исходном тексте, спасибо @Round за исправление
Объекты и частное состояние
В приведенном выше примере мы можем создать функцию с закрытым состоянием. В то же время мы также можем создать несколько функций с одним и тем же приватным состоянием. Исходя из этого, мы также можем создать объект с приватным состоянием.
Примерследующее:
function TodoStore(){
let todos = [];
function add(todo){
todos.push(todo);
}
function get(){
return todos.filter(isPriorityTodo).map(toTodoViewModel);
}
function isPriorityTodo(todo){
return task.type === "RE" && !task.completed;
}
function toTodoViewModel(todo) {
return { id : todo.id, title : todo.title };
}
return Object.freeze({
add,
get
});
}
TodoStore()
Функция возвращает объект с приватным состоянием. Извне мы не можем получить доступ к приватномуtodos
переменная иadd
иget
Оба замыкания имеют одинаковое частное состояние. это здесь,TodoStore()
является заводской функцией.
Замыкания против чистых функций
Замыкания — это функции, которые ссылаются на переменные во внешней области видимости.
Для лучшего понимания мы разделили внутреннюю функцию на два аспекта: замыкание и чистую функцию:
- Замыкания — это функции, которые ссылаются на переменные во внешней области видимости.
- Чистые функции — это те, которые не ссылаются на переменные во внешней области видимости, они обычно возвращают значение и не имеют побочных эффектов.
В приведенном выше примереadd()
иget()
функции являются замыканиями, аisPriorityTodo()
иtoTodoViewModel()
является чистой функцией.
Применение замыканий в функциональном программировании
Замыкания также широко используются в функциональном программировании. Например,underscore
в исходном кодеРаздел, связанный с функциямиВсе функции используют замыкания.
Декоратор функции — это функция более высокого порядка, которая принимает одну функцию в качестве аргумента и возвращает другую функцию, а возвращаемая функция — это разновидность функции-аргумента — Аллонже Javascript
Функции декоратора также используют замыкания.
Давайте посмотрим на следующееnot
Эта простая функция декоратора:
function not(fn){
return function decorator(...args){
return !fn.apply(this, args);
}
}
decorator()
Функция ссылается на переменную fn внешней области видимости, поэтому это замыкание.
Если вы хотите узнать больше о декораторах, вы можете проверить этостатья.
вывоз мусора
существуетJavascript
, локальные переменные уничтожаются, когда функция завершает выполнение, если на них еще нет ссылок. Когда сами замыкания также подвергаются сборке мусора, приватное состояние в этих замыканиях также подвергается сборке мусора. Обычно мы можем добиться этого, разорвав ссылку на замыкание.
на этопример, мы сначала создаемadd()
Закрытие.
let add = (function createAddClosure(){
let arr = [];
return function(obj){
arr.push(obj);
}
})();
Затем мы определяем еще две функции:
-
addALotOfObjects()
к переменной закрытияarr
добавляется объект. -
clearAllObjects()
Установите функцию закрытия наnull
.
И обе функции используются как обработчики событий:
function addALotOfObjects(){
for(let i=1; i<=10000;i++) {
add(new Todo(i));
}
}
function clearAllObjects(){
if(add){
add = null;
}
}
$("#add").click(addALotOfObjects);
$("#clear").click(clearAllObjects);
когда я нажимаюAdd
Когда кнопка нажата, она перейдет к переменной закрытияarr
Добавить 10 000todo
объект, снимок памяти выглядит следующим образом:
когда я нажимаюClear
кнопку, мы устанавливаем ссылку закрытия наnull
. Затем переменная закрытияarr
Будет собран мусор, снимок памяти выглядит следующим образом:
Избегайте глобальных переменных
существуетJavascript
, мы можем легко создавать глобальные переменные. Все, что определено в функции и{}
Переменные вне блока являются глобальными, как и функции, определенные в глобальной области видимости.
Вот пример определения фабричных функций, которые создают разные объекты. Чтобы не помещать все фабричные функции в глобальную область видимости, проще всего повесить их вapp
под глобальными переменными.
Примерследующее:
let app = Loader();
app.factory(function DataService(args){ return {}});
app.factory(function Helper(args){ return {}});
app.factory(function Mapper(args){ return {}});
app.factory(function Model(args){});
app.factory()
Методы также могут группировать различные фабричные функции в разные модули. Следующий пример —Timer
Заводские функции подразделяются наtools
под модуль.
app.factory("tools")(function Timer(args){ return {}});
мы можемapp
объект выставленstart
метод в качестве точки входа приложения, через функцию обратного вызоваfactories
параметры для доступа к этим заводским функциям. здесьstart()
Функцию можно вызвать только один раз, как показано ниже:
app.start(function startApplication(factories){
let helper = factories.Helper();
let dataService = factories.DataService();
let model = factories.Model({
dataService : dataService,
helper : helper,
timer : factories.tools.Timer()
});
});
A Composition Root is a (preferably) unique location in an application where modules are composed together.
Mark Seemann
loader
объект
разрешите намapp
идеально подходит какloader
объект,Примерследующее:
function Loader(){
let modules = Object.create(null);
let started = false;
function getNamespaceModule(modulesText){
let parent = modules;
if(modulesText){
let parts = modulesText.split('.');
for(let i=0; i<parts.length; i++){
let part = parts[i];
if (typeof parent[part] === "undefined") {
parent[part] = Object.create(null);
}
parent = parent[part];
}
}
return parent;
}
function addFunction(namespace, fn){
if(typeof(fn) !== "function") {
throw "Only functions can be added";
}
let module = getNamespaceModule(namespace);
let fnName = fn.name;
module[fnName] = fn;
}
function addNamespace(namespace){
return function(fn){
addFunction(namespace, fn)
}
}
function factory(){
if(typeof(arguments[0]) === "string"){
return addNamespace(arguments[0]);
} else {
return addFunction(null, arguments[0]);
}
}
function start(startApplication){
if(started){
throw "App can be started only once";
}
startApplication(Object.freeze(modules));
started = true;
}
return Object.freeze({
factory,
start
});
};
let app = Loader();
factory()
метод добавления новых фабричных функций во внутренние переменныеmodules
середина.
start()
Метод вызовет функцию обратного вызова и получит доступ к внутренним переменным в функции обратного вызова.
пройти черезfactory()
Определите фабричную функцию, которая будетstart()
Это такой аккуратный и элегантный способ быть единственной точкой входа для вызова различных фабричных функций в приложении для создания различных объектов.
это здесь,factory
иstart
Все замыкания.
Суммировать
Замыкание — это внутренняя функция, которая может обращаться к переменным во внешней области видимости.
Эти ссылочные переменные не уничтожаются до тех пор, пока не будет уничтожено замыкание.
закрытие делаетtimer
таймеры, обработка событий,AJAX
Асинхронные задачи, такие как запросы, проще.
Инкапсуляция может быть достигнута с помощью замыканий.
Наконец, хотите получить больше информации оJavascript
Для получения знаний, связанных с функциями, вы можете проверить следующие статьи:
Discover Functional Programming in JavaScript with this thorough introduction
Discover the power of first class functions
How point-free composition will make you a better functional programmer
Here are a few function decorators you can write from scratch