什么是JavaScript的闭包

XRuochen
XRuochen
Jul 20, 2017 · 6 min read

闭包是个很奇怪的概念。本文参考了这个的第一个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中存储的内容。虽然没有传参。


从数据形式来看:闭包是从外到内的数据形式,而递归是从内到外的数据形式。

)
XRuochen

Written by

XRuochen

make something original and worthwhile;谢谢你的阅读;有些文章也在这里:http://www.jianshu.com/u/4aff4c1f4f55;以及这里:https://ruochen95.github.io/

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade