什么是JavaScript的Object.prototype行为?

时间:2014-10-12 10:55:43

标签: javascript object prototype javascript-objects

我遇到了一段奇怪的代码片段,我根本无法理解,这里是:

var obj = function() {};
obj.prototype.x = 5;

var instance1 = new obj();

obj.prototype = {y: 6};

var instance2 = new obj();

console.log(instance1.x, instance1.y, instance2.x, instance2.y);
// 5, undefined, undefined, 6

现在,问题是:

  1. 为什么要记录5, undefined, undefined, 6而不是undefined, 6, undefined, 6
  2. 为什么更换原型并不像通常那样改变对象的所有实例的原型?
  3. V8引擎在此代码中一步一步地做什么?
  4. 编辑:如何更改所有实例的原型?
  5. 感谢每一个解释。

6 个答案:

答案 0 :(得分:9)

<强>解释

首先,您的两行代码会创建一个函数obj,并为其分配原型{x: 5}

当您创建此对象的实例时,它似乎具有对new'时存在的原型的内部引用。

在此之后,您将原型重新分配给{y: 6},这不会影响对第一个原型的instance1内部引用。

然后,当您创建instance2时,它具有对第二个原型的内部引用,因此,记录它们将生成5, undefined, undefined, 6

<强>#4

您可以,而不是将原型重新分配给新对象:

obj.prototype = {y: 6};

改为修改原型:

delete obj.prototype.x; // Setting to undefined should produce same behaviour
obj.prototype.y = 6;

这将产生输出:undefined, 6, undefined, 6

我已在Windows上的Chrome和Firefox最新版本上使用http://jsfiddle.net/9j3260gp/对此进行了测试。

答案 1 :(得分:5)

根据ECMA Script 5 specifications

  

在调用Function对象作为新创建的对象的构造函数之前,prototype属性的值用于初始化新创建的对象的[[Prototype]]内部属性。

很明显,prototype只是初始化[[Prototype]]属性。当我们创建一个对象时,[[Prototype]]被设置为构造函数的prototype对象,并建立原型链。在你的情况下,当你做

var obj = function() {};
obj.prototype.x = 5;

var instance1 = new obj();

[[Prototype]]看起来像这样

console.log(Object.getPrototypeOf(instance1));
# { x: 5 }

(是的,您可以使用[[Prototype]]功能访问Object.getPrototypeOf

因此,当JS引擎在x中查找instance1时,它会将值视为5,并且由于y未定义,因此它使用undefined

在第二种情况下,

obj.prototype = {y: 6};

var instance2 = new obj();

您正在更改prototype的{​​{1}}对象,以便使用此函数构造的新对象将使用分配给它的新对象。因此,对于obj

[[Prototype]]看起来像这样
instance2

这就是为什么console.log(Object.getPrototypeOf(instance2)); # { y: 6 } 无法在其中找到instance2,而是x


要回答更新的问题,

  

编辑:我将如何更改所有实例的原型?

您可以使用y更改旧对象的原型,就像这样

Object.setPrototypeOf

因为这会使Object.setPrototypeOf(instance1, { y: 6 }); [[Prototype]]instance1不同,我们只需更新构造函数的instance2对象,就像这样

prototype

现在,我们没有改变delete obj.prototype.x; obj.prototype.y = 6; instance1的内部属性。我们可以像这样查看

instance2

注意:约定是将构造函数命名为首字母大写字母。

答案 2 :(得分:2)

  
      
  1. 为什么此记录5undefinedundefined6代替undefined6undefined6
  2.   
  3. 为什么更换原型并不像通常那样改变对象的所有实例的原型?
  4.   

从根本上说,这归结为这样一个事实:对象引用是值,而不是数字,告诉JavaScript引擎(在你的情况下是V8)对象在内存中的位置。复制值时,您可以这样做:复制值。复制对象引用会复制引用(而不是对象),并且不会以任何方式将该值的目标与值的源相关联,而不是将b绑定到{{ 1}}:

a

因此,您的代码会记录它记录的内容,并且不会更改var a = 5; var b = a; a = 6; console.log(b, a); // 5, 6 的原型,原因与此代码记录同样的内容并且不会更改价值instance1

&#13;
&#13;
instance1.p
&#13;
&#13;
&#13;

让它抛出一些ASCII艺术(好吧,Unicode艺术),它也会回答:

  
      
  1. V8引擎在此代码中一步一步地做什么?
  2.   

运行之后:

var foo = {x: 5};
var instance1 = {p: foo};

foo = {y: 6};

var instance2 = {p: foo};

console.log(instance1.p.x, instance1.p.y, instance2.p.x, instance2.p.y);

...你在记忆中有类似的东西(忽略一些细节):

                 +−−−−−−−−−−−−−−−−−−−−−+             
                 |     (function)      |             
                 +−−−−−−−−−−−−−−−−−−−−−+             
obj<Ref55461>−−−>| prototype<Ref32156> |−−−−−−−−−−−+
                 +−−−−−−−−−−−−−−−−−−−−−+            \ 
                                                     \     
                                                      \    
                                                       \   +−−−−−−−−−−+
                                                        +−>| (object) |
                                                       /   +−−−−−−−−−−+
                                                      /    | x<5>     |
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+    /     +−−−−−−−−−−+
                      |         (object)        |   /
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+  /
instance1<Ref86545>−−>| [[Prototype]]<Ref32156> |−+
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+

由于对象引用是值,并且赋值始终是副本值,因此当V8(或任何其他引擎)创建var obj = function() {}; obj.prototype.x = 5; var instance1 = new obj(); 时,它从<{复制值{ {1}}(在概念上显示为instance1)至obj.prototype <Ref32156>内部广告位。

然后,当你做

instance1

...您正在更改[[Prototype]]中的值(此处显示为将obj.prototype = {y: 6}; 更改为obj.prototype),但这对值没有影响({{1在<Ref32156>&#39; s <Ref77458>广告位:

                 +−−−−−−−−−−−−−−−−−−−−−+                   
                 |     (function)      |                   
                 +−−−−−−−−−−−−−−−−−−−−−+                   +−−−−−−−−−−+
obj<Ref55461>−−−>| prototype<Ref77458> |−−−−−−−−−−−−−−−−−−>| (object) |
                 +−−−−−−−−−−−−−−−−−−−−−+                   +−−−−−−−−−−+
                                                           | y<6>     |
                                                           +−−−−−−−−−−+

                                                           +−−−−−−−−−−+
                                                        +−>| (object) |
                                                       /   +−−−−−−−−−−+
                                                      /    | x<5>     |
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+    /     +−−−−−−−−−−+
                      |         (object)        |   /
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+  /
instance1<Ref86545>−−>| [[Prototype]]<Ref32156> |−+
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+

......因此,当你这样做时

<Ref32156>

......你有:

                 +−−−−−−−−−−−−−−−−−−−−−+                   
                 |     (function)      |                   
                 +−−−−−−−−−−−−−−−−−−−−−+                   +−−−−−−−−−−+
obj<Ref55461>−−−>| prototype<Ref77458> |−−−−−−−−−−−−−−−−+−>| (object) |
                 +−−−−−−−−−−−−−−−−−−−−−+               /   +−−−−−−−−−−+
                                                      /    | y<6>     |
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+    /     +−−−−−−−−−−+
                      |         (object)        |   /      
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+  /       
instance2<Ref98465>−−>| [[Prototype]]<Ref77458> |−+        +−−−−−−−−−−+
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+       +−>| (object) |
                                                       /   +−−−−−−−−−−+
                                                      /    | x<5>     |
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+    /     +−−−−−−−−−−+
                      |         (object)        |   /
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+  /
instance1<Ref86545>−−>| [[Prototype]]<Ref32156> |−+
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+

...解释了instance1结果。

  
      
  1. 编辑:我将如何更改所有实例的原型?
  2.   

如果您想要实际更改他们用作原型的对象,只有在您想要更改的实例的引用时才能执行此操作,并且只能在支持ES2015的JavaScript引擎上执行此操作功能,使用Object.setPrototypeOf或(在Web浏览器环境中,如果对象最终从[[Prototype]]继承)通过__proto__ accessor property(不推荐):

var instance2 = new obj();

console.log更改对象中Object.prototype内部广告位的值(如果对象有,则设置为Object.setPrototypeOf(instance1, obj.prototype); )。

如果您无法访问这些实例,则无法执行此操作。

考虑到这个问题,我不认为你这样做,但是如果你只是想改变他们用作原型的对象的状态(可能是通过添加setPrototypeOf ),你当然可以在它上面设置属性,因为JavaScript的原型继承是&#34; live&#34; (这是从实例返回原型的实时链接),然后您可以在从原型继承的任何实例上访问这些属性,即使它们是在您进行更改之前创建的:

[[Prototype]]

答案 3 :(得分:1)

原型是一个与幕后新配对的功能。它适用于与new一起使用的该函数的所有实例。在第一个示例中,将.x = 5附加到原型,并且您创建的实例将.x = 5作为值。稍后您将原型修改为新对象。现在这是在任何新实例中使用的原型。所以这就是为什么第一个实例只有.x = 5,第二个只有.y = 6

答案 4 :(得分:1)

实例原型不引用类,而是引用原型对象本身。当您尝试Object.getPrototypeOf()查看实例引用的原型对象时,这将变得清晰。

Object.getPrototypeOf(instance1)
Object { x: 5, 1 more… }

Object.getPrototypeOf(instance2)
Object { y: 6 }

此字段getPrototypeOf引用应该是每个实例都存在的内部引用。在getPrototypeOf存在之前,您可以通过__proto__获得此信息。

答案 5 :(得分:0)

因为instance1已经创建。 new关键字通过执行构造函数创建新对象,在您的情况下为obj

因为您在初始化第一个实例后更改了原型,所以您不再具有构造函数的相同状态(例如原型),并且不能生成相同的对象。但是创建的那些仍然存在,参考旧的原型。

当您再次使用obj构造函数时,您正在创建另一个对象,该对象可以非常粗略地转换为传统类型的继承的术语,作为另一个类的实例。

编辑:#4 这个小提琴:http://jsfiddle.net/doy3g1fh/ 显示

obj.prototype.y=6

成功更改所有现有对象。所以答案显然是你不应该将新对象分配为原型,而只是修改当前的原型。