Professional javascript For Web Developers 第2版读书笔记之闭包

首先什么是匿名函数?

匿名函数就是没有名字的函数。

为什么要有没有名字的函数,而不给每个函数都起名字?

有些功能在某个作用域内只用一次而且很简单,没必要取个名字(当然取名字也可以),但是增加了代码冗余,因为这些取名字的工作都是在声明函数,声明函数是个苦力活,因为你一直在敲那些重复的function后面跟函数名,同时还要注意命名还不能跟已有函数重名,否则会覆盖。最重要的是减少了代码量却实现了相同的功能,维护的时候更方便。

function functionName(arg0, arg1, arg2) {
    
//function body

var functionName = function(arg0, arg1, arg2) {
    
//function body
}; 

前者是声明函数,在运行之前就会载入到相应的上下文(不论变量还是函数,浏览器都是先运行声明产生类似预编译的效果),后者是把函数作为一个表达式赋值给一个变量,只有当运行的时候才载入。

 

什么是闭包?

闭包就是一个函数,这个函数能访问其他函数的作用域内的变量。根据作用域链的规则要实现这个功能基本上只能靠嵌套函数实现,并且这个闭包函数是作为父函数的返回值返回,而且这个闭包函数通常是个匿名函数。

 

function createComparisonFunction(propertyName) {
                   
    
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对象


当匿名函数作为返回值从createComparisonFunction返回时,该匿名函数的作用域链才初始化出了3层对象(如图),同时createComparisonFunction的活动对象不能销毁因为当createComparisonFunction返回值时,内部的匿名函数的作用域链仍然有createComparisonFunction的活动对象,销毁的只是createComparisonFunction函数的作用域链,createComparisonFunction函数的活动对象仍然驻留内存中直到匿名函数销毁

 

 

//create function
var compareNames = createComparisonFunction(“name”);
                   
//call function
var result = compareNames({ name: “Nicholas” }, { name: “Greg”});
                   
//dereference function - memory can now be reclaimed
compareNames = null

设置compareNames=null使匿名函数没有被赋值给任何变量,因此可以被垃圾回收。

 

闭包有一个值得注意的特点是:闭包访问的父函数的变量,这个变量总是所有操作后最后一次的值

 

function createFunctions(){
    
var result = new Array();
    
    
for (var i=0; i  <  10; i++){
        result[i] 
= function(){
            
return i;
        };
    }
    
    
return result;

此例中,10个匿名函数在各自的作用域链中都拥有createFunctions的活动对象,并且它们都指向了相同的变量i,当createFunctions结束执行时,i的值已经是10了。

如果要避免这个问题,可以做如下修改

function createFunctions(){
    
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 name = “The Window”;
                   
var object = {
    name : “My Object”,
                   
    getNameFunc : 
function(){
        
return function(){
            
return this.name;
        };
    }
};
                   
alert(object.getNameFunc()());  
//”The Window” 


 匿名函数被认为是定义在全局上下文window中的,因此this总是指向window,从前面的图中也可以知道。

如果要让匿名函数访问父函数的name,则应修改为:

var name = “The Window”;
                   
var object = {
    name : “My Object”,
                   
    getNameFunc : 
function(){
        
var that = this;
        
return function(){
            
return that.name;
        };
    }
};
                   
alert(object.getNameFunc()());  
//”My Object” 

匿名函数总是拥有父函数的“活动对象


闭包的一个缺点是内存泄漏问题:

 

function assignHandler(){
    
var element = document.getElementById(“someElement”);
    element.onclick 
= function(){
        alert(element.id);
    };

此例中匿名函数作为element元素的onclick事件的处理函数,因此父函数和匿名函数产生了一个循环引用(匿名函数拥有父函数的活动对象),只要匿名函数存在,对element对象的引用始终存在,内存也不会被回收。

如要避免此问题,做如下修改

function assignHandler(){
    
var element = document.getElementById(“someElement”);
    
var id = element.id;
                   
    element.onclick 
= function(){
        alert(id);
    };
                   
    element 
= null;

此例中id作为element的id的副本被匿名函数引用,匿名函数中并没有直接引用父函数的变量,但是匿名函数仍然拥有父函数的活动对象,为了消除这种引用,设置element为null,因而消除了对idsomeElement的dom对象的引用

 

 

 

 

 

 

posted on 2010-07-26 23:41  MoonWalker  阅读(303)  评论(0编辑  收藏  举报

导航