一、JavaScript中闭包的概念:
官方解释:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。我是感觉很晦涩,我就不在这里咬文嚼字了。
二、闭包的解释
在介绍闭包之前,先理解编程语言作用域的概念。js是一种脚本语言,他划分作用域的方式也很特别:只有function可以划分作用域,和C,C++,java用{}来划分作用域不同。还有,如果要在一个函数里声明一个局部变量,一定要用关键字var,否则这个变量的作用域会上升为全局。
如:
function foo(){
a = 100;
var b = 200;
alert(a);//①100
alert(b);//②200
}
alert(a);//③100
alert(b);//④控制台报错,因为b的作用域在上述代码里只存在于foo中。出了foo函数的作用域,便被回收。
js闭包的最简单形式:
function foo(a){
return function(){
return a+1;
}
}
直观的看这个闭包形式,声明一个名字为foo,形参为a的函数,foo的返回值是一个匿名函数,并且在匿名函数的内部对a进行了操作。
如果对匿名函数不太理解,我们可以再举一个例子:
function f1(){
var i = 0;
function f2(){
alert(++i);//①f2中一定操作了 f1中的变量,闭包的效果才能体现。即,i的值可以在f1之外被引用。
}
return f2;//②f2一定是作为f1的返回值的。
}
var f3 = f1();//③用一个变量在f1外部引用f1
f3();
当代码执行完 var f3 = f1();之后,变量f3实际是指向了函数f2(),在执行f3()就会弹出i的值为1(第一次),然后每次执行f3(),alert弹出的值就+1。
当函数f1内部的函数f2被函数f1外部的变量f3引用时就构成了闭包。注意①和②的条件。
通过对js函数的执行过程的分析,我们可以深入理解和js闭包紧密相关的概念:
函数的执行环境(excution context)、活动对象(call boject)、作用域(scope)、作用域链(scope chain)
1、当定义函数f1的时候,js解释器会将函数f1的作用域链(scope chain)设置为定义f1时f1所在的“环境”。如果f1是一个全局函数,则scope chain中只有window对象。
2、当执行函数f1的时候,f1会进入相应的执行环境(excution context)。
3、当插件执行环境的过程中,首先会为f1添加一个scope属性,即f1的作用域,其值就是1步中的scope chain。即f1.scope = f1的作用域链。
4、然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过JavaScript代码直接访问。创建完活动对象后,把活动对象添加到f1的作用域的最顶端。此时f1的作用域包含了两个对象:f1的活动对象和window对象。
5、下一步是在活动对象上添加一个argument属性,它保存着调用函数f1时所传递的参数。
6、最后把所有函数f1的形参和内部的函数f2的引用也添加到f1的活动对象上。在这一步中,完成了函数f2的定义,因此如同第3步,函数f2的作用域链被设置为f2被定义时所在的环境,即f1的作用域。
至此,整个函数f1从定义到执行的步骤就完成了。此时f1返回函数f2的引用给f3,又函数f2的作用域链包含了对函数f1的活动对象的引用,也就是说f2可以访问到a中定义的所有变量和函数。函数f2被f3所引用,函数f2又依赖函数f1,因此函数f1在返回后不会被GC回收。
了解了作用域链,便可以知道函数f2在访问一个变量的时候,搜索顺序是:
1、先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数f1的活动对象,依次查找,直到找到为止。
2、如果函数f2存在property原型对象,则存在查找完自身的活动对象后先查找自身的原型对象,再继续查找。
3、如果整个作用域链上都无法找到,则返回undefined。
注意,
直观上,函数嵌套函数。效果上,函数外部可以访问到内部的变量或者对象。原因。。。可以理解函数嵌套时避免了垃圾回收,因为内部函数的作用,使得外部函数的生命周期变长了。
要想深入理解,需要仔细理解一下js的执行上下文,作用域链,活动对象,变量对象。
三、闭包的作用:
1、模拟命名空间
2、防止命名冲突
3、让指定的变量常驻内存,可以模拟python包机制。