因为前面刚刚写了一些关于变量作用域的知识,就想趁热打铁总结一下闭包。
我所理解的闭包就是函数内嵌函数用来访问局部变量,这样可以使局部变量私有化。
书中对闭包的定义是:函数对象可以通过作用域链相互关联起来,函数体内部的变量可以保存在函数作用域内,这种特性称为闭包。
先看一段代码
var scope="我是全局变量"; function checkscope(){ var scope="我是局部变量"; function f(){ return scope; } return f(); } console.log(checkscope());
输出:我是局部变量
这个很容易理解,在函数作用域中,在函数体内,局部变量的优先级高于同名的全局变量;变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的,而外层函数返回的是内嵌函数的结果,内嵌函数访问的是局部变量。
下面我们再看一个例子:
var scope="我是全局变量"; function checkscope(){ var scope="我是局部变量"; function f(){ return scope; } return f; } console.log(checkscope()());
输出:我是局部变量
这两段代码的区别就是,一个是返回的内嵌函数执行的结果,一个是返回的内嵌函数对象 。
这个用函数作用域链来解释是很明确的。当调用checkscope函数时产生活动对象,这个活动对象包括局部变量和内嵌函数,这个对象将会调用内嵌函数,并产生新的活动对象用来存储内嵌函数的局部变量。这样就形成了一条作用域链。嵌套的函数f()定义在这个作用域链里,所以不管在何时执行f(),返回的都是局部变量。
所以我们可以看到,闭包可以捕捉到局部变量和(参数),并一直保存下来,看起来像这些变量绑定到了在其中定义它们的外部函数。
这里和C语言不同的是,外部函数中定义的局部变量在函数返回后依然存在。因为C语言的函数局部变量是定义在CPU的栈中,如果了解JS作用域链的应该明白JS作用域链其实是一个对象列表或链表。
当调用函数时,会创建新的活动对象来保存局部变量,把这个对象添加至作用域链中,当函数返回时,就从作用域链中将这个绑定变量的对象删除。如果这个函数没有内嵌函数,也没有其他引用指向这个绑定对象,它就会被当做垃圾回收掉,如果定义了内嵌函数,每个嵌套的函数都各自对应一个作用链域,并且这个作用链域指向一个变量绑定对象。如果这些嵌套的函数对象在外部函数中保存下来,那么也会和所指定的变量绑定对象一样当做垃圾回收.但是,如果这个函数定义了嵌套函数,并将它作为返回值返回或者存储在某处的属性中,这时就会有一个外部引用指向这个嵌套函数,这样就不会被垃圾回收了并且它所指定的变量绑定对象也不会被垃圾回收。这就是闭包的实现机制。
举一个很常见的例子:
for(var i=0;i<elements.length;i++){
elements[i].onclick=function(){
alert(i);
}
}
你会发现会一直弹出4,这是为什么呢?很简单,在你点击的时候,for循环已经执行完毕了(由于是内部函数,作用域链不能被销毁,因为随时有点击的可能),最后i的值自然是4
那么如果我们使用闭包呢
for(var i=0;i<elements.length;i++){
(function(n){
elements[n].onclick=function(){
alert(n);
}
})(i);
}
注意这里使用的是闭包的匿名方法。
这时0到n次点击将会创建其对应的作用链域,这样就会有0-length个作用链域(闭包函数保存了局部变量),这样就会依次弹出0-length的值