Как работают блокировки JavaScript?

7660
голосов

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

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

задан e-satis 21 сент. '08 в 17:12
источник

89 ответов

  • 1
  • 2
  • 3
5639
голосов

Закрытие 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');

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

Два предложения одного предложения:

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

  • Или закрытие представляет собой стек стека, который выделяется, когда функция запускает свое выполнение и не освобождается после возвращения функции (как если бы "стек стека" был выделен в куче, а не в стеке!).

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

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

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

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

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

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

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

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

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

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

Дополнительные примеры

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

Пример 3

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

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

Пример 4

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

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

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

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

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

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

Пример 5

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

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. Когда анонимные функции вызывают в строке fnlist[j](); все они используют одно и то же единственное замыкание, и они используют текущее значение для i и item внутри этого одного замыкания (где i имеет значение 3, потому что цикл завершен, а item имеет значение 'item2'). Обратите внимание, что мы индексируем из 0, поэтому item имеет значение item2. И я ++ будет увеличивать i до значения 3.

Пример 6

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

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

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

Пример 7

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

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

Резюме

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

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

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

Ссылки

Спасибо

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


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

ответ дан Joel Anair 21 сент. '08 в 17:18
источник
3728
голосов

Всякий раз, когда вы видите ключевое слово 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.

ответ дан Ali 21 сент. '08 в 18:16
источник
2154
голосов

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

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

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

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

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

ответ дан Jacob Swartwood 24 июня '11 в 21:49
источник
653
голосов

Взяв вопрос серьезно, мы должны выяснить, что типичный 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();

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

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

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

Человек соломы

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

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

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

ответ дан jondavidjohn 26 февр. '13 в 22:40
источник
415
голосов

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

    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.

ответ дан Konrad Rudolph 21 сент. '08 в 17:24
источник
321
голос

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

  • Закрытие создается не только при возврате внутренней функции. Фактически, закрывающая функция не должна вообще возвращаться, чтобы ее закрытие было создано. Вместо этого вы можете назначить свою внутреннюю функцию переменной во внешней области или передать ее как аргумент другой функции, где ее можно было бы вызвать немедленно или в любое время позже. Поэтому закрытие закрывающей функции, вероятно, создается, как только вызывающая функция вызывается, поскольку любая внутренняя функция имеет доступ к этому закрытию всякий раз, когда вызывается внутренняя функция, до или после возвращения функции закрытия.
  • Закрытие не ссылается на копию старых значений переменных в своей области.. Переменные сами являются частью замыкания, поэтому значение, наблюдаемое при доступе к одной из этих переменных, является последним значение на момент его доступа. Вот почему внутренние функции, созданные внутри циклов, могут быть сложными, поскольку каждый из них имеет доступ к тем же внешним переменным, а не к захвату копии переменных во время создания или вызова функции.
  • "Переменные" в закрытии включают любые именованные функции, объявленные внутри функции. Они также включают аргументы функции. Закрытие также имеет доступ к его содержащим переменным закрытия, вплоть до глобальной области.
  • Закрытие использует память, но они не вызывают утечки памяти, поскольку JavaScript сам очищает свои собственные круговые структуры, на которые не ссылаются. Internet утечки памяти Explorer, связанные с закрытием, создаются, когда не удается отключить значения атрибутов DOM, которые ссылаются на замыкания, тем самым сохраняя ссылки на возможно круговые структуры.
ответ дан dlaliberte 08 апр. '10 в 16:54
источник
307
голосов

ОК, 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");
ответ дан Max Tkachenko 06 июня '13 в 13:22
источник
295
голосов

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

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

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

  • назначьте его var foo=1; или
  • просто напишите 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
ответ дан Florian Bösch 21 сент. '08 в 17:20
источник
199
голосов

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

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

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

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

Итак, каковы эти объекты закрытия?

ответ дан Nathan Long 07 февр. '11 в 23:57
источник
183
голосов

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

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

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

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 не использует "фреймы стека", но вместо этого использует "heap frames", которые могут сохраняться после вызова функции, которая заставила их вернуться.

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

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



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

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

ответ дан Matt 26 июня '13 в 1:22
источник
182
голосов

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

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

ответ дан Magne 18 февр. '14 в 0:14
источник
175
голосов

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

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

* Вопрос С#

ответ дан Chris S 20 апр. '10 в 11:16
источник
149
голосов

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

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

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

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

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

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

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

var badArr = createClosureArray();

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

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

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

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

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

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

Результат

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

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

ответ дан Chev 19 июня '13 в 23:45
источник
143
голосов

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

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

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

ответ дан mykhal 30 июля '11 в 17:27
источник
117
голосов

Я собрал интерактивный учебник по 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
ответ дан Nathan Whitehead 26 июля '11 в 7:37
источник
101
голос

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

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

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

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

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

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"

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

ответ дан Tero Tolonen 11 мая '15 в 19:35
источник
92
голосов

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

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

var a = 42;

function b() { return a; }

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


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

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

ответ дан floribon 13 февр. '15 в 22:39
источник
80
голосов

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

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

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);
ответ дан someisaac 20 апр. '10 в 11:10
источник
73
голосов

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

ответ дан Rakesh Pai 22 сент. '08 в 0:39
источник
72
голосов

Ты спишь и приглашаешь Дэн. Вы говорите Дэну, чтобы он привел один контроллер 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.");
ответ дан StewShack 20 июля '11 в 6:51
источник
72
голосов

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

// 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
ответ дан Gerardo Lima 03 мая '12 в 21:16
источник
68
голосов

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

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

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

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

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

Представьте, что события кнопки "Переполнение" и "Голос-вниз" реализованы как замыкания, voteUp_click и voteDown_click, которые имеют доступ к внешним переменным isVotedUp и isVotedDown, которые определены глобально. (Для простоты я имею в виду кнопки StakeOverflow 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
}

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

ответ дан John Pick 24 февр. '11 в 4:37
источник
55
голосов

Будучи отцом 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 похожа на factory. Это может быть немного factory с одной машиной внутри. Или он может содержать много других небольших фабрик, каждый из которых имеет множество машин, выполняющих разные рабочие места. В реальной одежде factory у вас могут появиться тряпки и бобины нитки, а футболки и джинсы выйдут наружу. Наш JavaScript factory обрабатывает только данные, он не может шить, просверлить отверстие или расплавить металл. В нашем JavaScript factory поступают данные, и данные выводятся.

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

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

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

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

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

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

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

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

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

Наш код начинается со словарной функции, поэтому мы знаем, что это одно! Тогда имя функции 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);

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

"Пойдем, поди, приди и стань человеком!"

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

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

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

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

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

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

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

ответ дан grateful 29 окт. '14 в 2:53
источник
55
голосов

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Затворы

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

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

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

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

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

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

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

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

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

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

(Вы также можете прочитать Что практическое использование для закрытия в JavaScript?)

ответ дан Arvand 15 авг. '15 в 16:38
источник
47
голосов

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

ответ дан dmi3y 04 марта '13 в 21:27
источник
45
голосов

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

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

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

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

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

ответ дан Stupid Stupid 16 мая '13 в 23:52
источник
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, если внешняя функция была установлена ​​на нуль?

Причина, по которой 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: горилла

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

ответ дан Michael Dziedzic 02 марта '15 в 21:08
источник
43
голосов

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

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

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

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

Пример:

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

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

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

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

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

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

bar();
ответ дан srgstm 25 окт. '12 в 21:12
источник
39
голосов

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

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

ответ дан mjmoody383 09 марта '13 в 6:24
источник
  • 1
  • 2
  • 3

Другие вопросы по меткам