Вопрос по javascript, scope, closures, function, variables – Как работают закрытия JavaScript?

7652

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

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

Для программистов на Java краткий ответ заключается в том, что это эквивалент функции внутреннего класса. Внутренний класс также содержит неявный указатель на экземпляр внешнего класса и используется для почти той же цели (то есть для создания обработчиков событий). Boris van Schooten
Понял это намного лучше отсюда:javascriptissexy.com/understand-javascript-closures-with-ease, Все еще нужнаclosure на закрытии после прочтения других ответов. :) Akhoy
Я нашел этот практический пример очень полезным:youtube.com/watch?v=w1s9PgtEoJs Abhi
Моя проблема с этими и многими ответами состоит в том, что они подходят к нему с абстрактной, теоретической точки зрения, а не начинают просто объяснять, почему замыкания необходимы в Javascript и в практических ситуациях, в которых вы их используете. В итоге вы получаете статью, которую вам нужно пролистать, все время думая: «Но, почему?». Я бы просто начал с: замыкания - это отличный способ справиться со следующими двумя реалиями JavaScript: a. область видимости находится на уровне функций, а не на уровне блоков и, б. большая часть того, что вы делаете на практике в JavaScript, является асинхронной / управляемой событиями. Jeremy Burton
@Redsandro С одной стороны, это значительно облегчает написание кода, управляемого событиями. Я мог бы запустить функцию при загрузке страницы, чтобы определить особенности HTML или доступных функций. Я могу определить и установить обработчик в этой функции и иметь всю эту контекстную информацию доступной каждый раз, когда обработчик вызывается без необходимости повторного запроса. Решите проблему один раз, повторно используйте на каждой странице, где нужен этот обработчик, с уменьшенными накладными расходами при повторном вызове обработчика. Вы когда-нибудь видели, чтобы одни и те же данные дважды отображались на языке, на котором их нет? Закрытия делают намного проще избежать подобных вещей. Erik Reppen

Ваш Ответ

30   ответов
205

Closures are simple:

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

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

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

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

If you are familiar with stack frames, these calculators seem strange: How can they keep accessing n after make_calculator returns? The answer is to imagine that JavaScript doesn't use "stack frames", but instead uses "heap frames", which can persist after the function call that made them returns.

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

That is pretty much all there is to closures.



* For example, it covers all the points in the "Closures for Dummies" article given in другой ответЗа исключением примера 6, который просто показывает, что переменные могут использоваться до их объявления, это хороший факт, который нужно знать, но совершенно не связанный с замыканиями. Это также охватывает все моменты впринятый ответ, за исключением точек (1), в которых функции копируют свои аргументы в локальные переменные (аргументы именованной функции), и (2) что при копировании чисел создается новый номер, но при копировании ссылки на объект вы получаете другую ссылку на тот же объект. Это также полезно знать, но опять же совершенно не связано с замыканиями. Это также очень похоже на пример вэтот ответ но немного короче и менее абстрактно. Это не распространяется на точкуэтот ответ или жеэтот комментарийЭто то, что JavaScript затрудняет подключениеcurrent значение переменной цикла в вашей внутренней функции: & quot; вставка & quot; Шаг может быть выполнен только с помощью вспомогательной функции, которая включает вашу внутреннюю функцию и вызывается на каждой итерации цикла. (Строго говоря, внутренняя функция обращается к копии переменной вспомогательной функции, а не к чему-либо подключенному.) Опять же, очень полезно при создании замыканий, но не является частью того, что такое замыкание или как оно работает. Существует дополнительная путаница из-за того, что замыкания работают по-разному в функциональных языках, таких как ML, где переменные связаны со значениями, а не с пространством хранения, предоставляя постоянный поток людей, которые понимают замыкания таким образом (а именно, «подключая»), что просто неверно для JavaScript, где переменные всегда связаны с пространством хранения, а не со значениями.

** Any outer function, if several are nested, or even in the global context, as этот ответ указывает четко.

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

