语句或语句块中的闭包问题

一般情况下,当一个函数实例被创建时,它唯一对应的一个闭包也就被创建。在下面的代码中,由于外部的构造器函数被执行两次,因此内部的foo函数也被创建了两个函数实例(以及闭包)并赋值给this对象的成员:

function MyObject() {
    function foo() {}
    this.method = foo;
}
obj1 = new MyObject();
obj2 = new MyObject();

//显示false,表明产生了两个对象
console.log(obj1.method === obj2.method);

这在函数之外(语句级别)也具有完全相同的表现。在下面的例子中,多个匿名函数的实例被赋给了obj的成员:

var obj = new Object();
for (var i = 0; i < 5; i++) {
    obj['method' + i] = function() {
        console.log(i);
    }
}
console.log(obj.method2 === obj.method3);

尽管是多个实例,但它们仍然是共享同一个外层函数闭包中的upvalue值——在上例中,外层函数闭包指的是全局闭包。因此下面例子所变现出来的,仍然只是闭包中对upvalue值访问的规则,而并非闭包或者函数实例的创建规则“出现了某种特例”:

var obj = new Object();
var events = {
  m1:'clicked',
  m2:'changed'
}
for(e in events){
  obj[e] = function () {
    console.log(events[e]);
  }
}
//显示false,表明是不同的函数实例
console.log(obj.m1 === obj.m2);

//方法m1()和m2()都都出相同值
//其原因,在于两个方法都访问全局闭包中的同一个upvalue值。
obj.m1();
obj.m2();

在这个例子中,王法m1与m2究竟输出何值,取决于前面的for…in语句在最后一次迭代中对e的置值。某些引擎中总保证for…in的顺序与events中声明时的属性顺序一致(例如SpiderMonkey),但也有一些引擎并没有这项约定。因此上例在不同的引擎中表现的结果未必一致,但m1()与m2()输出值总是相同的。

按照这段代码的本意,应该是每个函数实例输出不同值。对这个问题的处理之一,是再添加一个外层函数,利用“在函数内保存数据”的特性来为内部函数保存一个可访问的upvalue:

var obj = new Object();
var events = {
    m1: 'clicked',
    m2: 'changed'
}
for (e in events) {
    obj[e] = function(aValue) { //闭包lv1
        return function() { //闭包lv2
            console.log(events[aValue]);
        }
    }(e)
}

/*或者使用如下代码,在闭包内通过局部变量保存数据。
for (e in events) {
    obj[e] = function() { //闭包lv1
        var aValue = e;
        return function() { //闭包lv2
            console.log(events[aValue]);
        }
    }()
}*/

//下面将输出不同的值
obj.m1();
obj.m2();

由于闭包lv2引用了闭包lv1中的入口参数,因此两个闭包存在了关联关系。在obj的方法未被清除之前,两个闭包都不会被销毁,但lv1为lv2保存了一个可供访问的upvalue——这除了私有变量、函数之外,也包括它的传入参数。

很明显,上例的问题在于多了一层闭包,因此增加了系统消耗。不过这并非不可避免。我们要看清楚这个问题,其本质是一要一个地方来保存for…in列举中的每一个值e,而并非是“需要一个闭包来添加一个层”。那么,既然列举过程中产生了不同的函数实例,自然也可以将值e交给这些函数实例自己去保存:

var obj = new Object();
var events = {
    m1: 'clicked',
    m2: 'changed'
}
for (e in events) {
    (obj[e] = function(aValue) {
        //arguments.callee指向匿名函数自身
        console.log(events[arguments.callee.aValue]);
    }).aValue = e;
}
//下面将输出不同的值
obj.m1();
obj.m2();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值