Как бы вы объяснили JavaScript-замыкания кому-то, кто знает концепции, из которых они состоят (например, функции, переменные и т. П.), Но не понимает сами замыкания?
Я видел пример схемы, приведенный в Википедии, но, к сожалению, это не помогло.
Закрытие представляет собой сочетание:
- Функция и
- Ссылка на внешнюю область действия этой функции (лексическая среда)
Лексическая среда является частью каждого контекста выполнения (стекового фрейма) и представляет собой карту между идентификаторами (то есть именами локальных переменных) и значениями.
Каждая функция в JavaScript поддерживает ссылку на внешнюю лексическую среду. Эта ссылка используется для настройки контекста выполнения, созданного при вызове функции. Эта ссылка позволяет коду внутри функции «видеть» переменные, объявленные вне функции, независимо от того, когда и где вызывается функция.
Если функция была вызвана функцией, которая в свою очередь была вызвана другой функцией, то создается цепочка ссылок на внешние лексические среды. Эта цепочка называется цепочкой областей действия.
В следующем коде inner
создается закрытие с лексической средой контекста выполнения, созданной при foo
вызове, закрывающей переменную secret
:
function foo() {
const secret = Math.trunc(Math.random()*100)
return function inner() {
console.log(`The secret number is ${secret}.`)
}
}
const f = foo() // `secret` is not directly accessible from outside `foo`
f() // The only way to retrieve `secret`, is to invoke `f`
Другими словами: в JavaScript функции содержат ссылку на частный «блок состояния», к которому имеют доступ только они (и любые другие функции, объявленные в той же лексической среде). Этот блок состояния невидим для вызывающей функции, предоставляя отличный механизм для сокрытия данных и инкапсуляции.
И помните: функции в JavaScript могут передаваться как переменные (функции первого класса), означая, что эти пары функциональности и состояния могут передаваться по вашей программе: подобно тому, как вы можете передавать экземпляр класса в C ++.
Если бы в JavaScript не было замыканий, то между функциями пришлось бы явно передавать больше состояний, что делало списки параметров длиннее и шумнее кода.
Итак, если вы хотите, чтобы функция всегда имела доступ к частной части состояния, вы можете использовать замыкание.
... и часто мы действительно хотим , чтобы связать состояние с функцией. Например, в Java или C ++, когда вы добавляете частную переменную экземпляра и метод в класс, вы связываете состояние с функциональностью.
В C и большинстве других распространенных языков после возврата функции все локальные переменные больше не доступны, потому что стековый фрейм разрушен. В JavaScript, если вы объявляете функцию в другой функции, то локальные переменные внешней функции могут оставаться доступными после ее возвращения. Таким образом, в приведенном выше коде secret
остается доступным для объекта функции после того inner
, как он был возвращен из foo
.
Использование Закрытий
Замыкания полезны, когда вам нужно личное состояние, связанное с функцией. Это очень распространенный сценарий - и помните: у JavaScript не было синтаксиса классов до 2015 года, и он все еще не имеет синтаксиса закрытых полей. Замыкания удовлетворяют эту потребность.
Переменные частного экземпляра
В следующем коде функция toString
закрывает детали автомобиля.
function Car(manufacturer, model, year, color) {
return {
toString() {
return `${manufacturer} ${model} (${year}, ${color})`
}
}
}
const car = new Car('Aston Martin','V8 Vantage','2012','Quantum Silver')
console.log(car.toString())
Функциональное программирование
В следующем коде функция inner
закрывается как fn
и args
.
function curry(fn) {
const args = []
return function inner(arg) {
if(args.length === fn.length) return fn(...args)
args.push(arg)
return inner
}
}
function add(a, b) {
return a + b
}
const curriedAdd = curry(add)
console.log(curriedAdd(2)(3)()) // 5
Событийно-ориентированное программирование
В следующем коде функция onClick
закрывается над переменной BACKGROUND_COLOR
.
const $ = document.querySelector.bind(document)
const BACKGROUND_COLOR = 'rgba(200,200,242,1)'
function onClick() {
$('body').style.background = BACKGROUND_COLOR
}
$('button').addEventListener('click', onClick)
<button>Set background color</button>
Модульность
В следующем примере все детали реализации скрыты внутри сразу выполняемого выражения функции. Функции tick
и toString
закрытие по частному состоянию и функциям, которые им необходимы для завершения своей работы. Замыкания позволили нам модульно и инкапсулировать наш код.
let namespace = {};
(function foo(n) {
let numbers = []
function format(n) {
return Math.trunc(n)
}
function tick() {
numbers.push(Math.random() * 100)
}
function toString() {
return numbers.map(format)
}
n.counter = {
tick,
toString
}
}(namespace))
const counter = namespace.counter
counter.tick()
counter.tick()
console.log(counter.toString())
Примеры
Пример 1
Этот пример показывает , что локальные переменные не скопированные в замыкании: замыкание содержит ссылку на исходные переменные сами . Это как если бы стековый фрейм остался в памяти даже после выхода из внешней функции.
function foo() {
let x = 42
let inner = function() { console.log(x) }
x = x+1
return inner
}
var f = foo()
f() // logs 43
Пример 2
В следующем коде, тремя способами log
, increment
и update
все близкие по одной и той же лексической среде.
И каждый раз, когда createObject
вызывается, создается новый контекст выполнения (стековый фрейм) и создается совершенно новая переменная x
, а также новый набор функций ( log
и т. Д.), Которые закрываются над этой новой переменной.
function createObject() {
let x = 42;
return {
log() { console.log(x) },
increment() { x++ },
update(value) { x = value }
}
}
const o = createObject()
o.increment()
o.log() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42
Пример 3
Если вы используете переменные, объявленные с использованием var
, будьте осторожны, понимая, какую переменную вы закрываете. Переменные, объявленные с использованием var
, поднимаются. Это намного меньше проблем в современном JavaScript из-за введения let
и const
.
В следующем коде каждый раз вокруг цикла создается новая функция inner
, которая закрывается i
. Но поскольку он var i
находится вне цикла, все эти внутренние функции закрываются по одной и той же переменной, а это означает, что окончательное значение i
(3) печатается три раза.
function foo() {
var result = []
for (var i = 0; i < 3; i++) {
result.push(function inner() { console.log(i) } )
}
return result
}
const result = foo()
// The following will print `3`, three times...
for (var i = 0; i < 3; i++) {
result[i]()
}
Финальные очки:
- Всякий раз, когда функция объявляется в JavaScript, создается замыкание.
- Возврат
function
изнутри другой функции является классическим примером замыкания, поскольку состояние внутри внешней функции неявно доступно для возвращенной внутренней функции, даже после того, как внешняя функция завершила выполнение. - Всякий раз, когда вы используете
eval()
внутри функции, используется замыкание. В тексте выeval
можете ссылаться на локальные переменные функции, а в нестрогом режиме вы даже можете создавать новые локальные переменные с помощьюeval('var foo = …')
. - Когда вы используете
new Function(…)
( конструктор Function ) внутри функции, она не закрывается в своей лексической среде: вместо этого она закрывается в глобальном контексте. Новая функция не может ссылаться на локальные переменные внешней функции. - Закрытие в JavaScript похоже на сохранение ссылки ( НЕ копии) на область действия в точке объявления функции, которая, в свою очередь, сохраняет ссылку на ее внешнюю область и т. Д. Вплоть до глобального объекта в верхней части цепь прицела.
- Закрытие создается, когда функция объявлена; это закрытие используется для настройки контекста выполнения при вызове функции.
- Новый набор локальных переменных создается каждый раз, когда вызывается функция.
связи
- Дуглас Крокфорд смоделировал частные атрибуты и частные методы для объекта, используя замыкания.
- Отличное объяснение того, как замыкания могут вызвать утечки памяти в IE, если вы не будете осторожны.
Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure 'remembers' the environment in which it was created.
от developer.mozilla.org/en-US/docs/Web/JavaScript/Closureslet i = 0
вместо var i = 0
примера 5, он testList()
напечатает то, что вы хотели изначально. TLDR
Замыкание - это связь между функцией и ее внешней лексической (то есть, как написано) средой, так что идентификаторы (переменные, параметры, объявления функций и т. Д.), Определенные в этой среде, видны изнутри функции, независимо от того, когда или из где функция вызывается.
подробности
В терминологии спецификации ECMAScript можно сказать, что замыкание реализуется посредством [[Environment]]
ссылки на каждый объект-функцию, которая указывает на лексическую среду, в которой определена функция.
Когда функция вызывается с помощью внутреннего [[Call]]
метода, то [[Environment]]
ссылка на функцию-объект копируется в внешней среды ссылкой на записи среды вновь созданного контекста выполнения (кадра стека).
В следующем примере функция f
закрывается над лексической средой глобального контекста выполнения:
function f() {}
В следующем примере функция h
закрывается над лексической средой функции g
, которая, в свою очередь, закрывается над лексической средой глобального контекста выполнения.
function g() {
function h() {}
}
Если внутренняя функция возвращается внешней, то внешняя лексическая среда будет сохраняться после возвращения внешней функции. Это потому, что внешняя лексическая среда должна быть доступна, если внутренняя функция в конечном счете вызывается.
В следующем примере функция j
закрывается над лексическим окружением функции i
, что означает, что переменная x
видна изнутри функции j
, еще долго после того, как функция i
завершила выполнение:
function i() {
var x = 'mochacchino'
return function j() {
console.log('Printing the value of x, from within function j: ', x)
}
}
const k = i()
setTimeout(k, 500) // invoke k (which is j) after 500ms
В заключение, переменные во внешней лексической среде сами по себе доступны, а не копируются.
function l() {
var y = 'vanilla';
return {
setY: function(value) {
y = value;
},
logY: function(value) {
console.log('The value of y is: ', y);
}
}
}
const o = l()
o.logY() // The value of y is: vanilla
o.setY('chocolate')
o.logY() // The value of y is: chocolate
Цепочка лексических сред, связанная между контекстами выполнения посредством ссылок на внешнюю среду, образует цепочку областей действия и определяет идентификаторы, видимые из любой данной функции.
Обратите внимание, что в попытке улучшить ясность и точность, этот ответ был существенно изменен по сравнению с оригиналом.
console.log
таким образом. Если кому-то еще интересно, есть еще: developer.mozilla.org/en-US/docs/DOM/…var
). Замыкания трудно объяснить, потому что они используются для того, чтобы заставить поведение работать так, как все интуитивно ожидают, что оно все равно будет работать. Я считаю, что лучший способ объяснить их (и способ, которым я узнал, что они делают), это представить ситуацию без них:
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 поддерживает ссылку на внешнюю лексическую среду. Лексическая среда - это карта всех имен (например, переменных, параметров) в области видимости с их значениями.
Таким образом, всякий раз, когда вы видите function
ключевое слово, код внутри этой функции имеет доступ к переменным, объявленным вне функции.
function foo(x) {
var tmp = 3;
function bar(y) {
console.log(x + y + (++tmp)); // will log 16
}
bar(10);
}
foo(2);
Это будет регистрироваться, 16
потому что функция bar
закрывает параметр x
и переменную tmp
, которые существуют в лексическом окружении внешней функции foo
.
Функция bar
вместе с ее связью с лексическим окружением функции foo
является замыканием.
Функция не должна возвращаться , чтобы создать замыкание. Просто в силу своего объявления каждая функция закрывается в окружающей лексической среде, образуя замыкание.
function foo(x) {
var tmp = 3;
return function (y) {
console.log(x + y + (++tmp)); // will also log 16
}
}
var bar = foo(2);
bar(10); // 16
bar(10); // 17
Вышеприведенная функция также регистрирует 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, создается новый контекст выполнения ec
. Вместе с аргументами функции и целевым объектом этот контекст выполнения также получает ссылку на лексическую среду вызывающего контекста выполнения, то есть переменные, объявленные во внешней лексической среде (в вышеприведенном примере оба a
и b
) доступны из ec
.
Каждая функция создает замыкание, потому что каждая функция имеет ссылку на внешнее лексическое окружение.
Обратите внимание, что сами переменные видны из замыкания, а не из копий.
delete
не работает. Тем не менее, лексическая среда, которую функция будет переносить как [[Scope]] (и, в конечном счете, использовать в качестве основы для своей собственной лексической среды при вызове), определяется при выполнении оператора, определяющего функцию. Это означает , что функция является закрытие над содержимым ЦЕЛЫХ исполняющего объема, независимо от того, какие значения он на самом деле относится к и избегает сферы ли. Пожалуйста, посмотрите разделы 13.2 и 10 в спецификацииЗакрытие - это когда внутренняя функция имеет доступ к переменным в своей внешней функции. Это, вероятно, самое простое однострочное объяснение, которое вы можете получить для замыканий.
Это попытка прояснить несколько (возможных) недоразумений о замыканиях, которые появляются в некоторых других ответах.
- Закрытие создается не только когда вы возвращаете внутреннюю функцию. Фактически, функция включения вообще не должна возвращаться для создания своего замыкания. Вместо этого вы можете назначить свою внутреннюю функцию переменной во внешней области видимости или передать ее в качестве аргумента другой функции, где она может быть вызвана немедленно или в любое время позже. Следовательно, замыкание закрывающей функции, вероятно, создается, как только вызывается включающая функция, поскольку любая внутренняя функция имеет доступ к этому замыканию всякий раз, когда вызывается внутренняя функция, до или после возврата закрывающей функции.
- Закрытие не ссылается на копию старых значений переменных в своей области видимости. Сами переменные являются частью замыкания, и поэтому значение, видимое при обращении к одной из этих переменных, является самым последним значением на момент обращения к нему. Вот почему внутренние функции, созданные внутри циклов, могут быть хитрыми, поскольку каждая из них имеет доступ к одним и тем же внешним переменным, а не захватывает копию переменных во время создания или вызова функции.
- «Переменные» в замыкании включают любые именованные функции, объявленные внутри функции. Они также включают аргументы функции. Замыкание также имеет доступ к переменным содержащего замыкание, вплоть до глобальной области видимости.
- Замыкания используют память, но они не вызывают утечек памяти, поскольку JavaScript сам по себе очищает свои собственные циклические структуры, на которые нет ссылок. Утечки памяти в Internet Explorer, связанные с замыканиями, создаются, когда ему не удается отключить значения атрибутов DOM, которые ссылаются на замыкания, тем самым поддерживая ссылки на возможно круговые структуры.
Пример для первого пункта по dlaliberte:
Закрытие создается не только когда вы возвращаете внутреннюю функцию. Фактически, функция включения не должна возвращаться вообще. Вместо этого вы можете назначить свою внутреннюю функцию переменной во внешней области видимости или передать ее в качестве аргумента другой функции, где она может быть использована немедленно. Следовательно, замыкание закрывающей функции, вероятно, уже существует во время вызова закрывающей функции, поскольку любая внутренняя функция имеет доступ к ней, как только она вызывается.
var i;
function foo(x) {
var tmp = 3;
i = function (y) {
console.log(x + y + (++tmp));
}
}
foo(2);
i(3);
Можете ли вы объяснить закрытие 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 # вопрос
Некоторое время назад я написал сообщение в блоге, объясняющее закрытие. Вот то, что я сказал о замыканиях с точки зрения того, почему вы хотите его.
Замыкания - это способ дать функции иметь постоянные закрытые переменные, то есть переменные, о которых знает только одна функция, где она может отслеживать информацию за предыдущие периоды, когда она выполнялась.
В этом смысле они позволяют функции немного походить на объект с закрытыми атрибутами.
Полный пост:
devError = emailError("devinrhode2@googmail.com", errorString)
а затем иметь свою собственную версию общей функции emailError? Функции JavaScript могут получить доступ к своим:
- аргументы
- Локальные (то есть их локальные переменные и локальные функции)
- Среда, которая включает в себя:
- глобалы, включая DOM
- что-нибудь во внешних функциях
Если функция обращается к своей среде, то функция является замыканием.
Обратите внимание, что внешние функции не требуются, хотя они предлагают преимущества, которые я здесь не обсуждаю. Получая доступ к данным в своей среде, замыкание поддерживает эти данные живыми. В подклассе внешних / внутренних функций внешняя функция может создавать локальные данные и в конечном итоге выходить, и, тем не менее, если какая-либо внутренняя функция (и) выживает после выхода из внешней функции, то внутренняя функция (и) сохраняет локальные данные внешней функции. живой.
Пример замыкания, использующего глобальную среду:
Представьте, что события кнопок «Переполнение стека» «Голосование вверх» и «Голосование вниз» реализованы в виде замыканий, voiceUp_click и voiceDown_click, которые имеют доступ к внешним переменным isVotedUp и isVotedDown, которые определены глобально. (Для простоты я имею в виду кнопки «Голосовать» в StackOverflow, а не массив кнопок «Голосовать».)
Когда пользователь нажимает кнопку 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
}
Все четыре из этих функций являются замыканиями, поскольку все они имеют доступ к своей среде.
ПРЕДИСЛОВИЕ: этот ответ был написан, когда вопрос был:
Как сказал старый Альберт: «Если вы не можете объяснить это шестилетнему, вы действительно сами этого не понимаете». Ну, я попытался объяснить закрытие 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();
И хотя взрослые знали о настоящих принцессах, они никогда не поверили бы в единорогов или драконов, потому что никогда их не видели. Взрослые говорили, что они существуют только в воображении маленькой девочки.
Но мы знаем настоящую правду; что маленькая девочка с принцессой внутри ...
... действительно принцесса с маленькой девочкой внутри.
story()
функции, которая является единственным интерфейсом, который littleGirl
экземпляр открывает в мире магии. story
закрытие, но если бы код был var story = function() {}; return story;
тогда, littleGirl
было бы закрытие. По крайней мере, такое впечатление, которое я получаю от использования MDN «закрытых» методов с замыканиями : «Эти три публичные функции являются замыканиями, которые используют одну и ту же среду». story
это замыкание, ссылающееся на среду, предоставляемую в рамках princess
. princess
также является другим подразумеваемым замыканием, т. е. princess
и littleGirl
разделяют любую ссылку на parents
массив, который будет существовать обратно в среде / области действия, где littleGirl
существует и princess
определено. princess
чем написано. К сожалению, эта история сейчас немного неуместна в этой теме. Первоначально вопрос задавался для «объяснения закрытия JavaScript 5-летнему»; мой ответ был единственным, который даже пытался это сделать. Я не сомневаюсь, что это с треском провалилось бы, но, по крайней мере, у этого ответа мог быть шанс заинтересовать пятилетнего ребенка. Ты переспал и пригласил Дэна. Вы говорите Дэну принести один контроллер 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, чтобы объяснить, как работают замыкания. Что такое закрытие?
Вот один из примеров:
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
В информатике замыкание - это функция вместе со средой ссылки для нелокальных имен (свободных переменных) этой функции.
Технически, в 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, не столь типичен.
О развитии детства: от 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
функции делает это здесь.
Я знаю, что уже есть множество решений, но я думаю, что этот небольшой и простой скрипт может быть полезен для демонстрации концепции:
// 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
После вызова функции она выходит из области видимости. Если эта функция содержит что-то вроде функции обратного вызова, то эта функция обратного вызова все еще находится в области видимости. Если функция обратного вызова ссылается на некоторую локальную переменную в непосредственной среде родительской функции, то естественно, что вы ожидаете, что эта переменная будет недоступна для функции обратного вызова и вернет undefined.
Замыкания гарантируют, что любое свойство, на которое ссылается функция обратного вызова, доступно для использования этой функцией, даже если ее родительская функция, возможно, вышла из области видимости.
Из личного поста в блоге :
По умолчанию JavaScript знает два типа областей: глобальную и локальную.
var a = 1;
function b(x) {
var c = 2;
return x * c;
}
В приведенном выше коде переменная a и функция b доступны в любом месте кода (то есть глобально). Переменная c
доступна только в области b
действия функции (то есть локальной). Большинство разработчиков программного обеспечения не будут довольны отсутствием гибкости, особенно в больших программах.
Закрытия JavaScript помогают решить эту проблему, связывая функцию с контекстом:
function a(x) {
return function b(y) {
return x + y;
}
}
Здесь функция a
возвращает вызванную функцию b
. Поскольку b
он определен внутри a
, он автоматически имеет доступ ко всему a
, что определено x
в этом примере. Вот почему b
можно вернуть x
+ y
без объявления x
.
var c = a(3);
Переменной c
присваивается результат вызова с параметром 3. То есть экземпляр функции, b
где x
= 3. Другими словами, c
теперь функция эквивалентна:
var c = function b(y) {
return 3 + y;
}
Функция b
запоминает, что x
= 3 в своем контексте. Следовательно:
var d = c(4);
присвоит значение 3 + 4 d
, то есть 7.
Примечание : Если кто-то изменяет значение x
(скажем, x
= 22) после создания экземпляра функции b
, это также будет отражено b
. Следовательно, более поздний вызов c
(4) вернет 22 + 4, то есть 26.
Замыкания также можно использовать для ограничения области действия переменных и методов, объявленных глобально:
(function () {
var f = "Some message";
alert(f);
})();
Выше приведено закрытие, в котором функция не имеет имени, аргумента и вызывается немедленно. Выделенный код, который объявляет глобальную переменную f
, ограничивает область действия f
замыканием.
Теперь есть распространенное предупреждение JavaScript, в котором могут помочь замыкания:
var a = new Array();
for (var i=0; i<2; i++) {
a[i]= function(x) { return x + i ; }
}
Исходя из вышесказанного, большинство будет предполагать, что массив a
будет инициализирован следующим образом:
a[0] = function (x) { return x + 0 ; }
a[1] = function (x) { return x + 1 ; }
a[2] = function (x) { return x + 2 ; }
В действительности, это то, как инициализируется a, поскольку последнее значение i
в контексте равно 2:
a[0] = function (x) { return x + 2 ; }
a[1] = function (x) { return x + 2 ; }
a[2] = function (x) { return x + 2 ; }
Решение:
var a = new Array();
for (var i=0; i<2; i++) {
a[i]= function(tmp) {
return function (x) { return x + tmp ; }
} (i);
}
Аргумент / переменная tmp
содержит локальную копию изменяющегося значения i
при создании экземпляров функции.
Функция в 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();
Замыкания - это средство, с помощью которого внутренние функции могут ссылаться на переменные, присутствующие в их внешней окружающей функции после того, как их родительские функции уже завершены.
// A function that generates a new function for adding numbers.
function addGenerator( num ) {
// Return a simple function for adding two numbers
// with the first number borrowed from the generator
return function( toAdd ) {
return num + toAdd
};
}
// addFive now contains a function that takes one argument,
// adds five to it, and returns the resulting number.
var addFive = addGenerator( 5 );
// We can see here that the result of the addFive function is 9,
// when passed an argument of 4.
alert( addFive( 4 ) == 9 );
Соломенный Человек
Мне нужно знать, сколько раз была нажата кнопка, и что-то делать при каждом третьем нажатии ...
Достаточно очевидное решение
// 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)
Хорошо, разговаривая с 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, чтобы поиграть с ним.
Я бы просто указал им на страницу Mozilla Closures . Это лучшее, самое краткое и простое объяснение основ закрытия и практического использования, которое я нашел. Настоятельно рекомендуется всем, кто изучает JavaScript.
И да, я бы даже порекомендовал это 6-летнему ребенку - если 6-летний изучает замыкания, то логично, что они готовы понять краткое и простое объяснение, приведенное в статье.
Если вы хотите объяснить это шестилетнему ребенку, вы должны найти что-то намного более простое и без кода.
Просто скажите ребенку, что он «открыт», что говорит о том, что он может иметь отношения с некоторыми другими, своими друзьями. В какой-то момент он определил друзей (мы можем знать имена его друзей), и это закрытие. Если вы сфотографируете его и его друзей, то он «закрыт» относительно его способности дружить. Но в целом он "открытый". В течение всей его жизни у него будет много разных друзей. Один из этих наборов является закрытием.
Я нашел очень четкую главу 8 раздела 6 «Замыкания» JavaScript: «Полное руководство » Дэвида Фланагана, 6-е издание, O'Reilly, 2011. Я попробую перефразировать.
Когда вызывается функция, создается новый объект для хранения локальных переменных для этого вызова.
Область действия функции зависит от ее местоположения объявления, а не от места выполнения.
Теперь предположим, что внутренняя функция объявлена внутри внешней функции и ссылается на переменные этой внешней функции. Далее предположим, что внешняя функция возвращает внутреннюю функцию как функцию. Теперь есть внешняя ссылка на любые значения, находящиеся в области видимости внутренней функции (которая, по нашим предположениям, включает значения из внешней функции).
JavaScript сохранит эти значения, так как они остались в области текущего выполнения благодаря передаче из завершенной внешней функции. Все функции являются замыканиями, но интересующие замыкания являются внутренними функциями, которые в нашем предполагаемом сценарии сохраняют значения внешних функций в своей «оболочке» (я надеюсь, что я правильно использую язык здесь), когда они (внутренние функции) возвращаются от внешних функций. Я знаю, что это не соответствует требованиям шестилетнего возраста, но, надеюсь, это все еще полезно.
Функция выполняется в области объекта / функции, в которой она определена. Упомянутая функция может получить доступ к переменным, определенным в объекте / функции, где она была определена во время выполнения.
И просто воспринимайте это буквально .... как написано в коде: P
Для шестилетнего?
Вы и ваша семья живете в мифическом городе Анн Виль. У вас есть друг, который живет по соседству, поэтому вы звоните им и просите их выйти и поиграть. Вы набираете:
000001 (домашний джем)
Через месяц вы и ваша семья переезжаете из Энн-Виль в следующий город, но вы и ваш друг все еще поддерживаете связь, поэтому теперь вам нужно набрать код города, в котором живет ваш друг, прежде чем набирать их ' правильный номер:
001 000001 (annVille.jamiesHouse)
Через год после этого ваши родители переезжают в совершенно новую страну, но вы и ваш друг по-прежнему поддерживаете связь, поэтому после того, как ваши родители попросили вас сделать международные звонки, вы набираете:
01 001 000001 (myOldCountry.annVille.jamiesHouse)
Странно, однако, что после переезда в новую страну вы и ваша семья случайно переехали в новый город под названием Анн Виль ... и вы случайно подружились с каким-то новым человеком по имени Джейми ... Вы даете им вызов...
000001 (домашний джем)
Пугающий...
Настолько жуткий, что рассказываешь об этом Джейми из твоей старой страны ... Ты смеешься над этим. И вот однажды вы и ваша семья отправляетесь на отдых в старую страну. Вы посещаете свой старый город (Анн Виль) и идете в гости к Джейми ...
- "Правда? Еще один Джейми? В Энн Виль? В твоей новой стране !!?"
- "Да ... Давайте назовем их ..."
02 001 000001 (myNewCountry.annVille.jamiesHouse)
Мнения?
Более того, у меня есть множество вопросов о терпении современного шестилетнего ...
Ответ для шестилетнего ребенка (при условии, что он знает, что такое функция, что такое переменная и какие данные):
Функции могут возвращать данные. Одним из типов данных, которые вы можете вернуть из функции, является другая функция. Когда эта новая функция возвращается, все переменные и аргументы, используемые в функции, которая ее создала, не исчезают. Вместо этого родительская функция «закрывается». Другими словами, ничто не может заглянуть внутрь него и увидеть переменные, которые оно использовало, кроме функции, которую оно возвратило. Эта новая функция имеет особую возможность заглянуть внутрь функции, которая ее создала, и просмотреть данные внутри нее.
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
Другой действительно простой способ объяснить это с точки зрения объема:
Каждый раз, когда вы создаете меньшую область внутри большей области, меньшая область всегда сможет увидеть, что находится в большей области.
Я уверен, что Эйнштейн не сказал этого, ожидая, что мы выберем какой-нибудь эзотерический мозговой штурм и столкнем шестилетних с тщетными попытками свести их с ума (и что еще хуже для них - скучно ) вещи для их детских умов :) Если бы мне было шесть лет, я бы не хотел иметь таких родителей или не дружил бы с такими скучными филантропами, извините :)
В любом случае, для детей закрытие - это просто объятие , я полагаю, каким бы способом вы ни пытались объяснить :) А когда вы обнимаете своего друга, вы оба делитесь чем-то, что у вас, ребята, сейчас. Это обряд, когда ты обнимаешь кого-то, ты показываешь ей доверие и готовность позволить ей делать с тобой много вещей, которые ты не позволяешь и скрываешь от других. Это акт дружбы :).
Я действительно не знаю, как объяснить это детям 5-6 лет. Я также не думаю, что они оценят любые фрагменты кода JavaScript, такие как:
function Baby(){
this.iTrustYou = true;
}
Baby.prototype.hug = function (baby) {
var smiles = 0;
if (baby.iTrustYou) {
return function() {
smiles++;
alert(smiles);
};
}
};
var
arman = new Baby("Arman"),
morgan = new Baby("Morgana");
var hug = arman.hug(morgan);
hug();
hug();
Только для детей:
Закрытие это объятие
Ошибка является муха
ПОЦЕЛУЙ является обниматься! :)
Хорошо, 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");
closure
кода, в отличие от того,Object Literal
который повторно использует себя и точно так же снижает накладные расходы, но требует на 100% меньше кода для переноса.