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

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

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

30 ответов


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

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

закрытие не магия

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

закрытие не сложно чтобы понять как ядро концепции grokked. Однако их невозможно понять с помощью чтение любых теоретических или академически ориентированных объяснений!

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

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

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

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

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

вам нужно понять функцию "подъема переменных" в 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])} добавлена ссылка на анонимную функцию три раза в массив result. Если ты не такой ... знакомый с анонимными функциями подумайте об этом так:

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

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

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

item0 undefined
item1 undefined
item2 undefined

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

item0 1
item1 2
item2 3

Пример 7

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

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

резюме

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

конечные точки:

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

ссылки

спасибо

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


Оригинальный пост Морриса можно найти в Интернет-Архив.


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

function foo(x) {
  var tmp = 3;

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

  bar(10);
}

foo(2);

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

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

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, они не делают собственные копии.

тут - это буквальное число. Как и с другими литералами в JavaScript, когда foo называется число x is скопировал на 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. Чего нельзя было ожидать, так это ... --5--> просто относится к тому же объекту, что и age переменной! После нескольких звонков в bar, age.memb будет 2! Эта ссылка является основой для утечек памяти с объектами HTML.


предисловие: этот ответ был написан, когда вопрос был:

может ли кто-нибудь считать, что я 6 и странно заинтересован в этой теме ?

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


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

однажды:

жила-была принцесса...

