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

7654

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

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

  • 391
    Моя проблема с этими и многими ответами состоит в том, что они подходят к нему с абстрактной, теоретической точки зрения, а не начинают просто объяснять, почему замыкания необходимы в Javascript и тех практических ситуациях, в которых вы их используете. В конечном итоге вы получаете статью, которую вы должны пролистать, все время думая: «Но почему?». Я бы просто начал с: замыкания - это отличный способ справиться со следующими двумя реалиями JavaScript: a. область видимости находится на уровне функций, а не на уровне блоков и, б. большая часть того, что вы делаете на практике в JavaScript, является асинхронной / управляемой событиями.
  • 53
    @Redsandro С одной стороны, это значительно облегчает написание кода, управляемого событиями. Я мог бы запустить функцию при загрузке страницы, чтобы определить особенности HTML или доступных функций. Я могу определить и установить обработчик в этой функции и иметь всю эту контекстную информацию доступной каждый раз, когда обработчик вызывается без необходимости повторного запроса. Решите проблему один раз, повторно используйте ее на каждой странице, где требуется этот обработчик, с уменьшенными накладными расходами при повторном вызове обработчика. Вы когда-нибудь видели, чтобы одни и те же данные дважды отображались на языке, в котором их нет? Закрытия делают намного проще избежать подобных вещей.
Показать ещё 7 комментариев
Теги:
scope
function
variables
closures

89 ответов

6013
Лучший ответ

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

Представлено Morris on Tue, 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 может хитро возвращать панель функций и 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); } 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() функции, возвращаемый sayAlice() - это точно так же, как и ранее, но без временной переменной.

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

Tricky: также обратите внимание, say переменная say также находится внутри замыкания, и к ней может быть sayAlice() любая другая функция, которая может быть объявлена в 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 и 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 является закрытием ( конечно, вы даже не подумали бы о замене строк исходного кода во время выполнения, но...).
  • Можно получить объявления функций внутри деклараций функций внутри функций & mdash, и вы можете получить закрытие на более чем одном уровне.
  • Я думаю, что обычно замыкание является термином как для функции, так и для захваченных переменных. Обратите внимание, что я не использую это определение в этой статье!
  • Я подозреваю, что замыкания в JavaScript отличаются от тех, которые обычно встречаются на функциональных языках.

связи

благодаря

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


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

  • 160
    Brilliant. Мне особенно нравится: «Закрытие в JavaScript похоже на сохранение копии всех локальных переменных, как это было при выходе из функции».
  • 0
    Хорошая ссылка на явно популярный ответ. Автор делает смелый шаг, используя аналогии с вещами, которые не совсем точны в терминах Javascript, но соответствуют ментальной модели, уже существующей в сознании многих программистов, таких как вызовы функций, создают рамки ставок.
Показать ещё 31 комментарий
4016

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

function foo(x) {
  var tmp = 3;

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

  bar(10);
}

foo(2);

Это всегда будет регистрироваться 16, потому что bar может получить доступ к x который был определен как аргумент 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.

  • 19
    @feeela: Да, каждая функция JS создает замыкание. Переменные, на которые нет ссылок, вероятно, будут иметь право на сборку мусора в современных механизмах JS, но это не меняет того факта, что при создании контекста выполнения этот контекст имеет ссылку на включающий контекст выполнения и его переменные, и эта функция является объектом, который потенциально может быть перемещен в другую переменную область, сохраняя при этом исходную ссылку. Это закрытие.
  • 0
    @ Али Я только что обнаружил, что jsFiddle, который я предоставил, на самом деле ничего не доказывает, так как delete не удается. Тем не менее, лексическая среда, которую функция будет переносить как [[Scope]] (и, в конечном счете, использовать в качестве основы для своей собственной лексической среды при вызове), определяется при выполнении оператора, определяющего функцию. Это означает , что функция закрывает за полное содержание исполняющей сферы, независимо от того, каких значений он на самом деле относится к и выходит ли объем. Пожалуйста, смотрите разделы 13.2 и 10 в спецификации
Показать ещё 3 комментария
2273

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

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

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

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

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

  • 314
    Мне действительно нравится это объяснение. Для тех, кто читает и не следует, аналогия такова: функция princess () представляет собой сложную область, содержащую личные данные. За пределами функции личные данные не могут быть видны или доступны. Принцесса держит в своем воображении единорогов, драконов, приключений и т. Д. (Личные данные), и взрослые не могут увидеть их сами. НО воображение принцессы запечатлено в замыкании для функции story() , которая является единственным интерфейсом, littleGirl экземпляр littleGirl предоставляет миру магии.
  • 0
    Итак, здесь story - это закрытие, но если бы код был var story = function() {}; return story; тогда littleGirl будет закрытием. По крайней мере, такое впечатление, которое я получаю от использования MDN «закрытых» методов с замыканиями : «Эти три публичные функции являются замыканиями, которые используют одну и ту же среду».