eir parents, even after their parents are gone. This is what closures are for functions.

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

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

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

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

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

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

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

Can you explain closures to a 5-year-old?*

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

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

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

outerFunction(1);​

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

*A C# question

@syockit Нет, Мосс не прав. Закрытие созданоregardless от того, экранирует ли функция когда-либо область действия, в которой она определена, и безоговорочно созданная ссылка на лексическую среду родителя делает все переменные в родительской области действия доступными для всех функций, независимо от того, вызываются ли они вне или внутри области действия в которые они были созданы.
@ Мосс, это не мои комментарии, они разработчик Google.
Видя, что на innerFunction не ссылаются за пределы области видимости externalFunction, является ли интерпретатор достаточно умным, чтобы видеть, что он не нуждается в закрытии?
Если вы прочитаете описание, вы увидите, что ваш пример неверен. Вызов innerFunction находится в области действия внешней функции, а не, как говорится в описании, после возврата внешней функции. Каждый раз, когда вы вызываете externalFunction, создается новая innerFunction, которая затем используется в области видимости.
Код является «правильным» в качестве примера замыкания, даже если он не касается части комментария об использовании замыкания после возврата externalFunction. Так что это не отличный пример. Существует много других способов использования замыкания, которые не включают в себя возврат внутренней функции. например innerFunction может быть передана другой функции, где она вызывается немедленно или сохраняется и вызывается через некоторое время, и во всех случаях она имеет доступ к контексту externalFunction, который был создан при ее вызове.
3882

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

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.

Тотis закрытие Функция не должнаreturn для того, чтобы называться закрытием.Simply accessing variables outside of your immediate lexical scope creates a closure.

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

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

Здесь числоx это буквальное число. Как и с другими литералами в JavaScript, когдаfoo называется, номерx являетсяcopied вfoo в качестве аргументаx.

С другой стороны, JavaScript всегда использует ссылки при работе с объектами. Если сказать, ты звонилfoo с объектом, возвращаемое закрытие будетreference этот оригинальный объект!

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

@feeela: Да, каждая функция JS создает замыкание. Переменные, на которые нет ссылок, вероятно, будут иметь право на сборку мусора в современных механизмах JS, но это не меняет того факта, что при создании контекста исполнения этот контекст имеет ссылку на окружающий контекст выполнения и его переменные, и эта функция является объектом, который потенциально может быть перемещен в другую переменную область, сохраняя при этом эту исходную ссылку. Это закрытие.
Замыкания являются ответом JavaScript на объектно-ориентированное программирование на основе классов. JS не основан на классах, поэтому нужно было найти другой способ реализации некоторых вещей, которые не могли бы быть реализованы иначе.
Это был хороший ответ, пока он не попытался объяснить примитивные типы и ссылки. Это совершенно неправильно и говорит о копируемых литералах, которые на самом деле не имеют ничего общего.
это должен быть принятый ответ. Волшебство никогда не происходит во внутренней функции. Это происходит при назначении внешней функции переменной. Это создает новый контекст выполнения для внутренней функции, поэтому & quot; приватная переменная & quot; можно накапливать. Конечно, это возможно, поскольку переменная, которой назначена внешняя функция, поддерживает контекст. Первый ответ просто усложнит ситуацию, не объясняя, что на самом деле там происходит.
@ Я только что обнаружил, что предоставленная мной jsFiddle на самом деле ничего не доказывает, так какdelete выходит из строя. Тем не менее, лексическая среда, которую функция будет переносить как [[Scope]] (и, в конечном счете, использовать в качестве основы для собственной лексической среды при вызове), определяется, когда выполняется оператор, определяющий функцию. Это означает, что функцияis закрытие ВСЕХ содержимого исполняемой области, независимо от того, к каким значениям оно фактически относится и выходит ли из области. Пожалуйста, посмотрите на разделы 13.2 и 10 вthe spec
49

что он знает, что такое функция, что такое переменная и какие данные):

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

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

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

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

Кажется, что замыкание эквивалентно классам и внутренним классам в O.O.
201

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

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

338

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

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

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

  1. assign it with var foo=1; or
  2. just write var foo;

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

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

Example

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

Output

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

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

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

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

Также следует понимать, что локальные переменные в 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();
6664
JavaScript closures for beginners Submitted by Morris on Tue, 2006-02-21 10:19. Community-edited since. Closures are not magic

чтобы программист мог их понять & # x2014; используя рабочий код JavaScript. Это не для гуру или функциональных программистов.

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

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

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

Two brief summaries

When a function (foo) declares other functions (bar and baz), the family of local variables created in foo is not destroyed when the function exits. The variables merely become invisible to the outside world. foo can therefore cunningly return the functions bar and baz, and they can continue to read, write and communicate with each other through this closed-off family of variables ("the closure") that nobody else can meddle with, not even someone who calls foo again in future.

A closure is one way of supporting first-class functions; it is an expression that can reference variables within its scope (when it was first declared), be assigned to a variable, be passed as an argument to a function, or be returned as a function result.

An example of a closure

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

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 вы можете рассматривать ссылочную переменную функции как указатель на функцию.as well как скрытый указатель на замыкание.

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

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

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

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

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

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

More examples

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

Example 3

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

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

Example 4

Все три глобальные функции имеют общую ссылку наsame закрытие, потому что все они объявлены в течение одного вызова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

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

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

Example 5

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

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()или к нему можно обратиться рекурсивно в рамках внутренней функции.

Example 6

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

Вы должны понимать & quot; подъем переменных & quot; особенность в Javascript, чтобы понять этот пример.

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

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

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

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

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

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

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

item0 undefined
item1 undefined
item2 undefined

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

item0 1
item1 2
item2 3
Example 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;

Summary

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

Final points: Whenever you use function inside another function, a closure is used. Whenever you use eval() inside a function, a closure is used. The text you eval can reference local variables of the function, and within eval you can even create new local variables by using eval('var foo = …') When you use new Function(…) (the Function constructor) inside a function, it does not create a closure. (The new function cannot reference the local variables of the outer function.) A closure in JavaScript is like keeping a copy of all the local variables, just as they were when a function exited. It is probably best to think that a closure is always created just an entry to a function, and the local variables are added to that closure. A new set of local variables is kept every time a function with a closure is called (given that the function contains a function declaration inside it, and a reference to that inside function is either returned or an external reference is kept for it in some way). Two functions might look like they have the same source text, but have completely different behavior because of their 'hidden' closure. I don't think JavaScript code can actually find out if a function reference has a closure or not. If you are trying to do any dynamic source code modifications (for example: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));), it won't work if myFunction is a closure (of course, you would never even think of doing source code string substitution at runtime, but...). It is possible to get function declarations within function declarations within functions… and you can get closures at more than one level. I think normally a closure is a term for both the function along with the variables that are captured. Note that I do not use that definition in this article! I suspect that closures in JavaScript differ from those normally found in functional languages. Links Douglas Crockford's simulated private attributes and private methods for an object, using closures. A great explanation of how closures can cause memory leaks in IE if you are not careful. Thanks

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

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

Это звучит неплохо: & quot; Закрытие в JavaScript похоже на сохранение копии всех локальных переменных, как это было при выходе из функции. & Quot; Но это вводит в заблуждение по нескольким причинам. (1) Вызов функции не должен выходить, чтобы создать замыкание. (2) Это не копияvalues локальных переменных, но сами переменные. (3) Он не говорит, кто имеет доступ к этим переменным.
@ e-удовлетворительно - блестящая, как может показаться, «копия всех локальных переменных, так же, как они были при выходе из функции». вводит в заблуждение. Предполагается, что значения переменных копируются, но на самом деле это набор самих переменных, который не изменяется после вызова функции (за исключением «eval» может быть:blog.rakeshpai.me/2008/10/…). Предполагается, что функция должна вернуться до создания замыкания, но ее не нужно возвращать, прежде чем замыкание можно будет использовать в качестве замыкания.
Мне нравится, как этот пост начинается большими жирными буквами с надписью "Закрытие не волшебство". и заканчивает свой первый пример «Волшебством является то, что в JavaScript ссылка на функцию также имеет секретную ссылку на замыкание, которое было создано в».
Brilliant. Мне особенно нравится: «Закрытие в JavaScript похоже на сохранение копии всех локальных переменных, как это было при выходе из функции». e-satis
47

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

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

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

outerFunction();

ALERT: обезьяна

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

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

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

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

ALERT: обезьяна

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

И вот тут все становится действительно интересно. Если бы мы избавились от externalFunction, скажем, установили его в null, вы могли бы подумать, что referenceToInnerFunction потеряет свой доступ к значению externalVar. Но это не так.

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

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

outerFunction = null;
alert(referenceToInnerFunction());

ALERT: обезьяна ALERT: обезьяна

Но как это так? Как referenceToInnerFunction может все еще знать значение externalVar теперь, когда externalFunction был установлен в нуль?

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

//////////

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

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

    innerFunction();
}

outerFunction();

ALERT: горилла

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

550

The Straw Man

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

Fairly Obvious Solution

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

Теперь это сработает, но оно вторгается во внешнюю область, добавляя переменную, единственная цель которой - отслеживать количество. В некоторых ситуациях это будет предпочтительнее, поскольку вашему внешнему приложению может потребоваться доступ к этой информации. Но в этом случае мы меняем только поведение каждого третьего клика, поэтому предпочтительноenclose this functionality inside the event handler.

Consider this option

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.This behavior allows any function to have access to the scope in which it was created, indefinitely. Чтобы практически применить это, я немедленно вызываю функцию, которая возвращает другую функцию, и поскольку функция, которую я возвращаю, имеет доступ к внутренней переменной count (из-за поведения замыкания, описанного выше), это приводит к закрытой области видимости для использования в результате функция ... не так просто? Давайте разбавим его ...

A simple one-line closure

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

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

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

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

+1 заclosure behavior, Можем ли мы ограничитьclosure behavior вfunctions в javascript или это понятие также может быть применено к другим структурам языка?
Хороший пример, потому что он показывает, что "считать" во 2-м примере сохраняет значение «количество» и не сбрасывать в 0 каждый раз, когда «элемент» нажата. Очень информативно!
"I need to know how many times a button has been clicked, and do something on every third click..." Это привлекло мое внимание. Вариант использования и решение, показывающее, как замыкание не является такой загадочной вещью, и что многие из нас пишут их, но точно не знают официального названия.
@ Джеймс, даже если вы не согласны, его пример (и весь пост) - один из лучших, которые я видел. Хотя вопрос не старый и не решен для меня, он вполне заслуживает +1. e-satis
Я не согласен с вашим определением того, что такое закрытие. Нет причин, по которым он должен вызывать себя. Также немного упрощенно (и неточно) говорить, что он должен быть «возвращен». (много дискуссий по этому поводу в комментариях главного ответа на этот вопрос)
74

Arguments Locals (that is, their local variables and local functions) Environment, which includes: globals, including the DOM anything in outer functions

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

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

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

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

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

var isVotedUp = false;
var isVotedDown = false;

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

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

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

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

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

463

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

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

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

console.log(x + 3);

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

@ Jus12: точно. За кулисами замыкание - это просто пространство, где хранятся текущие значения переменных (& # x201C; bindings & # x201D;), как в вашем примере.
@ Матт, я не согласен. Примером являетсяnot должен исчерпывающе документировать все свойства. Это должно бытьreductive и проиллюстрировать характерную особенность концепции. ОП попросил простое объяснение (& # x201C; для шестилетнего & # x201D;). Примите принятый ответ: оно совершенноfails при предоставлении краткого объяснения, именно потому, что оно пытается быть исчерпывающим. (Я согласен с вами, что важным свойством JavaScript является то, что привязка выполняется по ссылке, а не по значению; но, опять же, успешное объяснение сводится к минимуму.)
Согласен. Назначение функциям значимых имен вместо традиционного «foobar» они мне тоже очень помогают. Семантика имеет значение.
так что в псевдо-языке, это в основном какalert(x+3, where x = 5),where x = 5 это закрытие. Я прав?
Это именно тот пример, который вводит в заблуждение многих людей, думая, что этоvalues которые используются в возвращаемой функции, а не самой изменяемой переменной. Если бы оно было изменено на «return x + = y» или, что еще лучше, на ту и другую функцию «x * = y», то было бы ясно, что ничего не копируется. Если вы привыкли складывать кадры, представьте себе, что вместо этого используйте кадры кучи, которые могут продолжать существовать после возврата из функции.
83

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

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

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

A closure is not only created when you return an inner function. In fact, the enclosing function does not need to return at all. You might instead assign your inner function to a variable in an outer scope, or pass it as an argument to another function where it could be used immediately. Therefore, the closure of the enclosing function probably already exists at the time that enclosing function was called since any inner function has access to it as soon as it is called.

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

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

Closures done right:
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]());
}

In the above code createClosure(n) is invoked in every iteration of the loop. Note that I named the variable n to highlight that it is a new variable created in a new function scope and is not the same variable as index which is bound to the outer scope.

This creates a new scope and n is bound to that scope; this means we have 10 separate scopes, one for each iteration.

createClosure(n) returns a function that returns the n within that scope.

Within each scope n is bound to whatever value it had when createClosure(n) was invoked so the nested function that gets returned will always return the value of n that it had when createClosure(n) was invoked.

Closures done wrong:
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]());
}

In the above code the loop was moved within the createClosureArray() function and the function now just returns the completed array, which at first glance seems more intuitive.

What might not be obvious is that since createClosureArray() is only invoked once only one scope is created for this function instead of one for every iteration of the loop.

Within this function a variable named index is defined. The loop runs and adds functions to the array that return index. Note that index is defined within the createClosureArray function which only ever gets invoked one time.

Because there was only one scope within the createClosureArray() function, index is only bound to a value within that scope. In other words, each time the loop changes the value of index, it changes it for everything that references it within that scope.

All of the functions added to the array return the SAME index variable from the parent scope where it was defined instead of 10 different ones from 10 different scopes like the first example. The end result is that all 10 functions return the same variable from the same scope.

After the loop finished and index was done being modified the end value was 10, therefore every function added to the array returns the value of the single index variable which is now set to 10.

Result

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

Я имею в виду, что каждая функция технически является замыканием, но важной частью является то, что функция определяет новую переменную внутри. Функция, которая получает возвращает только ссылкиn создан в новом закрытии. Мы просто возвращаем функцию, чтобы сохранить ее в массиве и вызвать позже.
Если вы хотите просто сохранить результат в массиве на первой итерации, вы можете встроить его следующим образом:arr[index] = (function (n) { return 'n = ' + n; })(index);, Но тогда вы сохраняете результирующую строку в массиве, а не вызываемую функцию, которая опровергает точку моего примера.
Не здесь "Закрытие сделано правильно" пример "замыкания внутри замыкания"?
Хорошее дополнение, спасибо. Просто, чтобы сделать это более понятным, можно представить, как «плохо» Массив создается в & quot; плохом & quot; цикл с каждой итерацией: 1-я итерация: [function () {return & apos; n = & apos; + 0;}] 2-я итерация: [(function () {return 'n =' + 1;}), (function () {return & npos '+ =; + 1;})] 3-я итерация: [( function () {return 'n =' + 2;}), (function () {return 'n =' + 2;}), (function () {return n = = + 2; })] и т. д. Таким образом, каждый раз, когда значение индекса изменяется, оно отражается во всех функциях, уже добавленных в массив.
С помощьюlet заvar исправляет разницу.
73

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

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

Enter image description here

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

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

First, when a function f is created, it is not created in an empty space. There is a current LexicalEnvironment object. In the case above, it’s window (a is undefined at the time of function creation).

Enter image description here

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

Enter image description here

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

Nested functions

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

Enter image description here

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

Closures

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

Enter image description here

Разметка LexicalEnvironments:

Enter image description here

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

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

The whole point is to ensure that if the inner function wants to access an outer variable in the future, it is able to do so.

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

The inner function keeps a reference to the outer LexicalEnvironment. The inner function may access variables from it any time even if the outer function is finished. The browser keeps the LexicalEnvironment and all its properties (variables) in memory until there is an inner function which references it.

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

714

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

НаРазвитие детства: от 5 до 7 лет это говорит:

Your child will be able to follow two-step directions. For example, if you say to your child, "Go to the kitchen and get me a trash bag" they will be able to remember that direction.

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

The kitchen is a closure that has a local variable, called trashBags. There is a function inside the kitchen called getTrashBag that gets one trash bag and returns it.

Мы можем закодировать это в 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

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

Each time makeKitchen() is called, a new closure is created with its own separate trashBags. The trashBags variable is local to the inside of each kitchen and is not accessible outside, but the inner function on the getTrashBag property does have access to it. Every function call creates a closure, but there would be no need to keep the closure around unless an inner function, which has access to the inside of the closure, can be called from outside the closure. Returning the object with the getTrashBag function does that here.
На самом деле, смущает, функция makeKitchencall это фактическое закрытие, а не кухонный объект, который он возвращает.
Слишком много меню и закусок, не хватает мяса и картошки. Вы можете улучшить этот ответ одним коротким предложением, например: «Закрытие - это запечатанный контекст функции, поскольку отсутствует какой-либо механизм определения объема, предоставляемый классами».
Пробираясь через других, я нашел этот ответ как самый простой способ объяснить, что и почему закрывает .is.
353

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

  • A closure is not only created when you return an inner function. In fact, the enclosing function does not need to return at all in order for its closure to be created. You might instead assign your inner function to a variable in an outer scope, or pass it as an argument to another function where it could be called immediately or any time later. Therefore, the closure of the enclosing function is probably created as soon as the enclosing function is called since any inner function has access to that closure whenever the inner function is called, before or after the enclosing function returns.
  • A closure does not reference a copy of the old values of variables in its scope. The variables themselves are part of the closure, and so the value seen when accessing one of those variables is the latest value at the time it is accessed. This is why inner functions created inside of loops can be tricky, since each one has access to the same outer variables rather than grabbing a copy of the variables at the time the function is created or called.
  • The "variables" in a closure include any named functions declared within the function. They also include arguments of the function. A closure also has access to its containing closure's variables, all the way up to the global scope.
  • Closures use memory, but they don't cause memory leaks since JavaScript by itself cleans up its own circular structures that are not referenced. Internet Explorer memory leaks involving closures are created when it fails to disconnect DOM attribute values that reference closures, thus maintaining references to possibly circular structures.
Эту статью трудно читать, но я думаю, что она действительно поддерживает то, что я говорю. В нем говорится: «Закрытие формируется путем возврата объекта функции [...] или путем непосредственного присвоения ссылки на такой объект функции, например, глобальной переменной.» Я не имею в виду, что GC не имеет значения. Скорее, из-за GC и потому, что внутренняя функция присоединена к контексту вызова внешней функции (или [[scope]], как говорится в статье), тогда не имеет значения, возвращается ли вызов внешней функции, потому что эта связь с внутренняя функция - важная вещь.
Кстати, я добавил этот «ответ» с разъяснениями, чтобы не обратиться к первоначальному вопросу напрямую. Вместо этого я надеюсь, что любой простой ответ (для 6-летнего ребенка) не содержит неправильных представлений об этом сложном предмете. Например. популярный вики-ответ выше гласит: «Закрытие - это когда вы возвращаете внутреннюю функцию». Помимо грамматической ошибки, это технически неправильно.
Джеймс, я сказал, что закрытие "вероятно" созданный во время вызова включающей функции, потому что вполне вероятно, что реализация может отложить создание замыкания до некоторого времени спустя, когда решит, что замыкание абсолютно необходимо. Если в функции включения не определена внутренняя функция, закрытие не требуется. Поэтому, возможно, он может подождать, пока первая внутренняя функция не будет создана, чтобы затем создать замыкание из контекста вызова включающей функции.
@ Beetroot-Beetroot Предположим, у нас есть внутренняя функция, которая передается другой функции, где она используется.before внешняя функция возвращается, и предположим, что мы также возвращаем ту же внутреннюю функцию из внешней функции. В обоих случаях это одинаково одна и та же функция, но вы говорите, что перед возвратом внешней функции внутренняя функция является «связанной»; в стек вызовов, тогда как после возврата внутренняя функция внезапно связывается с замыканием. Он ведет себя одинаково в обоих случаях; семантика идентична, так что вы не просто говорите о деталях реализации?
@ Beetroot-Beetroot, спасибо за ваш отзыв, и я рад, что вы заставили вас задуматься. Я все еще не вижу какой-либо семантической разницы между живым контекстом внешней функции и тем же контекстом, когда он становится закрытием, когда функция возвращается (если я понимаю ваше определение). Внутренняя функция не заботится. Сборку мусора не волнует, поскольку внутренняя функция в любом случае поддерживает ссылку на контекст / закрытие, и вызывающая сторона внешней функции просто отбрасывает свою ссылку на контекст вызова. Но это сбивает с толку людей, и, возможно, лучше просто назвать это контекстом вызова.
58

который в настоящее время обучает детей младшего возраста (и относительного новичка в области кодирования без формального образования, поэтому требуются исправления), я думаю, что этот урок лучше всего использовать в практической игре. Если шестилетний ребенок готов понять, что такое закрытие, тогда он достаточно взрослый, чтобы пойти самому. Я предлагаю вставить код в 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);

INSTRUCTIONS

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

КОД: Все написанное выше называетсяcode, Это написано в JavaScript.

JAVASCRIPT: JavaScript это язык. Как английский или французский или китайский языки. Есть много языков, которые понимают компьютеры и другие электронные процессоры. Для того чтобы JavaScript был понятен компьютеру, нужен переводчик. Представьте, что учитель, который говорит только по-русски, приходит преподавать ваш класс в школе. Когда учитель говорит: & # x432; & # x441; & # x435; & # x441; & # x434; & # x44F; & # x442; & # x441; & # x44F; & quot ;, класс не поймет. Но, к счастью, в вашем классе есть русский ученик, который говорит всем, что это значит «все садятся». - так вы все делаете. Класс подобен компьютеру, а русский ученик - переводчик. Для JavaScript самый распространенный интерпретатор называется браузером.

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

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

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

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

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

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

Note that /*...*/ and // stop code being read by the browser.

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

РОДИТЕЛИ: «круглые скобки»; или же() являются почтовым ящиком на двери фабрики функций JavaScript или почтовым ящиком на улице для отправки пакетов информации на фабрику. Иногда почтовый ящик может быть отмеченfor example cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime)в этом случае вы знаете, какие данные вы должны предоставить.

СКОБЫ: «Скобки» которые выглядят так{} тонированные окна нашей фабрики. Внутри фабрики вы можете видеть снаружи, но снаружи вы не можете видеть внутри.

THE LONG CODE EXAMPLE ABOVE

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

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

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

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

var person="an old lady";

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

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

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

sing(person);

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

"Come on sing, Come and get person!"

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

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

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

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

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

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

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

2311

Like the old Albert said : "If you can't explain it to a six-year old, you really don't understand it yourself.”. Well I tried to explain JS closures to a 27 years old friend and completely failed.

Can anybody consider that I am 6 and strangely interested in that subject ?

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

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

Once upon a time:

Там была принцесса ...

function princess() {

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

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */

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

    return {

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

        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

Но все, что они увидят, это маленькая девочка ...

var littleGirl = princess();

... рассказывать истории о магии и фэнтези.

littleGirl.story();

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

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

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

@BenjaminKrupp Я добавил явный комментарий к коду, чтобы показать / показать, что в теле тела есть больше операций.princess чем то, что написано. К сожалению, эта история сейчас немного неуместна в этой теме. Первоначально вопрос заключался в том, чтобы «объяснить закрытие JavaScript для 5-летней давности»; мой ответ был единственным, который даже пытался это сделать. Я не сомневаюсь, что это с треском провалилось, но, по крайней мере, этот ответ мог бы иметь интерес к 5-летнему возрасту.
На самом деле, для меня это имело смысл. И я должен признать, что, наконец, понимание закрытия JS с использованием рассказов о принцессах и приключениях заставляет меня чувствовать себя немного странно.
Так вотstory это закрытие, но если бы код былvar story = function() {}; return story; затемlittleGirl будет закрытием. По крайней мере, такое впечатление, которое я получаю отMDN's use of 'private' methods with closures: "Those three public functions are closures that share the same environment."
@ icc97, да,story является закрытием, ссылающимся на среду, предоставляемую в рамкахprincess. princess также другойimplied закрытие, то естьprincess иlittleGirl поделится любой ссылкой наparents массив, который будет существовать обратно в среде / области, гдеlittleGirl существует иprincess определено.
Мне действительно нравится это объяснение. Для тех, кто читает и не следует, аналогия такова: функция princess () представляет собой сложную область, содержащую личные данные. Вне функции частные данные не могут быть видны или доступны. Принцесса хранит единорогов, драконов, приключений и т. Д. В своем воображении (личные данные), и взрослые не могут увидеть их сами. НО воображение принцессы захвачено в закрытии дляstory() функция, которая является единственным интерфейсомlittleGirl экземпляр попадает в мир магии.
224

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

Closures are a way to let a function have persistent, private variables - that is, variables that only one function knows about, where it can keep track of info from previous times that it was run.

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

Полный пост:

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

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

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

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

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

Imagine - you are playing with your little brothers and sisters in the entire house, and you are moving around with your toys and brought some of them into your older brother's room. After a while your brother returned from the school and went to his room, and he locked inside it, so now you could not access toys left there anymore in a direct way. But you could knock the door and ask your brother for that toys. This is called toy's closure; your brother made it up for you, and he is now into outer scope.

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

78

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

Страница закрытий Mozilla, Это самый лучший, самыйconcise and simple explanation основ закрытия и практического использования, которые я нашел. Настоятельно рекомендуется всем, кто изучает JavaScript.

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

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

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

In computer science, a closure is a function together with a referencing environment for the nonlocal names (free variables) of that function.

Технически, вJavaScript, every function is a closure, Он всегда имеет доступ к переменным, определенным в окружающей области видимости.

посколькуscope-defining construction in JavaScript is a function, а не блок кода, как во многих других языках,what we usually mean by closure in JavaScript этоfunction working with nonlocal variables defined in already executed surrounding function.

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

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

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

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

Эмс

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

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

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

Хорошо сыграно и отвечает оригинальному постеру. Я думаю, что это лучший ответ. Я собирался использовать багаж аналогичным образом: представьте, что вы идете в дом бабушки, и вы упаковываете свой футляр Nintendo DS с игровыми картами внутри своего футляра, но затем кладете футляр в свой рюкзак и также кладете игровые карты в карманы рюкзака , а затем вы положили все это в большой чемодан с большим количеством игровых карт в карманах чемодана. Когда вы попадаете в дом бабушки, вы можете играть в любую игру на своем DS, если все внешние случаи открыты. Или что-то в этом роде.
102

I do not understand why the answers are so complex here.

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

var a = 42;

function b() { return a; }

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


There is no reason to believe closures are a complex design hack to address specific problems. No, closures are just about using a variable that comes from a higher scope from the perspective of where the function was declared (not run).

Now what it allows you to do can be more spectacular, see other answers.

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

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

Похожие вопросы