86 Вопрос: Как работают закрытия JavaScript?

вопрос создан в Sun, Apr 9, 2017 12:00 AM

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

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

    
7649
  1. Моя проблема с этими и многими ответами состоит в том, что они подходят к нему с абстрактной, теоретической точки зрения, а не начинают с простого объяснения, почему замыкания необходимы в Javascript и в практических ситуациях. в котором вы их используете. В итоге вы получаете статью, которую вы должны пролистать, все время думая: «Но почему?». Я бы просто начал с: замыкания - это отличный способ справиться со следующими двумя реалиями JavaScript: a. область видимости находится на уровне функций, а не на уровне блоков и, б. большая часть того, что вы делаете на практике в JavaScript, является асинхронной /управляемой событиями.
    2013-03-08 17: 22: 59Z
  2. @ Redsandro С одной стороны, это значительно облегчает написание кода, управляемого событиями. Я мог бы запустить функцию при загрузке страницы, чтобы определить особенности HTML или доступных функций. Я могу определить и установить обработчик в этой функции и иметь всю эту контекстную информацию доступной каждый раз, когда обработчик вызывается без необходимости повторного запроса. Решите проблему один раз, повторно используйте на каждой странице, где нужен этот обработчик, с уменьшенными накладными расходами при повторном вызове обработчика. Вы когда-нибудь видели, чтобы одни и те же данные дважды отображались на языке, в котором их нет? Закрытия позволяют намного легче избегать подобных вещей.
    2013-06-26 17: 02: 16Z
  3. Для программистов на Java короткий ответ заключается в том, что это функция, эквивалентная внутреннему классу. Внутренний класс также содержит неявный указатель на экземпляр внешнего класса и используется для почти той же цели (то есть для создания обработчиков событий).
    2014-06-19 10: 04: 21Z
  4. Здесь вы можете лучше понять это: javascriptissexy.com/understand-javascript-closures-with-ease . Еще нужно замыкание на закрытии после прочтения других ответов. :)
    2016-01-22 05: 41: 32Z
  5. Я нашел этот практический пример очень полезным: youtube.com/watch?v=w1s9PgtEoJs
    2016-07-06 17: 33: 55Z
30 ответов                              30                         

JavaScript-закрытия для начинающих

Опубликовано Morris в вторник, 2006-02-21 10:19. Сообщество отредактировано с тех пор.

Закрытия не являются магическими

Эта страница объясняет замыкания, чтобы программист мог их понять - используя работающий код JavaScript. Это не для гуру или функциональных программистов.

Замыкания несложно понять после того, как концепция ядра взломана. Однако их невозможно понять, прочитав какие-либо теоретические или академически ориентированные объяснения!

Эта статья предназначена для программистов, имеющих некоторый опыт программирования на основном языке и умеющих читать следующую функцию JavaScript:

р>

 
function sayHello(name) {
  var text = 'Hello ' + name;
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');

Два кратких резюме

  • Когда функция (foo) объявляет другие функции (bar и baz), семейство локальных переменных, созданных в foo, не уничтожается при выходе из функции. Переменные просто становятся невидимыми для внешнего мира. foo может поэтому хитро вернуть функции bar и baz, и они могут продолжатьчитать, писать и общаться друг с другом через это закрытое семейство переменных («замыкание»), с которыми никто не может вмешиваться, даже тот, кто в будущем снова позвонит foo.

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

Пример закрытия

Следующий код возвращает ссылку на функцию:

р>

 
function sayHello2(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"

Большинство программистов на JavaScript поймут, как ссылка на функцию возвращается в переменную (say2) в приведенном выше коде. Если вы этого не сделаете, то вам нужно посмотреть на это, прежде чем вы сможете научиться замыканиям. Программист, использующий C, мог бы думать о функции как о возвращении указателя на функцию, а переменные say и say2 были указателями на функцию.

Существует критическое различие между указателем C на функцию и ссылкой JavaScript на функцию. В JavaScript переменную ссылки на функцию можно рассматривать как указатель на функцию , а также как скрытый указатель на замыкание.

В приведенном выше коде есть замыкание, поскольку анонимная функция function() { console.log(text); } объявлена ​​ внутри другой функции, в данном примере sayHello2(). В JavaScript, если вы используете ключевое слово function внутри другой функции, вы создаете замыкание.

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

Если в JavaScript вы объявляете функцию в другой функции, то локальные переменные внешней функции могут оставаться доступными после ее возвращения. Это продемонстрировано выше, потому что мы вызываем функцию say2() после того, как вернулись из sayHello2(). Обратите внимание, что вызываемый нами код ссылается на переменную text, которая была локальной переменной функции sayHello2().

 
function() { console.log(text); } // Output of say2.toString();

Глядя на выходные данные say2.toString(), мы видим, что код ссылается на переменную text. Анонимная функция может ссылаться на text, которая содержит значение 'Hello Bob', поскольку локальные переменные sayHello2() тайно поддерживаются в замыкании.

Гений в том, что в JavaScript ссылка на функцию также имеет секретную ссылку на замыкание, в котором она была создана - подобно тому, как делегаты являются указателем на метод плюс секретная ссылка на объект.

Еще примеры

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

Пример 3

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

р>

 
function say667() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // logs 43

Пример 4

Все три глобальные функции имеют общую ссылку на одинаковое закрытие, потому что все они объявлены в одном вызове setupSomeGlobals().

р>

 
var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

Три функции имеют общий доступ к одному и тому же замыканию - локальным переменным setupSomeGlobals(), когда были определены три функции.

Обратите внимание, что в приведенном выше примере, если вы снова вызовете setupSomeGlobals(), будет создано новое замыкание (stack-frame!). Старые переменные gLogNumber, gIncreaseNumber, gSetNumber перезаписываются функциями new , которые имеют новое замыкание. (В JavaScript, когда вы объявляете функцию внутри другой функции, внутренняя функция (и) воссоздается /воссоздается снова каждый каждый раз, когда вызывается внешняя функция.)

Пример 5

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

р>

 
function sayAlice() {
    var say = function() { console.log(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs "Hello Alice"

Tricky: обратите внимание, что переменная say также находится внутри замыкания и может быть доступна любой другой функции, которая может быть объявлена ​​в пределах sayAlice(), или она может быть рекурсивно доступна внутри внутренней функции.

Пример 6

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

Чтобы понять этот пример, вам нужно понять функцию «поднятия переменной» в Javascript.

р>

 
function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs "item2 undefined" 3 times

Строка result.push( function() {console.log(item + ' ' + list[i])} добавляет ссылку на анонимную функцию три раза в массив результатов. Если вы не очень знакомы с анонимными функциями, подумайте об этом:

 
pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

Обратите внимание, что при запуске примера "item2 undefined" регистрируется три раза! Это потому, что, как и в предыдущих примерах, существует только одно замыкание для локальных переменных для buildList (это result, i, list и item). Когда анонимные функции вызываются на линии fnlist[j](); все они используют одно и то же замыкание и используют текущее значение для i и item в этом замыкании (где i имеет значение 3, поскольку цикл завершен, а item имеет значение 'item2'). Обратите внимание, что мы индексируем от 0, следовательно, item имеет значение item2. И i ++ будет увеличивать i до значения 3.

Может быть полезно посмотреть, что произойдет, если объявление уровня блока переменной item используется (через ключевое слово let) вместо объявления переменной в функциональной области через ключевое слово var. Если это изменение сделано, то каждая анонимная функция в массиве result имеет свое собственное закрытие; когда запускается пример, вывод будет следующим:

 
item0 undefined
item1 undefined
item2 undefined

Если переменная i также определена с использованием let вместо var, то вывод будет следующим:

 
item0 1
item1 2
item2 3

Пример 7

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

р>

 
function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

Резюме

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

Финальные очки:

  • Каждый раз, когда вы используете function внутри другой функции, используется замыкание.
  • Когда вы используете eval() внутри функции, используется замыкание. Текст, который вы eval можете ссылаться на локальные переменные функции, и в пределах eval вы даже можете создавать новые локальные переменные, используя eval('var foo = …')
  • Когда вы используете new Function(…) ( конструктор функций а>) внутри функции не создает замыкания. (Новая функция не может ссылаться на локальные переменные внешней функции.)
  • Закрытие в JavaScript похоже на сохранение копии всех локальных переменных, так же, как они были при выходе из функции.
  • Вероятно, лучше думать, что замыкание всегда создается просто записью в функцию, и к этому замыканию добавляются локальные переменные.
  • Новый набор локальных переменных сохраняется каждый раз, когда вызывается функция с замыканием (учитывая, что функция содержит объявление функции внутри нее, и ссылка на эту внутреннюю функцию либо возвращается, либо для нее сохраняется внешняя ссылка в некотором роде).
  • Две функции могут выглядеть так, как будто они имеют одинаковый исходный текст, но ведут себя совершенно по-разному из-за их «скрытого» закрытия. Я не думаю, что код JavaScript может на самом деле узнать, есть ли ссылка на функцию или нет.
  • Если вы пытаетесь внести какие-либо изменения в динамический исходный код (например, myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));), это не сработает, если myFunction является замыканием (конечно, вы никогда даже не подумали бы о подстановке строк исходного кода во время выполнения, но ...).
  • Можно получать объявления функций в объявлениях функций внутри функций ... и вы можете получать замыкания на нескольких уровнях.
  • Я думаю, что обычно замыкание является термином как для функции, так и для захваченных переменных. Обратите внимание, что я не использую это определение в этой статье!
  • Я подозреваю, что замыкания в JavaScript отличаются от тех, которые обычно встречаются в функциональных языках.

Ссылки

Спасибо

Если у вас есть только что изученные замыкания (здесь или где-либо еще!), меня интересуют любые ваши отзывы о любых изменениях, которые вы могли бы предложить, которые могли бы сделать эту статью более понятной. Отправьте электронное письмо на адрес morrisjohns.com (morris_closure @). Обратите внимание, что я не гуру в JavaScript - и не в замыканиях.

Оригинальное сообщение Морриса можно найти в интернет-архиве. . р>     

6854
2019-06-05 17: 50: 04Z
  1. Бриллиант. Мне особенно нравится: «Закрытие в JavaScript похоже на сохранение копии всех локальных переменных, как это было при выходе из функции».
    2008-09-21 14: 38: 40Z
  2. @ e-happ - блестящая, как может показаться, "копия всех локальных переменных, так же, как они были при выходе из функции", вводит в заблуждение. Предполагается, что значения переменных копируются, но на самом деле это набор самих переменных, который не изменяется после вызова функции (кроме 'eval' может быть: blog.rakeshpai.me/2008/10/… ). Предполагается, что функция должна вернуться до создания замыкания, но ее не нужно возвращать, прежде чем замыкание можно будет использовать в качестве замыкания.
    2011-08-08 15: 24: 48Z
  3. Это звучит хорошо: "Закрытие в JavaScript похоже на сохранение копии всех локальных переменных, как они были при выходе из функции". Но это вводит в заблуждение по нескольким причинам. (1) Вызов функции не должен выходить, чтобы создать замыкание. (2) Это не копия значений локальных переменных, а сами переменные. (3) Там не сказано, кто имеет доступ к этим переменным.
    2013-02-11 18: 20: 22Z
  4. В примере 5 показана «ошибка», когда код работает не так, как задумано. Но это не показывает, как это исправить. Этот другой ответ показывает, как это сделать.
    2013-06-24 19: 12: 21Z
  5. Мне нравится, как этот пост начинается большими жирными буквами с надписью «Закрытия не являются магическими» и заканчивается в первом примере «Волшебство заключается в том, что в JavaScript функция также ссылается на функцию» имеет секретную ссылку на замыкание, в котором оно было создано ".
    2014-09-25 02: 30: 17Z

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

р>

 
function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

Это всегда будет записывать 16, потому что bar может получить доступ к x, который был определен в качестве аргумента для foo, и он также может получить доступ к tmp из foo.

Это является закрытием. Функция не должна возвращать , чтобы вызываться замыканием. Простой доступ к переменным вне вашей непосредственной лексической области создает замыкание .

р>

 
function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2); // bar is now a closure.
bar(10);

Вышеприведенная функция также регистрирует 16, потому что bar все еще может ссылаться на x и tmp, даже если она больше не находится непосредственно внутри области.

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

Самый простойПример закрытия:

р>

 
var a = 10;

function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

Когда вызывается функция JavaScript, создается новый контекст выполнения. Вместе с аргументами функции и родительским объектом этот контекст выполнения также получает все переменные, объявленные вне его (в вышеприведенном примере оба 'a' и 'b').

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

Здесь число x является буквальным числом. Как и в случае с другими литералами в JavaScript, при вызове foo число x копируется в foo в качестве аргумента x.

С другой стороны, JavaScript всегда использует ссылки при работе с объектами. Если, скажем, вы вызвали foo с объектом, возвращаемое закрытие будет ссылкой на этот оригинальный объект!

р>

 
function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + tmp);
    x.memb = x.memb ? x.memb + 1 : 1;
    console.log(x.memb);
  }
}

var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);

