理解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上的,案例比较全,讲的比较深一点。
仔细理解案例,从中再加深对闭包的理解。
欢迎交流。
相关文章推荐
- 深入理解javascript之闭包
- 深入理解javascript原型和闭包(14)——从【自由变量】到【作用域链】
- 深入理解javascript原型和闭包(1)——一切都是对象
- 深入理解javascript中的闭包
- JavaScript 闭包深入理解(closure)
- JavaScript 闭包深入理解(closure)
- 深入理解javascript作用域和闭包
- javascript深入理解js闭包
- 深入理解javascript原型和闭包(10)——this
- 深入理解javascript原型和闭包(13)-【作用域】和【上下文环境】
- 深入理解JavaScript的闭包
- javascript深入理解js闭包
- 深入理解javascript原型和闭包(5)——instanceof
- 深入浅出理解JavaScript的闭包
- javascript:深入理解闭包——转自http://www.jb51.net/article/24101.htm
- javascript深入理解js闭包
- javascript深入理解js闭包
- javascript深入理解js闭包
- 深入理解javascript原型和闭包(4)——隐式原型
- 深入理解javascript原型和闭包(5)——instanceof