Как работают JavaScript-закрытия?

Как бы вы объяснили JavaScript-замыкания кому-то, кто знает концепции, из которых они состоят (например, функции, переменные и т. П.), Но не понимает сами замыкания?

Я видел пример схемы, приведенный в Википедии, но, к сожалению, это не помогло.

21.09.2008 14:12:07
Моя проблема с этими и многими ответами состоит в том, что они подходят к нему с абстрактной, теоретической точки зрения, а не начинают просто объяснять, почему замыкания необходимы в Javascript и в практических ситуациях, в которых вы их используете. В итоге вы получаете статью, которую вы должны пролистать, все время думая: «Но почему?». Я бы просто начал с: замыкания - отличный способ справиться со следующими двумя реалиями JavaScript: a. область видимости находится на уровне функций, а не на уровне блоков и, б. большая часть того, что вы делаете на практике в JavaScript, является асинхронной / управляемой событиями.
Jeremy Burton 8.03.2013 17:22:59
@Redsandro С одной стороны, это значительно облегчает написание кода, управляемого событиями. Я мог бы запустить функцию при загрузке страницы, чтобы определить особенности HTML или доступных функций. Я могу определить и установить обработчик в этой функции и иметь всю эту контекстную информацию доступной каждый раз, когда обработчик вызывается без необходимости повторного запроса. Решите проблему один раз, повторно используйте ее на каждой странице, где требуется этот обработчик, с уменьшенными накладными расходами при повторном вызове обработчика. Вы когда-нибудь видели, чтобы одни и те же данные дважды отображались на языке, в котором их нет? Закрытия делают намного проще избежать подобных вещей.
Erik Reppen 26.06.2013 17:02:16
@ Эрик Реппен спасибо за ответ. На самом деле, мне было любопытно узнать о преимуществах этого трудно читаемого closureкода, в отличие от того, Object Literalкоторый повторно использует себя и точно так же снижает накладные расходы, но требует на 100% меньше кода для переноса.
Redsandro 26.06.2013 17:38:22
Для программистов на Java короткий ответ - это функция, эквивалентная внутреннему классу. Внутренний класс также содержит неявный указатель на экземпляр внешнего класса и используется для почти той же цели (то есть для создания обработчиков событий).
Boris van Schooten 19.06.2014 10:04:21
Я нашел этот практический пример очень полезным: youtube.com/watch?v=w1s9PgtEoJs
Abhi 6.07.2016 17:33:55
30 ОТВЕТОВ
РЕШЕНИЕ

Закрытие представляет собой сочетание:

  1. Функция и
  2. Ссылка на внешнюю область действия этой функции (лексическая среда)

Лексическая среда является частью каждого контекста выполнения (стекового фрейма) и представляет собой карту между идентификаторами (то есть именами локальных переменных) и значениями.

Каждая функция в 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 похоже на сохранение ссылки ( НЕ копии) на область действия в точке объявления функции, которая, в свою очередь, сохраняет ссылку на ее внешнюю область и т. Д. Вплоть до глобального объекта в верхней части цепь прицела.
  • Закрытие создается, когда функция объявлена; это закрытие используется для настройки контекста выполнения при вызове функции.
  • Новый набор локальных переменных создается каждый раз, когда вызывается функция.

связи

7302
19.04.2020 16:02:05
Это звучит хорошо: «Закрытие в JavaScript похоже на сохранение копии всех локальных переменных, как это было при выходе из функции». Но это вводит в заблуждение по нескольким причинам. (1) Вызов функции не должен выходить, чтобы создать замыкание. (2) Это не копия значений локальных переменных, а сами переменные. (3) Там не сказано, кто имеет доступ к этим переменным.
dlaliberte 11.02.2013 18:20:22
В примере 5 показана «ошибка», когда код работает не так, как задумано. Но это не показывает, как это исправить. Этот другой ответ показывает способ сделать это.
Matt 24.06.2013 19:12:21
Мне нравится, как этот пост начинается большими жирными буквами с надписью «Закрытия не являются магическими» и заканчивается первым примером: «Волшебство состоит в том, что в JavaScript ссылка на функцию также имеет секретную ссылку на замыкание, в котором она была создана».
Andrew Macheret 25.09.2014 02:30:17
Пример №3 - смешивание замыканий с подъемом javascript. Теперь я думаю, что объяснить только замыкания достаточно сложно, не привлекая поведение подъема. Это помогло мне больше всего: 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/Closures
caramba 30.09.2015 08:50:10
ECMAScript 6 может изменить что-то в этой замечательной статье о закрытии. Например, если вы используете let i = 0вместо var i = 0примера 5, он testList()напечатает то, что вы хотели изначально.
Nier 10.05.2016 00:51:24

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

Цепочка лексических сред, связанная между контекстами выполнения посредством ссылок на внешнюю среду, образует цепочку областей действия и определяет идентификаторы, видимые из любой данной функции.

Обратите внимание, что в попытке улучшить ясность и точность, этот ответ был существенно изменен по сравнению с оригиналом.

375
17.02.2020 19:56:12
Ничего себе, никогда не знал, что вы можете использовать строковые замены console.logтаким образом. Если кому-то еще интересно, есть еще: developer.mozilla.org/en-US/docs/DOM/…
Flash 4.01.2013 02:59:32
Переменные, которые находятся в списке параметров функции, также являются частью замыкания (например, не только var).
Thomas Eding 18.03.2015 03:38:09
Замыкания больше похожи на объекты, классы и т. Д. Не уверен, почему многие не сравнивают эти два - нам, новичкам, будет легче учиться!
almaruf 23.05.2019 12:37:10

Замыкания трудно объяснить, потому что они используются для того, чтобы заставить поведение работать так, как все интуитивно ожидают, что оно все равно будет работать. Я считаю, что лучший способ объяснить их (и способ, которым я узнал, что они делают), это представить ситуацию без них:

    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.

490
17.03.2017 08:51:15
Это именно тот вид , например , что вводит в заблуждение многих людей, думая , что это ценности , которые используются в возвращаемой функции, а не сама изменяемая переменная. Если бы он был изменен на «return x + = y», или, что еще лучше, и ту, и другую функцию «x * = y», то было бы ясно, что ничего не копируется. Если вы привыкли складывать кадры, представьте себе, что вместо этого используйте кадры кучи, которые могут продолжать существовать после возврата из функции.
Matt 21.06.2013 12:36:49
@ Мэтт, я не согласен. Пример не должен исчерпывающе документировать все свойства. Он предназначен для упрощения и иллюстрирует существенную особенность концепции. ФП попросил простое объяснение («для шестилетнего ребенка»). Возьмите принятый ответ: он не дает точного объяснения именно потому, что пытается быть исчерпывающим. (Я согласен с вами, что важным свойством JavaScript является то, что привязка осуществляется по ссылке, а не по значению… но, опять же, успешное объяснение - это то, которое сводится к минимуму.)
Konrad Rudolph 21.06.2013 14:30:42
@KonradRudolph Мне нравится стиль и краткость вашего примера. Я просто рекомендую немного изменить его, чтобы заключительная часть, «Единственное решение ...», стала правдой. В настоящее время существует на самом деле другое, более простое решение для вашего сценария, который не не соответствует яваскрипту продолжений, и делает соответствуют распространенному заблуждению о том , что продолжения есть. Таким образом, пример в его нынешнем виде опасен. Это не связано с исчерпывающим списком свойств, оно связано с пониманием значения x в возвращаемой функции, что, в конце концов, является основным пунктом.
Matt 21.06.2013 16:15:08
@Matt Хм, я не уверен, что полностью вас понимаю, но начинаю понимать, что у вас может быть правильное мнение. Так как комментарии слишком короткие, не могли бы вы объяснить, что вы имеете в виду в gist / pastie или в чате? Спасибо.
Konrad Rudolph 22.06.2013 14:35:15
@KonradRudolph Я думаю, что я не был ясно о цели х + = у. Цель состояла в том, чтобы просто показать, что повторные вызовы возвращаемой функции продолжают использовать одну и ту же переменную x (в отличие от одного и того же значения , которое, как думают люди, «вставляется» при создании функции). Это похоже на первые два оповещения в вашей скрипке. Целью дополнительной функции x * = y было бы показать, что все возвращаемые функции имеют один и тот же x.
Matt 24.06.2013 20:24:55

Каждая функция в 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.

Каждая функция создает замыкание, потому что каждая функция имеет ссылку на внешнее лексическое окружение.

Обратите внимание, что сами переменные видны из замыкания, а не из копий.

