js вызовов стек Как работают блокировки JavaScript?



15 Answers

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

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

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




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

О развитии детства: от 5 до 7 лет говорится:

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

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

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

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

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

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

var kitchen = makeKitchen();

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

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

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



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

    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.




ОК, 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");




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

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

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

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

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




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

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




Я стараюсь лучше учиться на тестах GOOD / BAD. Мне нравится видеть рабочий код, за которым следует нерабочий код, с которым кто-то может столкнуться. Я собрал jsFiddle, который делает сравнение, и пытается свести различия к простейшим объяснениям, которые я мог бы придумать.

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

console.log('CLOSURES DONE RIGHT');

var arr = [];

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

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

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

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

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

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

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

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

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

var badArr = createClosureArray();

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

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

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

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

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

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

Результат

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

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




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



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

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

var a = 42;

function b() { return a; }

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


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

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




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




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



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

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

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

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

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

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

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

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

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

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

Затворы

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

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

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

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

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

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

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

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




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

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

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

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

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

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

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

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

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

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




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

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

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

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

Пример:

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

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

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

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

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

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

bar();



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

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




Related