理解JavaScript 闭包
2013-05-27 19:51
267 查看
几个概念
执行环境
JavaScript的解释器每次开始执行一个函数时,都会为那个函数创建一个执行环境。该环境的作用就是定义调用对象(call object)。词法作用域和作用域链
JavaScript中,变量的作用域(scope)是程序中定义这个变量的区域。全局变量,作用域是全局性的,函数体中的变量,包括参数,是局部变量,作用域是局部性的。重点:JavaScript使用词法作用域(lexical scoping),也就是说作用域在函数定义的时候已经确定了。
作用域链是为了更好的描述和帮你理解作用域而提供的一种方法。每个执行环境都有一个和它关联在一起的作用域链,它是一个对象列表或对象链。
函数定义和执行的过程
我们先给出一段代码:1 function sayHi(name){ 2 var greeting = " 新年快乐 " ; 3 alert(name + " , " + greeting); 4 } 5 sayHi( " Hu " );
1) 定义函数sayHi的时候,该函数的作用域是全局的,所以作用域链上只有一个对象window,我们为了便于理解,使用 scopeChain = [window]。
2) 运行sayHi的时候,进入解释器创建的执行环境,此时,执行环境创建一个调用对象(call object),并把这个对象添加到作用域顶端,scopeChain = [sayHi的调用对象, window]
3) 给sayHi调用对象添加一个arguments属性,保存传递的参数,并把内部greeting和参数name添加到调用对象上。
4) alert();之后,函数被GC回收
再来一段代码:
1 function sayGreeting(greeting){ 2 3 function _say(){ 4 alert( " Hu, " + greeting); 5 } 6 7 return _say; 8 } 9 var newYearGreeting = sayGreeting( " 新年快乐 " ); 10 var birthdayGreeting = sayGreeting( " 生日快乐 " ); 11 12 newYearGreeting(); 13 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.”
闭包:无非就是一个函数对象+相关的作用域,函数中的变量都是处在这个作用域中的。
从这里可以看出,广义上是个函数它都可以是闭包,只是我们常说的闭包,一般指的是嵌套函数中,把里面的函数返回给外部变量,该函数能捕捉到外函数中的自己需要用到的变量值。
举例:
1 function makeAdder(x) { 2 function adder(y) { 3 return x + y; 4 }; 5 return adder; 6 } 7 8 var add5 = makeAdder( 5 ); 9 var add10 = makeAdder( 10 ); 10 11 print(add5( 2 )); // 7 12 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上的,案例比较全,讲的比较深一点。
仔细理解案例,从中再加深对闭包的理解。
欢迎交流。
相关文章推荐
- 理解Javascript的闭包
- 理解javascript 闭包概念优缺点及应用
- javascript深入理解js闭包
- Javascript之旅——第十站:为什么都说闭包难理解呢?
- 深入理解javascript原型和闭包(11)——执行上下文栈
- javascript深入理解js闭包
- 轻松理解javascript中的闭包
- 深入理解javascript原型和闭包(8)——简述【执行上下文】上
- 深入理解javascript原型和闭包(18)——补充:上下文环境和作用域的关系
- javascript深入理解js闭包
- 深入理解javascript原型和闭包(4)——隐式原型
- javascript深入理解js闭包
- 理解javascript 对象,原型对象、闭包
- 深入理解Javascript 函数作用域 闭包
- 深入理解javascript原型和闭包(完结)
- 深入理解javascript原型和闭包(18)——补充:上下文环境和作用域的关系
- 深入理解javascript原型和闭包(16)——补this
- 深入理解javascript原型和闭包(17)——补充:上下文环境和作用域的关系
- 深入理解JavaScript原型和闭包系列
- JavaScript——理解闭包及作用