Как и ожидалось, каждый вызов bar(10) будет увеличиваться на x.memb. Чего нельзя ожидать, так это того, что x просто ссылается на тот же объект, что и переменная age! После пары звонков на bar age.memb будет 2! Эта ссылка является основой для утечек памяти с HTML-объектами.

    
3907
2019-05-22 12: 58: 05Z
  1. @ feeela: Да, каждая функция JS создает замыкание. Переменные, на которые нет ссылок, вероятно, будут иметь право на сборку мусора в современных механизмах JS, но это не меняет того факта, что при создании контекста выполнения этот контекст имеет ссылку на включающий контекст выполнения и его переменные, и эта функция является объектом, который потенциально может быть перемещен в другую переменную область, сохраняя при этом эту исходную ссылку. Это закрытие.
    2013-08-19 01: 31: 25Z
  2. @ Али Я только что обнаружил, что предоставленный мной jsFiddle на самом деле ничего не доказывает, так как delete не работает. Тем не менее, лексическая среда, которую функция будет переносить как [[Scope]] (и, в конечном счете, использовать в качестве основы для своей собственной лексической среды при вызове), определяется при выполнении оператора, определяющего функцию. Это означает, что функция закрывает все содержимое исполняемой области независимо от того, к каким значениям она фактически относится и выходит ли из области. Ознакомьтесь с разделами 13.2 и 10 в спецификации
    2013-08-20 17: 51: 46Z
  3. Это был хороший ответ, пока он не попытался объяснить примитивные типы и ссылки. Это совершенно неправильно и говорит о копируемых литералах, которые на самом деле не имеют ничего общего.
    2014-07-04 14: 53: 35Z
  4. Замыкания - это ответ JavaScript на объектно-ориентированное программирование на основе классов. JS не основан на классах, поэтому нужно было найти другой способ реализации некоторых вещей, которые не могли бы быть реализованы иначе.
    2014-09-18 10: 45: 56Z
  5. это должен быть принятый ответ. Волшебство никогда не происходит во внутренней функции. Это происходит при назначении внешней функции переменной. Это создает новый контекст выполнения для внутренней функции, так что «частная переменная» может накапливаться. Конечно, это возможно, поскольку переменная, которой назначена внешняя функция, поддерживает контекст. Первый ответ просто усложнит ситуацию, не объясняя, что на самом деле там происходит.
    2016-08-18 00: 26: 28Z

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

  

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

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

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

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

    
2348
2017-11-01 11: 40: 09Z
  1. Мне действительно нравится это объяснение. Для тех, кто читает и не следует, аналогия такова: функция princess () представляет собой сложную область, содержащую личные данные. За пределами функции личные данные не могут быть видны или доступны. Принцесса держит в своем воображении единорогов, драконов, приключений и т. Д. (Личные данные), и взрослые не могут увидеть их сами. НО воображение принцессы запечатлено в закрытии для функции story(), которая является единственным интерфейсом, который экземпляр littleGirl выставляет в мир магии.
    2013-02-28 07: 49: 20Z
  2. Итак, здесь story - это закрытие, но если бы код был var story = function() {}; return story;, то littleGirl был бы закрытием. По крайней мере, такое впечатление я получаю от использования MDN 'private 'методы с замыканиями : "Эти три открытые функции являются замыканиями, которые используют одну и ту же среду."
    2016-02-23 00: 58: 37Z
  3. @ icc97, да, story - это замыкание, ссылающееся на среду, предоставляемую в рамках princess. princess также является другим подразумеваемым замыканием, то есть princess и littleGirl будут совместно использовать любую ссылку на массив parents, который будет существовать обратно в среде /области действия, где существует littleGirl и определен princess.
    2016-03-01 16: 00: 04Z
  4. @ BenjaminKrupp Я добавил явный комментарий к коду, чтобы показать /показать, что в теле princess больше операций, чем написано. К сожалению, эта история сейчас немного неуместна в этой теме. Первоначально вопрос был о том, чтобы «объяснить закрытие JavaScript 5-летнему»; мой ответ был единственным, который даже пытался это сделать. Я не сомневаюсь, что это с треском провалилось бы, но, по крайней мере, у этого ответа мог быть шанс заинтересовать 5-летнего.
    2017-09-11 18: 45: 36Z
  5. На самом деле, для меня это имело смысл. И я должен признать, что, наконец, понимание закрытия JS с использованием рассказов о принцессах и приключениях заставляет меня чувствовать себя немного странно.
    2017-10-02 10: 03: 34Z

Принимая этот вопрос всерьез, мы должны выяснить, на что способен типичный шестилетний ребенок познавательно, хотя по общему признанию тот, кто интересуется JavaScript, не настолькоPical.

В развитии детства: от 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 делает это здесь.
722
2018-10-10 17: 50: 14Z
  1. На самом деле, путаница, функция makeKitchen call является фактическим закрытием, а не кухонным объектом, который она возвращает.
    2016-06-27 17: 56: 00Z
  2. Пройдя через остальных, я нашел этот ответ как самый простой способ объяснить, что и почему закрывает .is.
    2016-08-12 15: 12: 01Z
  3. Слишком много меню и закусок, недостаточно мяса и картофеля. Вы можете улучшить этот ответ одним коротким предложением, например: «Закрытие - это запечатанный контекст функции из-за отсутствия какого-либо механизма определения объема, предоставляемого классами».
    2017-05-13 16: 30: 28Z

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

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

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

р>

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

    
560
2017-11-13 04: 54: 05Z
  1. Я не согласен с вашим определением того, что такое замыкание. Нет причин, по которым он должен вызывать себя. Также немного упрощенно (и неточно) говорить, что его нужно «вернуть» (об этом много говорится в комментариях к верхнему ответу на этот вопрос)
    2013-02-26 19: 51: 22Z
  2. @ Джеймс, даже если вы не согласны, его пример (и весь пост) - один из лучших, которые я видел. Хотя этот вопрос не является старым и решенным для меня, он полностью заслуживает +1.
    2013-02-27 11: 20: 09Z
  3. "Мне нужно знать, сколько раз была нажата кнопка, и что-то делать при каждом третьем щелчке ..." ЭТО получило мой внимание. Вариант использования и решение, показывающее, как закрытие не такая загадочная вещь, и что многие из нас пишут их, но точно не знают официального названия.
    2014-01-10 13: 49: 37Z
  4. Хороший пример, поскольку он показывает, что «count» во 2-м примере сохраняет значение «count» и не сбрасывается в 0 при каждом нажатии «элемента». Очень информативно!
    2014-07-21 06: 19: 54Z
  5. + 1 для поведения закрытия . Можем ли мы ограничить поведение замыкания для функций в javascript или эту концепцию можно также применить к другим структурам языка?
    2015-03-08 19: 32: 34Z

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

р>

 
    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.

    
472
2017-03-17 08: 51: 15Z
  1. Я согласен. Предоставление функциям значимых имен вместо традиционных «foobar» также мне очень помогает. Семантика имеет значение.
    2010-04-08 14: 16: 40Z
  2. так что в псевдо-языке это в основном похоже на alert(x+3, where x = 5). where x = 5 является закрытием. Я прав?
    2010-12-22 09: 52: 33Z
  3. @ Jus12: точно. За кулисами замыкание - это просто пространство, где хранятся текущие значения переменных («привязки»), как в вашем примере.
    2010-12-22 11: 28: 09Z
  4. Это именно тот пример, который вводит в заблуждение многих людей, полагая, что в возвращаемой функции используются значения , а не Сама переменчивая переменная. Если бы он был изменен на «return x + = y», или, что еще лучше, и ту, и другую функцию «x * = y», то было бы ясно, что ничего не копируется. Для тех, кто привык складывать кадры, представьте себе, что вместо этого используйте кадры кучи, которые могут продолжать существовать после возврата из функции.
    2013-06-21 12: 36: 49Z
  5. @ Мэтт Я не согласен. Пример not должен исчерпывающе документировать все свойства. Он предназначен для того, чтобы быть редуктивным и иллюстрировать существенную особенность концепции. ФП попросил простое объяснение («для шестилетнего ребенка»). Возьмите принятый ответ: он терпит неудачу при предоставлении краткого объяснения именно потому, что пытается быть исчерпывающим. (Я согласен с вами, что важным свойством JavaScript является то, что привязка осуществляется по ссылке, а не по значению ... но, опять же, успешное объяснение сводится к минимуму.)
    2013-06-21 14: 30: 42Z

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

  • Замыкание создается не только при возврате внутренней функции. Фактически, закрывающая функция вообще не должна возвращаться , чтобы ее замыкание было создано. Вместо этого вы можете назначить свою внутреннюю функцию переменной во внешней области видимости или передать ее в качестве аргумента другой функции, где она может быть вызвана немедленно или в любое время позже. Следовательно, замыкание закрывающей функции, вероятно, создается , как только вызывается закрывающая функция , поскольку любая внутренняя функция имеет доступ к этому закрытию всякий раз, когда вызывается внутренняя функция, до или после возврата закрывающей функции.
  • Замыкание не ссылается на копию старых значений переменных в своей области. Сами переменные являются частью замыкания, и поэтому значение, видимое при обращении к одному из них переменные - это последнее значение на момент обращения к нему. Вот почему внутренние функции, созданные внутри циклов, могут быть хитрыми, поскольку каждая из них имеет доступ к одним и тем же внешним переменным, а не захватывает копию переменных во время создания или вызова функции.
  • «Переменные» в замыкании включают любые именованные функции , объявленные в функции. Они также включают аргументы функции. Замыкание также имеет доступ к переменным содержащего замыкание, вплоть до глобальной области видимости.
  • Замыкания используют память, но они не вызывают утечек памяти , поскольку JavaScript сам по себе очищает собственные циклические структуры, на которые нет ссылок. Утечки памяти в Internet Explorer, связанные с замыканиями, создаются, когда ему не удается отключить значения атрибутов DOM, которые ссылаются на замыкания, тем самым сохраняя ссылки на, возможно, циклические структуры.
359
2016-05-05 15: 00: 49Z
  1. Кстати, я добавил этот "ответ" с пояснениями, чтобы не обращаться к исходному вопросу напрямую. Вместо этого я надеюсь, что любой простой ответ (для 6-летнего ребенка) не содержит неверных представлений об этой сложной теме. Например. популярный вики-ответ выше гласит: «Закрытие - это когда вы возвращаете внутреннюю функцию». Помимо грамматической ошибки, это технически неправильно.
    2011-07-21 14: 15: 29Z
  2. Джеймс, я сказал, что замыкание «вероятно» создано во время вызова включающей функции, потому что вполне вероятно, что реализация может отложить создание замыкания до тех пор, пока не решит, что закрытие абсолютно необходимо. Если в функции включения не определена внутренняя функция, закрытие не требуется. Поэтому, возможно, он может подождать, пока будет создана первая внутренняя функция, а затем создать замыкание из контекста вызова включающей функции.
    2012-07-10 17: 27: 03Z
  3. @ Beetroot-Beetroot Предположим, у нас есть внутренняя функция, которая передается другой функции, где она используется до того, как вернется внешняя функция, и предположим, что мы также возвращаем ту же внутреннюю функцию из внешней функции. В обоих случаях это одна и та же функция, но вы говорите, что перед возвратом внешней функции внутренняя функция «привязывается» к стеку вызовов, тогда как после возврата внутренняя функция внезапно связывается с замыканием. Он ведет себя одинаково в обоих случаях; семантика идентична, так вы не говоритеПодробности реализации?
    2012-10-16 16: 06: 13Z
  4. @ Beetroot-Beetroot, спасибо за ваш отзыв, и я рад, что вы заставили вас задуматься. Я до сих пор не вижу какой-либо семантической разницы между живым контекстом внешней функции и тем же контекстом, когда она становится замыканием, когда функция возвращается (если я понимаю ваше определение). Внутренняя функция не волнует. Сборку мусора не волнует, поскольку внутренняя функция в любом случае поддерживает ссылку на контекст /замыкание, и вызывающая сторона внешней функции просто отбрасывает ссылку на контекст вызова. Но это сбивает с толку людей, и, возможно, лучше просто назвать это контекстом вызова.
    2012-10-18 00: 26: 54Z
  5. Эту статью трудно читать, но я думаю, что на самом деле она поддерживает то, что я говорю. В нем говорится: «Закрытие формируется путем возврата объекта функции [...] или путем непосредственного присвоения ссылки на такой объект функции, например, глобальной переменной». Я не имею в виду, что GC не имеет значения. Скорее, из-за GC и потому, что внутренняя функция присоединена к контексту вызова внешней функции (или [[scope]], как говорится в статье), тогда не имеет значения, возвращается ли вызов внешней функции, потому что это связывание с внутренним функция - важная вещь.
    2012-10-21 01: 49: 59Z

Хорошо, 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");
    
357
2018-10-10 18: 38: 01Z
  1. Хорошо сыграно и отвечает оригинальному постеру. Я думаю, что это лучший ответ. Я собирался использовать багаж таким же образом: представьте, что вы идете в дом бабушки, и вы упаковываете свой футляр Nintendo DS с игровыми картами внутри своего футляра, но затем кладете футляр в свой рюкзак и кладете игровые карты в карманы рюкзака, и Затем вы положили все это в большой чемодан с большим количеством игровых карт в карманах чемодана. Добравшись до дома бабушки, вы можете играть в любую игру на своем DS, если все внешние случаи открыты. или что-то в этом роде.
    2013-09-19 00: 37: 32Z

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

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

Переменная содержится в закрытии , если вы

  1. назначьте его с var foo=1; или
  2. просто напиши var foo;

Если внутренняя функция (функция, содержащаяся в другой функции) обращается к такой переменной, не определяя ее в своей области видимости с помощью var, она изменяет содержимое переменной во внешнем закрытии . р>

замыкание истекает во время выполнения функции, которая его породила. Если другие функции выберут его из closure /scope , в которой они определены (например, в качестве возвращаемых значений), они продолжат ссылаться на это замыкание .

Пример

р>

 
function example(closure) {
  // define somevariable to live in the closure of example
  var somevariable = 'unchanged';

  return {
    change_to: function(value) {
      somevariable = value;
    },
    log: function(value) {
      console.log('somevariable of closure %s is: %s',
        closure, somevariable);
    }
  }
}

closure_one = example('one');
closure_two = example('two');

closure_one.log();
closure_two.log();
closure_one.change_to('some new value');
closure_one.log();
closure_two.log();

Выход

 
somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged
    
352
2018-10-10 18: 39: 20Z
  1. Ух ты, никогда не знал, что в console.log можно использовать такие замены строк. Если кому-то еще интересно, есть еще: developer.mozilla.org/en /Docs-американские /DOM /...
    2013-01-04 02: 59: 32Z
  2. Переменные, которые находятся в списке параметров функции, также являются частью замыкания (например, не ограничиваются только var).
    2015-03-18 03: 38: 09Z
  3. Закрытия звучат больше как объекты, классы и т. д. Не уверен, почему многие люди не сравнивают эти два - нам, новичкам, будет легче учиться!
    2019-05-23 12: 37: 10Z

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

  

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

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

Полный пост:

Так что же это за штук замыкания? р>     

230
2013-01-28 02: 23: 21Z
  1. Итак, можно ли подчеркнуть основное преимущество замыканий в этом примере? Скажем, у меня есть функция emailError (sendToAddress, errorString), я мог бы затем сказать devError = emailError("devinrhode2@googmail.com", errorString), а затем иметь свою собственную версию общей функции emailError?
    2011-07-31 06: 42: 49Z
  2. После того, как я пролистал весь путь "размаха", я наконец-то понял, для чего они. Я подумал про себя: "О, это как частные переменные в объекте?" и бац. Это был следующий ответ, который я прочитал.
    2012-12-30 21: 20: 07Z
  3. Это объяснение и связанный с ним прекрасный пример в ссылке на (closure thingys) - лучший способ понять замыкания и должны быть прямо вверху!
    2019-05-05 09: 45: 39Z

Закрытия просты:

