1.闭包概念及闭包作用域链
闭包是指有权访问另外一个函数作用域中的变量的函数。创建闭包的常见的方式,就是在函数内部创建另外一个函数。
function closure(arg){
return function(){
//闭包
var value=arg;
return value;
}
}
作用域链的本质是指向一个变量对象的指针列表,它只引用但不实际包含变量对象。一般来讲,当函数执行完毕后,局部活动对象就会销毁,内存仅保存全局作用域。但是,闭包的情况不同,closure函数执行完毕后,其活动对象不会销毁,因为匿名函数的作用域链仍然引用这个活动对象。直到匿名函数被销毁后,closure函数的活动对象才会被销毁。
由于闭包会携带包含它的函数的作用域,因此会占用更多的内存,过度的使用闭包会导致内存占用过多,因此,在绝对必要时,再考虑使用闭包。
2.闭包与变量
闭包只能取得包含函数中任何变量的最后一个值。
function closure(){
var result=[ ];
for(var i=0;i<10;i++)
result[i]=function(){
return i;
}
}
函数数组每个都返回10,原因是,数组里函数保存着closure的活动对象,他们
都引用保存变量i的同一个活动对象,当closure执行完毕后,i变为10,因此,每个函数数组都返回10。
我们可以通过另一个匿名函数强制让闭包的行为符合预期。
function closure(){
var result=[ ];
for(var i=0;i<10;i++){
result[i]=function(num){
return function(){
return num;
};
}(i)
}
return result;
}
在这里没有把闭包直接赋值给数组,而是匿名自执行函数的结果返回给数组。当执行匿名函数时,i的值复制给了num,因此每个num值都是不同的值了。
3.闭包中的this对象
this对象是在运行时基于函数的执行环境绑定的。
var name="Window";
var object={
name: "my object",
getName: function(){
return function(){
return this.name;
}
}
}
alert(object.getName()()); //window
每个函数被调用时都会取得两个特殊变量:this和arguments。内部函数搜索这两个变量时,只会搜索到其活动对象为止,因此,不可能直接访问外部函数的这两个变量。不过将外部作用域中的this保存在一个闭包能够访问的变量里,就可以访问该对象了。
var name="window";
var object={
name : "my object",
getName:function(){
var that=this;
return function(){
return that.name;
}
}
}
alert(object.getName()()); //my object
在几种特殊的情况下,this的值可能会意外的变化。
var name="window";
var object={
name: "my object",
getName:function(){
return this.name;
}
}
以下是几种调用getName()方法及各自的结果。
object.getName(); //my object
(object.getName)(); //my object
(object.getName=object.getName)(); //window
第三行代码先执行赋值语句后调用,赋值时,this的值不能维持,结果为window;
4.闭包造成内存泄露
在IE9之前,如果闭包的作用域链中保存着一个HTML元素,那么意味着该元素无法被销毁。
function closure(){
var element=document.getElementById("elementID");
element.onclick=function(){
alert(element.id);
}
}
这里DOM对象element引用闭包函数,闭包函数作用域引用DOM对象,循环引用导致内存泄露。
解决方法:
function closure(){
var element=document.getElementById("elementId");
var id=element.id;
element.onclick=function(){
alert(id);
}
element=null;
}
先把element.id用局部变量id保存起来,并且在闭包中引用该变量消除了循环引用。但是,这样还不能消除内存泄露,闭包会引用包含函数的活动变量,而其中会有element。即使闭包闭包不直接引用element,包含函数的活动对象仍然会保存一个引用,因此有必要把element设为null。
5.闭包的作用
5.1利用闭包模仿块级作用域
JavaScript没有块级作用域,用匿名函数可以用来模仿块级作用域并避免出现命名参数冲突的问题。
(function(){
//块级作用域
})();
以上代码定义了一个匿名自执行函数,Javascript将function关键字当作函数声明的开始,而函数声明后面不能跟圆括号。然而,函数表达式的后面可以跟圆括号。要将函数声明转换为函数表达式,只要加上一对圆括号即可。
这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数,
这种做法可以减少闭包占用内存的问题。
5.2利用闭包在对象中创建私有变量
严格来说,javascript
没有私有成员的概念,所有对象的属性都是公有的。但是,有私有变量的概念,任何在在函数中定义的变量,都可以认为是私有变量。
function add(num1,num2){
var sum=num1+num2;
return sum;
}
在这个函数里有三个私有变量,num1,num2和sum。在函数内部可以访问,在外部无法访问。如果在函数内部创建一个闭包,那么闭包通过自己的作用域链就可以访问这些变量。利用这个,就可以创建访问私有变量的公用方法。
把有权访问私有变量和私有函数的公有方法称为特权方法。有两种方法创建特权方法:
(1)在构造函数中定义特权方法
function MyObject(){
var privateVariable=10;
function privateFunction(){
return false;
}
this.publicMethod=function(){
privateVariable++;
return privateFunction();
}
}
利用私有和特权成员,可以隐藏那些不应该被直接修改的数据。在构造函数中定义特权方法也有一个缺点,那就是你必须使用构造函数模式来达到这个目的。
(2)静态私有变量
(function(){
var privateVariable=10;
function privateFunction(){
return false;
}
MyObject=function(){
};
MyObject.prototype.publicMethod=function(){
privateVariable++;
return privateFunction();
}
})();
MyObject是全局变量,能够在私有作用域之外被访问到。这种方法创建的静态私有变量会因为使用原型而增进代码复用,但每个实例都没有自己的私有变量。