it-swarm.com.ru

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

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

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

7649
e-satis

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 перезаписываются новыми функциями, которые имеют новое закрытие. (В JavaScript всякий раз, когда вы объявляете функцию внутри другой функции, внутренняя функция (и) воссоздается/воссоздается каждый каждый раз, когда вызывается внешняя функция). )

Пример 5

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

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

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

Пример 6

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

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

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

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

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

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

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

Обратите внимание, что при запуске примера "item2 undefined" регистрируется три раза! Это связано с тем, что, как и в предыдущих примерах, существует только одно закрытие для локальных переменных для buildList (это result, i, 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 - ни в замыканиях.


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

6845
Joel Anair

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

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-объектами.

3905
Ali

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

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

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

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

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

2343
Jacob Swartwood

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

На Развитие детства: от 5 до 7 лет там написано:

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

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

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

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

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

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

var kitchen = makeKitchen();

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

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

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

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

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

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

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

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

    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
Konrad Rudolph

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

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

Хорошо, 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
Max Tkachenko

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

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

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

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

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

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

Пример

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
351
Florian Bösch

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

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

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

Полный пост:

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

230
Nathan Long

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

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

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

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

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

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

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

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

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

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

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



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

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

210
Matt

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

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

203
Magne

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

195
Chris S

Я склонен учиться лучше, сравнивая ХОРОШЕЕ/ПЛОХОЕ. Мне нравится видеть рабочий код, сопровождаемый нерабочим кодом, с которым кто-то может столкнуться. Я собрал 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, он меняет его для всего, что ссылается на него в этой области.

  • Все функции, добавленные в массив, возвращают одну и ту же переменную 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
Chev

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

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

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

Я собрал интерактивное руководство по 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
Nathan Whitehead

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

Секреты функций 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
Tero Tolonen

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

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

var a = 42;

function b() { return a; }

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


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

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

102
floribon

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

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

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

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

86
Rakesh Pai

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

// 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
Gerardo Lima

Ты переспал и пригласил Дэна. Вы говорите Дэну принести один контроллер 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
StewShack

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

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

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

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

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

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

Когда пользователь нажимает кнопку VoteUp, функция voiceUp_click проверяет, является ли isVotedDown == true, чтобы определить, следует ли голосовать за или просто отменить голосование с понижением. Функция VoteUp_click является закрытием, потому что она обращается к своей среде.

var isVotedUp = false;
var isVotedDown = false;

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

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

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

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

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

77
John Pick

Автор Замыкания довольно хорошо объяснил замыкания, объясняя причину, почему они нам нужны, а также объясняя 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 - это свойство объекта user, поэтому оно продолжает жить после завершения User.

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

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

Подвести итоги:

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

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

75
Arvand

Как отец 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  */  }

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

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

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

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

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

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

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

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

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

var person="an old lady";

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

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

После этого у нас есть линия

sing(person);

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

"Давай пой , иди и возьми человека !"

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

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

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

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

После закрытия идут линии

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

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

59
grateful

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

55
dmi3y

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

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

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
Stupid Stupid

Функция в 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
srgstm

Возможно, немного за пределами всего, кроме самого раннего из шестилетних, но несколько примеров, которые помогли сделать концепцию замыкания в 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();

ALERT: горилла

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

48
Michael Dziedzic

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

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

45
mjmoody383

Я верю в более короткие объяснения, поэтому смотрите изображение ниже.

Enter image description here

function f1() ..> Light Red Box

function f2() ..> Красная коробочка

Здесь у нас есть две функции, f1() и f2(). f2 () является внутренним по отношению к f1 (). f1 () имеет переменную var x = 10.

При вызове функции f1(), f2() может получить доступ к значению var x = 10.

Вот код:

function f1() {
    var x=10;

    function f2() {
        console.log(x)
    }

    return f2

}
f1()

f1(), ссылаясь здесь:

Enter image description here

42
Dinesh Kanivu

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

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

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

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

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

Кроме того, это очень полезный метод, который используется во многих известных средах, включая Angular, Node.js и jQuery:

Замыкания широко используются в Node.js; они рабочие лошадки в асинхронной неблокирующей архитектуре Node.js. Замыкания также часто используются в jQuery и почти для каждого фрагмента кода JavaScript, который вы читаете.

Но как выглядят замыкания в реальном кодировании? Посмотрите на этот простой пример кода:

function showName(firstName, lastName) {
      var nameIntro = "Your name is ";
      // this inner function has access to the outer function's variables, including the parameter
      function makeFullName() {
          return nameIntro + firstName + " " + lastName;
      }
      return makeFullName();
  }

  console.log(showName("Michael", "Jackson")); // Your name is Michael Jackson

Кроме того, это классический способ закрытия в jQuery, который каждый разработчик javascript и jQuery часто использовал:

$(function() {
    var selections = [];
    $(".niners").click(function() { // this closure has access to the selections variable
        selections.Push(this.prop("name")); // update the selections variable in the outer function's scope
    });
});

Но почему мы используем замыкания? когда мы используем это в реальном программировании? Каковы практические применения укупорочных средств? ниже приводится хорошее объяснение и пример от MDN:

Практические замыкания

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

Следовательно, вы можете использовать замыкание везде, где вы обычно можете использовать объект только с одним методом.

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

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

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

//javascript
function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
/*css*/
body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}

h2 {
  font-size: 1.2em;
}
<!--html><!-->
<p>Some paragraph text</p>
<h1>some heading 1 text</h1>
<h2>some heading 2 text</h2>

<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>

Для дальнейшего изучения закрытий я рекомендую вам посетить эту страницу по MDN: https://developer.mozilla.org/en/docs/Web/JavaScript/Closures

34
Alireza

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

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

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

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

001 000001 (annVille.jamiesHouse)

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

01 001 000001 (myOldCountry.annVille.jamiesHouse)

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

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

Жуткий...

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

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

02 001 000001 (myNewCountry.annVille.jamiesHouse)

Мнения?

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

30
Charlie

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

  function getFullName(a, b) {
  return a + b;
}

function makeFullName(fn) {

  return function(firstName) {

    return function(secondName) {

      return fn(firstName, secondName);
    }
  }
}

makeFullName(getFullName)("stack")("overflow"); // Stackoverflow
29
Shushanth Pallegar

Вот простой сценарий в реальном времени. Просто прочитайте его, и вы поймете, как мы использовали здесь закрытие (посмотрите, как меняется номер места).

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

function movieBooking(movieName) {
    var bookedSeatCount = 0;
    return function(name) {
        ++bookedSeatCount ;
        alert( name + " - " + movieName + ", Seat - " + bookedSeatCount )
    };
};

var MI1 = movieBooking("Mission Impossible 1 ");
var MI2 = movieBooking("Mission Impossible 2 ");

MI1("Mayur");
// alert
// Mayur - Mission Impossible 1, Seat - 1

MI1("Raju");
// alert
// Raju - Mission Impossible 1, Seat - 2

MI2("Priyanka");
// alert
// Raja - Mission Impossible 2, Seat - 1
28
Mayur Randive

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

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

Что такое закрытие?

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

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

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

Базовый пример замыканий в JavaScript:

function showName (firstName, lastName) {

  var nameIntro = "Your name is ";
  // this inner function has access to the outer function's variables, including the parameter
  ​function makeFullName () {
            
​    return nameIntro + firstName + " " + lastName;
        
  }
​
​  return makeFullName ();

}

​
showName ("Michael", "Jackson"); // Your name is Michael Jackson


Замыкания широко используются в Node.js; они рабочие лошадки в асинхронной неблокирующей архитектуре Node.js. Замыкания также часто используются в jQuery и почти для каждого фрагмента кода JavaScript, который вы читаете.

Классический пример jQuery замыканий:

$(function() {
​
​  var selections = []; 
  $(".niners").click(function() { // this closure has access to the selections variable​
    selections.Push (this.prop("name")); // update the selections variable in the outer function's scope​
  });
​});

Правила закрытия и побочные эффекты

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

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

function celebrityName (firstName) {
    var nameIntro = "This celebrity is ";
    // this inner function has access to the outer function's variables, including the parameter​
   function lastName (theLastName) {
        return nameIntro + firstName + " " + theLastName;
    }
    return lastName;
}
​
​var mjName = celebrityName ("Michael"); // At this juncture, the celebrityName outer function has returned.​
​
​// The closure (lastName) is called here after the outer function has returned above​
​// Yet, the closure still has access to the outer function's variables and parameter​
mjName ("Jackson"); // This celebrity is Michael Jackson


2. Замыкания хранят ссылки на переменные внешней функции:

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

function celebrityID () {
    var celebrityID = 999;
    // We are returning an object with some inner functions​
    // All the inner functions have access to the outer function's variables​
    return {
        getID: function ()  {
            // This inner function will return the UPDATED celebrityID variable​
            // It will return the current value of celebrityID, even after the changeTheID function changes it​
          return celebrityID;
        },
        setID: function (theNewID)  {
            // This inner function will change the outer function's variable anytime​
            celebrityID = theNewID;
        }
    }
​
}
​
​var mjID = celebrityID (); // At this juncture, the celebrityID outer function has returned.​
mjID.getID(); // 999​
mjID.setID(567); // Changes the outer function's variable​
mjID.getID(); // 567: It returns the updated celebrityId variable


3. Затвор сошел с ума

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

// This example is explained in detail below (just after this code box).​
​function celebrityIDCreator (theCelebrities) {
    var i;
    var uniqueID = 100;
    for (i = 0; i < theCelebrities.length; i++) {
      theCelebrities[i]["id"] = function ()  {
        return uniqueID + i;
      }
    }
    
    return theCelebrities;
}
​
​var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}];
​
​var createIdForActionCelebs = celebrityIDCreator (actionCelebs);
​
​var stalloneID = createIdForActionCelebs [0];

    console.log(stalloneID.id()); // 103


Больше можно найти здесь

  1. http://javascript.info/tutorial/closures

  2. http://www.javascriptkit.com/javatutors/closures.shtml

26
Abrar Jahin

Вот самый дзенский ответ, который я могу дать:

Что вы ожидаете от этого кода? Скажите мне в комментарии, прежде чем запустить его. Мне любопытно!

function foo() {
  var i = 1;
  return function() {
    console.log(i++);
  }
}

var bar = foo();
bar();
bar();
bar();

var baz = foo();
baz();
baz();
baz();

Теперь откройте консоль в вашем браузере (Ctrl + Shift + I или же F12, надеюсь) и вставьте код и нажмите Enter,.

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

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

Но если я ошибаюсь и вывод консоли удивил вас, дайте мне знать!

24
Andy

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

init: pass first what's needed...
action: in order to achieve something for later execution.

Шестилетнему ребенку я бы подчеркнул практический аспект закрытия:

Daddy: Listen. Could you bring mum some milk (2).
Tom: No problem.
Daddy: Take a look at the map that Daddy has just made: mum is there and daddy is here.
Daddy: But get ready first. And bring the map with you (1), it may come in handy
Daddy: Then off you go (3). Ok?
Tom: A piece of cake!

Пример : Дайте маме немного молока (= действие). Сначала приготовьтесь и принесите карту (= init).

function getReady(map) {
    var cleverBoy = 'I examine the ' + map;
    return function(what, who) {
        return 'I bring ' + what + ' to ' + who + 'because + ' cleverBoy; //I can access the map
    }
}
var offYouGo = getReady('daddy-map');
offYouGo('milk', 'mum');

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

offYouGo('potatoes', 'great mum');

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

Смотрите мой другой ответ, иллюстрирующий параллелизм между OO и ​​замыканиями:

Как "правильно" создать пользовательский объект в JavaScript?

22
roland

Учитывая следующую функцию

function person(name, age){

    var name = name;
    var age = age;

    function introduce(){
        alert("My name is "+name+", and I'm "+age);
    }

    return introduce;
}

var a = person("Jack",12);
var b = person("Matt",14);

Каждый раз, когда вызывается функция person, создается новое замыкание. Хотя переменные a и b имеют одну и ту же функцию introduce, она связана с различными замыканиями. И это закрытие все еще будет существовать даже после того, как функция person закончит выполнение.

Enter image description here

a(); //My name is Jack, and I'm 12
b(); //My name is Matt, and I'm 14

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

closure a = {
    name: "Jack",
    age: 12,
    call: function introduce(){
        alert("My name is "+name+", and I'm "+age);
    }
}

closure b = {
    name: "Matt",
    age: 14,
    call: function introduce(){
        alert("My name is "+name+", and I'm "+age);
    }
}

Предполагая, что вы знаете, как работает class на другом языке, я проведу аналогию.

Думай как

  • JavaScript function как constructor
  • local variables as instance properties
  • эти properties являются частными
  • inner functions as instance methods