Следующий простой пример охватывает все основные моменты замыканий JavaScript. *

Вот фабрика, которая производит калькуляторы, которые можно добавлять и умножать:

 
function make_calculator() {
  var n = 0; // this calculator stores a single number n
  return {
    add: function(a) {
      n += a;
      return n;
    },
    multiply: function(a) {
      n *= a;
      return n;
    }
  };
}

first_calculator = make_calculator();
second_calculator = make_calculator();

first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400

first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000

Ключевой момент: каждый вызов make_calculator создает новую локальную переменную n, которая продолжает использоваться add и multiply функций этого калькулятора еще долго после возвращения make_calculator.

Если вы знакомы со стековыми фреймами, эти калькуляторы кажутся странными: как они могут продолжать получать доступ к n после возвращения make_calculator? Ответ заключается в том, чтобы представить, что JavaScript не использует «стековые фреймы», а вместо этого использует «кучные фреймы», которые могут сохраняться после возврата вызова функции, который их сделал.

Внутренние функции, такие как add и multiply, которые обращаются к переменным, объявленным во внешней функции ** , называются замыканиями .

Это почти все, что нужно для замыканий.


* Например, он охватывает все пункты статьи «Затворы для чайников», приведенной в другой ответ , за исключением примера 6, в котором просто показано, что переменные можно использовать до их объявления, что приятно знать, но совершенно не связано с замыканиями. Он также охватывает все пункты в принятом ответе , за исключением пунктов (1), в которых работает полицейский.y их аргументы в локальные переменные (аргументы именованной функции) и (2) копирование чисел создает новый номер, но копирование ссылки на объект дает вам еще одну ссылку на тот же объект. Это также полезно знать, но опять же совершенно не связано с замыканиями. Это также очень похоже на пример в этом ответе , но немного короче и менее абстрактно. Он не распространяется на этот ответ или этот комментарий , из-за того, что JavaScript затрудняет подключение current значения переменной цикла в вашу внутреннюю функцию: шаг «подключения» может быть выполнен только с помощью вспомогательной функции, которая включает вашу внутреннюю функцию и вызывается на каждой итерации цикла. (Строго говоря, внутренняя функция обращается к копии переменной вспомогательной функции, а не подключает что-либо к ней.) Опять же, очень полезно при создании замыканий, но не является частью того, что такое замыкание или как оно работает. Существует дополнительная путаница из-за того, что замыкания работают по-разному в функциональных языках, таких как ML, где переменные связаны со значениями, а не с пространством хранения, предоставляя постоянный поток людей, которые понимают замыкания способом (а именно способом «подключения»), который является просто неверно для JavaScript, где переменные всегда связаны с пространством хранения, а не со значениями. р>

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

    
210
2017-05-23 12: 10: 46Z
  1. Что бы произошло, если бы вы вызвали: second_calculator = first_calculator (); вместо second_calculator = make_calculator (); ? Должно быть то же самое, верно?
    2016-10-07 05: 02: 17Z
  2. @ Ronen: поскольку first_calculator является объектом (а не функцией), вы не должны использовать скобки в second_calculator = first_calculator;, так как это присваивание, а не вызов функции. Чтобы ответить на ваш вопрос, тогда будет только один вызов make_calculator, поэтому будет сделан только один калькулятор, а переменные first_calculator и second_calculator будут ссылаться на один и тот же калькулятор, поэтому ответы будут 3, 403, 4433, 44330.
    2016-10-10 09: 49: 48Z

Как бы я объяснил это шестилетнему ребенку:

Вы знаете, как взрослые могут владеть домом, и они называют его домом? Когда у мамы есть ребенок, ребенок на самом деле ничего не имеет, верно? Но его родители владеют домом, поэтому, когда кто-то спрашивает ребенка «Где твой дом?», Он /она может ответить «этот дом!» И указать на дом его родителей. «Закрытие» - это способность ребенка всегда (даже если он находится за границей) быть в состоянии сказать, что у него есть дом, даже если домом действительно являются родители.

    
203
2016-01-16 02: 30: 44Z

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

Доказательство того, что этот пример создает закрытие, даже если внутренняя функция не возвращает

* Вопрос по C #

    
195
2017-05-23 11: 47: 32Z
  1. Если вы прочитаете описание, вы увидите, что ваш пример неверен. Вызов innerFunction находится в области действия внешней функции, а не, как говорится в описании, после возврата внешней функции. белыйКаждый раз, когда вы вызываете externalFunction, создается новая innerFunction, которая затем используется в области видимости.
    2010-12-06 16: 11: 06Z
  2. @ Мосс, это не мои комментарии, это разработчик Google
    2010-12-06 23: 09: 14Z
  3. Видя, что на внутреннюю функцию не ссылаются вне области применения externalFunction, достаточно ли толкован интерпретатор, чтобы увидеть, что он не нуждается в закрытии?
    2011-03-07 05: 49: 31Z
  4. Код является "правильным", как пример замыкания, даже если он не затрагивает часть комментария об использовании замыкания после возврата externalFunction. Так что это не отличный пример. Есть много других способов использования замыкания, которые не требуют возврата внутренней функции. например innerFunction может быть передана другой функции, где она вызывается немедленно или сохраняется и вызывается через некоторое время, и во всех случаях она имеет доступ к контексту externalFunction, который был создан при ее вызове.
    2011-08-04 14: 01: 11Z
  5. @ syockit Нет, Мосс не прав. Замыкание создается независимо от того, экранирует ли функция когда-либо область, в которой она определена, и безоговорочно созданная ссылка на лексическую среду родителя делает все переменные в родительской области доступными для всех функций, независимо от вызываются ли они вне или внутри области, в которой они были созданы.
    2013-08-21 13: 41: 18Z

Я склонен учиться лучше, сравнивая ХОРОШИЕ /ПЛОХИЕ. Мне нравится видеть рабочий код, сопровождаемый нерабочим кодом, с которым кто-то может столкнуться. Я собрал jsFiddle , который выполняет сравнение и пытается свести различия к простейшим объяснениям, которые я мог придумать с.

Затворы сделаны правильно:

 
console.log('CLOSURES DONE RIGHT');

var arr = [];

function createClosure(n) {
    return function () {
        return 'n = ' + n;
    }
}

for (var index = 0; index < 10; index++) {
    arr[index] = createClosure(index);
}

for (var index in arr) {
    console.log(arr[index]());
}
  • В приведенном выше коде createClosure(n) вызывается на каждой итерации цикла. Обратите внимание, что я назвал переменную n, чтобы подчеркнуть, что она является новой переменной, созданной в новой области действия функции, и не совпадает с переменной index, которая связана с внешней областью.

  • Это создает новую область, и n привязан к этой области; это означает, что у нас есть 10 отдельных областей, по одной на каждую итерацию.

  • createClosure(n) возвращает функцию, которая возвращает n в этой области.

  • Внутри каждой области видимости n связан с любым значением, которое было у нее при вызове createClosure(n), поэтому возвращаемая вложенная функция всегда будет возвращать значение n, которое было при вызове createClosure(n).

Замыкания сделаны неправильно:

 
console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

    for (var index = 0; index < 10; index++) {
        badArr[index] = function () {
            return 'n = ' + index;
        };
    }
    return badArr;
}

var badArr = createClosureArray();