Показать ещё 8 комментариев
662

Принимая этот вопрос всерьез, мы должны выяснить, что типичный 6-летний человек способен когнитивно, хотя, по общему признанию, тот, кто интересуется 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 функции getTrashBag делает это здесь.
  • 3
    На самом деле, смешение, вызов функции makeKitchen является фактическим закрытием, а не объект кухни , что она возвращается.
  • 3
    Пробираясь через остальных, я нашел этот ответ как самый простой способ объяснить, что и почему закрывает .is.
Показать ещё 1 комментарий
537

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

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

Довольно очевидное решение

// Declare counter outside event handler 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 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 the charm!");

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

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

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

Простое однострочное закрытие

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

  • 9
    Я не согласен с вашим определением, что такое закрытие. Нет причин, по которым он должен вызывать себя. Также немного упрощенно (и неточно) говорить, что его нужно «вернуть» (об этом много говорится в комментариях к верхнему ответу на этот вопрос).
  • 37
    @ Джеймс, даже если вы не согласны, его пример (и весь пост) - один из лучших, которые я видел. Хотя вопрос не старый и не решен для меня, он вполне заслуживает +1.
Показать ещё 6 комментариев
450

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

    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.

  • 24
    Согласен. Предоставление значимым именам функций вместо традиционных «foobar» также мне очень помогает. Семантика имеет значение.
  • 43
    поэтому в псевдо-языке это в основном похоже на alert(x+3, where x = 5) . where x = 5 - это замыкание. Я прав?
Показать ещё 8 комментариев
361

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

  • Закрытие создается не только при возврате внутренней функции. Фактически, закрывающая функция не должна вообще возвращаться, чтобы ее замыкание было создано. Вместо этого вы можете назначить свою внутреннюю функцию переменной во внешней области или передать ее как аргумент другой функции, где ее можно было бы вызвать немедленно или в любое время позже. Поэтому закрытие закрывающей функции, вероятно, создается, как только вызывающая функция вызывается, поскольку любая внутренняя функция имеет доступ к этому закрытию всякий раз, когда вызывается внутренняя функция, до или после возвращения функции закрытия.
  • Закрытие не ссылается на копию старых значений переменных в своей области. Сами переменные являются частью замыкания, поэтому значение, наблюдаемое при доступе к одной из этих переменных, является самым последним значением в момент его доступа. Вот почему внутренние функции, созданные внутри циклов, могут быть сложными, поскольку каждый из них имеет доступ к тем же внешним переменным, а не к захвату копии переменных во время создания или вызова функции.
  • "Переменные" в закрытии включают любые именованные функции, объявленные внутри функции. Они также включают аргументы функции. Закрытие также имеет доступ к своим содержащим переменным замыкания, вплоть до глобальной области.
  • Закрытие использует память, но они не вызывают утечки памяти, поскольку JavaScript сам по себе очищает свои собственные циклические структуры, на которые не ссылаются. Утечки памяти Internet Explorer, связанные с закрытием, создаются, когда не удается отключить значения атрибутов DOM, которые ссылаются на блокировки, тем самым сохраняя ссылки на возможно круговые структуры.
  • 19
    Кстати, я добавил этот «ответ» с разъяснениями, чтобы не обращаться к исходному вопросу напрямую. Вместо этого я надеюсь, что любой простой ответ (для 6-летнего ребенка) не содержит неверных представлений об этой сложной теме. Например, популярный вики-ответ выше гласит: «Закрытие - это когда вы возвращаете внутреннюю функцию». Помимо грамматической ошибки, это технически неправильно.
  • 13
    Джеймс, я сказал, что замыкание «вероятно» создается во время вызова включающей функции, потому что вполне вероятно, что реализация может отложить создание замыкания до некоторого времени спустя, когда решит, что замыкание абсолютно необходимо. Если в функции включения не определена внутренняя функция, закрытие не требуется. Поэтому, возможно, он может подождать, пока будет создана первая внутренняя функция, чтобы затем создать замыкание из контекста вызова включающей функции.
Показать ещё 11 комментариев
348

ОК, 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");
  • 24
    Хорошо сыграно и отвечает оригинальному постеру. Я думаю, что это лучший ответ. Я собирался использовать багаж аналогичным образом: представьте, что вы идете в дом бабушки, и вы упаковываете свой футляр Nintendo DS с игровыми картами внутри своего футляра, но затем кладете футляр в свой рюкзак и кладете игровые карты в карманы рюкзака, и Затем вы положили все это в большой чемодан с большим количеством игровых карт в карманах чемодана. Добравшись до дома бабушки, вы можете играть в любую игру на своем DS, если все внешние случаи открыты. Или что-то в этом роде.
322

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

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

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

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

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

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

