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

Конечно. Это нормальное и нормальное поведение. Вместо закрытия и повторного открытия вы можете rewind сохранить файл.

7647
задан 28 revs, 21 users 17% 9 April 2017 в 13:55
поделиться

8 ответов

Закрытия JavaScript для новичков

Отправленный Morris на вторник, 21.02.2006 10:19. Отредактированный сообществом с тех пор.

Закрытия не являются волшебными

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

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

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

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

Два кратких обзора

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

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

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

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

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

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

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

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

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

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

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

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

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

Больше примеров

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

Пример 3

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

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

Пример 4

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

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

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

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

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

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

Пример 5

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

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

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

Пример 6

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

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

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

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

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

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

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

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

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

item0 undefined
item1 undefined
item2 undefined

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

item0 1
item1 2
item2 3

Пример 7

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

function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj); // attention here: new closure assigned to a new variable!
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;

Сводка

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

Конечные пункты:

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

Ссылки

Спасибо

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


Исходное сообщение Morris может быть найдено в интернет-Архиве.

7080
ответ дан 22 November 2019 в 19:37
поделиться

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

88
ответ дан Rakesh Pai 9 April 2017 в 23:55
поделиться
  • 1
    Если это так, тогда Вы, к сожалению, can' t делают что you' ре, просящее сделать. – Andrew G. Johnson 7 December 2008 в 09:27

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

    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.

484
ответ дан 3 revs, 3 users 65% 9 April 2017 в 23:55
поделиться
  • 1
    I' m не уверенный, что you' ре, пытающееся достигнуть. Вы хотите использовать текстовую область с изображениями для отображения содержания пользователю, или Вы хотите использовать текстовую область в форме, где пользователь предоставляет текст и загружает изображения? – thomasmalt 25 September 2010 в 09:47

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

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.

, Что закрытие. Функция не имеет к [1 138] возврат для вызова закрытия. Просто получающие доступ переменные за пределами Вашего непосредственного лексического контекста создает закрытие .

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 может все еще относиться к [1 110] и 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 вызывается, новый контекст выполнения создается. Вместе с аргументами функции и родительским объектом, этот контекст выполнения также получает все переменные, объявленные за пределами него (в вышеупомянутом примере, и и 'b').

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

Здесь номер x является литеральным числом. Как с другими литералами в JavaScript, когда foo назван, номер x , скопировал в [1 120] как его аргумент 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);

Как ожидалось, каждый вызов к [1 123] увеличит x.memb. То, что не могло бы ожидаться, то, что x просто называет тот же объект age переменная! После того, как несколько вызовов к [1 127], age.memb будут 2! Эта ссылка является основанием для утечек памяти с объектами HTML.

3934
ответ дан 39 revs, 29 users 36% 9 April 2017 в 23:55
поделиться

закрытие во многом как объект. Это инстанцируют каждый раз, когда Вы вызываете функцию.

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

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

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

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

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

Пример

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

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

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

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

Вывод

somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged
363
ответ дан 6 revs, 5 users 49% 9 April 2017 в 23:55
поделиться
  • 1
    Это было предложено моим другом, Pratik Naik ( stackoverflow.com/users/639946/pratik-naik ), кто также, оказывается, находится в рабочей группе направляющих. Don' t-1 мое сообщение из-за этого, я просто распространял знание.:) – Hiral Desai 24 August 2011 в 16:32

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

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

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

Я все еще думаю, что объяснение 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&#39;t return

* AC # question

200
ответ дан 22 November 2019 в 19:37
поделиться

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

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

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);
92
ответ дан 22 November 2019 в 19:37
поделиться
Другие вопросы по тегам:

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