什么是JavaScript的闭包
闭包是个很奇怪的概念。本文参考了这个的第一个Morris的答案以及第二个答案。我认为,简单的来说,闭包可以用如下三种角度理解。
这个答案使用JavaScript代码来解释程序员能理解的闭包。并非面向大师或者functional programmers的。
一旦闭包的核心思想被理解后,其并不难掌握。然而,这些核心思想几乎不可能通过阅读任何学术报告来理解它们!
这篇文章面向有一些编程经验的程序员,以及能读懂以下JavaScript代码的人:
function sayHello(name) {
var text = 'Hello ' + name;
var say = function() { console.log(text); }
say();
}
sayHello('Joe');P.S. 参考资料有这个
对于闭包的一个例子
两句话概括:
- 闭包是一种支持first-class functions的手段;这是一种表达式,能够引用在它内部变量(当它第一次被申明的时候),当这个表达式被分配到一个变量上,被传递到函数上作为一个参数,或者作为函数的结果被返回。
- 闭包是一种堆框架,当一个函数开始执行的时候被分配上去,并且当函数返回值的时候并没有被释放(就好像一个“堆框架”被分配到了堆上而非栈上)
下面的代码返回了对于函数的引用
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程序员会理解如上代码:一个函数的引用传递给了一个参数。如果你没有理解,你需要稍微学习以下相关知识再回来:
- 什么是匿名函数
- 函数声明以及函数表达式的区别
使用C语言的程序员将认为这一函数为一个函数返回的指针,变量say1以及say2都是作为这个函数的指针。
在C指针指向函数和JavaScript指向函数有一个主要区别。在JavaScript中,你能认为一个函数的引用变量不但有指向函数的共功能,也有指向闭包的功能,后一种指向是隐藏的。
上面这个代码包含一个闭包,这是由于匿名函数function() { console.log(text); }在另一个函数内声明,也就是sayHello2。
在JS中,如果你使用了function这一关键词在另一个函数内,你就在创建一个闭包。
在C以及其他常见的语言中,当一个函数返回值的时候,由于栈结构被破坏,所有的本地变量都被释放了。
而在JS中,如果你在一个函数内声明另一个函数,那么本地变量依旧能在函数返回后被获取。如上代码所示,由于我们在获取了sayHello2的值后,调用了say2()。注意到我们能调用变量text,这一变量是sayHello2()的本地变量。
function() { console.log(text); } // Output of say2.toString();观察say2.toString()的返回值(”function () { console.log(text); }”),我们能看到用到了变量text。这一匿名函数能获取text的内部参数,是由于本地变量sayHello2()被保存在闭包中。
更多的例子
闭包可能会产生很奇怪的Bug,下面这个例子展示了本地变量并非被复制,它们存储了变量的引用。
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如果你调用了主函数一次,那么一个新的闭包就会被创造出来,之前的三个函数将被初始化:
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在最后一个例子中,每一次调用函数后,会给本地变量相应的不同的闭包。每一个函数内都有一个不同的闭包:
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;总结
如果没有理解的话最好的方式是研究例子。阅读解释比研究例子要难很多。如上的解释并非严谨,只是为了方便理解。
上面这个答案比较偏向于参数的“引用”,下面这个答案比较传统。
当你使用了function这一关键词在另一个funciton中的时候,内部函数能够取得外部函数的变量。
function foo(x) {
var tmp = 3;
function bar(y) {
console.log(x + y + (++tmp)); // will log 16
}
bar(10);
}
foo(2);bar()这个函数能获得x、y以及tmp中存储的内容。虽然没有传参。
从数据形式来看:闭包是从外到内的数据形式,而递归是从内到外的数据形式。