for (var index in badArr) {
    console.log(badArr[index]());
}
  • В приведенном выше коде цикл был перемещен внутри функции createClosureArray(), и теперь функция просто возвращает законченный массив, который на первый взгляд кажется более интуитивным.

  • Что может быть неочевидным, так это то, что createClosureArray() вызывается только после того, как для этой функции создается только одна область видимости, а не одна для каждой итерации цикла.

  • Внутри этой функции определена переменная с именем index. Цикл запускается и добавляет функции в массив, которые возвращают index. Обратите внимание, что index определен в функции createClosureArray, которая вызывается только один раз.

  • Поскольку в функции createClosureArray() была только одна область, index привязан только к значению в этой области. Другими словами, каждый раз, когда цикл изменяет значение index, он меняет его для всего, что ссылается на него в этой области.

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

  • После завершения цикла и изменения index конечное значение равнялось 10, поэтому каждая функция, добавленная в массив, возвращает значение единственной переменной index, которая теперь установлена ​​в 10.

Результат

  

ЗАКРЫТИЯ СДЕЛАНЫ ПРАВО
  n = 0
  n = 1
  n = 2
  n = 3
  n = 4
  n = 5
  n = 6
  n = 7
  n = 8
  n = 9

     

ЗАКРЫТИЯ СДЕЛАНЫ НЕПРАВИЛЬНО
  n = 10 бр>   n = 10
  n = 10
  n = 10
  n = 10
  n = 10
  n = 10
  n = 10
  n = 10
  n = 10

    
169
2017-03-27 17: 56: 11Z
  1. Хорошее дополнение, спасибо. Просто для ясности можно представить, как создается «плохой» массив в «плохом» цикле с каждой итерацией: 1-я итерация: [function () {return 'n =' + 0;}] 2-я итерация: [( function () {return 'n =' + 1;}), (function () {return 'n =' + 1;})] 3-я итерация: [(function () {return 'n =' + 2;}) , (function () {return 'n =' + 2;}), (function () {return 'n =' + 2;})] и т. д. Таким образом, каждый раз, когда значение индекса изменяется, оно отражается во всех функциях уже добавлен в массив.
    2016-04-08 22: 38: 08Z
  2. Использование let для var устраняет разницу.
    2017-10-16 10: 25: 19Z
  3. Разве здесь "Закрытие сделано правильно" является примером "замыкания внутри замыкания"?
    2017-12-29 10: 17: 01Z
  4. Я имею в виду, что каждая функция технически является замыканием, но важной частью является то, что функция определяет новую переменную внутри. Функция, которая получает, возвращает только ссылки n, созданные в новом замыкании. Мы просто возвращаем функцию, чтобы сохранить ее в массиве и вызвать позже.
    2017-12-29 18: 52: 58Z
  5. Если вы хотите просто сохранить результат в массиве на первой итерации, вы можете встроить его следующим образом: arr[index] = (function (n) { return 'n = ' + n; })(index);. Но тогда вы сохраняете результирующую строку в массиве, а не вызываемую функцию, которая побеждает точку моего примера.
    2017-12-29 18: 54: 55Z

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

  

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

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

    
161
2013-12-18 16: 48: 34Z
  1. Это лучшее объяснение закрытия JavaScript. Должен быть выбранный ответ. Остальные достаточно интересны, но на самом деле это полезно на практике для реальных JavaScript-кодеров.
    2018-02-04 12: 32: 46Z

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

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

Секреты функций JavaScript - это закрытые переменные

 
var parent = function() {
 var name = "Mary"; // secret
}

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

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

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

 
var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    // I can also see that "name" is "Mary"
  }
}

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

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

Чтобы жить, ребенок должен уйти, пока не стало слишком поздно

 
var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    return "My name is " + childName  +", child of " + name; 
  }
  return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside 

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

Итак, если вы назовете ребенка "Алиса", она ответит

 
child("Alice") => "My name is Alice, child of Mary"

Это все, что нужно сказать.

    
124
2017-07-13 11: 27: 32Z
  1. Это объяснение мне показалось наиболее разумным, поскольку оно не предполагает значительных предварительных знаний технических терминов. Объяснение, получившее наибольшее количество голосов, предполагает, что человек, который не понимает замыкания, имеет полное и полное понимание таких терминов, как «лексическая область» и «контекст исполнения» - хотя я могу понять их концептуально, я не думаю, что меня устраивают детали их, как мне следовало бы, и объяснение без жаргона - это то, что сделало закрытие, наконец, щелчком для меня, спасибо. В качестве бонуса, я думаю, это также объясняет, что сфера очень кратко.
    2015-05-17 20: 30: 27Z

Я не понимаю, почему ответы здесь такие сложные.

Вот закрытие:

 
var a = 42;

function b() { return a; }

Да. Вы, вероятно, используете это много раз в день.


  

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

     

Теперь то, что позволяет вам сделать, может быть более впечатляющим, см. другие ответы.

    
102
2015-02-21 23: 48: 56Z
  1. Этот ответ вряд ли поможет сбить с толку людей. Грубым эквивалентом в традиционном языке программирования может быть создание b () как метода для объекта, который также имеет закрытую константу или свойство a. На мой взгляд, удивительным является то, что объект области действия JS фактически предоставляет a как свойство, а не как константу. И вы заметите это важное поведение, только если измените его, как в return a++;
    2015-05-15 01: 34: 07Z
  2. Именно то, что сказал Джон. Прежде чем я наконец-то ухватился за замыкания, мне было трудно найти практические примеры. Да, floribon создал закрытие, но донаучил меня этому, ничему не научил бы меня.
    2015-10-29 23: 15: 01Z
  3. Это не определяет, что такое замыкание - это всего лишь пример, в котором оно используется. И это не учитывает нюанс того, что происходит, когда заканчивается область; Я не думаю, что у кого-то возникал вопрос о лексической области видимости, когда все области все еще существуют, особенно в случае глобальной переменной.
    2016-08-09 15: 51: 36Z

Пример для первой точки по dlaliberte:

  

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

 
var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);
    
91
2016-01-16 02: 39: 35Z
  1. К вашему сведению: запуск вышеуказанного показывает = > 9
    2010-05-19 20: 24: 03Z
  2. Небольшое разъяснение о возможной неоднозначности. Когда я сказал: «На самом деле, функция включения вообще не должна возвращаться». Я не имел в виду «не возвращать значение», но «все еще активен». Таким образом, пример не показывает этот аспект, хотя он показывает другой способ передачи внутренней функции во внешнюю область. Основной момент, который я пытался сделать, касается времени создания замыкания (для функции включения), так как некоторые люди, кажется, думают, что это происходит, когда функция включения возвращается. Другой пример необходим, чтобы показать, что замыкание создается, когда функция вызывается .
    2011-07-21 14: 03: 12Z

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

    
86
2012-12-24 11: 10: 56Z
  1. Это только половина объяснения. В отношении замыканий важно отметить, что если на внутреннюю функцию все еще ссылаются после выхода из внешней функции, старые значения внешней функции все еще доступны для внутренней.
    2008-09-21 22: 29: 13Z
  2. На самом деле, не старые значения внешней функции доступны для внутренней функции, а старые переменные , которые могут иметь новые значения, если какая-то функция смогла их изменить.
    2012-08-16 02: 39: 42Z

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

 
// 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
    
