Как работают JavaScript-закрытия?
Как бы вы объяснили JavaScript-замыкания кому-то, кто знает концепции, из которых они состоят (например, функции, переменные и т. П.), Но не понимает сами замыкания?
Я видел пример схемы, приведенный в Википедии, но, к сожалению, это не помогло.
86 ответов
JavaScript закрытия для начинающих
Опубликовано Morris в вторник, 2006-02-21 10:19. Сообщество отредактировано с тех пор.
Закрытие не волшебство
Эта страница объясняет замыкания, чтобы программист мог их понять - используя работающий код JavaScript. Это не для гуру или функциональных программистов.
Замыкания нетрудно понять, как только основная идея получится. Однако их невозможно понять, читая какие-либо теоретические или академически ориентированные объяснения!
Эта статья предназначена для программистов, имеющих некоторый опыт программирования на основном языке и умеющих читать следующую функцию JavaScript:
function sayHello(name) {
var text = 'Hello ' + name;
var say = function() { console.log(text); }
say();
}
sayHello('Joe');
Два кратких резюме
Когда функция (
foo
) объявляет другие функции (bar и baz), семейство локальных переменных, созданных вfoo
не уничтожается при выходе из функции. Переменные просто становятся невидимыми для внешнего мира.foo
поэтому может хитро вернуть функцииbar
а такжеbaz
и они могут продолжать читать, писать и общаться друг с другом через это закрытое семейство переменных ("замыкание"), с которыми никто не может вмешиваться, даже тот, кто вызываетfoo
снова в будущем.Закрытие является одним из способов поддержки первоклассных функций; это выражение, которое может ссылаться на переменные в своей области видимости (когда оно было впервые объявлено), быть назначенным переменной, передаваться в качестве аргумента функции или возвращаться как результат функции.
Пример закрытия
Следующий код возвращает ссылку на функцию:
function sayHello2(name) {
var text = 'Hello ' + name; // Local variable
var say = function() { console.log(text); }
return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"
Большинство программистов JavaScript поймут, как ссылка на функцию возвращается в переменную (say2
) в приведенном выше коде. Если вы этого не сделаете, то вам нужно посмотреть на это, прежде чем вы сможете научиться замыканиям. Программист, использующий C, мог бы думать о функции как о возвращении указателя на функцию, и что переменные say
а также say2
каждый из них был указателем на функцию.
Существует критическое различие между указателем C на функцию и ссылкой JavaScript на функцию. В JavaScript вы можете рассматривать переменную ссылки на функцию как имеющую как указатель на функцию, так и скрытый указатель на замыкание.
Приведенный выше код имеет закрытие, потому что анонимная функция function() { console.log(text); }
объявлен внутри другой функции, sayHello2()
в этом примере. В JavaScript, если вы используете function
Ключевое слово внутри другой функции, вы создаете замыкание.
В C и большинстве других распространенных языков после возврата функции все локальные переменные больше не доступны, потому что стековый фрейм разрушен.
В JavaScript, если вы объявляете функцию в другой функции, то локальные переменные внешней функции могут оставаться доступными после ее возвращения. Это продемонстрировано выше, потому что мы называем функцию say2()
после того как мы вернулись из sayHello2()
, Обратите внимание, что код, который мы вызываем, ссылается на переменную text
, которая была локальной переменной функции sayHello2()
,
function() { console.log(text); } // Output of say2.toString();
Глядя на вывод say2.toString()
мы можем видеть, что код ссылается на переменную text
, Анонимная функция может ссылаться text
который содержит значение 'Hello Bob'
потому что локальные переменные sayHello2()
были тайно поддерживаются в закрытии.
Гениальность в том, что в JavaScript ссылка на функцию также имеет секретную ссылку на замыкание, в котором она была создана - подобно тому, как делегаты являются указателем метода плюс секретная ссылка на объект.
Больше примеров
По какой-то причине замыкания кажутся действительно трудными для понимания, когда вы читаете о них, но когда вы видите некоторые примеры, становится ясно, как они работают (это заняло у меня некоторое время). Я рекомендую внимательно изучить примеры, пока вы не поймете, как они работают. Если вы начнете использовать замыкания без полного понимания того, как они работают, вы скоро создадите очень странные ошибки!
Пример 3
Этот пример показывает, что локальные переменные не копируются - они хранятся по ссылке. Словно стековый фрейм остается в памяти даже после выхода из внешней функции!
function say667() {
// Local variable that ends up within closure
var num = 42;
var say = function() { console.log(num); }
num++;
return say;
}
var sayNumber = say667();
sayNumber(); // logs 43
Пример 4
Все три глобальные функции имеют общую ссылку на одно и то же замыкание, потому что все они объявлены в одном вызове setupSomeGlobals()
,
var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
// Local variable that ends up within closure
var num = 42;
// Store some references to functions as global variables
gLogNumber = function() { console.log(num); }
gIncreaseNumber = function() { num++; }
gSetNumber = function(x) { num = x; }
}
setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5
var oldLog = gLogNumber;
setupSomeGlobals();
gLogNumber(); // 42
oldLog() // 5
Три функции имеют общий доступ к одному и тому же замыканию - локальные переменные setupSomeGlobals()
когда три функции были определены.
Обратите внимание, что в приведенном выше примере, если вы звоните setupSomeGlobals()
снова, затем создается новое замыкание (стековый фрейм!). Старый gLogNumber
, gIncreaseNumber
, gSetNumber
переменные перезаписываются новыми функциями с новым замыканием. (В JavaScript всякий раз, когда вы объявляете функцию внутри другой функции, внутренняя функция (и) воссоздается / воссоздается снова каждый раз, когда вызывается внешняя функция.)
Пример 5
В этом примере показано, что замыкание содержит все локальные переменные, которые были объявлены внутри внешней функции до ее выхода. Обратите внимание, что переменная alice
фактически объявлено после анонимной функции. Сначала анонимная функция объявляется, и когда эта функция вызывается, она может получить доступ к alice
переменная, потому что alice
находится в той же области (JavaScript делает подъем переменных). Также sayAlice()()
просто напрямую вызывает ссылку на функцию, возвращенную из sayAlice()
- это то же самое, что было сделано ранее, но без временной переменной.
function sayAlice() {
var say = function() { console.log(alice); }
// Local variable that ends up within closure
var alice = 'Hello Alice';
return say;
}
sayAlice()();// logs "Hello Alice"
Хитрый: обратите внимание на say
переменная также находится внутри замыкания и может быть доступна любой другой функции, которая может быть объявлена внутри sayAlice()
или к нему можно обратиться рекурсивно в рамках внутренней функции.
Пример 6
Это настоящий уловка для многих людей, поэтому вам нужно это понять. Будьте очень осторожны, если вы определяете функцию внутри цикла: локальные переменные из замыкания могут работать не так, как вы могли бы сначала подумать.
Вам нужно понять функцию "поднятия переменной" в Javascript, чтобы понять этот пример.
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + i;
result.push( function() {console.log(item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// Using j only to help prevent confusion -- could use i.
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList() //logs "item2 undefined" 3 times
Линия result.push( function() {console.log(item + ' ' + list[i])}
Добавляет ссылку на анонимную функцию три раза в массив результатов. Если вы не очень знакомы с анонимными функциями, подумайте об этом:
pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);
Обратите внимание, что когда вы запускаете пример, "item2 undefined"
вошел три раза! Это потому, что, как и в предыдущих примерах, есть только одно закрытие для локальных переменных для buildList
(которые result
, i
а также item
). Когда анонимные функции вызываются на линии fnlist[j]()
; все они используют одно и то же замыкание и используют текущее значение для i
а также item
в этом одном закрытии (где i
имеет значение 3
потому что цикл завершен, и item
имеет значение 'item2'
). Обратите внимание, мы индексируем от 0 отсюда item
имеет значение item2
, И i++ будет увеличиваться i
к стоимости 3
,
Может быть полезно посмотреть, что происходит, когда объявление переменной на уровне блока item
используется (через let
ключевое слово) вместо объявления переменной в функциональной области через var
ключевое слово. Если это изменение сделано, то каждая анонимная функция в массиве result
имеет свое собственное закрытие; когда пример запущен, результат будет следующим:
item0 undefined
item1 undefined
item2 undefined
Если переменная i
также определяется с помощью let
вместо var
, тогда вывод:
item0 1
item1 2
item2 3
Пример 7
В этом последнем примере каждый вызов основной функции создает отдельное закрытие.
function newClosure(someNum, someRef) {
// Local variables that end up within closure
var num = someNum;
var anArray = [1,2,3];
var ref = someRef;
return function(x) {
num += x;
anArray.push(num);
console.log('num: ' + num +
'; anArray: ' + anArray.toString() +
'; ref.someVar: ' + ref.someVar + ';');
}
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;
Резюме
Если все кажется совершенно неясным, лучше всего поиграть с примерами. Читать объяснения намного сложнее, чем понимать примеры. Мои объяснения замыканий, стековых фреймов и т. Д. Не являются технически правильными - это грубые упрощения, призванные помочь понять. Как только основная идея получена, вы можете подобрать детали позже.
Финальные очки:
- Всякий раз, когда вы используете
function
внутри другой функции используется замыкание. - Всякий раз, когда вы используете
eval()
внутри функции используется замыкание. Текст выeval
может ссылаться на локальные переменные функции, а внутриeval
Вы даже можете создавать новые локальные переменные, используяeval('var foo = …')
- Когда вы используете
new Function(…)
( конструктор функции) внутри функции, он не создает замыкание. (Новая функция не может ссылаться на локальные переменные внешней функции.) - Закрытие в JavaScript похоже на сохранение копии всех локальных переменных, как это было при выходе из функции.
- Вероятно, лучше всего думать, что замыкание всегда создается просто записью в функцию, и к этому замыканию добавляются локальные переменные.
- Новый набор локальных переменных сохраняется каждый раз, когда вызывается функция с замыканием (при условии, что функция содержит объявление функции внутри нее, и ссылка на эту внутреннюю функцию либо возвращается, либо для нее каким-либо образом сохраняется внешняя ссылка).
- Две функции могут выглядеть так, как будто они имеют одинаковый исходный текст, но ведут себя совершенно по-разному из-за их "скрытого" закрытия. Я не думаю, что код JavaScript может на самом деле узнать, есть ли ссылка на функцию или нет.
- Если вы пытаетесь внести какие-либо динамические изменения исходного кода (например:
myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));
), это не сработает, еслиmyFunction
это закрытие (конечно, вы никогда бы не подумали о подстановке строк исходного кода во время выполнения, но...). - Можно получить объявления функций в объявлениях функций внутри функций... и вы можете получить замыкания на более чем одном уровне.
- Я думаю, что обычно замыкание является термином как для функции, так и для захваченных переменных. Обратите внимание, что я не использую это определение в этой статье!
- Я подозреваю, что замыкания в JavaScript отличаются от тех, которые обычно встречаются в функциональных языках.
связи
- Имитируемые частные атрибуты и частные методы Дугласа Крокфорда для объекта с использованием замыканий.
- Отличное объяснение того, как замыкания могут вызвать утечки памяти в IE, если вы не будете осторожны.
Спасибо
Если вы только что узнали о замыканиях (здесь или где-либо еще!), Меня интересуют любые ваши отзывы о любых изменениях, которые вы могли бы предложить, которые могли бы сделать эту статью более понятной. Отправьте электронное письмо на адрес morrisjohns.com (morris_closure @). Обратите внимание, что я не гуру в JavaScript - ни в замыканиях.
Оригинальный пост Морриса можно найти в интернет-архиве.
Всякий раз, когда вы видите ключевое слово функции в другой функции, внутренняя функция имеет доступ к переменным во внешней функции.
function foo(x) {
var tmp = 3;
function bar(y) {
console.log(x + y + (++tmp)); // will log 16
}
bar(10);
}
foo(2);
Это всегда будет записывать 16, потому что bar
может получить доступ к x
который был определен как аргумент foo
и он также может получить доступ tmp
от foo
,
Это закрытие. Функция не должна возвращаться, чтобы быть вызванной замыканием. Простой доступ к переменным вне вашей непосредственной лексической области создает закрытие.
function foo(x) {
var tmp = 3;
return function (y) {
console.log(x + y + (++tmp)); // will also log 16
}
}
var bar = foo(2); // bar is now a closure.
bar(10);
Вышеприведенная функция также зарегистрирует 16, потому что bar
все еще может ссылаться на x
а также tmp
даже если он больше не находится внутри области видимости.
Тем не менее, так как tmp
все еще висит внутри bar
Закрытие, оно также увеличивается. Он будет увеличиваться каждый раз, когда вы звоните bar
,
Простейший пример замыкания:
var a = 10;
function test() {
console.log(a); // will output 10
console.log(b); // will output 6
}
var b = 6;
test();
Когда вызывается функция JavaScript, создается новый контекст выполнения. Вместе с аргументами функции и родительским объектом этот контекст выполнения также получает все переменные, объявленные вне его (в вышеприведенном примере как 'a' и 'b').
Можно создать более одной функции замыкания, либо возвращая их список, либо устанавливая их в глобальные переменные. Все это будет относиться к тому же x
и то же самое tmp
они не делают свои собственные копии.
Здесь число x
это буквальное число. Как и с другими литералами в JavaScript, когда foo
называется, номер x
копируется в foo
в качестве аргумента x
,
С другой стороны, JavaScript всегда использует ссылки при работе с объектами. Если сказать, ты звонил foo
с объектом возвращаемое замыкание будет ссылаться на этот оригинальный объект!
function foo(x) {
var tmp = 3;
return function (y) {
console.log(x + y + tmp);
x.memb = x.memb ? x.memb + 1 : 1;
console.log(x.memb);
}
}
var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);
Как и ожидалось, каждый звонок bar(10)
будет увеличиваться x.memb
, Чего нельзя ожидать, так это того, что x
просто ссылается на тот же объект, что и age
переменная! После пары звонков bar
, age.memb
будет 2! Эта ссылка является основой для утечек памяти с HTML-объектами.
ПРЕДИСЛОВИЕ: этот ответ был написан, когда вопрос был:
Как сказал старый Альберт: "Если вы не можете объяснить это шестилетнему, вы действительно сами этого не понимаете". Ну, я попытался объяснить закрытие JS другу 27 лет и потерпел неудачу.
Кто-нибудь может считать, что мне 6 лет и странно интересуюсь этой темой?
Я почти уверен, что был одним из немногих, кто попытался ответить на первый вопрос буквально. С тех пор вопрос менялся несколько раз, поэтому мой ответ теперь может показаться невероятно глупым и неуместным. Надеюсь, общая идея этой истории для некоторых остаётся забавной.
Я большой поклонник аналогий и метафор при объяснении сложных концепций, поэтому позвольте мне попробовать свои силы с историей.
Давным-давно:
Там была принцесса...
function princess() {
Она жила в чудесном мире, полном приключений. Она встретила своего очаровательного принца, объехала свой мир на единороге, сражалась с драконами, встречала говорящих животных и много других фантастических вещей.
var adventures = [];
function princeCharming() { /* ... */ }
var unicorn = { /* ... */ },
dragons = [ /* ... */ ],
squirrel = "Hello!";
/* ... */
Но ей всегда придется возвращаться в свой скучный мир дел и взрослых.
return {
И она часто рассказывала им о своем последнем удивительном приключении в роли принцессы.
story: function() {
return adventures[adventures.length - 1];
}
};
}
Но все, что они увидят, это маленькая девочка...
var littleGirl = princess();
... рассказывать истории о магии и фантазии.
littleGirl.story();
И хотя взрослые знали о настоящих принцессах, они никогда не поверили бы в единорогов или драконов, потому что никогда их не видели. Взрослые говорили, что они существуют только в воображении маленькой девочки.
Но мы знаем настоящую правду; что маленькая девочка с принцессой внутри...
... действительно принцесса с маленькой девочкой внутри.
Принимая этот вопрос всерьез, мы должны выяснить, на что способен типичный шестилетний ребенок познавательно, хотя, по общему признанию, тот, кто интересуется JavaScript, не настолько типичен.
О развитии детства: от 5 до 7 лет сказано:
Ваш ребенок сможет следовать двухшаговым инструкциям. Например, если вы скажете своему ребенку: "Иди на кухню и принеси мне мешок для мусора", они смогут запомнить это направление.
Мы можем использовать этот пример для объяснения замыканий следующим образом:
Кухня является закрытием, которое имеет локальную переменную, называемую
trashBags
, На кухне есть функция под названиемgetTrashBag
который получает один мешок для мусора и возвращает его.
Мы можем кодировать это в JavaScript, как это:
function makeKitchen() {
var trashBags = ['A', 'B', 'C']; // only 3 at first
return {
getTrashBag: function() {
return trashBags.pop();
}
};
}
var kitchen = makeKitchen();
console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A
Другие пункты, которые объясняют, почему замыкания интересны:
- Каждый раз
makeKitchen()
называется, новое закрытие создается со своим отдельнымtrashBags
, -
trashBags
переменная является локальной для каждой кухни и недоступна снаружи, но внутренняя функцияgetTrashBag
собственность имеет к нему доступ. - Каждый вызов функции создает замыкание, но не было бы необходимости хранить замыкание вокруг, если только внутренняя функция, имеющая доступ к внутренней части замыкания, не может быть вызвана извне замыкания. Возврат объекта с
getTrashBag
функция делает это здесь.
Соломенный Человек
Мне нужно знать, сколько раз была нажата кнопка, и что-то делать при каждом третьем нажатии...
Достаточно очевидное решение
// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');
element.addEventListener("click", function() {
// Increment outside counter
counter++;
if (counter === 3) {
// Do something every third time
console.log("Third time's the charm!");
// Reset counter
counter = 0;
}
});
<button id="button">Click Me!</button>
Теперь это сработает, но оно вторгается во внешнюю область, добавляя переменную, единственная цель которой - отслеживать количество. В некоторых ситуациях это будет предпочтительнее, поскольку вашему внешнему приложению может потребоваться доступ к этой информации. Но в этом случае мы меняем только поведение каждого третьего клика, поэтому желательно включить эту функцию в обработчик событий.
Рассмотрим этот вариант
var element = document.getElementById('button');
element.addEventListener("click", (function() {
// init the count to 0
var count = 0;
return function(e) { // <- This function becomes the click handler
count++; // and will retain access to the above `count`
if (count === 3) {
// Do something every third time
console.log("Third time's the charm!");
//Reset counter
count = 0;
}
};
})());
<button id="button">Click Me!</button>
Обратите внимание на несколько вещей здесь.
В приведенном выше примере я использую поведение закрытия JavaScript. Такое поведение позволяет любой функции иметь доступ к области, в которой она была создана, на неопределенный срок. Чтобы применить это на практике, я немедленно вызываю функцию, которая возвращает другую функцию, и поскольку функция, которую я возвращаю, имеет доступ к внутренней переменной count (из-за описанного выше поведения замыкания), это приводит к закрытой области видимости для использования в результате функция... не так просто? Давайте разбавим это...
Простое однострочное закрытие
// _______________________Immediately invoked______________________
// | |
// | Scope retained for use ___Returned as the____ |
// | only by returned function | value of func | |
// | | | | | |
// v v v v v v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();
Все переменные за пределами возвращаемой функции доступны для возвращаемой функции, но они не доступны напрямую возвращаемому объекту функции...
func(); // Alerts "val"
func.a; // Undefined
Возьми? Таким образом, в нашем основном примере переменная count содержится в замыкании и всегда доступна для обработчика событий, поэтому она сохраняет свое состояние от щелчка к щелчку.
Кроме того, это состояние закрытой переменной полностью доступно как для чтения, так и для присвоения его закрытым переменным.
Там вы идете; теперь вы полностью инкапсулируете это поведение.
Полный пост в блоге (включая соображения jQuery)
Замыкания трудно объяснить, потому что они используются для того, чтобы заставить поведение работать так, как все интуитивно ожидают, что оно все равно будет работать. Я нахожу лучший способ объяснить их (и способ, которым я узнал, что они делают), это представить ситуацию без них:
var bind = function(x) {
return function(y) { return x + y; };
}
var plus5 = bind(5);
console.log(plus5(3));
Что бы произошло здесь, если бы JavaScript не знал замыканий? Просто замените вызов в последней строке на тело метода (что в основном и делают вызовы функций), и вы получите:
console.log(x + 3);
Теперь, где определение x
? Мы не определили это в текущем объеме. Единственное решение состоит в том, чтобы позволить plus5
нести его область (или, скорее, область его родителя) вокруг. Сюда, x
четко определено и связано со значением 5.
Закрытие очень похоже на объект. Он создается каждый раз, когда вы вызываете функцию.
Область замыкания в JavaScript является лексической, что означает, что все, что содержится в функции, к которой принадлежит замыкание, имеет доступ к любой переменной, которая в нем находится.
Переменная содержится в закрытии, если вы
- назначить его с
var foo=1;
или же - просто пиши
var foo;
Если внутренняя функция (функция, содержащаяся в другой функции) обращается к такой переменной, не определяя ее в своей области видимости с помощью var, она изменяет содержимое переменной во внешнем замыкании.
Закрытие переживает время выполнения функции, которая его породила. Если другие функции делают это из замыкания / области видимости, в которой они определены (например, как возвращаемые значения), они будут продолжать ссылаться на это замыкание.
пример
function example(closure) {
// define somevariable to live in the closure of example
var somevariable = 'unchanged';
return {
change_to: function(value) {
somevariable = value;
},
log: function(value) {
console.log('somevariable of closure %s is: %s',
closure, somevariable);
}
}
}
closure_one = example('one');
closure_two = example('two');
closure_one.log();
closure_two.log();
closure_one.change_to('some new value');
closure_one.log();
closure_two.log();
Выход
somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged
Хорошо, 6-летний поклонник закрытий. Хотите услышать самый простой пример закрытия?
Давайте представим следующую ситуацию: водитель сидит в машине. Эта машина в самолете. Самолет в аэропорту. Возможность водителя получить доступ к вещам вне его автомобиля, но внутри самолета, даже если этот самолет покидает аэропорт, является закрытием. Вот и все. Когда вам исполнится 27 лет, посмотрите более подробное объяснение или пример ниже.
Вот как я могу преобразовать свою плоскую историю в код.
var plane = function(defaultAirport) {
var lastAirportLeft = defaultAirport;
var car = {
driver: {
startAccessPlaneInfo: function() {
setInterval(function() {
console.log("Last airport was " + lastAirportLeft);
}, 2000);
}
}
};
car.driver.startAccessPlaneInfo();
return {
leaveTheAirport: function(airPortName) {
lastAirportLeft = airPortName;
}
}
}("Boryspil International Airport");
plane.leaveTheAirport("John F. Kennedy");
Это попытка прояснить несколько (возможных) недоразумений о замыканиях, которые появляются в некоторых других ответах.
- Закрытие создается не только когда вы возвращаете внутреннюю функцию. Фактически, функция включения вообще не должна возвращаться для создания своего замыкания. Вместо этого вы можете назначить свою внутреннюю функцию переменной во внешней области видимости или передать ее в качестве аргумента другой функции, где она может быть вызвана немедленно или в любое время позже. Следовательно, замыкание закрывающей функции, вероятно, создается, как только вызывается закрывающая функция, поскольку любая внутренняя функция имеет доступ к этому закрытию всякий раз, когда вызывается внутренняя функция, до или после возврата закрывающей функции.
- Закрытие не ссылается на копию старых значений переменных в своей области видимости. Сами переменные являются частью замыкания, и поэтому значение, видимое при обращении к одной из этих переменных, является самым последним значением на момент обращения к нему. Вот почему внутренние функции, созданные внутри циклов, могут быть хитрыми, поскольку каждая из них имеет доступ к одним и тем же внешним переменным, а не захватывает копию переменных во время создания или вызова функции.
- "Переменные" в замыкании включают любые именованные функции, объявленные внутри функции. Они также включают аргументы функции. Замыкание также имеет доступ к переменным содержащего замыкание, вплоть до глобальной области видимости.
- Замыкания используют память, но они не вызывают утечек памяти, поскольку JavaScript сам по себе очищает свои собственные циклические структуры, на которые нет ссылок. Утечки памяти в Internet Explorer, связанные с замыканиями, создаются, когда ему не удается отключить значения атрибутов DOM, которые ссылаются на замыкания, таким образом сохраняя ссылки на, возможно, циклические структуры.
Некоторое время назад я написал сообщение в блоге, объясняющее закрытие. Вот то, что я сказал о замыканиях с точки зрения того, почему вы хотите его.
Замыкания - это способ дать функции иметь постоянные закрытые переменные, то есть переменные, о которых знает только одна функция, где она может отслеживать информацию за предыдущие периоды, когда она выполнялась.
В этом смысле они позволяют функции действовать как объект с закрытыми атрибутами.
Полный пост:
Как бы я объяснил это шестилетнему ребенку:
Вы знаете, как взрослые могут владеть домом, и они называют его домом? Когда у мамы есть ребенок, ребенок на самом деле ничего не имеет, верно? Но его родители владеют домом, поэтому, когда кто-то спрашивает ребенка "Где твой дом?", Он / она может ответить "этот дом!" И указывать на дом его родителей. "Закрытие" - это способность ребенка всегда (даже если он находится за границей) быть в состоянии сказать, что у него есть дом, даже если дом действительно принадлежит родителю.
Закрытия просты:
Следующий простой пример охватывает все основные моменты замыканий JavaScript. *
Вот фабрика, которая производит калькуляторы, которые могут складываться и умножаться:
function make_calculator() {
var n = 0; // this calculator stores a single number n
return {
add: function(a) {
n += a;
return n;
},
multiply: function(a) {
n *= a;
return n;
}
};
}
first_calculator = make_calculator();
second_calculator = make_calculator();
first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400
first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000
Ключевой момент: каждый звонок make_calculator
создает новую локальную переменную n
, который продолжает использоваться этим калькулятором add
а также multiply
функционирует долго после make_calculator
возвращается.
Если вы знакомы со стековыми фреймами, эти калькуляторы кажутся странными: как они могут продолжать доступ n
после make_calculator
возвращается? Ответ заключается в том, чтобы представить, что JavaScript не использует "стековые фреймы", а вместо этого использует "кучные фреймы", которые могут сохраняться после возврата вызова функции, который их сделал.
Внутренние функции, такие как add
а также multiply
, которые обращаются к переменным, объявленным во внешней функции **, называются замыканиями.
Это почти все, что нужно для замыканий.
* Например, он охватывает все пункты в статье "Замыкания для чайников", приведенной в другом ответе, кроме примера 6, в котором просто показано, что переменные можно использовать до того, как они объявлены, - хороший факт, который нужно знать, но совершенно не связанный с замыканиями. Он также охватывает все точки в принятом ответе, за исключением точек (1), в которых функции копируют свои аргументы в локальные переменные (аргументы именованной функции), и (2) что при копировании чисел создается новое число, но копируется ссылка на объект дает вам еще одну ссылку на тот же объект. Это также полезно знать, но опять же совершенно не связано с замыканиями. Это также очень похоже на пример в этом ответе, но немного короче и менее абстрактно. Он не охватывает суть этого ответа или этого комментария, который заключается в том, что JavaScript затрудняет вставку текущего значения переменной цикла в вашу внутреннюю функцию: шаг "вставки" может быть выполнен только с помощью вспомогательной функции, которая включает ваша внутренняя функция и вызывается на каждой итерации цикла. (Строго говоря, внутренняя функция обращается к копии переменной вспомогательной функции, а не к чему-либо подключенному.) Опять же, очень полезно при создании замыканий, но не является частью того, что такое замыкание или как оно работает. Существует дополнительная путаница из-за того, что замыкания работают по-разному в функциональных языках, таких как ML, где переменные связаны со значениями, а не с пространством хранения, предоставляя постоянный поток людей, которые понимают замыкания способом (а именно способом "подключения"), то есть просто неверно для JavaScript, где переменные всегда связаны с пространством хранения, а не со значениями.
** Любая внешняя функция, если несколько вложена, или даже в глобальном контексте, как ясно указывает этот ответ.
Можете ли вы объяснить закрытие 5-летнего ребенка?*
Я все еще думаю, что объяснение Google работает очень хорошо и сжато:
/*
* When a function is defined in another function and it
* has access to the outer function's context even after
* the outer function returns.
*
* An important concept to learn in JavaScript.
*/
function outerFunction(someNum) {
var someString = 'Hey!';
var content = document.getElementById('content');
function innerFunction() {
content.innerHTML = someNum + ': ' + someString;
content = null; // Internet Explorer memory leak for DOM reference
}
innerFunction();
}
outerFunction(1);
* AC# вопрос
Я склонен учиться лучше при сравнении ХОРОШЕГО / ПЛОХОГО. Мне нравится видеть рабочий код, сопровождаемый нерабочим кодом, с которым кто-то может столкнуться. Я собрал jsFiddle, который делает сравнение и пытается свести различия к простейшим объяснениям, которые я смог придумать.
Затворы сделаны правильно:
console.log('CLOSURES DONE RIGHT');
var arr = [];
function createClosure(n) {
return function () {
return 'n = ' + n;
}
}
for (var index = 0; index < 10; index++) {
arr[index] = createClosure(index);
}
for (var index in arr) {
console.log(arr[index]());
}
В приведенном выше коде
createClosure(n)
вызывается в каждой итерации цикла. Обратите внимание, что я назвал переменнуюn
подчеркнуть, что это новая переменная, созданная в новой области видимости функции и не совпадающая сindex
который связан с внешней областью.Это создает новую область и
n
связан с этой областью; это означает, что у нас есть 10 отдельных областей, по одной на каждую итерацию.createClosure(n)
возвращает функцию, которая возвращает n в этой области.В каждой области
n
связан с любой ценностью, когдаcreateClosure(n)
был вызван, поэтому возвращаемая вложенная функция всегда будет возвращать значениеn
что было когдаcreateClosure(n)
был вызван.
Замыкания сделаны неправильно:
console.log('CLOSURES DONE WRONG');
function createClosureArray() {
var badArr = [];
for (var index = 0; index < 10; index++) {
badArr[index] = function () {
return 'n = ' + index;
};
}
return badArr;
}
var badArr = createClosureArray();
for (var index in badArr) {
console.log(badArr[index]());
}
В приведенном выше коде цикл был перемещен в
createClosureArray()
function и function теперь просто возвращают законченный массив, который на первый взгляд кажется более интуитивным.Что не может быть очевидным, так это то, что с
createClosureArray()
вызывается только после того, как для этой функции создается только одна область видимости, а не одна для каждой итерации цикла.Внутри этой функции переменная с именем
index
определено. Цикл запускается и добавляет функции в массив, которые возвращаютindex
, Обратите внимание, чтоindex
определяется в рамкахcreateClosureArray
функция, которая вызывается только один раз.Потому что в рамках
createClosureArray()
функция,index
привязан только к значению в этой области. Другими словами, каждый раз, когда цикл меняет значениеindex
, он меняет его для всего, что ссылается на него в этой области.Все функции, добавленные в массив, возвращают ЖЕ
index
переменная из родительской области видимости, где она была определена вместо 10 разных из 10 разных областей, как в первом примере. Конечным результатом является то, что все 10 функций возвращают одну и ту же переменную из одной и той же области видимости.После окончания цикла и
index
было сделано с изменением, конечное значение было 10, поэтому каждая функция, добавленная в массив, возвращает значение единственногоindex
переменная, которая теперь установлена в 10.
Результат
ЗАКРЫТИЯ СДЕЛАНЫ ПРАВО
n = 0
n = 1
п = 2
n = 3
n = 4
п = 5
n = 6
n = 7
n = 8
n = 9ЗАКРЫТИЯ СДЕЛАНЫ НЕПРАВИЛЬНО
п = 10
п = 10
п = 10
п = 10
п = 10
п = 10
п = 10
п = 10
п = 10
п = 10
В информатике замыкание - это функция вместе со ссылочной средой для нелокальных имен (свободных переменных) этой функции.
Технически, в JavaScript каждая функция является замыканием. Он всегда имеет доступ к переменным, определенным в окружающей области видимости.
Поскольку определяющая область видимости в JavaScript является функцией, а не блоком кода, как во многих других языках, то, что мы обычно подразумеваем под замыканием в JavaScript, это функция, работающая с нелокальными переменными, определенными в уже выполненной окружающей функции.
Замыкания часто используются для создания функций с некоторыми скрытыми частными данными (но это не всегда так).
var db = (function() {
// Create a hidden object, which will hold the data
// it's inaccessible from the outside.
var data = {};
// Make a function, which will provide some access to the data.
return function(key, val) {
if (val === undefined) { return data[key] } // Get
else { return data[key] = val } // Set
}
// We are calling the anonymous surrounding function,
// returning the above inner function, which is a closure.
})();
db('x') // -> undefined
db('x', 1) // Set x to 1
db('x') // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.
Эмс
В приведенном выше примере используется анонимная функция, которая была выполнена один раз. Но это не должно быть. Это можно назвать (например, mkdb
) и выполняется позже, генерируя функцию базы данных каждый раз, когда она вызывается. Каждая сгенерированная функция будет иметь свой собственный скрытый объект базы данных. Другой пример использования замыканий - это когда мы не возвращаем функцию, а объект, содержащий несколько функций для разных целей, каждая из которых имеет доступ к одним и тем же данным.
Дети всегда будут помнить секреты, которыми они поделились со своими родителями, даже после того, как их родители ушли. Это то, что замыкания для функций.
Секреты функций JavaScript - это частные переменные
var parent = function() {
var name = "Mary"; // secret
}
Каждый раз, когда вы вызываете его, создается локальная переменная "имя" с именем "Мэри". И каждый раз при выходе из функции переменная теряется, а имя забывается.
Как вы можете догадаться, поскольку переменные создаются заново при каждом вызове функции, и никто больше не узнает их, должно быть секретное место, где они хранятся. Его можно назвать " Тайной комнатой", стековым или локальным, но это не имеет значения. Мы знаем, что они где-то спрятаны в памяти.
Но в JavaScript есть такая особенность, что функции, которые создаются внутри других функций, могут также знать локальные переменные своих родителей и сохранять их в течение всего срока их службы.
var parent = function() {
var name = "Mary";
var child = function(childName) {
// I can also see that "name" is "Mary"
}
}
Таким образом, пока мы находимся в родительской функции, она может создавать одну или несколько дочерних функций, которые совместно используют секретные переменные из секретного места.
Но грустная вещь в том, что если дочерний элемент также является частной переменной его родительской функции, он также умрет, когда родительский элемент закончится, и секреты умрут вместе с ними.
Чтобы жить, ребенок должен уйти, пока не стало слишком поздно
var parent = function() {
var name = "Mary";
var child = function(childName) {
return "My name is " + childName +", child of " + name;
}
return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside
И теперь, несмотря на то, что Мэри "больше не бежит", память о ней не потеряна, и ее ребенок всегда будет помнить ее имя и другие секреты, которыми они поделились во время совместной жизни.
Итак, если вы назовете ребенка "Алиса", она ответит
child("Alice") => "My name is Alice, child of Mary"
Это все, что можно сказать.
Я собрал интерактивное руководство по JavaScript, чтобы объяснить, как работают замыкания. Что такое закрытие?
Вот один из примеров:
var create = function (x) {
var f = function () {
return x; // We can refer to x here!
};
return f;
};
// 'create' takes one argument, creates a function
var g = create(42);
// g is a function that takes no arguments now
var y = g();
// y is 42 here
Я не понимаю, почему ответы здесь такие сложные.
Вот закрытие:
var a = 42;
function b() { return a; }
Да. Вы, вероятно, используете это много раз в день.
Нет оснований полагать, что замыкания - это сложный проектный взлом для решения конкретных проблем. Нет, замыкания - это просто использование переменной, которая выходит из более широкой области видимости с точки зрения того, где функция была объявлена (не запущена).
Теперь то, что он позволяет вам делать, может быть более впечатляющим, смотрите другие ответы.
Закрытие - это когда внутренняя функция имеет доступ к переменным в своей внешней функции. Это, вероятно, самое простое однострочное объяснение, которое вы можете получить для замыканий.
Пример для первого пункта по dlaliberte:
Закрытие создается не только когда вы возвращаете внутреннюю функцию. Фактически, функция включения не должна возвращаться вообще. Вместо этого вы можете назначить свою внутреннюю функцию переменной во внешней области видимости или передать ее в качестве аргумента другой функции, где она может быть использована немедленно. Следовательно, замыкание закрывающей функции, вероятно, уже существует во время вызова закрывающей функции, поскольку любая внутренняя функция имеет доступ к ней, как только она вызывается.
var i;
function foo(x) {
var tmp = 3;
i = function (y) {
console.log(x + y + (++tmp));
}
}
foo(2);
i(3);
Я знаю, что уже есть множество решений, но я предполагаю, что этот небольшой и простой скрипт может быть полезен для демонстрации концепции:
// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
var _count = 0; // not accessible outside this function
var sequencer = function () {
return _count++;
}
return sequencer;
}
var fnext = makeSequencer();
var v0 = fnext(); // v0 = 0;
var v1 = fnext(); // v1 = 1;
var vz = fnext._count // vz = undefined
Автор Closures довольно хорошо объяснил замыкания, объясняя причину, почему они нам нужны, а также объясняя LexicalEnvironment, что необходимо для понимания замыканий.
Вот резюме:
Что делать, если к переменной обращаются, но она не локальна? Как здесь:
В этом случае интерпретатор находит переменную во внешнем LexicalEnvironment
объект.
Процесс состоит из двух этапов:
- Во-первых, когда функция f создается, она не создается в пустом пространстве. Существует текущий объект LexicalEnvironment. В приведенном выше случае это окно (a не определено во время создания функции).
Когда функция создается, она получает скрытое свойство с именем [[Scope]], которое ссылается на текущую LexicalEnvironment.
Если переменная читается, но нигде не может быть найдена, генерируется ошибка.
Вложенные функции
Функции могут быть вложены одна в другую, образуя цепочку LexicalEnvironments, которую также можно назвать цепочкой областей действия.
Итак, функция g имеет доступ к g, a и f.
Затворы
Вложенная функция может продолжать жить после завершения внешней функции:
Разметка LexicalEnvironments:
Как мы видим, this.say
является свойством в объекте пользователя, поэтому он продолжает жить после завершения пользователя.
И если вы помните, когда this.say
создается, он (как и каждая функция) получает внутреннюю ссылку this.say.[[Scope]]
к текущему LexicalEnvironment. Таким образом, LexicalEnvironment текущего выполнения пользователя остается в памяти. Все переменные User также являются его свойствами, поэтому они также тщательно сохраняются, а не отбрасываются, как обычно.
Весь смысл в том, чтобы гарантировать, что если внутренняя функция захочет получить доступ к внешней переменной в будущем, она сможет это сделать.
Подвести итоги:
- Внутренняя функция сохраняет ссылку на внешнее LexicalEnvironment.
- Внутренняя функция может обращаться к переменным из нее в любое время, даже если внешняя функция завершена.
- Браузер сохраняет LexicalEnvironment и все его свойства (переменные) в памяти, пока не найдется внутренняя функция, которая ссылается на него.
Это называется закрытием.
Ты переспал и пригласил Дэна. Вы говорите Дэну принести один контроллер XBox.
Дэн приглашает Пола. Дэн просит Пола принести одного контролера. Сколько контролеров было привезено на вечеринку?
function sleepOver(howManyControllersToBring) {
var numberOfDansControllers = howManyControllersToBring;
return function danInvitedPaul(numberOfPaulsControllers) {
var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
return totalControllers;
}
}
var howManyControllersToBring = 1;
var inviteDan = sleepOver(howManyControllersToBring);
// The only reason Paul was invited is because Dan was invited.
// So we set Paul's invitation = Dan's invitation.
var danInvitedPaul = inviteDan(howManyControllersToBring);
alert("There were " + danInvitedPaul + " controllers brought to the party.");
Функции JavaScript могут получить доступ к своим:
- аргументы
- Локальные (то есть их локальные переменные и локальные функции)
- Среда, которая включает в себя:
- глобалы, включая DOM
- что-нибудь во внешних функциях
Если функция обращается к своей среде, то функция является замыканием.
Обратите внимание, что внешние функции не требуются, хотя они предлагают преимущества, которые я здесь не обсуждаю. Получая доступ к данным в своей среде, замыкание поддерживает эти данные живыми. В подклассе внешних / внутренних функций внешняя функция может создавать локальные данные и в конечном итоге завершать работу, и, тем не менее, если какая-либо внутренняя функция (и) выживает после выхода из внешней функции, то внутренняя функция (и) сохраняет локальные данные внешней функции. в живых.
Пример замыкания, использующего глобальную среду:
Представьте себе, что события кнопок "Переполнение стека" "Голосование вверх" и "Голосование вниз" реализованы в виде замыканий, voiceUp_click и voiceDown_click, которые имеют доступ к внешним переменным isVotedUp и isVotedDown, которые определены глобально. (Для простоты я имею в виду кнопки "Голосовать" в Stack Overflow, а не массив кнопок "Голосовать".)
Когда пользователь нажимает кнопку VoteUp, функция voiceUp_click проверяет, является ли isVotedDown == true, чтобы определить, следует ли голосовать за или просто отменить голосование с понижением. Функция VoteUp_click является закрытием, потому что она обращается к своей среде.
var isVotedUp = false;
var isVotedDown = false;
function voteUp_click() {
if (isVotedUp)
return;
else if (isVotedDown)
SetDownVote(false);
else
SetUpVote(true);
}
function voteDown_click() {
if (isVotedDown)
return;
else if (isVotedUp)
SetUpVote(false);
else
SetDownVote(true);
}
function SetUpVote(status) {
isVotedUp = status;
// Do some CSS stuff to Vote-Up button
}
function SetDownVote(status) {
isVotedDown = status;
// Do some CSS stuff to Vote-Down button
}
Все четыре из этих функций являются замыканиями, поскольку все они имеют доступ к своей среде.
Как отец 6-летнего ребенка, который в настоящее время обучает детей младшего возраста (и относительного новичка в области кодирования без формального образования, поэтому требуются исправления), я думаю, что этот урок лучше всего использовать в практической игре. Если шестилетний ребенок готов понять, что такое закрытие, тогда он достаточно взрослый, чтобы пойти самому. Я бы предложил вставить код в jsfiddle.net, немного пояснить и оставить их в покое, чтобы придумать уникальную песню. Пояснительный текст ниже, вероятно, больше подходит для 10-летнего.
function sing(person) {
var firstPart = "There was " + person + " who swallowed ";
var fly = function() {
var creature = "a fly";
var result = "Perhaps she'll die";
alert(firstPart + creature + "\n" + result);
};
var spider = function() {
var creature = "a spider";
var result = "that wiggled and jiggled and tickled inside her";
alert(firstPart + creature + "\n" + result);
};
var bird = function() {
var creature = "a bird";
var result = "How absurd!";
alert(firstPart + creature + "\n" + result);
};
var cat = function() {
var creature = "a cat";
var result = "Imagine That!";
alert(firstPart + creature + "\n" + result);
};
fly();
spider();
bird();
cat();
}
var person="an old lady";
sing(person);
ИНСТРУКЦИИ
ДАННЫЕ: Данные представляют собой набор фактов. Это могут быть цифры, слова, измерения, наблюдения или даже просто описания вещей. Вы не можете прикоснуться к нему, почувствовать запах или попробовать его. Вы можете записать это, сказать это и услышать это. Вы можете использовать это, чтобы создать сенсорный запах и вкус, используя компьютер. Это может быть полезно на компьютере с помощью кода.
КОД: Все написанное выше называется кодом. Это написано в JavaScript.
JAVASCRIPT: JavaScript это язык. Как английский или французский или китайский языки. Есть много языков, которые понимают компьютеры и другие электронные процессоры. Для того чтобы JavaScript был понятен компьютеру, нужен переводчик. Представьте, что учитель, который говорит только по-русски, приходит преподавать ваш класс в школе. Когда учитель говорит "все садятся", класс не понимает. Но, к счастью, у вас в классе есть русский ученик, который говорит всем, что это означает "все садятся" - так вы все делаете. Класс подобен компьютеру, а русский ученик - переводчик. Для JavaScript самый распространенный интерпретатор называется браузером.
БРАУЗЕР: при подключении к Интернету на компьютере, планшете или телефоне для посещения веб-сайта вы используете браузер. Примеры, которые вы можете знать, это Internet Explorer, Chrome, Firefox и Safari. Браузер может понимать JavaScript и сообщать компьютеру, что ему нужно делать. Инструкции JavaScript называются функциями.
ФУНКЦИЯ: функция в JavaScript похожа на фабрику. Это может быть небольшая фабрика с одной машиной внутри. Или это может быть много других маленьких фабрик, на каждом из которых много машин делают разные работы. На реальной фабрике одежды у вас могут быть пачки тканей и бобин ниток, а также футболки и джинсы. Наша фабрика JavaScript обрабатывает только данные, она не может шить, сверлить или расплавлять металл. В нашем JavaScript фабричные данные поступают, а данные выходят.
Все эти данные звучат немного скучно, но это действительно очень круто; у нас может быть функция, которая сообщает роботу, что делать на ужин. Допустим, я приглашаю вас и вашего друга в мой дом. Тебе больше всего нравятся куриные ножки, мне нравятся колбаски, твой друг всегда хочет того, что ты хочешь, а мой друг не ест мяса.
У меня нет времени ходить по магазинам, поэтому функция должна знать, что у нас в холодильнике, чтобы принимать решения. Каждый ингредиент имеет разное время приготовления, и мы хотим, чтобы робот одновременно подавал все в горячем виде. Нам нужно предоставить функции данные о том, что нам нравится, функция может "разговаривать" с холодильником, а функция может управлять роботом.
Функция обычно имеет имя, скобки и фигурные скобки. Как это:
function cookMeal() { /* STUFF INSIDE THE FUNCTION */ }
Обратите внимание, что /*...*/
а также //
остановить код, читаемый браузером.
ИМЯ: Вы можете вызывать функцию практически из любого нужного вам слова. Пример "cookMeal" типичен для объединения двух слов и введения второго заглавной буквы в начале, но это не обязательно. В нем не может быть пробела, и это не может быть число само по себе.
РОДИТЕЛИ: "круглые скобки" или ()
это почтовый ящик на двери фабрики функций JavaScript или почтовый ящик на улице для отправки пакетов информации на фабрику. Иногда почтовый ящик может быть отмечен, например, cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime)
в этом случае вы знаете, какие данные вы должны предоставить.
БРАСЫ: "Брекеты", которые выглядят так {}
тонированные окна нашей фабрики. Внутри фабрики вы можете видеть снаружи, но снаружи вы не можете видеть внутри.
Пример длинного кода выше
Наш код начинается со слова function, поэтому мы знаем, что он один! Затем название функции sing - это мое собственное описание того, о чем эта функция. Тогда круглые скобки (). Скобки всегда есть для функции. Иногда они пусты, а иногда в них что-то есть. В этом есть слово: (person)
, После этого есть такая скобка {
, Это отмечает начало функции sing(). У него есть партнер, который отмечает конец sing() вот так }
function sing(person) { /* STUFF INSIDE THE FUNCTION */ }
Так что эта функция может иметь отношение к пению и может потребовать некоторые данные о человеке. Внутри него есть инструкции, чтобы что-то делать с этими данными.
Теперь, после функции sing(), ближе к концу кода находится строка
var person="an old lady";
VARIABLE: буквы var означают "переменная". Переменная похожа на конверт. Снаружи этот конверт помечен как "человек". Внутри он содержит листок бумаги с информацией, которая нужна нашей функции, некоторые буквы и пробелы, соединенные вместе, как кусок строки (это называется строкой), которые делают фразу, читающую "старуху". Наш конверт может содержать другие виды вещей, такие как числа (называемые целыми числами), инструкции (называемые функциями), списки (называемые массивами). Потому что эта переменная записана вне скобок {}
и поскольку вы можете видеть сквозь тонированные окна, когда находитесь внутри фигурных скобок, эту переменную можно увидеть из любого места кода. Мы называем это "глобальной переменной".
ГЛОБАЛЬНАЯ ПЕРЕМЕННАЯ: человек - это глобальная переменная, означающая, что если вы измените ее значение со "пожилой женщины" на "молодой человек", человек будет оставаться молодым человеком, пока вы не решите изменить его снова и что любая другая функция в Код может видеть, что это молодой человек. Нажмите кнопку F12 или просмотрите настройки параметров, чтобы открыть консоль разработчика браузера, и введите "person", чтобы увидеть, что это за значение. Тип person="a young man"
чтобы изменить его, а затем снова введите "person", чтобы увидеть, что оно изменилось.
После этого у нас есть линия
sing(person);
Эта строка вызывает функцию, как если бы она вызывала собаку
"Давай петь, иди и получить человека!"
Когда браузер загрузит код JavaScript, достигший этой строки, он запустит функцию. Я поставил строку в конце, чтобы убедиться, что в браузере есть вся информация, необходимая для его запуска.
Функции определяют действия - основная функция - пение. Он содержит переменную под названием firstPart, которая применяется к пению о человеке, которое относится к каждому из стихов песни: "Был" + человек + ", который проглотил". Если вы введете firstPart в консоли, вы не получите ответ, потому что переменная заблокирована в функции - браузер не может видеть внутри окрашенных окон фигурных скобок.
ЗАКРЫТИЯ: замыкания - это меньшие функции, которые находятся внутри большой функции sing(). Маленькие фабрики внутри большой фабрики. Каждый из них имеет свои собственные скобки, которые означают, что переменные внутри них не видны снаружи. Вот почему имена переменных (существо и результат) могут повторяться в замыканиях, но с разными значениями. Если вы введете эти имена переменных в окне консоли, вы не получите их значение, потому что оно скрыто двумя слоями тонированных окон.
Все замыкания знают, что такое переменная функции sing() под названием firstPart, потому что они могут видеть из своих тонированных окон.
После закрытия идут линии
fly();
spider();
bird();
cat();
Функция sing() будет вызывать каждую из этих функций в том порядке, в котором они заданы. Тогда работа функции sing() будет выполнена.
Хорошо, разговаривая с 6-летним ребенком, я бы, возможно, использовал следующие ассоциации.
Представьте себе - вы играете со своими маленькими братьями и сестрами во всем доме, вы ходите со своими игрушками и приносите некоторые из них в комнату вашего старшего брата. Через некоторое время ваш брат вернулся из школы и пошел в свою комнату, и он запер ее внутри, так что теперь вы больше не можете получить прямой доступ к оставленным там игрушкам. Но вы могли бы постучать в дверь и попросить вашего брата за эти игрушки. Это называется закрытием игрушки; твой брат сделал это для тебя, и теперь он во внешнем масштабе.
Сравните с ситуацией, когда дверь была заблокирована сквозняком и никто внутри (выполнение общих функций), а затем произошел какой-то локальный пожар и сгорел помещение (сборщик мусора:D), а затем была построена новая комната, и теперь вы можете покинуть ее. другие игрушки там (новый экземпляр функции), но никогда не получите те же игрушки, которые были оставлены в первом экземпляре комнаты.
Для продвинутого ребенка я бы поставил что-то вроде следующего. Это не идеально, но это заставляет вас чувствовать, что это такое:
function playingInBrothersRoom (withToys) {
// We closure toys which we played in the brother's room. When he come back and lock the door
// your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
var closureToys = withToys || [],
returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.
var brotherGivesToyBack = function (toy) {
// New request. There is not yet closureToys on brother's hand yet. Give him a time.
returnToy = null;
if (toy && closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.
for ( countIt = closureToys.length; countIt; countIt--) {
if (closureToys[countIt - 1] == toy) {
returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
break;
}
}
returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
}
else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
returnToy = 'Behold! ' + closureToys.join(', ') + '.';
closureToys = [];
}
else {
returnToy = 'Hey, lil shrimp, I gave you everything!';
}
console.log(returnToy);
}
return brotherGivesToyBack;
}
// You are playing in the house, including the brother's room.
var toys = ['teddybear', 'car', 'jumpingrope'],
askBrotherForClosuredToy = playingInBrothersRoom(toys);
// The door is locked, and the brother came from the school. You could not cheat and take it out directly.
console.log(askBrotherForClosuredToy.closureToys); // Undefined
// But you could ask your brother politely, to give it back.
askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
askBrotherForClosuredToy(); // The brother gives you all the rest
askBrotherForClosuredToy(); // Nothing left in there
Как видите, игрушки, оставленные в комнате, по-прежнему доступны через брата, и не важно, заперта ли комната. Вот jsbin, чтобы поиграть с ним.
Функция в JavaScript - это не просто ссылка на набор инструкций (как в языке C), но она также включает в себя скрытую структуру данных, которая состоит из ссылок на все нелокальные переменные, которые она использует (захваченные переменные). Такие двухсекционные функции называются замыканиями. Каждая функция в JavaScript может считаться закрытием.
Замыкания являются функциями с состоянием. Это несколько похоже на "это" в том смысле, что "это" также предоставляет состояние для функции, но функция и "это" являются отдельными объектами ("это" - просто причудливый параметр, и единственный способ навсегда связать его с функция заключается в создании замыкания). Хотя "this" и функция всегда живут отдельно, функцию нельзя отделить от ее закрытия, и язык не предоставляет средств для доступа к захваченным переменным.
Поскольку все эти внешние переменные, на которые ссылается лексически вложенная функция, фактически являются локальными переменными в цепочке своих лексически включающих функций (глобальные переменные можно считать локальными переменными некоторой корневой функции), и каждое отдельное выполнение функции создает новые экземпляры из ее локальных переменных следует, что при каждом выполнении функции, возвращающей (или иным образом передающей ее, например, регистрирующей в качестве обратного вызова) вложенную функцию, создается новое замыкание (со своим потенциально уникальным набором ссылочных нелокальных переменных, которые представляют ее выполнение контекст).
Также следует понимать, что локальные переменные в JavaScript создаются не в кадре стека, а в куче и уничтожаются только тогда, когда на них никто не ссылается. Когда функция возвращается, ссылки на ее локальные переменные уменьшаются, но они все равно могут быть ненулевыми, если во время текущего выполнения они стали частью замыкания и все еще ссылаются на свои лексически вложенные функции (что может произойти, только если ссылки на эти вложенные функции были возвращены или иным образом перенесены в некоторый внешний код).
Пример:
function foo (initValue) {
//This variable is not destroyed when the foo function exits.
//It is 'captured' by the two nested functions returned below.
var value = initValue;
//Note that the two returned functions are created right now.
//If the foo function is called again, it will return
//new functions referencing a different 'value' variable.
return {
getValue: function () { return value; },
setValue: function (newValue) { value = newValue; }
}
}
function bar () {
//foo sets its local variable 'value' to 5 and returns an object with
//two functions still referencing that local variable
var obj = foo(5);
//Extracting functions just to show that no 'this' is involved here
var getValue = obj.getValue;
var setValue = obj.setValue;
alert(getValue()); //Displays 5
setValue(10);
alert(getValue()); //Displays 10
//At this point getValue and setValue functions are destroyed
//(in reality they are destroyed at the next iteration of the garbage collector).
//The local variable 'value' in the foo is no longer referenced by
//anything and is destroyed too.
}
bar();
Возможно, немного за пределами всего, кроме самого раннего из шестилетних, но несколько примеров, которые помогли сделать концепцию замыкания в JavaScript, меня зацепили.
Закрытие - это функция, которая имеет доступ к области действия другой функции (ее переменным и функциям). Самый простой способ создать замыкание - использовать функцию внутри функции; причина в том, что в JavaScript функция всегда имеет доступ к области действия своей содержащей функции.
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
alert(outerVar);
}
innerFunction();
}
outerFunction();
ALERT: обезьяна
В приведенном выше примере вызывается externalFunction, которая, в свою очередь, вызывает innerFunction. Обратите внимание, что externalVar доступен для innerFunction, о чем свидетельствует его правильное оповещение о значении externalVar.
Теперь рассмотрим следующее:
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
return outerVar;
}
return innerFunction;
}
var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());
ALERT: обезьяна
Параметр referenceToInnerFunction имеет значение externalFunction(), который просто возвращает ссылку на innerFunction. Когда вызывается referenceToInnerFunction, она возвращает externalVar. Опять же, как и выше, это демонстрирует, что innerFunction имеет доступ к outerVar, переменной externalFunction. Кроме того, интересно отметить, что он сохраняет этот доступ даже после завершения externalFunction.
И вот тут все становится действительно интересно. Если бы мы избавились от externalFunction, скажем, установили его в null, вы могли бы подумать, что referenceToInnerFunction потеряет свой доступ к значению outerVar. Но это не так.
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
return outerVar;
}
return innerFunction;
}
var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());
outerFunction = null;
alert(referenceToInnerFunction());
ALERT: обезьяна ALERT: обезьяна
Но как это так? Как referenceToInnerFunction может все еще знать значение externalVar теперь, когда externalFunction был установлен в нуль?
Причина, по которой referenceToInnerFunction все еще может получить доступ к значению externalVar, заключается в том, что, когда замыкание было впервые создано путем размещения innerFunction внутри externalFunction, innerFunction добавила ссылку на область видимости externalFunction (ее переменные и функции) в свою цепочку областей действия. Это означает, что innerFunction имеет указатель или ссылку на все переменные externalFunction, включая externalVar. Таким образом, даже когда externalFunction закончила выполнение или даже если она удалена или имеет значение null, переменные в ее области видимости, такие как externalVar, остаются в памяти из-за выдающейся ссылки на них со стороны внутренней функции, которая была возвращена referenceToInnerFunction. Чтобы по-настоящему освободить externalVar и остальные переменные externalFunction из памяти, вам необходимо избавиться от этой выдающейся ссылки на них, скажем, установив referenceToInnerFunction в значение null.
//////////
Еще две вещи о замыканиях, чтобы отметить. Во-первых, замыкание всегда будет иметь доступ к последним значениям своей содержащей функции.
function outerFunction() {
var outerVar = "monkey";
function innerFunction() {
alert(outerVar);
}
outerVar = "gorilla";
innerFunction();
}
outerFunction();
ALERT: горилла
Во-вторых, когда создается замыкание, оно сохраняет ссылку на все переменные и функции своей включающей функции; это не может выбирать. И, тем не менее, затворы следует использовать с осторожностью или, по крайней мере, осторожно, так как они могут занимать много памяти; многие переменные могут храниться в памяти еще долго после завершения выполнения содержащей их функции.
Ответ для шестилетнего ребенка (при условии, что он знает, что такое функция, что такое переменная и какие данные):
Функции могут возвращать данные. Один вид данных, который вы можете вернуть из функции - это другая функция. Когда эта новая функция возвращается, все переменные и аргументы, используемые в функции, которая ее создала, не исчезают. Вместо этого родительская функция "закрывается". Другими словами, ничто не может заглянуть внутрь него и увидеть переменные, которые оно использовало, кроме функции, которую оно возвратило. Эта новая функция имеет особую возможность заглянуть внутрь функции, которая ее создала, и просмотреть данные внутри нее.
function the_closure() {
var x = 4;
return function () {
return x; // Here, we look back inside the_closure for the value of x
}
}
var myFn = the_closure();
myFn(); //=> 4
Другой действительно простой способ объяснить это с точки зрения объема:
Каждый раз, когда вы создаете меньшую область внутри большей области, меньшая область всегда сможет увидеть, что находится в большей области.
Я бы просто указал им на страницу "Закрытия Mozilla". Это лучшее, самое краткое и простое объяснение основ закрытия и практического использования, которое я нашел. Настоятельно рекомендуется всем, кто изучает JavaScript.
И да, я бы даже порекомендовал это 6-летнему ребенку - если 6-летний учится о замыканиях, то логично, что они готовы понять краткое и простое объяснение, приведенное в статье.