пример

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
  • 50
    Ничего себе, никогда не знал, что вы можете использовать строковые замены в console.log как это. Если кому-то еще интересно, есть еще: developer.mozilla.org/en-US/docs/DOM/…
  • 6
    Переменные, которые находятся в списке параметров функции, также являются частью замыкания (например, не ограничиваются только var ).
208

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

Следующий простой пример охватывает все основные моменты закрытия 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, где переменные всегда привязаны к пространству хранения и никогда не относятся к значениям.

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

  • 0
    Что бы произошло, если бы вы позвонили: second_calculator = first_calculator (); вместо second_calculator = make_calculator (); ? Должно быть так же, верно?
  • 3
    @Ronen: поскольку first_calculator является объектом (а не функцией), вы не должны использовать скобки в second_calculator = first_calculator; , поскольку это присваивание, а не вызов функции. Чтобы ответить на ваш вопрос, будет только один вызов make_calculator, поэтому будет создан только один калькулятор, а переменные first_calculator и second_calculator будут ссылаться на один и тот же калькулятор, поэтому ответы будут 3, 403, 4433, 44330.
206

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

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

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

Полное сообщение:

Итак, что это за закрытие?

  • 0
    Так можно ли было бы подчеркнуть основное преимущество замыканий на этом примере? Скажем, у меня есть функция emailError (sendToAddress, errorString), после чего я мог бы сказать devError = emailError("[email protected]", errorString) а затем иметь свою собственную версию общей функции emailError?
  • 7
    После того, как я пролистал весь путь «расколов», я начал наконец понимать, для чего они. Я подумал про себя: "О, это как частные переменные в объекте?" и бац. Это был следующий ответ, который я прочитал.
196

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

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

178

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

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