function princess() {

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

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */
    return {
        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

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

On развитие детей: от 5 до 7 лет он говорит:

ваш ребенок сможет следовать двухэтапным указаниям. Например, если вы скажете своему ребенку: "иди на кухню и принеси мне мешок для мусора", они смогут вспомнить, что направление.

мы можем использовать этот пример для объяснения замыканий следующим образом:

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

мы можем закодировать это в JavaScript следующим образом:

function makeKitchen() {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A

дополнительные моменты, которые объясняют, почему закрытие интересно:

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

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

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

Довольно Очевидное Решение

// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
<button id="button">Click Me!</button>

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

рассмотреть этот вариант

var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
<button id="button">Click Me!</button>

обратите внимание на несколько вещей здесь.

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

простое закрытие одной строки

//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();

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

func();  // Alerts "val"
func.a;  // Undefined

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

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

вот вы идете; теперь вы полностью инкапсулируете такое поведение.

Полный Пост В Блоге (включая соображения jQuery)


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

    var bind = function(x) {
        return function(y) { return x + y; };
    }
    
    var plus5 = bind(5);
    console.log(plus5(3));

что произойдет здесь, Если JavaScript не знаете закрытия? Просто замените вызов в последней строке его телом метода (которое в основном является функцией звонки делают) и вы получаете:

console.log(x + 3);

теперь, где определение x? Мы не определили его в текущей области. Единственное решение-позволить plus5 нести его область (или, скорее, область его родителя) вокруг. Сюда,x хорошо определен и привязан к значению 5.


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

  • закрытие создается не только при возврате внутренней функции. фактически, заключительная функция не нужно возвращаться вообще для того, чтобы его закрытие было создано. Вместо этого вы можете назначить свою внутреннюю функцию переменной во внешней области или передать ее в качестве аргумента другой функции, где она можно позвонить немедленно или в любое время позже. Таким образом, закрытие функции enclosing, вероятно, создается как только функция enclosing вызывается поскольку любая внутренняя функция имеет доступ к этому закрытию всякий раз, когда вызывается внутренняя функция, до или после возвращения функции enclosing.
  • закрытие не ссылается на копию старые значения переменных в своей области. сами переменные являются частью закрытия, и поэтому значение, видимое при доступе к одной из этих переменных, является последним значением в момент доступа. Вот почему внутренние функции, созданные внутри циклов, могут быть сложными, поскольку каждый из них имеет доступ к одним и тем же внешним переменным, а не захватывает копию переменных во время создания или вызова функции.
  • "переменные" в закрытии включают любые именованные функции объявлена внутри функции. Они также включают аргументы функции. Закрытие также имеет доступ к содержащим его переменным закрытия, вплоть до глобальной области.
  • закрытие использует память, но они не вызывают утечки памяти поскольку JavaScript сам по себе очищает свои собственные круговые структуры, на которые не ссылаются. Утечки памяти Internet Explorer, связанные с замыканиями, создаются, когда не удается отключить значения атрибутов DOM, ссылающиеся на замыкания, таким образом сохраняя ссылки на возможно круговые структуры.

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

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

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

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

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

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

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

пример

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

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

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

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

полный пост:

так что это за штуки с закрытием?


закрытие просто:

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

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

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

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

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

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

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

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

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

это в значительной степени все, что есть для закрытия.



* например, он охватывает все пункты в статье "закрытие для чайников", приведенной в еще один ответ, за исключением примера 6, который просто показывает, что переменные могут использоваться до их объявления, хороший факт, чтобы знать, но совершенно не связанный с закрытиями. Он также охватывает все пункты в принятый ответ, за исключением точек (1), которые функции копируют свои аргументы в локальные переменные (именованные аргументы функции), и (2), что копирование чисел создает новое число, но копирование ссылки на объект дает вам другую ссылку на тот же объект. Это также хорошо знать,но опять же совершенно не связано с закрытиями. Это также очень похоже на пример в ответ но немного короче и менее абстрактно. Она не покрывает пункт ответ или комментарий, который заключается в том, что JavaScript затрудняет подключение настоящее значение переменной цикла в вашу внутреннюю функцию: шаг "подключение" может быть выполнен только с вспомогательной функцией, которая заключает вашу внутреннюю функцию и вызывается на каждой итерации цикла. (Строго говоря, внутренняя функция обращается к копии переменной вспомогательной функции, а не к чему-либо подключенному.) Опять же, очень полезно, когда создание замыканий, но не часть того, что такое закрытие или как оно работает. Дополнительная путаница возникает из-за замыканий работает по-разному в функциональных языках, таких как мл, где переменные связаны со значениями, А не для хранения, обеспечивая постоянный поток людей, которые понимают закрытия В образом, (а именно "подключение", кстати), что просто некорректно для JavaScript, где переменные всегда привязаны к памяти, а не значения.

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


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

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


можете ли вы объяснить закрытие для 5-летнего ребенка?*

Я все еще думаю объяснение Google работает очень хорошо и лаконично:

/*
*    When a function is defined in another function and it
*    has access to the outer function's context even after
*    the outer function returns.
*
* An important concept to learn in JavaScript.
*/

function outerFunction(someNum) {
    var someString = 'Hey!';
    var content = document.getElementById('content');
    function innerFunction() {
        content.innerHTML = someNum + ': ' + someString;
        content = null; // Internet Explorer memory leak for DOM reference
    }
    innerFunction();
}

outerFunction(1);​

Proof that this example creates a closure even if the inner function doesn't return

*в C# вопрос


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

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

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

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

результат

ЗАКРЫТИЕ СДЕЛАНО ПРАВИЛЬНО
n = 0
n = 1
северный= 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


Википедия о закрытиях:

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

технически, в 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.

ems

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


Я собрал интерактивный учебник JavaScript, чтобы объяснить, как работают закрытия. Что такое закрытие?

вот один из примеров:

var create = function (x) {
    var f = function () {
        return x; // We can refer to x here!
    };
    return f;
};
// 'create' takes one argument, creates a function

var g = create(42);
// g is a function that takes no arguments now

var y = g();
// y is 42 here

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

секретами для функций JavaScript являются частные переменные

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

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

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

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

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

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

так жить, ребенок должен уйти, прежде чем это слишком поздно!--6-->

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"

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


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

вот замыкание:

var a = 42;

function b() { return a; }

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


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

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


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

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

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);

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


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

// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
    var _count = 0; // not accessible outside this function
    var sequencer = function () {
        return _count++;
    }
    return sequencer;
}

var fnext = makeSequencer();
var v0 = fnext();     // v0 = 0;
var v1 = fnext();     // v1 = 1;
var vz = fnext._count // vz = undefined

ты переночуешь у нас и приглашаешь Дэна. Скажи Дэну принести один контроллер XBox.

Дэн приглашает Павла. Дэн просит пола принести один контроллер. Сколько контролеров пришло на вечеринку?

function sleepOver(howManyControllersToBring) {

    var numberOfDansControllers = howManyControllersToBring;

    return function danInvitedPaul(numberOfPaulsControllers) {
        var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
        return totalControllers;
    }
}

var howManyControllersToBring = 1;

var inviteDan = sleepOver(howManyControllersToBring);

// The only reason Paul was invited is because Dan was invited. 
// So we set Paul's invitation = Dan's invitation.

var danInvitedPaul = inviteDan(howManyControllersToBring);

alert("There were " + danInvitedPaul + " controllers brought to the party.");

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

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

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

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

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

представьте, что кнопка переполнения стека Vote-Up и Vote-Down события реализуются как замыкания, voteUp_click и voteDown_click, которые имеют доступ к внешним переменным isVotedUp и isVotedDown, которые определены глобально. (Для простоты я имею в виду кнопки голосования вопроса StackOverflow, а не массив кнопок голосования ответа.)

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

var isVotedUp = false;
var isVotedDown = false;

function voteUp_click() {
  if (isVotedUp)
    return;
  else if (isVotedDown)
    SetDownVote(false);
  else
    SetUpVote(true);
}

function voteDown_click() {
  if (isVotedDown)
    return;
  else if (isVotedUp)
    SetUpVote(false);
  else
    SetDownVote(true);
}

function SetUpVote(status) {
  isVotedUp = status;
  // Do some CSS stuff to Vote-Up button
}

function SetDownVote(status) {
  isVotedDown = status;
  // Do some CSS stuff to Vote-Down button
}

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


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

что делать, если доступ к переменной, но она не является локальной? Как здесь:

Enter image description here

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

процесс состоит из двух шагов:

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

Enter image description here

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

Enter image description here

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

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

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

Enter image description here

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

закрытие

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

Enter image description here

разметка LexicalEnvironments:

Enter image description here

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

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

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

подведем итоги:

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

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


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

function sing(person) {

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

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

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

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

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

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

var person="an old lady";

sing(person);

- инструкции

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

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

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

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

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

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

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

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

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

обратите внимание, что /*...*/ и // стоп-код читается броузер.

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

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

брекеты: "брекеты", которые выглядят так {} подкрашиванные окна нашей фабрики. Изнутри фабрики вы можете видеть снаружи, но снаружи вы не можете видеть внутри.

ДЛИННЫЙ ПРИМЕР КОДА ВЫШЕ

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

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

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

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

var person="an old lady";

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

ГЛОБАЛЬНАЯ ПЕРЕМЕННАЯ:человек является глобальной переменной, что означает, что если вы измените ее значение с "старая леди" на "молодой человек",человек будет молодой человек, пока вы не решите снова изменить его и что любая другая функция в коде видно, что это молодой человек. Нажмите F12 кнопка или посмотреть параметры настройки, чтобы открыть консоль разработчика браузера и введите "person", чтобы увидеть, что это за значение. Тип person="a young man" изменить и введите "человек" еще раз, чтобы увидеть, что он изменился.

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

sing(person);

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

"приехали на петь, приехать и забрать человек!"

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

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

закрытие: закрытие-это меньшие функции, которые находятся внутри big sing ()


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

закрытие; твой брат сделал это для тебя, и теперь он во внешнем область.

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

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

function playingInBrothersRoom (withToys) {
  // We closure toys which we played in the brother's room. When he come back and lock the door
  // your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
  var closureToys = withToys || [],
      returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.

  var brotherGivesToyBack = function (toy) {
    // New request. There is not yet closureToys on brother's hand yet. Give him a time.
    returnToy = null;
    if (toy && closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.

      for ( countIt = closureToys.length; countIt; countIt--) {
        if (closureToys[countIt - 1] == toy) {
          returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
          break;
        }
      }
      returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
    }
    else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
      returnToy = 'Behold! ' + closureToys.join(', ') + '.';
      closureToys = [];
    }
    else {
      returnToy = 'Hey, lil shrimp, I gave you everything!';
    }
    console.log(returnToy);
  }
  return brotherGivesToyBack;
}
// You are playing in the house, including the brother's room.
var toys = ['teddybear', 'car', 'jumpingrope'],
    askBrotherForClosuredToy = playingInBrothersRoom(toys);

// The door is locked, and the brother came from the school. You could not cheat and take it out directly.
console.log(askBrotherForClosuredToy.closureToys); // Undefined

// But you could ask your brother politely, to give it back.
askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
askBrotherForClosuredToy(); // The brother gives you all the rest
askBrotherForClosuredToy(); // Nothing left in there

Как вы можете видеть, игрушки, оставленные в комнате, по-прежнему доступны через брата и независимо от того, заперта ли комната. Вот в jsbin, чтобы поиграть с ним.


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

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

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

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

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


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

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

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

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

пример:

function foo (initValue) {
   //This variable is not destroyed when the foo function exits.
   //It is 'captured' by the two nested functions returned below.
   var value = initValue;

   //Note that the two returned functions are created right now.
   //If the foo function is called again, it will return
   //new functions referencing a different 'value' variable.
   return {
       getValue: function () { return value; },
       setValue: function (newValue) { value = newValue; }
   }
}

function bar () {
    //foo sets its local variable 'value' to 5 and returns an object with
    //two functions still referencing that local variable
    var obj = foo(5);

    //Extracting functions just to show that no 'this' is involved here
    var getValue = obj.getValue;
    var setValue = obj.setValue;

    alert(getValue()); //Displays 5
    setValue(10);
    alert(getValue()); //Displays 10

    //At this point getValue and setValue functions are destroyed
    //(in reality they are destroyed at the next iteration of the garbage collector).
    //The local variable 'value' in the foo is no longer referenced by
    //anything and is destroyed too.
}

bar();

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

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

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

outerFunction();

предупреждение: обезьяна

в приведенном выше примере вызывается outerFunction, который в свою очередь вызывает innerFunction. Обратите внимание, как outerVar доступен для innerFunction, что подтверждается его правильным предупреждением значения outerVar.

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

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

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

предупреждение: обезьяна

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

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

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

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

outerFunction = null;
alert(referenceToInnerFunction());

предупреждение: обезьяна Внимание: обезьяна

но как это так? Как referenceToInnerFunction все еще знает значение outerVar теперь, когда outerFunction имеет значение null?

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

//////////

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

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

    innerFunction();
}

outerFunction();

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

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


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

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