3974
28.03.2020 05:44:05
@feeela: Да, каждая функция JS создает замыкание. Переменные, на которые нет ссылок, вероятно, будут иметь право на сборку мусора в современных механизмах JS, но это не меняет того факта, что при создании контекста выполнения этот контекст имеет ссылку на включающий контекст выполнения и его переменные, и эта функция является объектом, который потенциально может быть перемещен в другую переменную область, сохраняя при этом эту исходную ссылку. Это закрытие.
user2437417 19.08.2013 01:31:25
@ Али Я только что обнаружил, что jsFiddle, который я предоставил, на самом деле ничего не доказывает, так как deleteне работает. Тем не менее, лексическая среда, которую функция будет переносить как [[Scope]] (и, в конечном счете, использовать в качестве основы для своей собственной лексической среды при вызове), определяется при выполнении оператора, определяющего функцию. Это означает , что функция является закрытие над содержимым ЦЕЛЫХ исполняющего объема, независимо от того, какие значения он на самом деле относится к и избегает сферы ли. Пожалуйста, посмотрите разделы 13.2 и 10 в спецификации
Asad Saeeduddin 20.08.2013 17:51:46
Это был хороший ответ, пока он не попытался объяснить примитивные типы и ссылки. Это совершенно неправильно и говорит о копируемых литералах, которые на самом деле не имеют ничего общего.
Ry-♦ 4.07.2014 14:53:35
Замыкания являются ответом JavaScript на объектно-ориентированное программирование на основе классов. JS не основан на классах, поэтому нужно было найти другой способ реализации некоторых вещей, которые не могли бы быть реализованы иначе.
Bartłomiej Zalewski 18.09.2014 10:45:56
это должен быть принятый ответ. Волшебство никогда не происходит во внутренней функции. Это происходит при назначении внешней функции переменной. Это создает новый контекст выполнения для внутренней функции, так что «приватная переменная» может накапливаться. Конечно, это возможно, поскольку переменная, которой назначена внешняя функция, поддерживает контекст. Первый ответ просто усложнит ситуацию, не объясняя, что там на самом деле происходит.
Albert Gao 18.08.2016 00:26:28

Закрытие - это когда внутренняя функция имеет доступ к переменным в своей внешней функции. Это, вероятно, самое простое однострочное объяснение, которое вы можете получить для замыканий.

87
24.12.2012 11:10:56
Это только половина объяснения. В отношении замыканий важно отметить, что если на внутреннюю функцию все еще ссылаются после выхода из внешней функции, старые значения внешней функции все еще доступны для внутренней.
pcorcoran 21.09.2008 22:29:13
На самом деле, это не старые значения внешней функции, которые доступны для внутренней функции, а старые переменные , которые могут иметь новые значения, если какая-то функция смогла их изменить.
dlaliberte 16.08.2012 02:39:42

Это попытка прояснить несколько (возможных) недоразумений о замыканиях, которые появляются в некоторых других ответах.

  • Закрытие создается не только когда вы возвращаете внутреннюю функцию. Фактически, функция включения вообще не должна возвращаться для создания своего замыкания. Вместо этого вы можете назначить свою внутреннюю функцию переменной во внешней области видимости или передать ее в качестве аргумента другой функции, где она может быть вызвана немедленно или в любое время позже. Следовательно, замыкание закрывающей функции, вероятно, создается, как только вызывается включающая функция, поскольку любая внутренняя функция имеет доступ к этому замыканию всякий раз, когда вызывается внутренняя функция, до или после возврата закрывающей функции.
  • Закрытие не ссылается на копию старых значений переменных в своей области видимости. Сами переменные являются частью замыкания, и поэтому значение, видимое при обращении к одной из этих переменных, является самым последним значением на момент обращения к нему. Вот почему внутренние функции, созданные внутри циклов, могут быть хитрыми, поскольку каждая из них имеет доступ к одним и тем же внешним переменным, а не захватывает копию переменных во время создания или вызова функции.
  • «Переменные» в замыкании включают любые именованные функции, объявленные внутри функции. Они также включают аргументы функции. Замыкание также имеет доступ к переменным содержащего замыкание, вплоть до глобальной области видимости.
  • Замыкания используют память, но они не вызывают утечек памяти, поскольку JavaScript сам по себе очищает свои собственные циклические структуры, на которые нет ссылок. Утечки памяти в Internet Explorer, связанные с замыканиями, создаются, когда ему не удается отключить значения атрибутов DOM, которые ссылаются на замыкания, тем самым поддерживая ссылки на возможно круговые структуры.
