1、变量的作用域
程序归根结底就是对数据的操作,JavaScript亦是如此。数据都被储存在变量中,用一个变量名进行标识。在同一个程序中可能存在大量的变量,容易产生命名冲突,引起数据操作的混乱,可以想象,早期的程序设计者们一定面临着这样的问题,于是就产生了对变量进行分区划片、隔离处理的需求。这个限定变量作用范围,断绝与外部联系的独立空间就是作用域。
2、JavaScript的作用域
作用域又被称为执行环境。每种语言都有自己对作用域的设定,JavaScript中用于设定作用域的方法非常简单,只有一个,那就是函数(当初函数之所以被设计出来可能就是用来划分作用域的,当然,这只是一个猜测)。函数之内的叫局部作用域,函数之外是一个整体,叫全局作用域。全局作用域就像是大海,而局部作用域就像是一个个互不相干的孤岛,变量不在海上,就在岛内。
3、作用域链
作用域并非简单的一个个独立存在,由于存在嵌套关系,变量的执行环境其实是一个由内向外延伸的作用域链,每个函数的作用域都是从自身出发,沿着嵌套关系向外扩展,直到全局。简单来说就是子函数可以访问父函数的作用域,全局环境作为所有人的“父函数”,可以被所有人访问。这个关系倒过来就不行了,父函数不能读取子函数的变量,在全局作用域中也不能读取函数内部的变量。为什么会是这样?因为JavaScript引擎对标识符的解析就是沿着作用域链由内而外进行的。
4、闭包
闭包是一种特殊的函数结构,它可以“颠覆”上述作用域链设定的访问顺序,实现外部也能访问函数内部的变量。实现的方法很简单,如果函数A想访问函数B中的变量,就在函数B中return函数A,这么做的思路是把函数A打入函数B内部,将B的作用域窃为己有。这样在其它地方调用A,A都能引用B中的变量。看上去很神奇的样子,拿个简单的例子测一下。
果然,虽然是在外面调用的函数son,但也获得了parent内部的变量a
son是在什么时候“偷窃”了函数A的作用域呢?是在创建函数的时候。当引擎解析到function时,就会预先把函数的作用域设置好。所以只要在函数内部定义函数,子函数就会获得父函数的作用域,我们唯一的问题就是无法直接在外部调用子函数,所以要加个return,再通过父函数的调用获得return出来的子函数。
5、使用闭包的注意事项
第一、由于闭包附带的了更多的变量,占用的内存资源也就更多,所以可能导致网页性能问题;
第二、闭包直接继承父元素的作用域,因此对该作用域的修改会影响父元素内部的变量;
第三、闭包继承的父元素作用域中的变量都是“最后的值”,这个看上去就像是面试中容易出现的“陷阱题”,在《JavaScript高级程序设计》和下面给出的廖雪峰的教程中有详细案例和说明。
参考资料:
廖雪峰:http://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/00143449934543461c9d5dfeeb848f5b72bd012e1113d15000
阮一峰:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
《JavaScript高级程序设计》第三版 P178~P182