构造函数中闭包的神秘行为

时间:2016-03-04 11:28:32

标签: javascript

考虑以下代码,

function Test(){
  var a = 'prabhu';
  Test.prototype.print = function (){ alert(a); a=10; }
}

我正在为Test创建对象实例,

var x = new Test();
var y = new Test();

现在访问原型中可用的两者的print方法

x.print() //"prabhu"  ok!!
x.print() //10  Double ok!!

但是

y.print() //10  what the?? It should be prabhu. Isn't it?

这是怎么回事?因为在创建y实例时,将调用constructor并且它将覆盖其原型中的print函数。它会为值closure的{​​{1}}创建a。因此,通过"prabhu"访问它时,它应该打印print()吗?为什么这不会发生?对这种行为的逐步解释将非常有用。

P.S:如果我们在任何地方使用"prabhu"代替this.aa,我就知道代码的行为。所以请不要建议使用它。我努力弄清楚为什么上面解释的行为正在发生。

1 个答案:

答案 0 :(得分:9)

永远不要在构造函数中的的构造函数上为prototype对象分配函数,它会设置此串扰。

原因是通过new Test创建的对象有一个实时链接到它的原型,而不是它的副本。这意味着如果您修改原型对象,下次对象在其上查找某些内容(如print)时,它会看到新对象。这是Good Thing™,但它确实意味着您不应该从Test.prototype内分配给Test

您正在看到您所看到的内容,因为当您执行var y = new Test()时,您正在更改现有x使用的原型,并对其print功能进行处理以便关闭来自第二次调用的a(创建y的那个),而不是第一次调用。因此,如果您在执行此操作后致电x.print(),则更新的a是第二个,而不是第一个。

让我们通过以下代码:

function Test(){
  var a = 'prabhu';
  Test.prototype.print = function (){ alert(a); a=10; }
}

var x = new Test();

此时,我们已将此记录在内存中(省略了一些不相关的细节):

    +---------------+   
x-->|   (object)    |   
    +---------------+     +----------+     
    | [[Prototype]] |---->| (object) |     
    +---------------+     +----------+   +-----------------+   
                          | print    |-->| (function)      |   
                          +----------+   +-----------------+   +---------------+
                                         | env             |-->| Environment 1 |
                                         | alert(a); a=10; |   +---------------+
                                         +-----------------+   | a: 'prabhu'   |                
                                                               +---------------+

现在我们这样做:

var y = new Test();

现在我们有:

                                         +-----------------+   +---------------+
    +---------------+                    | (function)      |   | Environment 1 |
x-->|   (object)    |                    +-----------------+   +---------------+
    +---------------+                    | env             |-->| a: 'prabhu'   |
    | [[Prototype]] |--+                 | alert(a); a=10; |   +---------------+
    +---------------+  |                 +-----------------+                    
                       |
                       |  +----------+   +-----------------+   +---------------+
                       +->| (object) |   | (function)      |   | Environment 2 |
                       |  +----------+   +-----------------+   +---------------+
                       |  | print    |-->| env             |-->| a: 'prabhu'   |
                       |  +----------+   | alert(a); a=10; |   +---------------+
                       |                 +-----------------+
    +---------------+  |
y-->|   (object)    |  |
    +---------------+  |
    | [[Prototype]] |--+
    +---------------+

请注意,旧的print函数不再以任何方式连接到对象x;它已被新的替换,它指的是第二次调用Test创建的 new 环境。 旧功能和它用来关闭的环境现在都有资格进行垃圾收集。

然后:

x.print() //"prabhu"  ok!!

给我们(我现在已经离开了垃圾;也许它被收集了,也许不是,没关系)

                                         +-----------------+   +---------------+
    +---------------+                    | (function)      |   | Environment 1 |
x-->|   (object)    |                    +-----------------+   +---------------+
    +---------------+                    | env             |-->| a: 'prabhu'   |
    | [[Prototype]] |--+                 | alert(a); a=10; |   +---------------+
    +---------------+  |                 +-----------------+                    
                       |
                       |  +----------+   +-----------------+   +---------------+
                       +->| (object) |   | (function)      |   | Environment 2 |
                       |  +----------+   +-----------------+   +---------------+
                       |  | print    |-->| env             |-->| a: 10         |
                       |  +----------+   | alert(a); a=10; |   +---------------+
                       |                 +-----------------+
    +---------------+  |
y-->|   (object)    |  |
    +---------------+  |
    | [[Prototype]] |--+
    +---------------+

请注意,该调用将a第二次调用更改为Test(创建y的那个),而不是第一个(垃圾) )。

然后:

x.print() //10  Double ok!!

不会改变任何东西。

最后:

y.print() //10  what the?? It should be prabhu. Isn't it?

出于完全相同的原因显示10 x.print()显示10:print函数正在使用第二次调用a中的Test ,而不是第一个。