84
2016-05-09 11: 32: 47Z

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

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

  1. Аргументы
  2. Локальные (то есть их локальные переменные и локальные функции)
  3. Среда, которая включает в себя:
    • глобальные переменные, включая DOM
    • что-нибудь во внешних функциях

Если функция обращается к своей среде, то функция является замыканием.

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

Пример замыкания, использующего глобальную среду:

Представьте себе, что события кнопки «Переполнение стека» «Голосование вверх» и «Голосование вниз» реализованы в виде замыканий, voiceUp_click и voiceDown_click, которые имеют доступ к внешним переменным isVotedUp и isVotedDown, которые определены глобально. (Для простоты я имею в виду кнопки «Голосовать» в StackOverflow, а не массив кнопок «Голосовать».)

Когда пользователь нажимает кнопку VoteUp, функция voiceUp_click проверяет, является ли isVotedDown == true, чтобы определить, следует ли голосовать за или просто отменить голосование с понижением. Функция voiceUp_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
}

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

    
77
2016-06-08 22: 16: 22Z

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

Что делать, если к переменной обращаются, но она не является локальной? Как здесь:

 Введите описание изображения здесь

В этом случае интерпретатор находит переменную в внешний LexicalEnvironment объект.

Процесс состоит из двух этапов:

  1. Во-первых, когда функция f создается, она не создается в пустом пространство. Существует текущий объект LexicalEnvironment. В этом случае выше, это окно (во время функции не определено создание).

 Введите описание изображения здесь

Когда функция создается, она получает скрытое свойство с именем [[Scope]], которое ссылается на текущую LexicalEnvironment.

 Введите описание изображения здесь

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

Вложенные функции

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

 Введите описание изображения здесь

Итак, функция g имеет доступ к g, a и f.

Затворы

Вложенная функция может продолжать жить после завершения внешней функции:

 Введите описание изображения здесь

Разметка LexicalEnvironments:

 Введите описание изображения здесь

Как мы видим, this.say - это свойство в пользовательском объекте, поэтому оно продолжает жить после завершения User.

И, если вы помните, когда создается this.say, он (как и каждая функция) получает внутреннюю ссылку this.say.[[Scope]] на текущую среду LexicalEnvironment. Таким образом, LexicalEnvironment текущего выполнения пользователя остается в памяти. Все переменные User также являются его свойствами, поэтому они также тщательно сохраняются, а не отбрасываются, как обычно.

Весьважно убедиться, что если внутренняя функция хочет получить доступ к внешней переменной в будущем, она может это сделать.

Подводя итог:

  1. Внутренняя функция сохраняет ссылку на внешний LexicalEnvironment.
  2. Внутренняя функция может обращаться к переменным из нее в любое время, даже если внешняя функция завершена.
  3. Браузер хранит LexicalEnvironment и все его свойства (переменные) в памяти до тех пор, пока не появится внутренняя функция, ссылающаяся на него.

Это называется закрытием.

    
75
2018-05-14 20: 51: 20Z

Как отец 6-летнего ребенка, который в настоящее время обучает детей младшего возраста (и относительного новичка в области кодирования без формального образования, поэтому требуются исправления), я думаю, что этот урок будет лучше всего использовать в практической игре. Если шестилетний ребенок готов понять, что такое закрытие, тогда он достаточно взрослый, чтобы пойти самому. Я бы предложил вставить код в jsfiddle.net, немного пояснить и оставить их в покое, чтобы придумать уникальную песню. Пояснительный текст ниже, вероятно, больше подходит для 10-летнего.

 
function sing(person) {

    var firstPart = "There was " + person + " who swallowed ";

    var fly = function() {
        var creature = "a fly";
        var result = "Perhaps she'll die";
        alert(firstPart + creature + "\n" + result);
    };

    var spider = function() {
        var creature = "a spider";
        var result = "that wiggled and jiggled and tickled inside her";
        alert(firstPart + creature + "\n" + result);
    };

    var bird = function() {
        var creature = "a bird";
        var result = "How absurd!";
        alert(firstPart + creature + "\n" + result);
    };

    var cat = function() {
        var creature = "a cat";
        var result = "Imagine That!";
        alert(firstPart + creature + "\n" + result);
    };

    fly();
    spider();
    bird();
    cat();
}

var person="an old lady";

sing(person);

ИНСТРУКЦИЯ ПО

ДАННЫЕ: данные представляют собой набор фактов. Это могут быть цифры, слова, измерения, наблюдения или даже просто описания вещей. Вы не можете прикоснуться к нему, почувствовать запах или попробовать его. Вы можете записать это, сказать это и услышать это. Вы можете использовать его, чтобы создать сенсорный запах и вкус, используя компьютер. Это может быть полезно на компьютере с помощью кода.

КОД: все написанное выше называется кодом . Это написано на JavaScript.

JAVASCRIPT: JavaScript - это язык. Как английский или французский или китайский языки. Есть много языков, которые понимают компьютеры и другие электронные процессоры. Для того чтобы JavaScript был понятен компьютеру, нужен переводчик. Представьте, что учитель, который говорит только по-русски, приходит преподавать ваш класс в школе. Когда учитель говорит «все садятся», класс не понимает. Но, к счастью, у вас в классе есть русский ученик, который говорит всем, что это означает, что «все садятся» - так вы все делаете. Класс подобен компьютеру, а русский ученик - переводчик. Для JavaScript самый распространенный интерпретатор называется браузером.

БРАУЗЕР: при подключении к Интернету на компьютере, планшете или телефоне для посещения веб-сайта вы используете браузер. Примеры, которые вы можете знать, это Internet Explorer, Chrome, Firefox и Safari. Браузер может понимать JavaScript и сообщать компьютеру, что ему нужно делать. Инструкции JavaScript называются функциями.

ФУНКЦИЯ. Функция в JavaScript похожа на фабрику. Это может быть небольшая фабрика с одной машиной внутри. Или это может быть много других маленьких фабрик, на каждом из которых много машин делают разные работы. На реальной фабрике одежды у вас могут быть пачки тканей и бобин ниток, а также футболки и джинсы. Наша фабрика JavaScript обрабатывает только данные, она не может шить, сверлить или расплавлять металл. В нашей фабрике JavaScript данные поступают, а данные выходят.

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

У меня нет времени ходить по магазинам, поэтому функция должна знать, что у нас в холодильнике, чтобы принимать решения. Каждый ингредиент имеет разное время приготовления, и мы хотим, чтобы робот одновременно подавал все в горячем виде. Нам нужно предоставить функции данные о том, что нам нравится, функция может «разговаривать» с холодильником, а функция может управлять роботом.

Функция обычно имеет имя, скобки и фигурные скобки. Вот так:

 
function cookMeal() {  /*  STUFF INSIDE THE FUNCTION  */  }

Обратите внимание, что /*...*/ и // останавливают чтение кода браузером.

ИМЯ: Вы можете вызывать функцию практически из любого нужного вам слова. Пример «cookMeal» типичен для объединения двух слов и введения второго заглавной буквы в начале, но это не обязательно. В нем не должно быть пробела, и это не может быть число само по себе.

PARENTHESES: «круглые скобки» или () - это почтовый ящик на двери фабрики функций JavaScript или почтовый ящик на улице для отправки пакетов информации на фабрику. Иногда почтовый ящик может быть помечен , например, cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime), и в этом случае вы знаете, какие данные вам нужно предоставить.

