JavaScript闭包是如何工作的? [英] How do JavaScript closures work?

查看:124
本文介绍了JavaScript闭包是如何工作的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



如何解释JavaScript闭包对知道他们包含的概念(例如函数,变量等)的人,但不了解闭包本身?

我看到了Stack Overflow上的计划示例,但不幸的是它没有帮助。

解决方案

初学者的JavaScript闭包



提交by Morris on Tue,2006-02-21 10:19。



关闭不是魔术



此页面说明了closures,以便程序员能够理解—使用有效的JavaScript代码。



一旦核心概念被锁定,闭包就不难了解。

但是,通过阅读任何学术论文或关于他们的学术性信息,他们是不可能理解的!



本文适用于具有主流语言编程经验的程序员,谁可以读取以下JavaScript函数:

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



关闭示例



两个一句汇总:


  • 闭包是支持一流函数;它是一个可以引用其范围内的变量(在第一次声明时),分配给变量,作为参数传递给函数或作为函数结果返回的表达式。或者

  • 一个闭包是一个堆栈框架,在函数开始执行时分配,在函数返回之后不释放



以下代码返回对函数的引用:

  function sayHello2(name){
var text ='Hello'+ name; //局部变量
var say = function(){console.log(text); }
return say;
}
var say2 = sayHello2('Bob');
say2(); // logsHello Bob

大多数JavaScript程序员都将理解如何返回对函数的引用在上面的代码中的变量( say2 )。如果你不,你需要在你可以学习闭包。 AC程序员会认为函数返回一个指向函数的指针,变量 say2 一个函数的指针。



函数的C指针和JavaScript函数之间有一个关键的区别。在JavaScript中,您可以将函数引用变量视为具有指向函数的指针作为指向闭包的隐藏指针。



上面的代码有一个闭包,因为匿名函数 function(){console.log(text); } 在中声明另一个函数 sayHello2()在JavaScript中,如果您在另一个函数中使用 function 关键字,那么您将创建一个闭包。



大多数其他常用语言 之后函数返回后,所有的局部变量不再可访问,因为堆栈框架被销毁。



,如果在另一个函数中声明一个函数,那么从调用的函数返回后,局部变量仍然可以访问。这在上面已经说明,因为我们从 sayHello2()返回后调用 say2()注意,我们调用的代码引用变量 text ,它是函数 sayHello2()的局部变量 / code>。

  function(){console.log(text); } //输出say2.toString(); 

查看 say2.toString()的输出,我们可以看到代码引用变量 text 。匿名函数可以引用 text ,其中包含值'Hello Bob',因为 sayHello2()保存在闭包中。



神奇的是,在JavaScript中,函数引用也有一个秘密引用闭包已创建于—类似于委托是方法指针加上对对象的秘密引用。



更多示例



原因,关闭看起来真的很难理解,当你阅读他们,但当你看到一些例子,你可以点击他们的工作(它花了我一会儿)。
我建议您仔细阅读示例,直到您了解它们的工作原理。



示例3



如果您在不完全了解其工作原理的情况下开始使用closure,则很快就会创建一些非常奇怪的bug。

