JavaScript 闭包四

在创建可访问的内部函数的函数体之外解析该内部函数就会构成闭包。这表明闭包很容易创建,但这样一来可能会导致一种结果,即没有认识到闭包是一种语言特性的 JavaScript 作者,会按照内部函数能完成多种任务的想法来使用内部函数。但他们对使用内部函数的结果并不明了,而且根本意识不到创建了闭包,或者那样做意味着什么。

正如下一节谈到 IE 中内存泄漏问题时所提及的,意外创建的闭包可能导致严重的负面效应,而且也会影响到代码的性能。问题不在于闭包本身,如果能够真正做到谨慎地使用它们,反而会有助于创建高效的代码。换句话说,使用内部函数会影响到效率。

使用内部函数最常见的一种情况就是将其作为 DOM 元素的事件处理器。例如,下面的代码用于向一个链接元素添加 onclick 事件处理器:

/* 定义一个全局变量,通过下面的函数将它的值
   作为查询字符串的一部分添加到链接的 - href - 中:
*/
var quantaty = 5;
/* 当给这个函数传递一个链接(作为函数中的参数 - linkRef -)时,
   会将一个 onclick 事件处理器指定给该链接,该事件处理器
   将全局变量 - quantaty - 的值作为字符串添加到链接的 - href -
   属性中,然后返回 true 使该链接在单击后定位到由  - href -
   属性包含的查询字符串指定的资源:
*/
function addGlobalQueryOnClick(linkRef){
    /* 如果可以将参数 - linkRef - 通过类型转换为 ture
      (说明它引用了一个对象):
    */
    if(linkRef){
        /* 对一个函数表达式求值,并将对该函数对象的引用
           指定给这个链接元素的 onclick 事件处理器:
        */
        linkRef.onclick = function(){
            /* 这个内部函数表达式将查询字符串
               添加到附加事件处理器的元素的 - href - 属性中:
            */
            this.href += ('?quantaty='+escape(quantaty));
            return true;
        };
    }
}无论什么时候调用 addGlobalQueryOnClick 函数,都会创建一个新的内部函数(通过赋值构成了闭包)。从效率的角度上看,如果只是调用一两次 addGlobalQueryOnClick 函数并没有什么大的妨碍,但如果频繁使用该函数,就会导致创建许多截然不同的函数对象(每对内部函数表达式求一次值,就会产生一个新的函数对象)。

上面例子中的代码没有关注内部函数在创建它的函数外部可以访问(或者说构成了闭包)这一事实。实际上,同样的效果可以通过另一种方式来完成。即单独地定义一个用于事件处理器的函数,然后将该函数的引用指定给元素的事件处理属性。这样,只需创建一个函数对象,而所有使用相同事件处理器的元素都可以共享对这个函数的引用:

/* 定义一个全局变量,通过下面的函数将它的值
   作为查询字符串的一部分添加到链接的 - href - 中:
*/
var quantaty = 5;
/* 当把一个链接(作为函数中的参数 - linkRef -)传递给这个函数时,
   会给这个链接添加一个 onclick 事件处理器,该事件处理器会
   将全局变量  - quantaty - 的值作为查询字符串的一部分添加到
   链接的 - href -  中,然后返回 true,以便单击链接时定位到由
   作为 - href - 属性值的查询字符串所指定的资源:
*/
function addGlobalQueryOnClick(linkRef){
    /* 如果 - linkRef - 参数能够通过类型转换为 true
    (说明它引用了一个对象):
    */
    if(linkRef){
        /* 将一个对全局函数的引用指定给这个链接
           的事件处理属性,使函数成为链接元素的事件处理器:
        */
        linkRef.onclick = forAddQueryOnClick;
    }
}
/* 声明一个全局函数,作为链接元素的事件处理器,
   这个函数将一个全局变量的值作为要添加事件处理器的
   链接元素的  - href - 值的一部分:
*/
function forAddQueryOnClick(){
    this.href += ('?quantaty='+escape(quantaty));
    return true;
}在上面例子的第一个版本中,内部函数并没有作为闭包发挥应有的作用。在那种情况下,反而是不使用闭包更有效率,因为不用重复创建许多本质上相同的函数对象。

