读《JavaScript高级程序设计》第7章有感。
一、究竟闭包是什么?
闭包是指有权访问另一个函数作用域中的变量的函数。
个人感悟:
通过书中的这句定义,按中文语法分析,说白了,闭包就是一种函数,而这种函数可以访问另一个函数作用域中的变量。
那为什么这种函数有这样牛B的功能呢?其实,它是利用了函数的作用域链。
二、创建闭包的常见方式:在一个函数内部创建另一个函数
function createComparisonFunction(propertyName){ return function(object1,object2){ var value1 = object1[propertyName]; var value2 = object2[propertyName]; if(value1 < value2){ return -1; }else if(value1 > value2){ return 1; }else{ return 0; } }; }
留意这个例子中标红的地方。标红处是一个内部函数的代码,而这个内部函数正是匿名函数。
这两行代码访问了外部函数中的变量propertyName。即使这个内部函数被返回了,而且是在其他地方被调用了,但它仍然可以访问变量propertyName。
之所以还能够访问这个变量,是因为这个匿名函数的作用域链中包含了createComparisonFunction()的作用域(在作用域链中是以变量对象表示)
三、闭包能够起什么作用?为什么闭包能够起作用?
说到这里,我们不得不回想一下之前学过的关于作用域链的知识。只有透彻理解作用域链,才能真正理解闭包起什么作用,为什么能够起作用。
关于作用域链的知识,在我上一篇博文中已经详细说明,在这就不重复叙述了,我只讲关键的地方。
首先,当调用某个函数的时候,系统会为函数创建一个执行环境,并且通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。而函数的活动对象作为变量对象被推入执行环境作用域链的最前端。
可见,当函数被调用时,会创建两个东西,一个是函数的执行环境,另一个则是其相应的作用域链。
一般来说,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(也就是全局执行环境的变量对象)。
但是,闭包的情况有所不同。原因就是:
在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。
回到上面的例子:
function createComparisonFunction(propertyName){ return function(object1,object2){ var value1 = object1[propertyName]; var value2 = object2[propertyName]; if(value1 < value2){ return -1; }else if(value1 > value2){ return 1; }else{ return 0; } }; }
可以看出,在createComparisonFunction()函数内部定义的匿名函数的作用域链中,包含外部函数createComparisonFunction()的活动对象。
在匿名函数从createComparisonFunction()中被返回后,它的作用域链就变成了下图所示:
此时,匿名函数可以访问在createComparisonFunction()中定义的所有变量。
更重要的是,当createComparisonFunction()函数执行完毕后,也就是返回了匿名函数后,它的活动对象也不会被销毁,这是因为匿名函数的作用域链仍然在引用这个活动对象。
也就是说,当createComparisonFunction()函数返回后,它的执行环境的作用域链会被销毁,但它的活动对象仍然被引用。根据JS的GC策略,因为该活动对象引用次数不为0,因此它会留在内存中,直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁。
四、闭包与变量
由上知,闭包是通过作用域链这种机制来实现的,因此,它也有着一个值得我们注意的副作用,那就是:
闭包只能取得包含函数中任何变量的最后一个值,因为闭包只是引用其作用域链上的变量对象。
例子不敲了,自己推敲一下吧哈~书上有,懒得敲了。
五、总结
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存,过度使用闭包可能会导致内存占用过多,这方面要注意一下。
这本JS基础书真心好,闭包这部分得多看看。
关于闭包的一些应用将在下一篇文章讲述。
共勉。