365
5.05.2016 15:00:49
Джеймс, я сказал, что замыкание «вероятно» создается во время вызова включающей функции, потому что вполне вероятно, что реализация может отложить создание замыкания до некоторого времени спустя, когда решит, что замыкание абсолютно необходимо. Если в функции включения не определена внутренняя функция, закрытие не требуется. Поэтому, возможно, он может подождать, пока будет создана первая внутренняя функция, чтобы затем создать замыкание из контекста вызова включающей функции.
dlaliberte 10.07.2012 17:27:03
@ Beetroot-Beetroot Предположим, у нас есть внутренняя функция, которая передается другой функции, где она используется до возврата внешней функции, и предположим, что мы также возвращаем ту же внутреннюю функцию из внешней функции. В обоих случаях это одна и та же функция, но вы говорите, что перед возвратом внешней функции внутренняя функция «привязывается» к стеку вызовов, тогда как после возврата внутренняя функция внезапно связывается с замыканием. Он ведет себя одинаково в обоих случаях; семантика идентична, так вы не просто говорите о деталях реализации?
dlaliberte 16.10.2012 16:06:13
@ Beetroot-Beetroot, спасибо за ваш отзыв, и я рад, что вы заставили вас задуматься. Я до сих пор не вижу какой-либо семантической разницы между живым контекстом внешней функции и тем же контекстом, когда она становится закрытием, когда функция возвращается (если я понимаю ваше определение). Внутренняя функция не волнует. Сборку мусора не волнует, так как внутренняя функция в любом случае поддерживает ссылку на контекст / закрытие, и вызывающая сторона внешней функции просто отбрасывает свою ссылку на контекст вызова. Но это сбивает с толку людей, и, возможно, лучше просто назвать это контекстом вызова.
dlaliberte 18.10.2012 00:26:54
Эту статью трудно читать, но я думаю, что она действительно поддерживает то, что я говорю. В нем говорится: «Закрытие формируется путем возврата объекта функции [...] или путем непосредственного присвоения ссылки на такой объект функции, например, глобальной переменной». Я не имею в виду, что GC не имеет значения. Скорее, из-за GC и потому, что внутренняя функция присоединена к контексту вызова внешней функции (или [[scope]], как говорится в статье), тогда не имеет значения, возвращается ли вызов внешней функции, потому что это связывание с внутренним функция это важная вещь.
dlaliberte 21.10.2012 01:49:59
Отличный ответ! Следует добавить, что все функции закрываются по всему содержимому выполняемой области действия, в которой они определены. Неважно, ссылаются ли они на некоторые или ни на одну из переменных из родительской области: ссылка на лексическое окружение родительской области безоговорочно сохраняется как [[Scope]]. Это видно из раздела о создании функций в спецификации ECMA.
Asad Saeeduddin 21.08.2013 13:36:14

Пример для первого пункта по dlaliberte:

Закрытие создается не только когда вы возвращаете внутреннюю функцию. Фактически, функция включения не должна возвращаться вообще. Вместо этого вы можете назначить свою внутреннюю функцию переменной во внешней области видимости или передать ее в качестве аргумента другой функции, где она может быть использована немедленно. Следовательно, замыкание закрывающей функции, вероятно, уже существует во время вызова закрывающей функции, поскольку любая внутренняя функция имеет доступ к ней, как только она вызывается.

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);
91
16.01.2016 02:39:35
Небольшое уточнение о возможной двусмысленности. Когда я сказал: «На самом деле, функция включения не должна возвращаться вообще». Я не имел в виду «не возвращать значение», но «все еще активен». Таким образом, пример не показывает этот аспект, хотя он показывает другой способ передачи внутренней функции во внешнюю область. Основной момент, который я пытался сделать, касается времени создания замыкания (для включающей функции), так как некоторые люди, кажется, думают, что это происходит, когда возвращающая включающая функция. Другой пример необходим, чтобы показать, что замыкание создается при вызове функции .
dlaliberte 21.07.2011 14:03:12

Можете ли вы объяснить закрытие 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 # вопрос

199
23.05.2017 11:47:32
Код является «правильным», как пример замыкания, даже если он не затрагивает часть комментария об использовании замыкания после возврата externalFunction. Так что это не отличный пример. Есть много других способов использования замыкания, которые не требуют возврата к внутренней функции. например, innerFunction может быть передана другой функции, где она вызывается немедленно или сохраняется и вызывается через некоторое время, и во всех случаях она имеет доступ к контексту externalFunction, который был создан при ее вызове.
dlaliberte 4.08.2011 14:01:11
@syockit Нет, Мосс не прав. Закрытие создается независимо от того, экранируется ли когда-либо функция от области, в которой она определена, и безоговорочно созданная ссылка на лексическую среду родителя делает все переменные в родительской области доступными для всех функций, независимо от того, вызываются они снаружи или внутри. область, в которой они были созданы.
Asad Saeeduddin 21.08.2013 13:41:18

Некоторое время назад я написал сообщение в блоге, объясняющее закрытие. Вот то, что я сказал о замыканиях с точки зрения того, почему вы хотите его.

Замыкания - это способ дать функции иметь постоянные закрытые переменные, то есть переменные, о которых знает только одна функция, где она может отслеживать информацию за предыдущие периоды, когда она выполнялась.

В этом смысле они позволяют функции немного походить на объект с закрытыми атрибутами.

Полный пост:

Так что же это за штуковины?

236
28.01.2013 02:23:21
Так можно ли было бы подчеркнуть основное преимущество замыканий на этом примере? Скажем, у меня есть функция emailError (sendToAddress, errorString), которую я мог бы затем сказать, devError = emailError("devinrhode2@googmail.com", errorString)а затем иметь свою собственную версию общей функции emailError?
Devin G Rhode 31.07.2011 06:42:49
Это объяснение и соответствующий прекрасный пример в ссылке на (closure thingys) - лучший способ понять замыкания и должны быть прямо вверху!
HopeKing 5.05.2019 09:45:39

Функции JavaScript могут получить доступ к своим:

  1. аргументы
  2. Локальные (то есть их локальные переменные и локальные функции)
  3. Среда, которая включает в себя:
    • глобалы, включая 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
}

Все четыре из этих функций являются замыканиями, поскольку все они имеют доступ к своей среде.

78
8.06.2016 22:16:22

ПРЕДИСЛОВИЕ: этот ответ был написан, когда вопрос был:

Как сказал старый Альберт: «Если вы не можете объяснить это шестилетнему, вы действительно сами этого не понимаете». Ну, я попытался объяснить закрытие 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();

И хотя взрослые знали о настоящих принцессах, они никогда не поверили бы в единорогов или драконов, потому что никогда их не видели. Взрослые говорили, что они существуют только в воображении маленькой девочки.

Но мы знаем настоящую правду; что маленькая девочка с принцессой внутри ...

... действительно принцесса с маленькой девочкой внутри.

2428
1.11.2017 11:40:09
Мне действительно нравится это объяснение. Для тех, кто читает и не следует, аналогия такова: функция princess () представляет собой сложную область, содержащую личные данные. За пределами функции личные данные не могут быть видны или доступны. Принцесса держит в своем воображении единорогов, драконов, приключений и т. Д. (Личные данные), и взрослые не могут увидеть их сами. НО воображение принцессы запечатлено в закрытии для story()функции, которая является единственным интерфейсом, который littleGirlэкземпляр открывает в мире магии.
Patrick M 28.02.2013 07:49:20
Итак, вот storyзакрытие, но если бы код был var story = function() {}; return story;тогда, littleGirlбыло бы закрытие. По крайней мере, такое впечатление, которое я получаю от использования MDN «закрытых» методов с замыканиями : «Эти три публичные функции являются замыканиями, которые используют одну и ту же среду».
icc97 23.02.2016 00:58:37
@ icc97, да, storyэто замыкание, ссылающееся на среду, предоставляемую в рамках princess. princessтакже является другим подразумеваемым замыканием, т. е. princessи littleGirlразделяют любую ссылку на parentsмассив, который будет существовать обратно в среде / области действия, где littleGirlсуществует и princessопределено.
Jacob Swartwood 1.03.2016 16:00:04
@BenjaminKrupp Я добавил явный комментарий к коду, чтобы показать / подразумевать, что в теле больше операций, princessчем написано. К сожалению, эта история сейчас немного неуместна в этой теме. Первоначально вопрос задавался для «объяснения закрытия JavaScript 5-летнему»; мой ответ был единственным, который даже пытался это сделать. Я не сомневаюсь, что это с треском провалилось бы, но, по крайней мере, у этого ответа мог быть шанс заинтересовать пятилетнего ребенка.
Jacob Swartwood 11.09.2017 18:45:36
На самом деле, для меня это имело смысл. И я должен признать, что, наконец, понимание закрытия JS с использованием рассказов о принцессах и приключениях заставляет меня чувствовать себя немного странно.
Crystallize 2.10.2017 10:03:34

