Вопрос

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

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

Это было полезно?

Решение

Замыкания JavaScript для начинающих

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

Закрытие - это не волшебство

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Еще примеры

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

Пример 3

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

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

Пример 4

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

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

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

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

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

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

Пример 5

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

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

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

Пример 6

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

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

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

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

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

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

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

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

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

item0 undefined
item1 undefined
item2 undefined

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

item0 1
item1 2
item2 3

Пример 7

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

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, ни в закрытиях.


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

Другие советы

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

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

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

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

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

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

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

Вкл . Развитие в детском возрасте:От 5 до 7 лет в нем говорится:

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

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

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

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

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

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

var kitchen = makeKitchen();

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

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

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

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

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

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

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

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

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

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

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

Рассмотрим этот вариант

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Полная запись В Блоге (включая соображения по jQuery)

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

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

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

console.log(x + 3);

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

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

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

ОК, 6-летний любитель закрытий.Хотите услышать простейший пример закрытия?

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

Вот как я могу преобразовать свою историю о самолете в код.

var plane = function(defaultAirport) {

  var lastAirportLeft = defaultAirport;

  var car = {
    driver: {
      startAccessPlaneInfo: function() {
        setInterval(function() {
          console.log("Last airport was " + lastAirportLeft);
        }, 2000);
      }
    }
  };
  car.driver.startAccessPlaneInfo();

  return {
    leaveTheAirport: function(airPortName) {
      lastAirportLeft = airPortName;
    }
  }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");

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

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

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

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

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

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

Пример

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

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

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

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

Выходной сигнал

somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged

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

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

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

Полный пост:

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

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

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

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

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

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

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

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

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

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

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

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



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

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

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

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

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

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

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

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

outerFunction(1);​

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

* Вопрос по C #

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

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

console.log('CLOSURES DONE RIGHT');

var arr = [];

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

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

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

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

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

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

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

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

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

var badArr = createClosureArray();

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

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

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

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

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

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

Результат

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

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

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

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

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

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

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

var db = (function() {
    // Create a hidden object, which will hold the data
    // it's inaccessible from the outside.
    var data = {};

    // Make a function, which will provide some access to the data.
    return function(key, val) {
        if (val === undefined) { return data[key] } // Get
        else { return data[key] = val } // Set
    }
    // We are calling the anonymous surrounding function,
    // returning the above inner function, which is a closure.
})();

db('x')    // -> undefined
db('x', 1) // Set x to 1
db('x')    // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.

ems

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

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

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

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

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

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

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

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

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

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

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

Но в 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"

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

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

Вот вам и завершение:

var a = 42;

function b() { return a; }

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


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

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

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

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

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

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

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

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

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

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

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

function sleepOver(howManyControllersToBring) {

    var numberOfDansControllers = howManyControllersToBring;

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

var howManyControllersToBring = 1;

var inviteDan = sleepOver(howManyControllersToBring);

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

var danInvitedPaul = inviteDan(howManyControllersToBring);

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

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

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

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

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

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

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

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

var isVotedUp = false;
var isVotedDown = false;

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

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

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

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

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

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

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

Enter image description here

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

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

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

Enter image description here

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

Enter image description here

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

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

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

Enter image description here

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

Закрытия

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

Enter image description here

Разметка лексической среды:

Enter image description here

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

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

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

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

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

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

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

function sing(person) {

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

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

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

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

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

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

var person="an old lady";

sing(person);

Инструкции

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

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

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

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

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

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

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

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

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

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

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

КРУГЛЫЕ СКОБКИ:"Круглые скобки" или () являются почтовым ящиком на двери фабрики функций JavaScript или почтовым ящиком на улице для отправки пакетов информации на фабрику.Иногда почтовый ящик может быть помечен например cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime), и в этом случае вы знаете, какие данные вы должны ему предоставить.

ФИГУРНЫЕ СКОБКИ:"Фигурные скобки", которые выглядят примерно так {} это тонированные стекла нашего завода.Изнутри фабрики вы можете видеть, что происходит снаружи, но снаружи вы не можете заглянуть внутрь.

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

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

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

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

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

var person="an old lady";

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

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

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

sing(person);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

function the_closure() {
  var x = 4;
  return function () {
    return x; // Here, we look back inside the_closure for the value of x
  }
}

var myFn = the_closure();
myFn(); //=> 4

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

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

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

Замыкания - это функции с состоянием.Это несколько похоже на "this" в том смысле, что "this" также предоставляет состояние для функции, но function и "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();

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

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

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

outerFunction();

ТРЕВОГА:обезьяна

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

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

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

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

ТРЕВОГА:обезьяна

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

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

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

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

outerFunction = null;
alert(referenceToInnerFunction());

ТРЕВОГА:обезьяна ТРЕВОГА:обезьяна

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

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

//////////

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

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

    innerFunction();
}

outerFunction();

ТРЕВОГА:горилла

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top