此示例显示不会复制局部变量—它们通过引用保存。当外部函数退出时,它就像是在内存中保持一个堆栈框架!

  function say667(){
//结束于闭包内的局部变量
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(){
//结束于闭包内的局部变量
var num = 42;
//将一些对函数的引用存储为全局变量
gLogNumber = function(){console.log(num); }
gIncreaseNumber = function(){num ++; }
gSetNumber = function(x){num = x; }
}

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

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog()// 5

共享访问同一封闭—



请注意,在上面的示例中,如果在上面的例子中,如果在定义了三个函数时, setupSomeGlobals你再次调用 setupSomeGlobals(),然后创建一个新的闭包(stack-frame!)。变量将覆盖旧的 gLogNumber gIncreaseNumber gSetNumber 具有新关闭的函数。 (在JavaScript中,每当在另一个函数中声明一个函数时,内部函数将在每次时重新调用外部函数。)



示例5



这对于许多人来说是一个真正的东西,所以你需要理解它。如果你在循环中定义一个函数,要非常小心:闭包中的局部变量不会像你先想的那样。

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

function testList(){
var fnlist = buildList([1,2,3]);
//使用j只是为了防止混淆 - 可以使用i。
for(var j = 0; j< fnlist.length; j ++){
fnlist [j]();
}
}

testList()// logsitem2 undefined3次

result.push(function(){console.log(item +''+ list [i])} 如果你不熟悉匿名函数想象它:

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

请注意,当您运行示例时,item2 undefined会提醒三次!这是因为,对于 buildList 的局部变量只有一个闭包。当 fnlist [j]();它们都使用相同的单个闭包,并且它们使用当前值 i item i 的值为 3 ,因为循环已完成, / code>的值为'item2')。注意,我们从0开始索引,因此 item 的值为 item2 。并且i ++将 i 增加到值 3



< h3>示例6

此示例显示,closure包含在退出之前在外部函数中声明的任何局部变量。注意,变量 alice 实际上是在匿名函数之后声明的。匿名函数首先声明;当调用该函数时,它可以访问 alice 变量,因为 alice 在同一范围href =http://stackoverflow.com/a/372​​5763/1269037>变量提升)。
sayAlice()只是直接调用 sayAlice()—它与以前执行的操作完全相同,但没有临时变量。

  function sayAlice(){
var say = function(){console.log(alice); }
//结束于闭包内的局部变量
var alice ='Hello Alice';
return say;
}
sayAlice()(); //记录Hello Alice

Tricky:还要注意,变量也在闭包内,可以通过可能在 sayAlice()中声明的任何其他函数访问。



示例7



最后一个例子显示每个调用为局部变量创建一个单独的闭包。每个函数声明都有不是一个闭包。

$ <$ p <$ p> $

  function newClosure(someNum,someRef){ 
//结束于闭包内的局部变量
var num = someNum;
var anArray = [1,2,3];
var ref = someRef;
return function(x){
num + = x;
anArray.push(num);
console.log('num:'+ num +
'\\\
anArray'+ anArray.toString()+
'\\\
ref.someVar'+ ref.someVar);
}
}
obj = {someVar:4};
fn1 = newClosure(4,obj);
fn2 = newClosure(5,obj);
fn1(1); // num:5; anArray:1,2,3,5; ref.someVar:4;
fn2(1); // num:6; anArray:1,2,3,6; ref.someVar:4;
obj.someVar ++;
fn1(2); // num:7; anArray:1,2,3,5,7; ref.someVar:5;
fn2(2); // num:8; anArray:1,2,3,6,8; ref.someVar:5;



摘要



如果一切似乎完全不清楚那么最好的做法是玩这些例子。阅读解释比理解示例困难得多。
我对闭包和堆栈框架等的解释在技术上是不正确的—它们是旨在帮助理解的粗略简化。



最终点:




  • 每当在另一个函数中使用 function 时,都会使用闭包。

  • > eval()在函数内部,使用闭包。文本 eval 可以引用函数的局部变量,在 eval 中甚至可以创建新的局部变量 eval('var foo = ...')

  • c $ c>(函数构造函数) )在函数内部,它不会创建一个闭包。 (新函数不能引用外层函数的局部变量。)

  • JavaScript中的闭包就像保留所有局部变量的副本,就像退出函数时一样。

  • 最好的办法是,只要在函数入口处创建一个闭包,并将局部变量添加到该闭包中。

  • 每次调用具有闭包的函数时,都会保存一组新的局部变量(假定函数在其中包含函数声明,并且在某些情况下将返回对该函数的引用或保留对该引用的外部引用)方式)。

  • 两个函数可能看起来像是相同的源文本,但由于它们的隐藏闭包而具有完全不同的行为。我不认为JavaScript代码实际上可以找出一个函数引用是否有闭包。

  • 如果你试图做任何动态源代码修改(例如: myFunction = Function(myFunction.toString()。replace(/ Hello /,'Hola')); ),如果 myFunction 是一个闭包(当然,你永远不会想到在运行时执行源代码字符串替换,但...)。

  • 可以获得函数声明函数中的函数声明—

  • 通常情况下,闭包是函数以及捕获的变量的术语。请注意,我在本文中不使用该定义!

  • 我怀疑JavaScript中的闭包不同于通常在函数式语言中发现的闭包。



链接




  • Douglas Crockford的模拟私有属性和私有方法,使用闭包。

  • a href =http://www.codeproject.com/jscript/LeakPatterns.asp =nofollow noreferrer>如果您不小心,会导致IE 内存泄漏。



感谢



如果您只是学习闭包,那么我感兴趣的任何反馈从您的任何更改,你可能建议,可以使本文更清楚。发送电子邮件至morrisjohns.com(morris_closure @)。请注意,我不是JavaScript上的大师






Morris的原始帖子可以在 Internet Archive


How would you explain JavaScript closures to someone with a knowledge of the concepts they consist of (for example functions, variables and the like), but does not understand closures themselves?

I have seen the Scheme example given on Stack Overflow, but unfortunately it did not help.

解决方案

JavaScript Closures for Beginners

Submitted by Morris on Tue, 2006-02-21 10:19. Community-edited since.

Closures Are Not Magic

This page explains closures so that a programmer can understand them — using working JavaScript code. It is not for gurus or functional programmers.

Closures are not hard to understand once the core concept is grokked. However, they are impossible to understand by reading any academic papers or academically oriented information about them!