Ты переспал и пригласил Дэна. Вы говорите Дэну принести один контроллер 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.");
82
20.07.2011 15:16:26

Я собрал интерактивное руководство по 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
137
25.10.2014 22:38:03

Википедия о замыканиях :

В информатике замыкание - это функция вместе со средой ссылки для нелокальных имен (свободных переменных) этой функции.

Технически, в 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) и выполнено позже, генерируя функцию базы данных каждый раз, когда это вызывается. Каждая сгенерированная функция будет иметь свой собственный скрытый объект базы данных. Другой пример использования замыканий - это когда мы не возвращаем функцию, а объект, содержащий несколько функций для разных целей, каждая из которых имеет доступ к одним и тем же данным.

164
18.12.2013 16:48:34
Это лучшее объяснение закрытия JavaScript. Должен быть выбранный ответ. Остальные достаточно интересны, но этот на самом деле полезен практическим способом для реальных JavaScript-кодеров.
geoidesic 4.02.2018 12:32:46

Принимая этот вопрос всерьез, мы должны выяснить, на что способен типичный шестилетний ребенок познавательно, хотя, по общему признанию, тот, кто интересуется 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функции делает это здесь.
748
10.10.2018 17:50:14
На самом деле, смешение, функция makeKitchen вызов является фактическим закрытием, а не объект кухни , что она возвращается.
dlaliberte 27.06.2016 17:56:00
Пробираясь через других, я нашел этот ответ как самый простой способ объяснить, что и почему закрывает .is.
Chetabahana 12.08.2016 15:12:01
Слишком много меню и закусок, не хватает мяса и картошки. Вы можете улучшить этот ответ одним коротким предложением, например: «Закрытие - это запечатанный контекст функции из-за отсутствия какого-либо механизма определения объема, предоставляемого классами».
Staplerfahrer 13.05.2017 16:30:28

Я знаю, что уже есть множество решений, но я думаю, что этот небольшой и простой скрипт может быть полезен для демонстрации концепции:

// 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
86
9.05.2016 11:32:47

После вызова функции она выходит из области видимости. Если эта функция содержит что-то вроде функции обратного вызова, то эта функция обратного вызова все еще находится в области видимости. Если функция обратного вызова ссылается на некоторую локальную переменную в непосредственной среде родительской функции, то естественно, что вы ожидаете, что эта переменная будет недоступна для функции обратного вызова и вернет undefined.

Замыкания гарантируют, что любое свойство, на которое ссылается функция обратного вызова, доступно для использования этой функцией, даже если ее родительская функция, возможно, вышла из области видимости.

14
5.05.2016 15:05:50

Из личного поста в блоге :

По умолчанию 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при создании экземпляров функции.

20
4.01.2013 14:25:46

Функция в 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();
49
5.05.2016 16:04:06

Замыкания - это средство, с помощью которого внутренние функции могут ссылаться на переменные, присутствующие в их внешней окружающей функции после того, как их родительские функции уже завершены.

// 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 );
14
4.01.2013 14:27:23

Соломенный Человек

Мне нужно знать, сколько раз была нажата кнопка, и что-то делать при каждом третьем нажатии ...

Достаточно очевидное решение

// 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)

581
13.11.2017 04:54:05
Я не согласен с вашим определением, что такое закрытие. Нет причин, по которым он должен вызывать себя. Также немного упрощенно (и неточно) говорить, что его нужно «вернуть» (об этом много говорится в комментариях к лучшему ответу на этот вопрос)
James Montagne 26.02.2013 19:51:22
@ Джеймс, даже если вы не согласны, его пример (и весь пост) - один из лучших, которые я видел. Хотя вопрос не старый и не решен для меня, он вполне заслуживает +1.
e-satis 27.02.2013 11:20:09
«Мне нужно знать, сколько раз была нажата кнопка, и делать что-то при каждом третьем щелчке ...» ЭТО привлек мое внимание. Вариант использования и решение, показывающее, как закрытие не такая загадочная вещь, и что многие из нас пишут их, но точно не знают официального названия.
Chris22 10.01.2014 13:49:37
Хороший пример, потому что он показывает, что «count» во 2-м примере сохраняет значение «count» и не сбрасывается в 0 при каждом нажатии «элемента». Очень информативно!
Adam 21.07.2014 06:19:54
+1 за закрытие поведения . Можно ли ограничить поведение замыкания для функций в JavaScript или эта концепция может быть также применена к другим структурам языка?
Dziamid 8.03.2015 19:32:34