类似地考量同样适用于对象的构造函数。与下面代码中的构造函数框架类似的代码并不罕见:

function ExampleConst(param){
    /* 通过对函数表达式求值创建对象的方法,
      并将求值所得的函数对象的引用赋给要创建对象的属性:
    */
    this.method1 = function(){
        ... // 方法体。
    };
    this.method2 = function(){
        ... // 方法体。
    };
    this.method3 = function(){
        ... // 方法体。
    };
    /* 把构造函数的参数赋给对象的一个属性:*/
    this.publicProp = param;
}每当通过 new ExampleConst(n) 使用这个构造函数创建一个对象时,都会创建一组新的、作为对象方法的函数对象。因此,创建的对象实例越多,相应的函数对象也就越多。

Douglas Crockford 提出的模仿 JavaScript 对象私有成员的技术,就利用了将对内部函数的引用指定给在构造函数中构造对象的公共属性而形成的闭包。如果对象的方法没有利用在构造函数中形成的闭包,那么在实例化每个对象时创建的多个函数对象,会使实例化过程变慢,而且将有更多的资源被占用,以满足创建更多函数对象的需要。

这那种情况下,只创建一次函数对象,并把它们指定给构造函数 prototype 的相应属性显然更有效率。这样一来,它们就能被构造函数创建的所有对象共享了:

function ExampleConst(param){
    /* 将构造函数的参数赋给对象的一个属性:*/
    this.publicProp = param;
}
/* 通过对函数表达式求值,并将结果函数对象的引用
      指定给构造函数原型的相应属性来创建对象的方法:
*/
ExampleConst.prototype.method1 = function(){
    ... // 方法体。
};
ExampleConst.prototype.method2 = function(){
    ... // 方法体。
};
ExampleConst.prototype.method3 = function(){
    ... // 方法体。
};

六、Internet Explorer 的内存泄漏问题


Internet Explorer Web 浏览器(在 IE 4 到 IE 6 中核实)的垃圾收集系统中存在一个问题,即如果 ECMAScript 和某些宿主对象构成了 “循环引用”,那么这些对象将不会被当作垃圾收集。此时所谓的宿主对象指的是任何 DOM 节点(包括 document 对象及其后代元素)和 ActiveX 对象。如果在一个循环引用中包含了一或多个这样的对象,那么这些对象直到浏览器关闭都不会被释放,而它们所占用的内存同样在浏览器关闭之前都不会交回系统重用。

当两个或多个对象以首尾相连的方式相互引用时,就构成了循环引用。比如对象 1 的一个属性引用了对象 2 ,对象 2 的一个属性引用了对象 3,而对象 3 的一个属性又引用了对象 1。对于纯粹的 ECMAScript 对象而言,只要没有其他对象引用对象 1、2、3,也就是说它们只是相互之间的引用,那么仍然会被垃圾收集系统识别并处理。但是,在 Internet Explorer 中,如果循环引用中的任何对象是 DOM 节点或者 ActiveX 对象,垃圾收集系统则不会发现它们之间的循环关系与系统中的其他对象是隔离的并释放它们。最终它们将被保留在内存中,直到浏览器关闭。

闭包非常容易构成循环引用。如果一个构成闭包的函数对象被指定给,比如一个 DOM 节点的事件处理器,而对该节点的引用又被指定给函数对象作用域中的一个活动(或可变)对象,那么就存在一个循环引用。DOM_Node.onevent ->function_object.[[scope]] ->scope_chain ->Activation_object.nodeRef ->DOM_Node。形成这样一个循环引用是轻而易举的,而且稍微浏览一下包含类似循环引用代码的网站(通常会出现在网站的每个页面中),就会消耗大量(甚至全部)系统内存。

多加注意可以避免形成循环引用,而在无法避免时,也可以使用补偿的方法,比如使用 IE 的 onunload 事件来来清空(null)事件处理函数的引用。时刻意识到这个问题并理解闭包的工作机制是在 IE 中避免此类问题的关键。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值