This article is intended for programmers with some programming experience in a mainstream language, and who can read the following JavaScript function:

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

An Example of a Closure

Two one sentence summaries:

  • 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), assigned to a variable, passed as an argument to a function, or returned as a function result. Or
  • a closure is a stack frame which is allocated when a function starts its execution, and not freed after the function returns (as if a 'stack frame' were allocated on the heap rather than the stack!).

The following code returns a reference to a function:

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"

Most JavaScript programmers will understand how a reference to a function is returned to a variable (say2) in the above code. If you don't, then you need to before you can learn closures. A C programmer would think of the function as returning a pointer to a function, and that the variables say and say2 were each a pointer to a function.

There is a critical difference between a C pointer to a function and a JavaScript reference to a function. In JavaScript, you can think of a function reference variable as having both a pointer to a function as well as a hidden pointer to a closure.

The above code has a closure because the anonymous function function() { console.log(text); } is declared inside another function, sayHello2() in this example. In JavaScript, if you use the function keyword inside another function, you are creating a closure.

In C and most other common languages, after a function returns, all the local variables are no longer accessible because the stack-frame is destroyed.

In JavaScript, if you declare a function within another function, then the local variables can remain accessible after returning from the function you called. This is demonstrated above, because we call the function say2() after we have returned from sayHello2(). Notice that the code that we call references the variable text, which was a local variable of the function sayHello2().

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

Looking at the output of say2.toString(), we can see that the code refers to the variable text. The anonymous function can reference text which holds the value 'Hello Bob' because the local variables of sayHello2() are kept in a closure.

The magic is that in JavaScript a function reference also has a secret reference to the closure it was created in — similar to how delegates are a method pointer plus a secret reference to an object.

More examples

For some reason, closures seem really hard to understand when you read about them, but when you see some examples you can click to how they work (it took me a while). I recommend working through the examples carefully until you understand how they work. If you start using closures without fully understanding how they work, you would soon create some very weird bugs!

Example 3

This example shows that the local variables are not copied — they are kept by reference. It is kind of like keeping a stack-frame in memory when the outer function exits!

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

All three global functions have a common reference to the same closure because they are all declared within a single call to 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

The three functions have shared access to the same closure — the local variables of setupSomeGlobals() when the three functions were defined.

Note that in the above example, if you call setupSomeGlobals() again, then a new closure (stack-frame!) is created. The old gLogNumber, gIncreaseNumber, gSetNumber variables are overwritten with new functions that have the new closure. (In JavaScript, whenever you declare a function inside another function, the inside function(s) is/are recreated again each time the outside function is called.)

Example 5

This one is a real gotcha for many people, so you need to understand it. Be very careful if you are defining a function within a loop: the local variables from the closure do not act as you might first think.

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

The line result.push( function() {console.log(item + ' ' + list[i])} adds a reference to an anonymous function three times to the result array. If you are not so familiar with anonymous functions think of it like:

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

Note that when you run the example, "item2 undefined" is alerted three times! This is because just like previous examples, there is only one closure for the local variables for buildList. When the anonymous functions are called on the line fnlist[j](); they all use the same single closure, and they use the current value for i and item within that one closure (where i has a value of 3 because the loop had completed, and item has a value of 'item2'). Note we are indexing from 0 hence item has a value of item2. And the i++ will increment i to the value 3.

Example 6

This example shows that the closure contains any local variables that were declared inside the outer function before it exited. Note that the variable alice is actually declared after the anonymous function. The anonymous function is declared first; and when that function is called it can access the alice variable because alice is in the same scope (JavaScript does variable hoisting). Also sayAlice()() just directly calls the function reference returned from sayAlice() — it is exactly the same as what was done previously, but without the temporary variable.

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"

Tricky: note also that the say variable is also inside the closure, and could be accessed by any other function that might be declared within sayAlice(), or it could be accessed recursively within the inside function.

Example 7

This final example shows that each call creates a separate closure for the local variables. There is not a single closure per function declaration. There is a closure for each call to a function.

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 +
            '\nanArray ' + anArray.toString() +
            '\nref.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

If everything seems completely unclear then the best thing to do is to play with the examples. Reading an explanation is much harder than understanding examples. My explanations of closures and stack-frames, etc. are not technically correct — they are gross simplifications intended to help understanding. Once the basic idea is grokked, you can pick up the details later.

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 on 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 behaviour 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 the 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

Thanks

If you have just learned closures (here or elsewhere!), then I am interested in any feedback from you about any changes you might suggest that could make this article clearer. Send an email to morrisjohns.com (morris_closure @). Please note that I am not a guru on JavaScript — nor on closures.


Original post by Morris can be found in the Internet Archive.

这篇关于JavaScript闭包是如何工作的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