Professional javascript For Web Developers 第2版读书笔记之闭包
首先什么是匿名函数?
匿名函数就是没有名字的函数。
为什么要有没有名字的函数,而不给每个函数都起名字?
有些功能在某个作用域内只用一次而且很简单,没必要取个名字(当然取名字也可以),但是增加了代码冗余,因为这些取名字的工作都是在声明函数,声明函数是个苦力活,因为你一直在敲那些重复的function后面跟函数名,同时还要注意命名还不能跟已有函数重名,否则会覆盖。最重要的是减少了代码量却实现了相同的功能,维护的时候更方便。
//function body
}
var functionName = function(arg0, arg1, arg2) {
//function body
};
前者是声明函数,在运行之前就会载入到相应的上下文(不论变量还是函数,浏览器都是先运行声明产生类似预编译的效果),后者是把函数作为一个表达式赋值给一个变量,只有当运行的时候才载入。
什么是闭包?
闭包就是一个函数,这个函数能访问其他函数的作用域内的变量。根据作用域链的规则要实现这个功能基本上只能靠嵌套函数实现,并且这个闭包函数是作为父函数的返回值返回,而且这个闭包函数通常是个匿名函数。
return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
};
}
此例中内部的匿名函数访问了父函数的propertyName变量,而且当匿名函数作为createComparisonFunction的返回值return了之后,这个匿名函数仍然能访问这个propertyName。为什么呢?
当一个函数被调用时,一个处于执行的上下文环境被创建,这个上下文环境创建了一个作用域链并且指派给了内部属性(Scope),同时一个活动对象被初始化,这个活动对象拥有this,arguments和其他显示命名的参数。然后Scope链中指向的第一个对象便是这个活动对象,紧接着,父函数也创建出了一个活动对象,类似的过程,Scope中指向的第2个对象是这个父函数的活动对象,这个过程一直持续向上递归,直到最后的父函数是当前的window对象或者说Scope链的最顶层指向的当前的全局上下文即window对象
var compareNames = createComparisonFunction(“name”);
//call function
var result = compareNames({ name: “Nicholas” }, { name: “Greg”});
//dereference function - memory can now be reclaimed
compareNames = null;
设置compareNames=null使匿名函数没有被赋值给任何变量,因此可以被垃圾回收。
闭包有一个值得注意的特点是:闭包访问的父函数的变量,这个变量总是所有操作后最后一次的值
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
此例中,10个匿名函数在各自的作用域链中都拥有createFunctions的活动对象,并且它们都指向了相同的变量i,当createFunctions结束执行时,i的值已经是10了。
如果要避免这个问题,可以做如下修改
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
}
return result;
}
因为函数是值传递,因此当i作为匿名函数参数传递给num时,只是匿名函数内部会产生一个i的副本,此时匿名函数内部没有其他指向i的引用。
匿名函数中的this
var object = {
name : “My Object”,
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //”The Window”
匿名函数被认为是定义在全局上下文window中的,因此this总是指向window,从前面的图中也可以知道。
如果要让匿名函数访问父函数的name,则应修改为:
var object = {
name : “My Object”,
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //”My Object”
匿名函数总是拥有父函数的“活动对象”
闭包的一个缺点是内存泄漏问题:
var element = document.getElementById(“someElement”);
element.onclick = function(){
alert(element.id);
};
}
此例中匿名函数作为element元素的onclick事件的处理函数,因此父函数和匿名函数产生了一个循环引用(匿名函数拥有父函数的活动对象),只要匿名函数存在,对element对象的引用始终存在,内存也不会被回收。
如要避免此问题,做如下修改
var element = document.getElementById(“someElement”);
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
此例中id作为element的id的副本被匿名函数引用,匿名函数中并没有直接引用父函数的变量,但是匿名函数仍然拥有父函数的活动对象,为了消除这种引用,设置element为null,因而消除了对id为someElement的dom对象的引用
posted on 2010-07-26 23:41 MoonWalker 阅读(303) 评论(0) 编辑 收藏 举报