您的位置:首页 > Web前端 > JavaScript

理解JavaScript 闭包

2013-05-28 18:42 363 查看

几个概念

执行环境

JavaScript的解释器每次开始执行一个函数时,都会为那个函数创建一个执行环境。该环境的作用就是定义调用对象(call object)。

词法作用域和作用域链

JavaScript中,变量的作用域(scope)是程序中定义这个变量的区域。全局变量,作用域是全局性的,函数体中的变量,包括参数,是局部变量,作用域是局部性的。

重点:JavaScript使用词法作用域(lexical scoping),也就是说作用域在函数定义的时候已经确定了。

作用域链是为了更好的描述和帮你理解作用域而提供的一种方法。每个执行环境都有一个和它关联在一起的作用域链,它是一个对象列表或对象链。

函数定义和执行的过程

我们先给出一段代码:

function sayHi(name){
var greeting = "新年快乐";
alert(name + ", " + greeting);
}
sayHi("Hu");


1) 定义函数sayHi的时候,该函数的作用域是全局的,所以作用域链上只有一个对象window,我们为了便于理解,使用 scopeChain = [window]。

2) 运行sayHi的时候,进入解释器创建的执行环境,此时,执行环境创建一个调用对象(call object),并把这个对象添加到作用域顶端,scopeChain = [sayHi的调用对象, window]

3) 给sayHi调用对象添加一个arguments属性,保存传递的参数,并把内部greeting和参数name添加到调用对象上。

4) alert();之后,函数被GC回收

再来一段代码:

function sayGreeting(greeting){

function _say(){
alert("Hu," + greeting);
}

return _say;
}
var newYearGreeting = sayGreeting("新年快乐");
var birthdayGreeting = sayGreeting("生日快乐");

newYearGreeting();
birthdayGreeting();


这段代码sayGreeting函数中又定义了一个_say函数,sayGreeting定义和执行的过程是这样的:

1) 定义sayGreeting函数的时候,该函数的作用域已知是全局的,作用域链scopeChain = [window]

2) 运行sayGreeting的时候,执行环境依然创建了一个调用对象,并添加其到作用域链的顶端:scopeChain = [sayGreeting的调用对象, window]

3) 给sayGreeting调用对象添加一个arguments属性,保存传递的参数,一起添加到调用对象上。

4) 这时定义了_say函数,此时它的作用域设置为sayGreeting的的作用域链。并返回_say函数的引用给newYearGreeting.

5) birthdayGreeting 这一句,又重新执行了1-4的过程,并返回_say函数的引用给birthdayGreeting.

6) 运行newYearGreeting和birthdayGreeting的时候:

6.1) 执行环境创建了newYearGreeting的调用对象,并添加其到作用域链的顶端,我们已知_say的作用域链为[sayGreeting的调用对象, window],

现在为:[_say调用对象, sayGreeting的调用对象, window],添加arguments属性和参数到调用对象上。

6.1) 这时就运行到了alert()这一句,有个greeting的这个变量,我们需要得到它的值(变量名解析),JavaScript开始在作用域链上查找,

首先查找_say调用对象,发现没有greeting;然后查找sayGreeting的调用对象,greeting="新年快乐",结束查找,alert出 "Hu,新年快乐"。

这里大家应该会发现一个问题,在运行newYearGreeting()的时候,sayGreeting已经运行结束了,按照回收机制,应该已经销毁了整个对象了,但是实际情况是"新年快乐",还存在。

这是为什么呢?因为运行sayGreeting("新年快乐")的时候产生了一个闭包,newYearGreeing和birthdayGreeting都是闭包,你会发现同时它们也是函数对象。

闭包的定义:A "closure" is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).

但是我觉得这个更好理解:“A closure is nothing more than a function object with a related scope in which the function’s variables are resolved.”

闭包:无非就是一个函数对象+相关的作用域,函数中的变量都是处在这个作用域中的。

从这里可以看出,广义上是个函数它都可以是闭包,只是我们常说的闭包,一般指的是嵌套函数中,把里面的函数返回给外部变量,该函数能捕捉到外函数中的自己需要用到的变量值。

举例:

function makeAdder(x) {
function adder(y) {
return x + y;
};
return adder;
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

print(add5(2));  // 7
print(add10(2)); // 12


如这个例子:add5是一个闭包。makeAdder(5)这句就是产生闭包的过程,adder(y)这个内部函数在此时捕捉了makeAdder函数的参数x,由于返回adder, add5引用了它,所以adder不会被回收,同时被adder捕捉的变量x也不会被回收。所以,print(add5(2))的时候,返回5+2=7,这里的5就是运行了makeAdder(5)时,x存储的值。同理add10(2)就是10+2。

提醒一点:makeAdder(5) 和 makeAdder(10) 是分别创建了各自单独的执行环境,所以add5 和 add10的作用域链也就是各自单独一条作用域链了。

那么我们理解闭包,最关键的点就是:JavaScript使用词法作用域,也就是说函数的作用域在定义函数的时候就已经确定,运行函数的时候只是在作用域链顶端添加了调用对象。

闭包使用案例

闭包的使用网上有很多案例,大家可以去搜搜看,我推荐几个个人认为不错的:

https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures 这个通俗易懂。

http://msdn.microsoft.com/en-us/magazine/ff696765.aspx MSDN上的,案例比较全,讲的比较深一点。

仔细理解案例,从中再加深对闭包的理解。

欢迎交流。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: