Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript中的闭包问题多种实例分析 #7

Open
SimonZhangITer opened this issue Feb 22, 2017 · 0 comments
Open

JavaScript中的闭包问题多种实例分析 #7

SimonZhangITer opened this issue Feb 22, 2017 · 0 comments

Comments

@SimonZhangITer
Copy link
Owner

一般情况下,当一个函数实例被创建时,它唯一对应的一个闭包也就被创建。在下面的代码中,由于外部的构造器函数被执行两次,因此内部的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();
@SimonZhangITer SimonZhangITer changed the title 语句或语句块中的闭包问题 语句或语句块中的闭包问题实例分析 Feb 22, 2017
@SimonZhangITer SimonZhangITer changed the title 语句或语句块中的闭包问题实例分析 JavaScript中的闭包问题多种实例分析 Mar 8, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant