Конечно. Это нормальное и нормальное поведение. Вместо закрытия и повторного открытия вы можете rewind
сохранить файл.
Отправленный 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 ссылка на функцию также имеет секретную ссылку на закрытие, это было создано в — подобный тому, как делегаты являются указателем метода плюс секретная ссылка на объект.
По некоторым причинам закрытия кажутся действительно трудными понять, когда Вы читаете о них, но когда Вы видите некоторые примеры, становится ясно, как они работают (это взяло меня некоторое время). Я рекомендую работать через примеры тщательно, пока Вы не понимаете, как они работают. Если Вы начинаете использовать закрытия, полностью не понимая, как они работают, Вы скоро создали бы некоторые очень странные ошибки!
Этот пример показывает, что локальные переменные не копируются — они сохранены ссылкой. Это - как будто стековый фрейм остается в живых в памяти даже после внешних функциональных выходов!
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
Все три глобальных функции имеют общую ссылку к тому же закрытию, потому что они все объявляются в единственном вызове к 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, каждый раз, когда Вы объявляете функцию в другой функции, внутренняя функция (функции) воссоздается снова каждый раз, когда внешняя функция вызвана.)
Этот пример показывает, что закрытие содержит любые локальные переменные, которые были объявлены во внешней функции, прежде чем это вышло. Обратите внимание что переменная 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()
, или к этому можно было получить доступ рекурсивно во внутренней функции.
Этот - реальный глюк для многих людей, таким образом, необходимо понять это. Будьте очень осторожны при определении функции в цикле: локальные переменные от закрытия не могут действовать, как Вы могли бы сначала думать.
Необходимо понять "переменную спускоподъемная" функция в 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
В этом заключительном примере каждый вызов к основной функции создает отдельное закрытие.
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(…)
(Функциональный конструктор) в функции, это не создает закрытие. (Новая функция не может сослаться на локальные переменные внешней функции.)myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));
), это не будет работать если myFunction
закрытие (конечно, Вы даже не думали бы о выполнении строковой замены исходного кода во времени выполнения, но...).Если Вы только что изучили закрытия (здесь или в другом месте!), затем я интересуюсь любой обратной связью от Вас о любых изменениях, Вы могли бы предположить, что это могло сделать эту статью более четкой. Пошлите электронное письмо morrisjohns.com (morris_closure). Обратите внимание на то, что я не гуру на JavaScript — ни на закрытиях.
Исходное сообщение Morris может быть найдено в интернет-Архиве.
Закрытие состоит в том, где внутренняя функция имеет доступ к переменным в его внешней функции. Это - вероятно, самое простое короткое объяснение, которое можно получить для закрытий.
Закрытия трудно объяснить, потому что они используются, чтобы заставить некоторое поведение работать, что все интуитивно ожидают работать так или иначе. Я нахожу лучший способ объяснить их (и способ, которым я изучил то, что они делают), должен вообразить ситуацию без них:
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.
Каждый раз, когда Вы видите функциональное ключевое слово в другой функции, внутренняя функция имеет доступ к переменным во внешней функции.
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.
закрытие во многом как объект. Это инстанцируют каждый раз, когда Вы вызываете функцию.
объем закрытие в JavaScript является лексическим, что означает, что все, что содержится в функции закрытие , принадлежит, имеет доступ к любой переменной, которая находится в нем.
переменная А содержится в закрытие
var foo=1;
или var foo;
, Если внутренняя функция (функция, содержавшая в другой функции), получает доступ к такой переменной, не определяя его в ее собственном объеме с var, это изменяет содержание переменной во внешнем закрытие .
А закрытие переживает время выполнения функции, которая породила его. Если другие функции сделают его из закрытие/объем , в котором они определяются (например, как возвращаемые значения), то они продолжат ссылаться на тот закрытие .
function example(closure) {
// define somevariable to live in the closure of example
var somevariable = 'unchanged';
return {
change_to: function(value) {
somevariable = value;
},
log: function(value) {
console.log('somevariable of closure %s is: %s',
closure, somevariable);
}
}
}
closure_one = example('one');
closure_two = example('two');
closure_one.log();
closure_two.log();
closure_one.change_to('some new value');
closure_one.log();
closure_two.log();
somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged
Это попытка прояснить несколько (возможных) недоразумений относительно замыканий, которые появляются в некоторых других ответах.
Я все еще думаю, что объяснение 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);
* AC # question
Пример для первой точки от dlaliberte:
Замыкание создается не только тогда, когда вы возвращаете внутреннюю функцию. Фактически, закрывающая функция вообще не нуждается в возврате. Вместо этого вы можете назначить свою внутреннюю функцию переменной во внешней области или передать ее в качестве аргумента другой функции, где ее можно будет использовать немедленно. Следовательно, закрытие включающей функции, вероятно, уже существует в то время, когда включающая функция была вызвана, поскольку любая внутренняя функция имеет к ней доступ, как только она вызывается.
var i;
function foo(x) {
var tmp = 3;
i = function (y) {
console.log(x + y + (++tmp));
}
}
foo(2);
i(3);