Хорошо, разговаривая с 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, чтобы поиграть с ним.

56
25.10.2014 22:52:13

Я бы просто указал им на страницу Mozilla Closures . Это лучшее, самое краткое и простое объяснение основ закрытия и практического использования, которое я нашел. Настоятельно рекомендуется всем, кто изучает JavaScript.

И да, я бы даже порекомендовал это 6-летнему ребенку - если 6-летний изучает замыкания, то логично, что они готовы понять краткое и простое объяснение, приведенное в статье.

45
25.10.2014 22:54:15
Я согласен: упомянутая страница Mozilla особенно проста и лаконична. Удивительно, но ваш пост не был оценен так широко, как другие.
Brice Coustillas 28.04.2018 08:21:59

Если вы хотите объяснить это шестилетнему ребенку, вы должны найти что-то намного более простое и без кода.

Просто скажите ребенку, что он «открыт», что говорит о том, что он может иметь отношения с некоторыми другими, своими друзьями. В какой-то момент он определил друзей (мы можем знать имена его друзей), и это закрытие. Если вы сфотографируете его и его друзей, то он «закрыт» относительно его способности дружить. Но в целом он "открытый". В течение всей его жизни у него будет много разных друзей. Один из этих наборов является закрытием.

15
25.10.2014 22:57:06
Мне нравится, ассоциация близкого друга - закрытие, приятно. Хорошая мимика для сохранения картинки не только для ребенка :) Хоть и немного размыто, звучит немного как «когда ты вырастешь, ты поймешь», мне бы не хотелось таких ответов, когда мне было 6 лет, но мне нравится сейчас
dmi3y 15.03.2013 13:43:21

Я нашел очень четкую главу 8 раздела 6 «Замыкания» JavaScript: «Полное руководство » Дэвида Фланагана, 6-е издание, O'Reilly, 2011. Я попробую перефразировать.

  1. Когда вызывается функция, создается новый объект для хранения локальных переменных для этого вызова.

  2. Область действия функции зависит от ее местоположения объявления, а не от места выполнения.

Теперь предположим, что внутренняя функция объявлена ​​внутри внешней функции и ссылается на переменные этой внешней функции. Далее предположим, что внешняя функция возвращает внутреннюю функцию как функцию. Теперь есть внешняя ссылка на любые значения, находящиеся в области видимости внутренней функции (которая, по нашим предположениям, включает значения из внешней функции).

JavaScript сохранит эти значения, так как они остались в области текущего выполнения благодаря передаче из завершенной внешней функции. Все функции являются замыканиями, но интересующие замыкания являются внутренними функциями, которые в нашем предполагаемом сценарии сохраняют значения внешних функций в своей «оболочке» (я надеюсь, что я правильно использую язык здесь), когда они (внутренние функции) возвращаются от внешних функций. Я знаю, что это не соответствует требованиям шестилетнего возраста, но, надеюсь, это все еще полезно.

17
5.05.2016 16:13:48

Функция выполняется в области объекта / функции, в которой она определена. Упомянутая функция может получить доступ к переменным, определенным в объекте / функции, где она была определена во время выполнения.

И просто воспринимайте это буквально .... как написано в коде: P

19
8.11.2018 09:38:54

Для шестилетнего?

Вы и ваша семья живете в мифическом городе Анн Виль. У вас есть друг, который живет по соседству, поэтому вы звоните им и просите их выйти и поиграть. Вы набираете:

000001 (домашний джем)

Через месяц вы и ваша семья переезжаете из Энн-Виль в следующий город, но вы и ваш друг все еще поддерживаете связь, поэтому теперь вам нужно набрать код города, в котором живет ваш друг, прежде чем набирать их ' правильный номер:

001 000001 (annVille.jamiesHouse)