Каждый раз, когда вызывается function

  • Будет создано новое object, содержащее все локальные переменные.
  • Методы этого объекта имеют доступ к "properties" этого объекта экземпляра.
22
Vitim.us

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

Что такое замыкание?

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

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

Замыкания - это функции, которые ссылаются на независимые (свободные) переменные. Другими словами, функция, определенная в замыкании, "запоминает" среду, в которой она была создана.

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

Закрытие - это стековый фрейм, который не освобождается при возврате функции. (Как будто "стековый фрейм" был malloc'ed вместо того, чтобы быть в стеке!)

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

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

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

Использование замыканий:

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

Вы можете эмулировать концепцию инкапсуляции в JavaScript, используя замыкания.

Замыкания широко используются в jQuery и Node.js .

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

Пример замыканий:

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

Пример 1. Закрытие здесь достигается путем возврата функции.

function makeAdder(x) {
    return function(y) {
        return x + y;
    };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

Пример 2: Закрытие здесь достигается путем возврата литерала объекта.

function makeAdder(x) {
    return {
        add: function(y){
            return x + y;
        }
    }
}

var add5 = makeAdder(5);
console.log(add5.add(2));//7

var add10 = makeAdder(10);
console.log(add10.add(2));//12

Пример 3: замыкания в jQuery

$(function(){
    var name="Closure is easy";
    $('div').click(function(){
        $('p').text(name);
    });
});

Полезные ссылки:

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

21
Ravi

Чтобы понять замыкания, вы должны перейти к программе и буквально выполнить, как будто вы находитесь во время выполнения. Давайте посмотрим на этот простой кусок кода:

Enter image description here

JavaScript выполняет код в два этапа:

  • Фаза компиляции // JavaScript не является чисто интерпретируемым языком
  • Фаза исполнения

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

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

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

Здесь, когда настраивается область действия g, он получает лексический указатель на родительскую область видимости f. Область действия f является закрытием для g. В JavaScript, если есть какая-то ссылка на функции, объекты или области действия, если вы можете как-то к ним добраться, сборщик мусора не будет. Поэтому, когда myG работает, у него есть указатель на область действия f, которая является его закрытием. Эта область памяти не будет собирать мусор даже после возвращения f. Это закрытие, что касается времени выполнения.

ТАК ЧТО IS ЗАКРЫТИЕ?

  • Это неявная, постоянная связь между функцией и ее областью видимости ...
  • Скрытая ссылка [[scope]] определения функции (лямбда).
  • Содержит цепочку областей действия (предотвращает сбор мусора).
  • Он используется и копируется как "ссылка на внешнюю среду" при каждом запуске функции.

НЕЗАКОННОЕ ЗАКРЫТИЕ

var data = "My Data!";
setTimeout(function() {
  console.log(data); // Prints "My Data!"
}, 3000);

ЯВНЫЕ ЗАКРЫТИЯ

function makeAdder(n) {
  var inc = n;
  var sum = 0;
  return function add() {
    sum = sum + inc;
    return sum;
  };
}

var adder3 = makeAdder(3);

Очень интересный доклад о замыканиях и многом другом Ариндам Пол - JavaScript VM внутренности, EventLoop, Async и ScopeChains.

21
Nikhil Ranjan

Замыкание - это функция внутри функции , которая имеет доступ к переменным и параметрам своей "родительской" функции.

Пример:

function showPostCard(Sender, Receiver) {

    var PostCardMessage = " Happy Spring!!! Love, ";

    function PreparePostCard() {
        return "Dear " + Receiver + PostCardMessage + Sender;
    }

    return PreparePostCard();
}
showPostCard("Granny", "Olivia");
21
enb081

Познакомьтесь с иллюстрированным объяснением : Как закрытия JavaScript работают за кулисами ,.

В статье объясняется, как объекты области видимости (или LexicalEnvironments) распределяются и используются интуитивно понятным способом. Мол, для этого простого скрипта:

"use strict";

var foo = 1;
var bar = 2;

function myFunc() {
  //-- Define local-to-function variables
  var a = 1;
  var b = 2;
  var foo = 3;
}

//-- And then, call it:
myFunc();

При выполнении кода верхнего уровня мы имеем следующее расположение объектов области видимости:

Enter image description here

И когда вызывается myFunc(), мы имеем следующую цепочку областей видимости:

Enter image description here

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

Смотрите вышеупомянутую статью для всех деталей.

21
Dmitry Frank

Версия изображения для этого ответа: [Решено]

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

Пример 1:

enter image description here

Пример 2:

enter image description here

Пример 3: enter image description here

20
christian Nguyen

(Я не принимаю во внимание 6-летнюю вещь.)

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

var name = 'Rafael';

var sayName = function() {
  console.log(name);
};

Вы видите, что sayName не имеет определения для переменной name, но использует значение name, которое было определено за пределами sayName (в родительской области).

Допустим, вы передаете sayName в качестве параметра другой функции, которая будет вызывать sayName в качестве обратного вызова:

functionThatTakesACallback(sayName);

Обратите внимание, что:

  1. sayName будет вызываться изнутри functionThatTakesACallback (предположим, что, поскольку я не реализовал functionThatTakesACallback в этом примере).
  2. Когда вызывается sayName, он записывает значение переменной name.
  3. functionThatTakesACallback не определяет переменную name (ну, может, но это не имеет значения, поэтому предположим, что это не так).

Таким образом, у нас sayName вызывается внутри functionThatTakesACallback и ссылается на переменную name, которая не определена внутри functionThatTakesACallback.

Что происходит потом? ReferenceError: name is not defined?

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

Итак: Даже если name не находится в области, где будет вызываться функция sayName (внутри functionThatTakesACallback), sayName может получить доступ к значению name, которое зафиксировано в замыкании, связанном с sayName.

-

Из книги Красноречивый JavaScript :

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

20
Rafael Eyng

Этот ответ представляет собой краткое изложение этого видео на YouTube Javascript Closures . Так что полный кредит на это видео.

Замыкания - это не что иное, как функции с сохранением состояния, которые поддерживают состояния своих частных переменных.

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

enter image description here

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

enter image description here

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

function Counter() {
           var counter = 0;

           var Increment = function () {
               counter++;
               alert(counter);
           }
           return {
               Increment
           }
       }

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

var x = Counter(); // get the reference of the closure
x.Increment(); // Displays 1
x.Increment(); // Display 2 ( Maintains the private variables)

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

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

Объект, который следует принципам OOP, самодостаточен, следует за абстракцией, следует за инкапсуляцией и т.д. Без замыканий в Javascript это сложно реализовать.

enter image description here

18
Shivprasad Koirala

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

function outer(x){
     return function inner(y){
         return x+y;
     }
}

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

var add10 = outer(10);
add10(20); // The result will be 30
add10(40); // The result will be 50

var add20 = outer(20);
add20(20); // The result will be 40
add20(40); // The result will be 60
18
Mohammed Safeer

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

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

var x = 1;
function myFN() {
  alert(x); //1, as opposed to undefined.
}
// Or
function a() {
   var x = 1;
   function b() {
       alert(x); //1, as opposed to undefined.
   }
   b();
}

И что?

Закрытие не является чем-то особенным для пользователя JavaScript, пока вы не подумаете о том, на что была бы похожа жизнь без него. В других языках переменные, используемые в функции, очищаются, когда эта функция возвращается. В приведенном выше примере x был бы "нулевым указателем", и вам нужно было бы установить getter и setter и начать передавать ссылки. Не похоже на JavaScript, верно? Спасибо могучему закрытию.

Почему я должен волноваться?

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

17
Harry Robbins

Из личного сообщение в блоге :

По умолчанию JavaScript знает два типа областей: глобальную и локальную.

var a = 1;

function b(x) {
    var c = 2;
    return x * c;
}

В приведенном выше коде переменная a и функция b доступны в любом месте кода (то есть глобально). Переменная c доступна только в области действия функции b (то есть локальной). Большинство разработчиков программного обеспечения не будут довольны отсутствием гибкости, особенно в больших программах.

Закрытия JavaScript помогают решить эту проблему, связывая функцию с контекстом:

function a(x) {
    return function b(y) {
        return x + y;
    }
}

Здесь функция a возвращает функцию с именем b. Поскольку b определено в a, он автоматически имеет доступ ко всему, что определено в a, то есть x в этом примере. Вот почему b может возвращать x + y без объявления x.

var c = a(3);

Переменной c присваивается результат вызова a с параметром 3. То есть экземпляром функции b где x = 3. Другими словами, c теперь является функцией, эквивалентной:

var c = function b(y) {
    return 3 + y;
}

Функция b запоминает, что x = 3 в своем контексте. Следовательно:

var d = c(4);

назначит значение 3 + 4 для d, то есть 7.

Примечание : если кто-то изменяет значение x (скажем, x = 22) после создания экземпляра функции b, это будет отражено и в b , Следовательно, более поздний вызов c (4) вернет 22 + 4, то есть 26.

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

(function () {
    var f = "Some message";
    alert(f);
})();

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

Теперь есть распространенное предупреждение JavaScript, в котором могут помочь замыкания:

var a = new Array();

for (var i=0; i<2; i++) {
    a[i]= function(x) { return x + i ; }
}

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

a[0] = function (x) { return x + 0 ; }
a[1] = function (x) { return x + 1 ; }
a[2] = function (x) { return x + 2 ; }

В действительности, это то, как инициализируется a, поскольку последнее значение i в контексте равно 2:

a[0] = function (x) { return x + 2 ; }
a[1] = function (x) { return x + 2 ; }
a[2] = function (x) { return x + 2 ; }

Решение:

var a = new Array();

for (var i=0; i<2; i++) {
    a[i]= function(tmp) {
        return function (x) { return x + tmp ; }
    } (i);
}

Аргумент/переменная tmp содержит локальную копию изменяющегося значения i при создании экземпляров функции.

17
Jérôme Verstrynge

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

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

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

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

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

17
Jim

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

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

Я действительно не знаю, как объяснить это детям 5-6 лет. Я также не думаю, что они оценят любые фрагменты кода JavaScript, такие как:

function Baby(){
    this.iTrustYou = true;
}

Baby.prototype.hug = function (baby) {
    var smiles = 0;

    if (baby.iTrustYou) {
        return function() {
            smiles++;
            alert(smiles);
        };
    }
};

var
   arman = new Baby("Arman"),
   morgan = new Baby("Morgana");

var hug = arman.hug(morgan);
hug();
hug();

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

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

Ошибка - это муха

ПОЦЕЛУЙ - это придурок! :)

15
Arman McHitarian

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

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

14
Jean-Baptiste Yunès

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

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

14
moha297

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

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

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

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

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

13
Juan Garcia

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

// A function that generates a new function for adding numbers.
function addGenerator( num ) {
    // Return a simple function for adding two numbers
    // with the first number borrowed from the generator
    return function( toAdd ) {
        return num + toAdd
    };
}

// addFive now contains a function that takes one argument,
// adds five to it, and returns the resulting number.
var addFive = addGenerator( 5 );
// We can see here that the result of the addFive function is 9,
// when passed an argument of 4.
alert( addFive( 4 ) == 9 );
13
ketan

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

var add = (function() {
  var counter = 0;
  return function() {
    return counter += 1;
  }
})();

add();
add();
add();
// The counter is now 3

Пример объяснил:

  • Переменной add присваивается возвращаемое значение функции, вызывающей себя.
  • Самопризывающая функция запускается только один раз. Он устанавливает счетчик на ноль (0) и возвращает выражение функции.
  • Таким образом, add становится функцией. "Замечательная" часть заключается в том, что он может получить доступ к счетчику в родительской области видимости.
  • Это называется закрытием JavaScript. Это позволяет функции иметь "частные" переменные.
  • Счетчик защищен областью действия анонимной функции и может быть изменен только с помощью функции добавления.

Источник

13
Premraj

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

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

Естественно, результаты одной бейсбольной игры полностью отделены от других игр.

Закрытие - это особый способ, которым г-н Кодер держит счет всех своих волшебных бейсбольных игр отдельно.

13
b_dev

Буратино: закрытие в 1883 году (за столетие до JavaScript)

Я думаю, что это лучше всего объяснить 6-летнему ребенку с приключением в Ницце ... Часть Приключения Буратино , где Буратино проглатывается негабаритной рыбой-ловушкой ...

var tellStoryOfPinocchio = function(original) {

  // Prepare for exciting things to happen
  var pinocchioFindsMisterGeppetto;
  var happyEnding;

  // The story starts where Pinocchio searches for his 'father'
  var pinocchio = {
    name: 'Pinocchio',
    location: 'in the sea',
    noseLength: 2
  };

  // Is it a dog... is it a fish...
  // The dogfish appears, however there is no such concept as the belly
  // of the monster, there is just a monster...
  var terribleDogfish = {
    swallowWhole: function(snack) {
      // The swallowing of Pinocchio introduces a new environment (for the
      // things happening inside it)...
      // The BELLY closure... with all of its guts and attributes
      var mysteriousLightLocation = 'at Gepetto\'s ship';

      // Yes: in my version of the story the monsters mouth is directly
      // connected to its belly... This might explain the low ratings
      // I had for biology...
      var mouthLocation = 'in the monsters mouth and then outside';

      var puppet = snack;


      puppet.location = 'inside the belly';
      alert(snack.name + ' is swallowed by the terrible dogfish...');

      // Being inside the belly, Pinocchio can now experience new adventures inside it
      pinocchioFindsMisterGeppetto = function() {
        // The event of Pinocchio finding Mister Geppetto happens inside the
        // belly and so it makes sence that it refers to the things inside
        // the belly (closure) like the mysterious light and of course the
        // hero Pinocchio himself!
        alert(puppet.name + ' sees a mysterious light (also in the belly of the dogfish) in the distance and swims to it to find Mister Geppetto! He survived on ship supplies for two years after being swallowed himself. ');
        puppet.location = mysteriousLightLocation;

        alert(puppet.name + ' tells Mister Geppetto he missed him every single day! ');
        puppet.noseLength++;
      }

      happyEnding = function() {
        // The escape of Pinocchio and Mister Geppetto happens inside the belly:
        // it refers to Pinocchio and the mouth of the beast.
        alert('After finding Mister Gepetto, ' + puppet.name + ' and Mister Gepetto travel to the mouth of the monster.');
        alert('The monster sleeps with its mouth open above the surface of the water. They escape through its mouth. ');
        puppet.location = mouthLocation;
        if (original) {
          alert(puppet.name + ' is eventually hanged for his innumerable faults. ');
        } else {
          alert(puppet.name + ' is eventually turned into a real boy and they all lived happily ever after...');
        }
      }
    }
  }

  alert('Once upon a time...');
  alert('Fast forward to the moment that Pinocchio is searching for his \'father\'...');
  alert('Pinocchio is ' + pinocchio.location + '.');
  terribleDogfish.swallowWhole(pinocchio);
  alert('Pinocchio is ' + pinocchio.location + '.');
  pinocchioFindsMisterGeppetto();
  alert('Pinocchio is ' + pinocchio.location + '.');
  happyEnding();
  alert('Pinocchio is ' + pinocchio.location + '.');

  if (pinocchio.noseLength > 2)
    console.log('Hmmm... apparently a little white lie was told. ');
}

tellStoryOfPinocchio(false);

 
13
Ron Deijkers

Возможно, вам следует рассмотреть объектно-ориентированную структуру вместо внутренних функций. Например:

var calculate = {
    number: 0,
    init: function (num) {
        this.number = num;
    },
    add: function (val) {
        this.number += val;
    },
    rem: function (val) {
        this.number -= val;
    }
};

И прочитайте результат из переменной calc.number, которой все равно нужно "возвращаться".

12
Psy Chip

Закрытие - это блок кода, который соответствует трем критериям:

  • Это может быть передано как значение и

  • исполняется по требованию любого, кто имеет эту ценность, в то время

  • он может ссылаться на переменные из контекста, в котором он был создан (то есть он закрыт по отношению к доступу к переменным, в математическом смысле слова "закрыт").

(Слово "закрытие" на самом деле имеет неточное значение, и некоторые люди не думают, что критерий № 1 является частью определения. Я думаю, что это так.)

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

Автор: Пол Кантрелл, @ http://innig.net/software/Ruby/closures-in-Ruby

12
Magne

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

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

12
goonerify

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

Пример:

var outer = function(params){ //Outer function defines a variable called params
    var inner = function(){ // Inner function has access to the params variable of the outer function
        return params;
    }
    return inner; //Return inner function exposing it to outer scope
},
myFunc = outer("myParams");
myFunc(); //Returns "myParams"
11
Bhojendra Rauniyar

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

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

enter image description here

function getA() {
  var a = [];

  // this action happens later,
  // after the function returned
  // the `a` value
  setTimeout(function() {
    a.splice(0, 0, 1, 2, 3, 4, 5);
  });

  return a;
}

var a = getA();
out('What is `a` length?');
out('`a` length is ' + a.length);

setTimeout(function() {
  out('No wait...');
  out('`a` length is ' + a.length);
  out('OK :|')
});
<pre id="output"></pre>

<script>
  function out(k) {
    document.getElementById('output').innerHTML += '> ' + k + '\n';
  }
</script>
11
Eugene Tiurin

Учитывая, что вопрос состоит в том, чтобы объяснить это просто, как если бы 6-летний, мой ответ был бы:

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

10
Raul Martins

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

Функции первого класса

В языках программирования функции считаются гражданами первого класса, если ими можно манипулировать, как и любым другим типом данных. Например, первоклассные функции могут создаваться во время выполнения и назначаться переменным. Они также могут быть переданы и возвращены другими функциями. Помимо соответствия ранее упомянутым критериям, функции JavaScript также имеют свои собственные свойства и методы. В следующем примере показаны некоторые возможности функций первого класса. В этом примере две функции создаются и присваиваются переменным "foo" и "bar". Функция, сохраненная в "foo", отображает диалоговое окно, а "bar" просто возвращает любой аргумент, переданный ей. Последняя строка примера делает несколько вещей. Во-первых, функция, хранящаяся в "bar", вызывается с "foo" в качестве аргумента. Затем "bar" возвращает ссылку на функцию "foo". Наконец, вызывается возвращенная ссылка "foo", в результате чего отображается "Hello World!".

var foo = function() {
  alert("Hello World!");
};

var bar = function(arg) {
  return arg;
};

bar(foo)();

Внутренние функции

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

function add(value1, value2) {
  function doAdd(operand1, operand2) {
    return operand1 + operand2;
  }

  return doAdd(value1, value2);
}

var foo = add(1, 2);
// foo equals 3

Одной из важных характеристик внутренних функций является то, что они имеют неявный доступ к области видимости внешней функции. Это означает, что внутренняя функция может использовать переменные, аргументы и т.д. Внешней функции. В предыдущем примере аргументы add () " value1 " и " value2 " были переданы doAdd () в качестве аргументов " operand1 " и "operand2". Однако в этом нет необходимости, поскольку doAdd () имеет прямой доступ к " value1 " и " value2 ". Предыдущий пример был переписан ниже, чтобы показать, как doAdd () может использовать " значение1 " и " значение2 ".

function add(value1, value2) {
  function doAdd() {
    return value1 + value2;
  }

  return doAdd();
}

var foo = add(1, 2);
// foo equals 3

Создание замыканий

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

function add(value1) {
  return function doAdd(value2) {
    return value1 + value2;
  };
}

var increment = add(1);
var foo = increment(2);
// foo equals 3

Есть несколько вещей, которые стоит отметить в этом примере.

Функция add () возвращает свою внутреннюю функцию doAdd (). Возвращая ссылку на внутреннюю функцию, создается замыкание. "Value1" - это локальная переменная add () и нелокальная переменная doAdd (). Нелокальные переменные относятся к переменным, которые не входят ни в локальную, ни в глобальную область. "Value2" является локальной переменной doAdd (). Когда вызывается add (1), замыкание создается и сохраняется в "приращении". В ссылочной среде замыкания "значение1" связано со значением один. Связанные переменные также называются закрытыми. Отсюда и название. Когда вызывается приращение (2), вводится замыкание. Это означает, что вызывается doAdd (), а переменная "value1" содержит значение "один". По сути, замыкание можно рассматривать как создание следующей функции.

function increment(value2) {
  return 1 + value2;
}

Когда использовать замыкания

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

Работа с таймерами

Замыкания полезны, когда используются вместе с функциями setTimeout () и setInterval () . Чтобы быть более конкретным, замыкания позволяют передавать аргументы в функции обратного вызова setTimeout () и setInterval () . Например, следующий код печатает строку "some message" один раз в секунду, вызывая showMessage () .

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Closures</title>
  <meta charset="UTF-8" />
  <script>
    window.addEventListener("load", function() {
      window.setInterval(showMessage, 1000, "some message<br />");
    });

    function showMessage(message) {
      document.getElementById("message").innerHTML += message;
    }
  </script>
</head>
<body>
  <span id="message"></span>
</body>
</html>

К сожалению, Internet Explorer не поддерживает передачу аргументов обратного вызова через setInterval (). Вместо отображения "некоторого сообщения" в Internet Explorer отображается "неопределенное" (поскольку в showMessage () фактически не передается никакого значения). Чтобы обойти эту проблему, можно создать замыкание, которое связывает аргумент "message" с желаемым значением. Затем замыкание можно использовать как функцию обратного вызова для setInterval (). Чтобы проиллюстрировать эту концепцию, код JavaScript из предыдущего примера был переписан ниже для использования замыкания.

window.addEventListener("load", function() {
  var showMessage = getClosure("some message<br />");

  window.setInterval(showMessage, 1000);
});

function getClosure(message) {
  function showMessage() {
    document.getElementById("message").innerHTML += message;
  }

  return showMessage;
}

Эмуляция личных данных

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

В следующем примере показан конструктор для простого класса Person. Когда каждый Person создается, ему присваивается имя через аргумент " name ". Внутренне Person сохраняет свое имя в переменной " _ name ". В соответствии с хорошими практиками объектно-ориентированного программирования для получения имени также предусмотрен метод getName () .

function Person(name) {
  this._name = name;

  this.getName = function() {
    return this._name;
  };
}

Существует еще одна серьезная проблема с классом Person. Поскольку JavaScript не поддерживает личные данные, ничто не мешает кому-то еще прийти и изменить имя. Например, следующий код создает персона с именем Colin, а затем меняет его имя на Tom.

var person = new Person("Colin");

person._name = "Tom";
// person.getName() now returns "Tom"

Лично мне бы не понравилось, если бы кто-нибудь мог прийти и юридически сменить мое имя. Чтобы этого не происходило, можно использовать закрытие, чтобы сделать переменную "_name" закрытой. Конструктор Person был переписан ниже с использованием замыкания. Обратите внимание, что "_name" теперь является локальной переменной конструктора Person вместо свойства объекта. Замыкание формируется потому, что внешняя функция Person () предоставляет внутреннюю функцию, создавая открытый метод getName () .

function Person(name) {
  var _name = name;

  this.getName = function() {
    return _name;
  };
}

Теперь, когда вызывается getName (), он гарантированно возвращает значение, которое было первоначально передано конструктору. Кто-то еще может добавить новое свойство "_name" к объекту, но внутренняя работа объекта не будет затронута, пока они ссылаются на переменную, связанную закрытием. Следующий код показывает, что переменная "_name" действительно является закрытой.

var person = new Person("Colin");

person._name = "Tom";
// person._name is "Tom" but person.getName() returns "Colin"

Когда не использовать замыкания

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

в циклах

Создание замыканий внутри циклов может привести к ошибочным результатам. Пример этого показан ниже. В этом примере созданы три кнопки. При нажатии "button1" должно отображаться предупреждение "Нажатая кнопка 1". Аналогичные сообщения должны отображаться для "button2" и "button3". Однако при запуске этого кода на всех кнопках отображается "Нажатая кнопка 4". Это связано с тем, что к тому времени, когда нажата одна из кнопок, цикл завершается, а переменная цикла достигает конечного значения четыре.

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Closures</title>
  <meta charset="UTF-8" />
  <script>
    window.addEventListener("load", function() {
      for (var i = 1; i < 4; i++) {
        var button = document.getElementById("button" + i);

        button.addEventListener("click", function() {
          alert("Clicked button " + i);
        });
      }
    });
  </script>
</head>
<body>
  <input type="button" id="button1" value="One" />
  <input type="button" id="button2" value="Two" />
  <input type="button" id="button3" value="Three" />
</body>
</html>

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

function getHandler(i) {
  return function handler() {
    alert("Clicked button " + i);
  };
}
window.addEventListener("load", function() {
  for (var i = 1; i < 4; i++) {
    var button = document.getElementById("button" + i);
    button.addEventListener("click", getHandler(i));
  }
});

Ненужное использование в конструкторах

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

function Person(name) {
  var _name = name;

  this.getName = function() {
    return _name;
  };

  this.sayHello = function() {
    alert("Hello!");
  };
}

Каждый раз, когда создается экземпляр Person, время тратится на создание метода sayHello (). Если создается много объектов Person, это становится пустой тратой времени. Лучшим подходом было бы добавить sayHello () к прототипу Person. При добавлении к прототипу все объекты Person могут использовать один и тот же метод. Это экономит время в конструкторе, не создавая замыкания для каждого экземпляра. Предыдущий пример переписан ниже с посторонним замыканием, перенесенным в прототип.

function Person(name) {
  var _name = name;

  this.getName = function() {
    return _name;
  };
}

Person.prototype.sayHello = function() {
  alert("Hello!");
};

Что следует помнить

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

ссылка

10
Durgesh Pandey

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

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

var pure = function pure(x){
  return x 
  // only own environment is used
}

var foo = "bar"

var closure = function closure(){
  return foo
  // foo is free variable from the outer environment
}

src: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure

10
soundyogi

MDN объясняет это лучше всего, я думаю:

Замыкания - это функции, которые ссылаются на независимые (свободные) переменные. Другими словами, функция, определенная в замыкании, "запоминает" среду, в которой она была создана.

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

var closure = function() {
  var count = 0;
  return function() {
    count++;
    console.log(count);
  };
};

var counter = closure();

counter() // returns 1
counter() // returns 2
counter() // returns 3

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

9
Brandon Kent

Мне нравится определение замыкания Кайла Симпсона:

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

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

Вот модифицированный пример, который он приводит в своей серии книг "Вы не знаете JS: Области применения и замыкания".

function foo() {
  var a = 2;

  function bar() {
    console.log( a );
  }
  return bar;
}

function test() {
  var bz = foo();
  bz();
}

// prints 2. Here function bar referred by var bz is outside 
// its lexical scope but it can still access it
test(); 
9
TastyCode

Вот как начинающий обернул голову вокруг Closures, как функция обернута внутри тела функции, также известного как Closures.

Определение из книги "Говоря JavaScript" "Закрытие - это функция плюс связь с областью, в которой была создана функция" - Dr.Axel Rauschmayer

Так как это может выглядеть? Вот пример

function newCounter() {
  var counter = 0;
   return function increment() {
    counter += 1;
   }
}

var counter1 = newCounter();
var counter2 = newCounter();

counter1(); // Number of events: 1
counter1(); // Number of events: 2
counter2(); // Number of events: 1
counter1(); // Number of events: 3

newCounter закрывается с увеличением , На счетчик можно ссылаться и получать к нему доступ .

counter1 и counter2 будут отслеживать их собственные значения.

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

8
devlighted

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

В JavaScript это происходит, когда вы:

  • Определить одну функцию внутри другой функции
  • Внутренняя функция вызывается после того, как возвращена внешняя функция
// 'name' is resolved in the namespace created for one invocation of bindMessage
// the processor cannot enter this namespace by the time displayMessage is called
function bindMessage(name, div) {

    function displayMessage() {
        alert('This is ' + name);
    }

    $(div).click(displayMessage);
}
8
Pawel Furmaniak

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

Вы знаете, что это за объекты?

Объекты - это вещи, которые имеют свойства и делают вещи.

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

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

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

8
ejectamenta

Лучший способ - объяснить эти понятия постепенно:

переменные

console.log(x);
// undefined

Здесь undefined - это способ JavaScript сказать: "Я понятия не имею, что означает x".

Переменные похожи на теги.

Можно сказать, тег x указывает на значение 42:

var x = 42;
console.log(x);
// 42

Теперь JavaScript знает, что означает x.

Вы также можете переназначить переменную.

Заставьте тег x указывать на другое значение:

x = 43;
console.log(x);
// 43

Теперь x означает что-то еще.

Scope

Когда вы создаете функцию, она имеет свой "ящик" для переменных.

function A() {
  var x = 42;
}

console.log(x);

// undefined

Снаружи коробки вы не можете видеть, что находится внутри коробки.

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

var x = 42;

function A() {
  console.log(x);
}

// 42

Внутри функции A у вас есть "доступ к области действия" к x.

Теперь, если у вас есть две коробки рядом:

function A() {
  var x = 42;
}

function B() {
  console.log(x);
}

// undefined

Внутри функции B у вас нет доступа к переменным внутри функции A.

Но если вы поместите определение функции B внутри функции A:

function A() {

  var x = 42;

  function B() {
    console.log(x);
  }

}

// 42

Теперь у вас есть "доступ к области".

функция

В JavaScript вы запускаете функцию, вызывая ее:

function A() {
  console.log(42);
}

Как это:

A();

// 42

Функции как значения

В JavaScript вы можете указывать тег на функцию, как указывать на число:

var a = function() {
  console.log(42);
};

Переменная a теперь означает функцию, которую вы можете запустить.

a();
// 42

Вы также можете передать эту переменную:

setTimeout(a, 1000);

В секунду (1000 миллисекунд) вызывается функция a:

// 42

Объем закрытия

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

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

В JavaScript функции сохраняют свой доступ к переменным внешней области видимости. Даже когда их передают куда-то еще.

var a = function() {

  var text = 'Hello!'

  var b = function() {
    console.log(text);
    // inside function `b`, you have access to `text`
  };

  // but you want to run `b` later, rather than right away
  setTimeout(b, 1000);

}

Что происходит сейчас?

// 'Hello!'

Или подумайте об этом:

var c;

var a = function() {

  var text = 'Hello!'

  var b = function() {
    console.log(text);
    // inside function `b`, you have access to `text`
  };

  c = b;

}

// now we are out side of function `a`
// call `a` so the code inside `a` runs
a(); 

// now `c` has a value that is a function
// because what happened when `a` ran

// when you run `c`
c();

// 'Hello!'

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

Даже если a завершил работу, и теперь вы запускаете c вне a.

То, что только что произошло здесь, называется " замыкание " в JavaScript.

7
David Rosson

Закрытие не сложно понять. Это зависит только от точки зрения.

Мне лично нравится использовать их в повседневной жизни.

function createCar()
{
    var rawMaterial = [/* lots of object */];
    function transformation(rawMaterials)
    {
       /* lots of changement here */
       return transformedMaterial;
    }
    var transformedMaterial = transformation(rawMaterial);
    function assemblage(transformedMaterial)
    {
        /*Assemblage of parts*/
        return car;
    }
    return assemblage(transformedMaterial);
}

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

7
Alexis

Там когда-то был пещерный человек

function caveman {

у которого был очень особенный рок,

var rock = "diamond";

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

return {
    getRock: function() {
        return rock;
    }
};
}

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

var friend = caveman();
var rock = friend.getRock();

Довольно умный пещерный человек.

7
NinjaBeetle

Давайте начнем отсюда, как определено в MDN: Замыкания являются функциями, которые ссылаются на независимые (свободные) переменные (переменные, которые используется локально, но определено в прилагаемой области действия). Другими словами, эти функции "запоминают" среду, в которой они были созданы.

Лексическая область видимости
Примите во внимание следующее:

function init() {
  var name = 'Mozilla'; // name is a local variable created by init
  function displayName() { // displayName() is the inner function, a closure
    alert(name); // use variable declared in the parent function    
  }
  displayName();    
}
init();

init () создает локальную переменную с именем name и функцию с именем displayName (). Функция displayName () является внутренней функцией, которая определена внутри init () и доступна только в теле функции init (). Функция displayName () не имеет собственных локальных переменных. Однако, поскольку внутренние функции имеют доступ к переменным внешних функций, displayName () может получить доступ к имени переменной, объявленной в родительской функции init ().

function init() {
    var name = "Mozilla"; // name is a local variable created by init
    function displayName() { // displayName() is the inner function, a closure
        alert (name); // displayName() uses variable declared in the parent function    
    }
    displayName();    
}
init();

Запустите код и обратите внимание, что оператор alert () в функции displayName () успешно отображает значение переменной name, объявленной в ее родительской функции. Это пример лексической области видимости, которая описывает, как синтаксический анализатор разрешает имена переменных, когда функции вложены. Слово "лексический" относится к тому факту, что лексическая область видимости использует место, где переменная объявлена ​​в исходном коде, чтобы определить, где эта переменная доступна. Вложенные функции имеют доступ к переменным, объявленным в их внешней области видимости.

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

function makeFunc() {
  var name = 'Mozilla';
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

Запуск этого кода имеет тот же эффект, что и в предыдущем примере функции init (), приведенной выше: на этот раз строка "Mozilla" будет отображаться в окне предупреждения JavaScript. Что отличается - и интересно - то, что внутренняя функция displayName () возвращается из внешней функции перед выполнением.

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

Причина в том, что функции в JavaScript закрывают формы. Закрытие - это сочетание функции и лексической среды, в которой эта функция была объявлена. Эта среда состоит из любых локальных переменных, которые находились в области действия во время создания замыкания. В этом случае myFunc является ссылкой на экземпляр функции displayName, созданной при запуске makeFunc. Экземпляр displayName поддерживает ссылку на свою лексическую среду, в которой существует имя переменной. По этой причине, когда вызывается myFunc, имя переменной остается доступным для использования, и "Mozilla" передается для оповещения.

Вот немного более интересный пример - функция makeAdder:

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

В этом примере мы определили функцию makeAdder (x), которая принимает один аргумент x и возвращает новую функцию. Функция, которую она возвращает, принимает единственный аргумент y и возвращает сумму x и y.

По сути, makeAdder - это фабрика функций - она ​​создает функции, которые могут добавить конкретное значение к своему аргументу. В приведенном выше примере мы используем нашу фабрику функций для создания двух новых функций - одна добавляет 5 к своему аргументу, а другая добавляет 10.

add5 и add10 - оба замыкания. Они разделяют одно и то же определение тела функции, но хранят разные лексические среды. В лексической среде add5 х равен 5, а в лексической среде для add10 х равен 10.

Практические замыкания

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

Следовательно, вы можете использовать замыкание везде, где вы обычно можете использовать объект только с одним методом.

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

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

body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}

h2 {
  font-size: 1.2em;
}

Наши интерактивные кнопки размера текста могут изменять свойство font-size элемента body, а корректировки будут приниматься другими элементами на странице благодаря относительным единицам. Вот JavaScript:

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

size12, size14 и size16 теперь являются функциями, которые изменят размер основного текста до 12, 14 и 16 пикселей соответственно. Мы можем прикрепить их к кнопкам (в данном случае ссылки) следующим образом:

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>


function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

чтобы узнать больше о замыканиях, посетите ссылка на MDN

6
Alireza

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

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

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

Одним из лучших (или ближайших к простейшим) является пересказ примера Морриса "Замыкания для чайников".

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

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

Доказав и продемонстрировав это себе, я немного поиграл:

http://jsfiddle.net/9ZMyr/2/

function sayHello(name) {
  var text = 'Hello ' + name; // Local variable
  console.log(text);
  var sayAlert = function () {
      alert(text);
  }
  return sayAlert;
}

sayHello(); 
/* This will write 'Hello undefined' to the console (in Chrome anyway), 
but will not alert though since it returns a function handle to nothing). 
Since no handle or reference is created, I imagine a good js engine would 
destroy/dispose of the internal sayAlert function once it completes. */

// Create a handle/reference/instance of sayHello() using the name 'Bob'
sayHelloBob = sayHello('Bob');
sayHelloBob();

// Create another handle or reference to sayHello with a different name
sayHelloGerry = sayHello('Gerry');
sayHelloGerry();

/* Now calling them again demonstrates that each handle or reference contains its own 
unique local variable memory space. They remain in memory 'forever' 
(or until your computer/browser explode) */
sayHelloBob();
sayHelloGerry();

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

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

Для меня это приводит, по крайней мере, к базовым концепциям конструкторов, oop практикам, синглетам против экземпляров с их собственными данными и т.д. И т.д.

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

6
williambq

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

В математике оператор соединения является функцией частично упорядоченного множества, которая возвращает наименьший объект, больший или равный его аргументам. В символах объедините [a, b] = d так, чтобы d> = a и d> = b, но не существует такого e, что d> e> = a или d> e> = b.

Таким образом, соединение дает вам самую маленькую вещь "больше", чем части.

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

Таким образом, замыкание для переменных a, b, c является наименьшей областью действия (в решетке областей действия для вашей программы!), Которая вводит a, b и c в область действия.

6
nomen

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

 var Closure = (function () {
    // This is a closure
    // Any methods, variables and properties you define here are "private"
    // and can't be accessed from outside the function.

    //This is a private variable
    var foo = "";

    //This is a private method
    var method = function(){

    }
})();

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

var Closure = (function () {
  // This is a closure
  // Any methods, variables and properties you define here are "private"
  // and can't be accessed from outside the function.

  //This is a private variable
  var foo = "";

  //This is a private method
  var method = function(){

  }

  //The method will be accessible from outside the closure
  return {
    method: method
  }

})();

Closure.method();

Надеюсь, поможет. С Уважением,

6