问题

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

我曾在维基百科上看到计划范例,但很遗憾,没有帮助.



解决方法

JavaScript Closures for Beginners

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

Closures Are Not Magic

本页说明了closures,以便程序员可以使用有效的JavaScript代码来理解它们.它不适用于古茹或功能程序员.

一旦核心概念被锁定,关闭就不难了解.但是,他们不可能通过阅读任何学术论文或面向学术的信息来了解他们!

本文面向具有主流语言编程经验的程序员,并且可以阅读以下JavaScript函数:

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

An Example of a Closure

两个一句话摘要:

  • 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. 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!).

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

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的程序员会认为函数返回一个指向函数的指针,变量 say2 都是指向函数的指针.

函数的C指针和JavaScript对函数的引用之间有一个关键的区别.在JavaScript中,你可以将函数引用变量看作是一个指向函数的指针以及作为指向闭包的隐藏指针.

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

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

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

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

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

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

More examples

由于某种原因,当你阅读它们时,闭包似乎很难理解,但当你看到一些例子时,你可以点击它们的工作方式(我花了一段时间). 我建议仔细阅读示例,直到你了解它们如何工作.如果你开始使用闭包而不完全理解它们如何工作,你很快就会创建一些非常古怪的错误!

Example 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

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

当定义了三个函数时,这三个函数共享访问同一个闭包 - setupSomeGlobals()的局部变量.

请注意,在上面的示例中,如果再次调用 setupSomeGlobals(),则会创建一个新的闭包(stack-frame!).旧的 gLogNumber , gIncreaseNumber , gSetNumber 变量将被具有新闭包的函数覆盖. (在JavaScript中,每当在另一个函数中声明一个函数时,每次每次调用外部函数时都会重新创建内部函数.)

Example 5

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

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 的局部变量只有一个闭包.当在 fnlist [j]()行上调用匿名函数时;它们都使用相同的单个闭包,并且它们在一个闭包内使用 i item 的当前值(其中 i 的值为 3 ,因为循环已完成, item 的值为'item2').注意,我们从0索引,因此 item 的值为 item2 .并且i ++将 i 递增为 3 的值.

Example 6

此示例显示,闭包包含在退出之前在外部函数中声明的任何局部变量.注意,变量 alice 实际上是在匿名函数之后声明的.匿名函数首先声明;并且当调用该函数时,它可以访问 alice 变量,因为 alice 在同一范围内(JavaScript does 可变提升). 此外, sayAlice()()只是直接调用从 sayAlice()返回的函数引用 - 它与以前做的完全相同, / p>

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:还要注意,变量也在闭包内,可以通过任何可能在 sayAlice()中声明的其他函数进行访问可以在内部函数内递归地访问.

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 +
            '\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

如果一切似乎完全不清楚,那么最好的办法是玩这些例子.阅读解释比理解示例困难得多. 我对闭包和堆栈框架等的解释在技术上不正确 - 它们是用于帮助理解的粗略简化.一旦基本的想法是grokked,你可以稍后拿起细节.

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

如果您只是学习闭包(在这里或其他地方!),那么我对您的任何反馈意见感兴趣,任何您可能建议的更改,可以使本文更清楚.发送电子邮件至morrisjohns.com(morris_closure @).请注意,我不是JavaScript的大师,也不是封闭.


Morris的原始文章可以在 Internet Archive .




相关问题推荐