Через год после этого ваши родители переезжают в совершенно новую страну, но вы и ваш друг по-прежнему поддерживаете связь, поэтому после того, как ваши родители попросили вас сделать международные звонки, вы набираете:

01 001 000001 (myOldCountry.annVille.jamiesHouse)

Странно, однако, что после переезда в новую страну вы и ваша семья случайно переехали в новый город под названием Анн Виль ... и вы случайно подружились с каким-то новым человеком по имени Джейми ... Вы даете им вызов...

000001 (домашний джем)

Пугающий...

Настолько жуткий, что рассказываешь об этом Джейми из твоей старой страны ... Ты смеешься над этим. И вот однажды вы и ваша семья отправляетесь на отдых в старую страну. Вы посещаете свой старый город (Анн Виль) и идете в гости к Джейми ...

  • "Правда? Еще один Джейми? В Энн Виль? В твоей новой стране !!?"
  • "Да ... Давайте назовем их ..."

02 001 000001 (myNewCountry.annVille.jamiesHouse)

Мнения?

Более того, у меня есть множество вопросов о терпении современного шестилетнего ...

30
25.10.2014 22:59:36
так что в этом здесь? jamiesHouse? Я не понимаю
vivoconunxino 17.12.2014 11:28:01
Нет. AnnVille - это закрытие. Если вы живете в annVille, вы можете позвонить напрямую .jamiesHouse. Если вы живете за пределами AnnVille, вам придется позвонить в annVille.jamiesHouse (конечно, при условии, что я решил представить население annVille всему миру). Имейте в виду, что это предназначено, чтобы быть учебником для ребенка 6 лет, без абсолютно никакого вовлеченного кода. Точное объяснение, очевидно, потребует более подробной информации.
Charlie 18.12.2014 12:37:33

Ответ для шестилетнего ребенка (при условии, что он знает, что такое функция, что такое переменная и какие данные):

Функции могут возвращать данные. Одним из типов данных, которые вы можете вернуть из функции, является другая функция. Когда эта новая функция возвращается, все переменные и аргументы, используемые в функции, которая ее создала, не исчезают. Вместо этого родительская функция «закрывается». Другими словами, ничто не может заглянуть внутрь него и увидеть переменные, которые оно использовало, кроме функции, которую оно возвратило. Эта новая функция имеет особую возможность заглянуть внутрь функции, которая ее создала, и просмотреть данные внутри нее.

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

Другой действительно простой способ объяснить это с точки зрения объема:

Каждый раз, когда вы создаете меньшую область внутри большей области, меньшая область всегда сможет увидеть, что находится в большей области.

49
25.10.2014 23:02:19

Я уверен, что Эйнштейн не сказал этого, ожидая, что мы выберем какой-нибудь эзотерический мозговой штурм и столкнем шестилетних с тщетными попытками свести их с ума (и что еще хуже для них - скучно ) вещи для их детских умов :) Если бы мне было шесть лет, я бы не хотел иметь таких родителей или не дружил бы с такими скучными филантропами, извините :)

В любом случае, для детей закрытие - это просто объятие , я полагаю, каким бы способом вы ни пытались объяснить :) А когда вы обнимаете своего друга, вы оба делитесь чем-то, что у вас, ребята, сейчас. Это обряд, когда ты обнимаешь кого-то, ты показываешь ей доверие и готовность позволить ей делать с тобой много вещей, которые ты не позволяешь и скрываешь от других. Это акт дружбы :).

Я действительно не знаю, как объяснить это детям 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();

Только для детей:

Закрытие это объятие

Ошибка является муха

ПОЦЕЛУЙ является обниматься! :)

15
5.05.2016 16:19:34

Хорошо, 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");

373
10.10.2018 18:38:01
Хорошо сыграно и отвечает оригинальному постеру. Я думаю, что это лучший ответ. Я собирался использовать багаж таким же образом: представьте, что вы идете в дом бабушки, и вы упаковываете свой футляр Nintendo DS с игровыми картами в своем футляре, но затем кладете футляр в свой рюкзак и кладете игровые карты в карманы рюкзака, и Затем вы положили все это в большой чемодан с большим количеством игровых карт в карманах чемодана. Добравшись до дома бабушки, вы можете играть в любую игру на своем DS, если все внешние случаи открыты. Или что-то в этом роде.
slartibartfast 19.09.2013 00:37:32