BRACES: «Подтяжки», которые выглядят так: {} - это тонированные стекла нашегоactory. Внутри фабрики вы можете видеть снаружи, но снаружи вы не можете видеть внутри.

ПРИМЕР ДЛИТЕЛЬНОГО КОДА ВЫШЕ

Наш код начинается со слова function , поэтому мы знаем, что оно одно! Тогда имя функции sing - это мое собственное описание того, о чем эта функция. Затем в скобках () . Скобки всегда есть для функции. Иногда они пусты, а иногда в них что-то есть. В этом есть слово: (person). После этого есть фигурная скобка, подобная этой {. Это отмечает начало функции sing () . У него есть партнер, который отмечает конец sing () , как этот }

 
function sing(person) {  /* STUFF INSIDE THE FUNCTION */  }

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

Теперь, после функции sing () , ближе к концу кода находится строка

 
var person="an old lady";

VARIABLE: буквы var означают «переменная». Переменная похожа на конверт. Снаружи этот конверт помечен как «человек». Внутри он содержит листок бумаги с информацией, которая нужна нашей функции, некоторые буквы и пробелы, соединенные вместе, как кусок строки (это называется строкой), что делает фразу «старая женщина». Наш конверт может содержать другие виды вещей, такие как числа (называемые целыми числами), инструкции (называемые функциями), списки (называемые массивами ). Поскольку эта переменная записана вне всех фигурных скобок {}, и поскольку вы можете видеть сквозь тонированные окна, когда находитесь внутри фигурных скобок, эту переменную можно увидеть из любого места в коде. Мы называем это «глобальной переменной».

GLOBAL VARIABLE: person - это глобальная переменная, означающая, что если вы измените ее значение со «старой леди» на «молодой человек», person сохранит быть молодым человеком, пока вы не решите изменить его снова, и любая другая функция в коде сможет увидеть, что это молодой человек. Нажмите кнопку F12 или посмотрите параметры, чтобы открыть консоль разработчика браузера и введите «person», чтобы увидеть, что это за значение. Введите person="a young man", чтобы изменить его, а затем снова введите «person», чтобы увидеть, что оно изменилось.

После этого у нас есть строка

 
sing(person);

Эта строка вызывает функцию, как если бы она вызывала собаку

  

"Давай пой , иди и найди человека !"

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

Функции определяют действия - основная функция - пение. Он содержит переменную с именем firstPart , которая применяется к пению о человеке, которое относится к каждому из стихов песни: «Был« + человек + », который проглотил». Если вы введете firstPart в консоли, вы не получите ответ, потому что переменная заблокирована в функции - браузер не может видеть внутри окрашенных окон фигурных скобок.

ЗАКРЫТИЯ: замыкания - это меньшие функции, которые находятся внутри большой функции sing () . Маленькие фабрики внутри большой фабрики. Каждый из них имеет свои собственные скобки, которые означают, что переменные внутри них не видны снаружи. Вот почему имена переменных ( существо и результат ) могут повторяться в замыканиях, но с разными значениями. Если вы введете эти имена переменных в окне консоли, вы не получите их значение, потому что оно скрыто двумя слоями затемненных окон.

Все затворы знают, что такое переменная функции sing () с именем firstPart , потому что они могут видеть из своих тонированных окон.

После закрытия появляются строки

 
fly();
spider();
bird();
cat();

Функция sing () будет вызывать каждую из этих функций в том порядке, в котором они даны. Тогда работа функции sing () будет выполнена.

    
59
2016-06-08 22: 11: 57Z

Ладно, разговаривая с 6-летним ребенком, я мог бы использовать следующие ассоциации.

  

Представьте себе - вы играете со своими маленькими братьями и сестрами по всему дому, вы ходите со своими игрушками и приносите некоторые из них в комнату вашего старшего брата. Через некоторое время ваш брат вернулся из школы и пошел в свою комнату, и он запер ее внутри, так что теперь вы больше не могли получить прямой доступ к оставленным там игрушкам. Но вы могли бы постучать в дверь и попросить вашего брата за эти игрушки. Это называется игрушкой закрытие ; твой брат сделал это для тебя, и чТеперь e находится во внешней области .

Сравните с ситуацией, когда дверь была заблокирована сквозняком и никого внутри (выполнение общих функций), а затем произошел какой-то локальный пожар и сгорел помещение (сборщик мусора: 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 , чтобы поиграть с ним.

    
55
2014-10-25 22: 52: 13Z

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

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

 
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

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

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

    
50
2014-10-25 23: 02: 19Z
  1. Кажется, что замыкание эквивалентно классам и внутренним классам в O.O.
    2017-02-15 06: 29: 45Z

Функция в 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
2016-05-05 16: 04: 06Z

Perhaнемного больше, чем все, кроме самого преждевременного из шестилетних, но несколько примеров, которые помогли сделать концепцию замыкания в JavaScript, меня зацепили.

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

р>

 
function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    innerFunction();
}

outerFunction();

ALERT: обезьяна

В приведенном выше примере вызывается externalFunction, которая, в свою очередь, вызывает innerFunction. Обратите внимание, что externalVar доступен для innerFunction, о чем свидетельствует правильное оповещение о значении externalVar.

Теперь рассмотрим следующее:

р>

 
function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

ALERT: обезьяна

referenceToInnerFunction имеет значение externalFunction (), которая просто возвращает ссылку на innerFunction. Когда вызывается referenceToInnerFunction, она возвращает externalVar. Опять же, как и выше, это демонстрирует, что innerFunction имеет доступ к outerVar, переменной externalFunction. Кроме того, интересно отметить, что он сохраняет этот доступ даже после того, как externalFunction завершит выполнение.

И вот тут все становится действительно интересно. Если бы мы избавились от externalFunction, скажем, установили его в null, вы могли бы подумать, что referenceToInnerFunction потеряет свой доступ к значению externalVar. Но это не так.

р>

 
function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

outerFunction = null;
alert(referenceToInnerFunction());

ALERT: обезьяна ALERT: обезьяна

Но как это так? Как referenceToInnerFunction может все еще знать значение externalVar теперь, когда externalFunction был установлен в нуль?

Причина, по которой referenceToInnerFunction по-прежнему может получать доступ к значению externalVar, заключается в том, что когда замыкание было впервые создано путем размещения innerFunction внутри externalFunction, innerFunction добавила ссылку на область действия externalFunction (ее переменные и функции) в свою цепочку областей действия. Это означает, что innerFunction имеет указатель или ссылку на все переменные externalFunction, включая externalVar. Таким образом, даже когда externalFunction завершает выполнение или даже если он удален или имеет значение null, переменные в его области видимости, такие как outerVar, остаются в памяти из-за выдающейся ссылки на них со стороны внутренней функции, которая была возвращена referenceToInnerFunction. Чтобы по-настоящему освободить externalVar и остальные переменные externalFunction из памяти, вам нужно избавиться от этой выдающейся ссылки на них, например, установив для referenceToInnerFunction значение null.

//////////р>

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

р>

 
function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    outerVar = "gorilla";

    innerFunction();
}

outerFunction();

Предупреждение: горилла

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

    
48
2015-04-29 15: 37: 06Z

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

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

    
45
2014-10-25 22: 54: 15Z
  1. Я согласен: упомянутая страница Mozilla особенно проста и лаконична. Удивительно, но ваш пост не был оценен так широко, как другие.
    2018-04-28 08: 21: 59Z
источник размещен Вот