/*
*    When a function is defined in another function and it
*    has access to the outer function 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);​

Изображение 1972

* AС# вопрос

  • 19
    Если вы прочитаете описание, вы увидите, что ваш пример неверен. Вызов innerFunction находится в области действия внешней функции, а не, как говорится в описании, после возврата внешней функции. Всякий раз, когда вы вызываете externalFunction, создается новая innerFunction, которая затем используется в области видимости.
  • 8
    @ Мосс, это не мои комментарии, они разработчик Google
Показать ещё 3 комментария
161

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

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

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

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

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

var db = (function() {
    // Create a hidden object, which will hold the data
    // it 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 impossible to access the data object itself.
// We are able to get or set individual it.

Эмс

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

  • 0
    Это лучшее объяснение закрытия JavaScript. Должен быть выбранный ответ. Остальные достаточно интересны, но этот на самом деле полезен практическим способом для реальных JavaScript-кодеров.
156

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

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

результат

CLOSURES DONE RIGHT
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

CLOSURES DONE WRONG
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10

  • 1
    Хорошее дополнение, спасибо. Просто для ясности можно представить, как создается «плохой» массив в «плохом» цикле с каждой итерацией: 1-я итерация: [function () {return 'n =' + 0;}] 2-я итерация: [( function () {return 'n =' + 1;}), (function () {return 'n =' + 1;})] 3-я итерация: [(function () {return 'n =' + 2;}) , (function () {return 'n =' + 2;}), (function () {return 'n =' + 2;})] и т. д. Таким образом, каждый раз, когда значение индекса изменяется, оно отражается во всех функциях уже добавлен в массив.
  • 1
    Использование let для var исправляет разницу.
Показать ещё 3 комментария
122

Я собрал интерактивный учебник по 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
121

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

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

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

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

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

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

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

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

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

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

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"

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

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

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

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

var a = 42;

function b() { return a; }

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


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

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

  • 4
    Этот ответ, похоже, не поможет сбить с толку людей. Грубым эквивалентом в традиционном языке программирования может быть создание b () как метода для объекта, который также имеет частную константу или свойство a . На мой взгляд, удивительно то , что объект сфера JS эффективно обеспечивает как свойство , а не постоянной. a И вы заметите это важное поведение, только если измените его, как return a++;
  • 0
    Именно то, что сказал Джон. До того, как я наконец-то ухватился за замыкания, мне было трудно найти практические примеры. Да, floribon создал закрытие, но для необразованного меня это ничему не научило бы.
Показать ещё 1 комментарий
87

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

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

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);
  • 4
    К вашему сведению: запуск вышеуказанных шоу => 9
  • 0
    Небольшое уточнение о возможной двусмысленности. Когда я сказал: «На самом деле функция включения вообще не должна возвращаться». Я не имел в виду «не возвращать значение», но «все еще активен». Таким образом, пример не показывает этот аспект, хотя он показывает другой способ передачи внутренней функции во внешнюю область. Основной момент, который я пытался сделать, касается времени создания замыкания (для функции включения), так как некоторые люди, кажется, думают, что это происходит, когда функция включения возвращается. Другой пример необходим, чтобы показать, что замыкание создается при вызове функции.
77

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

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

Вы спите, и вы приглашаете Дэн. Вы говорите Дэну, чтобы он привел один контроллер 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 invitation = Dan invitation.

var danInvitedPaul = inviteDan(howManyControllersToBring);

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

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

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

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

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

Изображение 1973

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

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

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

Изображение 1974

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

Изображение 1975

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

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

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

Изображение 1976

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

Затворы

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

Изображение 1977

Маркировка LexicalEnvironments:

Изображение 1978

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

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

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

Обобщить:

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

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

70

Функции JavaScript могут получить доступ к следующим функциям:

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

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

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

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

Представьте, что события кнопки "Переполнение стека" и "Голос-вниз" реализованы как закрытие, voteUp_click и voteDown_click, которые имеют доступ к внешним переменным isVotedUp и isVotedDown, которые определены глобально. (Для простоты я имею в виду кнопки StateOverflow Question Vote, а не массив кнопок Answer Vote).

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

var isVotedUp = false;
var isVotedDown = false;

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

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

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

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

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

58

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

function sing(person) {

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

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

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

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

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

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

var person="an old lady";

sing(person);

ИНСТРУКЦИИ

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

var person="an old lady";

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

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

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

sing(person);

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

"Come on sing, Come and get person!"

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

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

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

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

После замыканий приходят линии

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

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

50

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

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

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

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

function playingInBrothersRoom (withToys) {
  // We closure toys which we played in the brother 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 inner use.

  var brotherGivesToyBack = function (toy) {
    // New request. There is not yet closureToys on brother 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 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, чтобы поиграть с ним.

46

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

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

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

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

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

  • 0
    Кажется, что замыкание эквивалентно классам и внутренним классам в ОО
45

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

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

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

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

Пример:

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

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

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

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

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

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

bar();
44

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

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

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

outerFunction();

ALERT: обезьяна

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

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

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

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

ALERT: обезьяна

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

И здесь все становится действительно интересным. Если бы мы избавились от 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 все еще знает значение outerVar теперь, когда для параметра externalFunction установлено значение null?

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

//////////

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

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

    innerFunction();
}

outerFunction();

ALERT: горилла

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

42

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

Изображение 1979

function f1()..> Light Red Box

function f2()..> Red Small Box

Здесь мы имеем две функции: 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() вызывающий здесь:

Изображение 1980

  • 0
    @KennethWorden Что вы подразумеваете под закрытием ??? Это очень простое закрытие. Функция f2 () может получить доступ к переменной вне ее области, рассматриваемой как замыкание
41

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

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

  • 0
    Я согласен: упомянутая страница Mozilla особенно проста и лаконична. Удивительно, но ваш пост не был оценен так широко, как другие.
32

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

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

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

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

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

Кроме того, это очень полезный метод, который используется во многих известных средах, включая 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 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 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

30

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

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

000001 (jamiesHouse)

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

001 000001 (annVille.jamiesHouse)

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

01 001 000001 (myOldCountry.annVille.jamiesHouse)

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

000001 (jamiesHouse)

Ужасный...

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

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

02 001 000001 (myNewCountry.annVille.jamiesHouse)

Мнения?

Что еще, у меня есть масса вопросов о терпении современного шестилетнего...

  • 8
    так .. что здесь за клочок? jamiesHouse? Я не понимаю
  • 0
    Нет. AnnVille - это закрытие. Если вы живете в annVille, вы можете напрямую позвонить в .jamiesHouse. Если вы живете за пределами AnnVille, вам придется позвонить в annVille.jamiesHouse (конечно, при условии, что я решил представить население annVille всему миру). Имейте в виду, что это предназначено, чтобы быть учебником для ребенка 6 лет, без абсолютно никакого вовлеченного кода. Точное объяснение, очевидно, потребует более подробной информации.
29

В закрытии 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
27

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

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

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
26

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

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

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

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

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

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

Основной пример закрытий в JavaScript:

function showName (firstName, lastName) {

  var nameIntro = "Your name is ";
  // this inner function has access to the outer function 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 scope​
  });
​});

Закрытие правил и побочных эффектов

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

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

function celebrityName (firstName) {
    var nameIntro = "This celebrity is ";
    // this inner function has access to the outer function 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 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 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 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 variable​
mjID.getID(); // 567: It returns the updated celebrityId variable


3. Закрытие Gone Awry

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


Подробнее можно найти here-

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

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

23

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

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

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 newbies - игнорировать "undefined" в конце), то у вас уже есть бессловесное понимание. В словах переменная i является частью закрытия экземпляра внутренней функции.

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

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

  • 0
    1 2 3 1 2 3 . Ага. Но на более традиционном языке я бы ожидал синтаксических ошибок, иначе 1 1 1 1 1 1 .
  • 0
    FWIW, чтобы получить эффект 1 1 1 1 1 1 в JS, вы можете поставить var j = i; внутри внутренней функции, а затем используйте console.log(j++); вместо. Бесполезно, но более знакомо некоторым из нас. Поэтому в этом сценарии я думаю, что замыкания более интуитивны для непосвященных, чем для многих обученных программистов.
Показать ещё 4 комментария
22

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

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

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

Изображение 1983

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 на другом языке, я сделаю аналогию.

Подумайте как

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

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

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

(Я не учитываю 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":

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

21

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

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

К 6-летнему я хотел бы подчеркнуть практический аспект закрытия:

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');

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

См. Мой другой ответ, иллюстрирующий параллелизм между ОО и закрытием:

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

  • 1
    «Параллельная» ссылка была весьма полезна для меня (учитывая мое прошлое ООП). Для баланса это также помогает, если также прочитать это: «Закрытия и объекты эквивалентны» buff.ly/1gpPIBY
  • 1
    Mozilla соглашается: «вы можете использовать замыкание везде, где вы можете использовать объект только одним методом». developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
20

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

Изображение 1984

JavaScript запускает код в два этапа:

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

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

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

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

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

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

  • Это неявная, постоянная связь между функцией и ее цепочкой областей...
  • Указание функции (лямбда) скрыто [[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);

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

20

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

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

"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();

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

Изображение 1981

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

Изображение 1982

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

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

20

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

Пример:

function showPostCard(Sender, Receiver) {

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

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

    return PreparePostCard();
}
showPostCard("Granny", "Olivia");
20

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

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

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

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

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

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

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

Языки, такие как Java, предоставляют возможность объявлять методы private, что означает, что их можно вызывать только другими методами в одном классе. 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);
    });
});

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

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

19

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

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

Пример 1:

Изображение 1985

Пример 2:

Изображение 1986

Пример 3: Изображение 1987

18

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

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

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

Изображение 1988

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

Изображение 1989

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

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)

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

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

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

Изображение 1990

17

Закрытие - это то, что многие разработчики 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? Поблагодарите могущественное закрытие.

Почему это должно меня беспокоить?

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

16

Следующий пример - простая иллюстрация закрытия 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
16

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

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

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

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

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

15

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

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

var a = 1;

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

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

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

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

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

var c = a(3);

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

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

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

var d = c(4);

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

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

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

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

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

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

var a = new Array();

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

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

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

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

14

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

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

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

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

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

Ошибка в мухе

KISS - это смуще! :)

13

Закрытие Понимание начинается с Scope Chain

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

Цепочка охвата:

Каждый фрагмент кода JavaScript (глобальный код или функции) имеет связанную с ним цепочку областей видимости. Эта цепочка областей видимости - это список или цепочка объектов, которые определяют переменные, которые являются "в области" для этого кода. Когда JavaScript нужно искать значение переменной x (процесс, называемый переменным разрешением), он начинается с поиска первого объекта в цепочке. Если этот объект имеет свойство с именем x, используется значение этого свойства. Если первый объект не имеет свойства с именем x, JavaScript продолжает поиск со следующим объектом в цепочке. Если второй объект не имеет свойства с именем x, поиск переходит к следующему объекту и так далее. Если x не является свойством какого-либо из объектов в цепочке областей видимости, то x не является видимым для этого кода, и возникает ошибка ReferenceError. В коде JavaScript верхнего уровня (т.е. Код, не содержащийся в каких-либо определениях функций) цепочка областей видимости состоит из одного объекта, глобального объекта. В не-вложенной функции цепочка областей видимости состоит из двух объектов. Первый - это объект, который определяет параметры функций и локальные переменные, а второй - глобальный объект. Во вложенной функции цепочка областей действия имеет три или более объектов. Важно понять, как создается эта цепочка объектов. Когда функция ОПРЕДЕЛЕНА, она сохраняет цепочку областей действия в действии. Когда эта функция INVOKED, она создает новый объект для хранения своих локальных переменных и добавляет этот новый объект в цепочку хранимых цепей для создания новой более длинной цепочки, которая представляет область действия для этой функции. Это становится более интересным для вложенных функций, потому что каждый раз, когда вызывается внешняя функция, внутренняя функция определяется снова. Поскольку цепочка областей действия различна для каждого вызова внешней функции, внутренняя функция будет тонко различной при каждом ее определении - код внутренней функции будет идентичным при каждом вызове внешней функции, но цепочка областей, связанная с этим код будет другим. Это понятие цепи видимости имеет решающее значение для понимания закрытий.

Закрытие:

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

Пример:

var scope = "global scope"; // A global variable
function checkscope() {
  var scope = "local scope"; // A local variable
  function f() {
    return scope;
  } // Return the value in scope here
  return f();
}
checkscope(); // => "local scope"

Функция checkscope() объявляет локальную переменную, а затем определяет и вызывает функцию, возвращающую значение этой переменной. Вам должно быть понятно, почему вызов checkscope() возвращает "локальную область". Теперь немного изменим код. Можете ли вы сказать, что этот код вернется?

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

Вот конкретный пример закрытия:

var uniqueInteger = (function() {//Определить и вызывать var counter = 0;//Частное состояние функции ниже return function() {return counter++;};})();

Чтобы понять этот код, вы должны внимательно его прочитать. На первый взгляд первая строка кода выглядит так, что она назначает функцию переменной uniqueInteger. Фактически, код определяет и вызывает (как намекнул открытая скобка в первой строке) функцию, поэтому это возвращаемое значение функции, которая присваивается uniqueInteger. Теперь, если мы изучим тело функции, мы видим, что его возвращаемое значение является другой функцией. Именно этому вложенному объекту функции присваивается uniqueInteger. Вложенная функция имеет доступ к переменным в области видимости и может использовать переменную счетчика, определенную во внешней функции. Как только эта внешняя функция вернется, ни один другой код не сможет увидеть переменную счетчика: внутренняя функция имеет эксклюзивный доступ к ней.

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

function counter() {var n = 0; return { count: function() { return n++; return {count: function() {return n++; }, reset: function() { n = 0; }, reset: function() {n = 0; } }; }}; } var c = counter(), d = counter(); //Создаем два счетчика c.count(); //=> 0 d.count(); //=> 0: они подсчитывают независимо c.reset(); // методы reset() и count() совместно используют состояние c.count(); //=> 0: потому что мы перезагружаем c d.count(); //=> 1: d не был сброшен

Функция counter() возвращает объект "counter". Этот объект имеет два метода: count() возвращает следующее целое число, а reset() сбрасывает внутреннее состояние. Первое, что нужно понять, это то, что два метода совместно используют доступ к частной переменной n. Второе, что нужно понять, это то, что каждый вызов функции counter() создает новую цепочку областей видимости и новую приватную переменную. Поэтому, если вы дважды вызываете counter(), вы получаете два объекта-счетчика с разными частными переменными. Вызов count() или reset() для одного объекта счетчика не влияет на другой.

Закрытие легко понять, если вы просто принимаете лексическое правило определения: функции выполняются с использованием цепочки областей действия, которая действовала, когда они были определены. Однако некоторые программисты считают, что запутывание запутывает, потому что они попадают в подробности imple-. Разумеется, они думают, что локальные переменные, определенные во внешней функции, перестают существовать, когда внешняя функция возвращается, так как может вложенная функция выполнить с использованием цепочки областей, которая больше не существует? Если вы задаетесь вопросом об этом your-, то вы, вероятно, были подвержены низкоуровневым языкам программирования, таким как C и архитектурам на основе стека: если функции локальных переменных определены в стеке процессора, тогда они действительно прекратили бы есть, когда функция вернулась. Но помните наше определение сферы действия. Мы описали его как список объектов, а не стек привязок. Каждый раз, когда вызывается функция JavaScript, создается новый объект для хранения локальных переменных для этого вызова, и этот объект добавляется в цепочку областей видимости. Когда функция возвращается, этот объект привязки переменных удаляется из цепочки областей видимости. Если не было вложенных функций, ссылок на объект привязки больше нет, и он собирает мусор. Если были определены вложенные функции, то каждая из этих функций имеет ссылку на цепочку областей видимости, и эта цепочка областей относится к объекту привязки переменных. Однако, если эти объекты вложенных функций остаются в пределах их внешней функции, то они сами будут собирать мусор вместе с объектом привязки переменных, на который они ссылаются. Но если функция определяет вложенную функцию и возвращает ее или сохраняет ее в каком-либо свойстве, тогда будет внешняя ссылка на вложенную функцию. Это не будет сбор мусора, а объект привязки переменных, к которому он относится, не будет собираться с мусором.

13

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

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

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

Пример:

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

Источник

13

Pinocchio: Закрытие в 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\ 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

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

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

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

Закрытие - это особый способ, которым Mr.Coder сохраняет за собой все его магические игры в бейсбол.

13

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

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

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

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

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

13

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

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

  • 0
    Мне нравится, ассоциация близкого друга - закрытие, приятно. Хорошая мимика для сохранения картинки не только для ребенка :) Хоть и немного размыто, звучит немного как «когда ты вырастешь, ты поймешь», мне бы не хотелось таких ответов, когда мне было 6: D, но мне нравится сейчас
12

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

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

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

  • 1
    Я не верю, что это даже закрытие.
  • 1
    @JohnGibb Я считаю, что создает замыкание (функции могут получить доступ к вычисляемому объекту по имени), но затем вообще не использует замыкание.
Показать ещё 2 комментария
12

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

  • Он может передаваться как ценность и

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

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

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

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

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

12

закрытие

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

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

Простая аналогия

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

function tourist(home) {
  var places = [];
  return {
    country: function() {
      console.log("Country: " + home);
    },
    visit: function(destination) {
      places.push(destination);
    },
    tell: function() {
      for (var i = 0; i < places.length; i++) {
        console.log(places[i]);
      }
    }
  };
}
var x = tourist('India');
x.visit('Ireland');
x.country();
x.tell();
  • И дома, и места являются локальными переменными для функции туриста
  • Функция возвращает объект, который образует замыкание по переменным внутри функции
  • Каждый раз, когда вызывается функция, замыкание формируется в среде, которая сохраняет текущее состояние локальных переменных.
  • Переменные места являются частными и к ним нельзя получить доступ напрямую, используя объект x.

Другой пример

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
  • Оба add5 и add10 являются закрытием.
  • Они используют одно и то же определение тела, но сохраняют разные среды.
  • В среде add5 x равно 5, а в отношении add10 - x.

использование

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

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

Кроме того, JavaScript не поддерживает частные методы/переменные, которые можно имитировать с помощью закрытий.

Дополнительные ссылки

12

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

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

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

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

Изображение 1991

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>
  • 1
    IMG здесь используется в качестве теста на дальтонизм.
11

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

Пример:

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"
10

Закрытие - это несколько продвинутая и часто неправильно понятая особенность языка 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

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

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

  return doAdd();
}

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

Creating Closures

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

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), замыкание создается и сохраняется в "increment". В среде ссылок закрытия ссылок значение "1" привязано к значению 1. Перечисленные переменные также считаются закрытыми. Здесь происходит закрытие имени. Когда вызывается приращение (2), вводится замыкание. Это означает, что doAdd() вызывается, а переменная value1 имеет значение 1. Закрытие по существу можно рассматривать как создание следующей функции.

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

When to Use Closures

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

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

Замыкания полезны при использовании в сочетании с функциями setTimeout() и setInterval(). Чтобы быть более конкретным, замыкания позволяют передавать аргументы функции обратного вызова setTimeout() и setInterval(). Например, следующий код печатает строку "некоторое сообщение" один раз в секунду, вызывая 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 отображает "undefined" (поскольку на showMessage() фактически не передается значение). Чтобы обойти эту проблему, можно создать замыкание, которое связывает аргумент "сообщение" с желаемым значением. Затем замыкание можно использовать в качестве функции обратного вызова для 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. Когда создается каждый человек, ему присваивается имя через аргумент "имя". Внутри пользователь сохраняет свое имя в переменной "_name". После хороших методов объектно-ориентированного программирования метод getName() также предоставляется для извлечения имени.

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

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

Есть еще одна серьезная проблема с классом Person. Поскольку JavaScript не поддерживает личные данные, ничто не мешает кому-то еще прийти и изменить имя. Например, следующий код создает Person с именем 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"

When Not to Use Closures

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

В циклах

Создание замыканий внутри циклов может привести к ошибочным результатам. Пример этого показан ниже. В этом примере создаются три кнопки. Когда нажимается кнопка "1", должно отображаться предупреждение, которое гласит "Щелкнуть кнопку 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));
  }
});

Unnecessary Use in Constructors

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

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

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

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

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

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

9

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

Мне нравится Kyle Simpson определение закрытия:

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

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

Вот измененный пример, который он дает в своей серии книг "You Do not Know JS: Scopes & Closures".

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

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

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

  • 2
    Не обязательно быть строкой раньше, вы можете объявить var или функцию в той же области видимости, в которой вы объявляете функцию после объявления этой функции, и она тоже это увидит. JavaScript это не переводчик строк.
  • 1
    Я полагаю, что 6-летнему ребенку было бы легче визуализировать этот пример, вместо того, чтобы понимать, как концепция охвата и интерпретатор вступают в игру.
8

Вот как новичок обернул одну голову вокруг 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

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

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

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

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

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

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

8

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

В 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);
}
7

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

Я лично люблю использовать их в повседневной жизни.

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

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

function caveman {

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

var rock = "diamond";

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

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

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

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

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

7

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

переменные

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 означает что-то еще.

Объем

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

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.

6

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

Лексический обзор
Рассмотрим следующее:

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" передается в alert.

Вот несколько интересный пример - функция 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 x равно 5, тогда как в лексической среде для add10 x равно 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

Самый простой пример использования, который я могу придумать, чтобы объяснить закрытие 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();

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

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

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

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

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

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

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

6

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

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

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

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

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

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

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

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

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

6

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

Это похоже на то, что некоторые функции покрывают.

Таким образом, для 6-летнего, это можно объяснить, давая аналогию. Скажем, я строю робота. Этот робот может многое сделать. Среди этих вещей я запрограммировал его, чтобы подсчитать количество птиц, которых он видит в небе. Каждый раз, когда он видел 25 птиц, он должен был сказать мне, сколько птиц он видел с самого начала.

Я не знаю, сколько птиц он видел, если он не сказал мне. Только он знает. Это личное пространство. Это в основном память робота. Скажем, я дал ему 4 ГБ.

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

Эта аналогия немного сосательна, но кто-то может ее улучшить, я думаю.

5

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

Закрытие можно сравнить с книгой с закладкой на книжной полке.

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

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

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

Пример кода:

function book() {
   var pages = [....]; //array of pages in your book
   var bookMarkedPage = 20; //bookmarked page number
   function getPage(){
       return pages[bookMarkedPage];
   }
   return getPage;
}

var myBook = book(),
    myPage = myBook.getPage();

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

Таким образом, функция call book() возвращает ссылку на закрытие, то есть не только функцию, но и ссылку на книгу и ее контекст, то есть ссылку на функцию getPage, состояние страниц и переменные bookMarkedPage.

Некоторые моменты, которые следует учитывать:

Пункт 1: Книжная полка, как и стек функций, имеет ограниченное пространство, поэтому используйте ее с умом.

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

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

4

Также... Возможно, мы должны немного сократить ваш 27-летний друг, потому что вся концепция "закрытия" на самом деле (!)... вуду!

Под этим я подразумеваю: (а) вы не интуитивно ожидаете этого... И... (б) когда кто-то тратит время, чтобы объяснить это вам, вы, конечно же, не ожидаете, что это сработает!

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

Когда вы, наконец, поймете, что такое возможно, тогда... уверен... любая реакция после-факта будет: "whoa-aaa (!)... kew-el-lll... (!! !)"

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

Как я сказал: "Это вуду".

  • 0
    Меня попросили рассмотреть этот ответ (который я считаю блестящим), но я разрываюсь, поскольку он служит для расширения первоначального вопроса ОП без ответа.
  • 0
    Я думаю, что это актуально, потому что ОП "пытался объяснить своему другу" ... ЧТО ТАКОЕ "закрытие". Случалось, что замыкания были одной из тех вещей, которые мне самому было трудно обернуть, когда я впервые столкнулся с ними. Многие идеи в компьютерном программировании интуитивно понятны. Закрытия нет. «Получение указателя на функцию», конечно, эта часть очевидна. Но чтобы эта функция, когда она выполняется, волшебным образом имела доступ к переменным в контексте, существовавшем во время создания указателя функции ?! И все же это так.
Показать ещё 1 комментарий
4

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

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

Если "это" означает нечто иное, чем в другом месте, то вы знаете, что это два разных закрытия.

2

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

function multiplier(n) {
    function multiply(x) {
          return n*x;
    }
    return mutliply;
}

var 10xmultiplier = multiplier(10);
var x = 10xmultiplier(5); // x= 50

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

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

Мы можем добиться этого, потому что Javascript (в дополнение к прототипному ООП) позволяет программировать функционально, когда функции более высокого порядка могут принимать другие функции в качестве аргументов (функции класса fisrt). функциональное программирование в Википедии

Я настоятельно рекомендую вам прочитать эту книгу Кайла Симпсона: 2 одна часть серии книг посвящена закрытию, и она называется областью и закрытием. вы не знаете js: бесплатное чтение на github

2

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

var ClusureDemo = function() {
    //privare variables
    var localVa1, localVa2;

    //private functions
    var setVaOne = function(newVa) {
        localVa1 = newVa;
    },
    setVaTwo = function(newVa) {
        localVa2 = newVa;
    },
    getVaOne = function() {
        return localVa1;
    },
    getVaTwo = function() {
        return localVa2;
    };

    return {
        //public variables and functions
        outVaOne : localVa1,
        outVaTwo : localVa2,
        setVaOne : setVaOne,
        setVaTwo : setVaTwo,
        getVaOne : getVaOne,
        getVaTwo : getVaTwo
    };
};

//Test Demo
var app = new ClusureDemo();
app.outVaOne = 'Hello Variable One';
app.outVaTwo = 'Hello Variable Two';
app.setVaOne(app.outVaOne);
app.setVaTwo(app.outVaTwo);

alert(app.getVaOne());
alert(app.getVaTwo());

демонстрация

2

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

2

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

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

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

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

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

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

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

var closure = createclosure(varForClosure);
closure(param1);  // closure has access to whatever createclosure gave it access to,
                  // including the parameter storing varForClosure.

против

var contextvar = varForClosure; // use a struct for storing more than one..
contextclosure(contextvar, param1);

против

var contextobj = new contextclass(varForClosure);
contextobj->objclosure(param1);

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

-5

Пример вызывающей функции.

 var clicked  = false;
 for(var i=0;i<temp.length;i++){
   (function(index){
     if(clicked) return false;
     $(temp[index]).click(function(){
     if($(temp[index]).text()=="" && !$(".cell1").val()){
       $(this).text(player1Val);
       $(".cell1").val(true);
       console.log("first player clicked ");
       clicked = true;
       return false;
    }
});
})(i);
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню