什么是闭包?
按照通俗的理解就是函数里面的函数,该函数能访问包含它的函数的变量。按照比较官方的理解:闭包是指有权访问另一个函数作用域的变量的函数。For Example:
function funcA() {
var a = 1, b = 2;
return funcB();
function funcB() {
// 注意,a 和 b 在本函数里根本没有定义,但是竟然能访问到
return a + b;
}
}
var a = funcA();
// a 将会等于 3
为什么funcB能访问到已经返回后funcA的变量呢?我们先了解一下函数被调用的过程。
当一个函数被调用后,会创建一个执行环境以及相应的变量对象、作用域链。在之前的一篇文章我曾经介绍过这三个概念。现在简单介绍一下。执行环境中定义了变量或函数有权访问的其他数据,决定了他们各自的行为,也就是说执行环境规定了你能访问什么,不能访问什么。那么这些规则保存在哪里呢?答案是在一个变量对象里面。这个变量保存着环境变量中所有的变量和函数。这个对象是不可以访问的。而作用域链是保证执行环境有权访问的所有变量和函数的有序访问。方向是就近原则。位于作用域前端的变量对象是本身的环境,比如这个环境是函数的话,函数的活动对象就是处于最前端的。以此类推下一个是直接包含环境的变量对象,再下一个是间接的包含环境。。。直到全局执行环境。比如:funcB能访问funcA的变量和函数,也能访问window对象的变量和函数。
还有一个事实是:全局环境的变量在函数执行始终是存在的,这个无可厚非,但是想funcA这个函数只有在函数执行过程中存在,在这之后包括他的执行环境和变量对象都会被销毁,那为什么funcB还能访问funcA的变量对象呢?这其实就是闭包的神秘之处了。
闭包可以将包含它的函数的活动对象(变量对象)添加到它的作用域链中,正是由于闭包的作用域链仍然在引用外部函数的活动对象,所以当funcA返回时,虽然它的执行环境的作用域链被销毁了,但是它的活动对象还在内存中。知道匿名函数会销毁.(因此使用过多的闭包可能占用过多的内存)
闭包带来的副作用
1、闭包只能取得包含函数中任意变量的最后一个值。
function createFunctions(){
var result =new Array();
for (var i =0;i<10;i++)
{
result[i] = function (){
return i;
}
}
return result;
}
var result = createFunctions();
alert(result[0]()); //10
表面上看result是返回一个0-9的数组,但resulti结果却是全部是10.原因是当createFunctions()返回时,变量的i值都是10了。
为什么会这样子呢?
我们先理解一下程序的思路,在上面的代码中其实每次执行闭包函数function (){
return i;
的时候,闭包函数都保存了createFunctions函数里面所有的变量,而且每次执行闭包函数的时候createFunctions函数里面的变量都会不变,当执行完createFunctions函数之后,createFunctions里面的i已经变为10了,又由于闭包函数不是立即执行,而是在createFunctions函数已经完全执行完之后执行的,所以最后每次闭包函数访问的都createFunctions完全执行完的变量(此时i已经是10了)。
解决办法:
既然是因为闭包函数不能立即执行的问题,那么我们就利用一个匿名函数通过立即执行把相应的i值立即放进闭包函数里面(此时对于闭包函数而言,i就是一个常数,也就是执行前已经拥有的值)看代码:
function createFunctions(){
var result =new Array();
for (var i =0;i<10;i++)
{
result[i] = function(num){
return function(){
return num;
}
}(i);
}
return result;
}
var result = createFunctions();
alert(result[0]()); //0
这样的话,闭包里面的num都是匿名函数立即执行时传进来的参数i;
2、this对象的问题
先来看一段代码:
var name ="周运金";
var object ={
name:"李佳家",
getName:function(){
return function(){
this.name;
}
}
}
alert(object.getName()()); //"周运金"
最后返回的周运金,也就是说this指针指向了window对象。但是根据之前的理解:当函数在全局变量中执行时,this等于window,当属被作为某个对象的内置方法时this指向该对象。
原因在于匿名函数具有全局性,因此this指针通常指向了window。这种情况下要this指向object也行,采用下面的做法:
var name ="周运金";
var object ={
name:"李佳家",
getName:function(){
var that=this;
return function(){
that.name;
}
}
}
alert(object.getName()()); //李佳家
因为getName这个函数是object纯正的内置函数,所以可以访问this对象,然后将这个对象传给闭包。这也不算是高级的做法,其实有点硬来的意思了,哈哈.
3、引起内存泄漏
这都是IE之前版本的对Jscript的垃圾回收机制惹的祸,这之间文章有提到过垃圾回收机制,常用的是标记清除。很少见的是IE之前版本的引用计数的垃圾机制。由于闭包在外部函数返回后还在使用外部函数的变量对象,所以此时外部函数的变量可能会被循环引用,导致引用次数无法减少,此时就会造成内存泄漏。比如:
function ass(){
var element =document.getElementById('someElement');
element.onclick =function (){
alert(element.id);
};
}
解决方法:
function ass(){
var element =document.getElementById('someElement');
var id=element.id;
element.onclick =function (){
alert(id);
};
element=null;
}
改进的代码有:将elment.id的一个副本保存在一个变量中,在闭包中引用该变量,消除了循环引用。最后还得手动清除element.因为即使必要不直接引用element,包含函数的活动对象也会保存一个引用。
以上就是闭包与作用链的问题。