我遇到了一段奇怪的代码片段,我根本无法理解,这里是:
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
现在,问题是:
5, undefined, undefined, 6
而不是undefined, 6, undefined, 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)
- 为什么此记录
5
,undefined
,undefined
,6
代替undefined
,6
,undefined
,6
?- 为什么更换原型并不像通常那样改变对象的所有实例的原型?
醇>
从根本上说,这归结为这样一个事实:对象引用是值,而不是数字,告诉JavaScript引擎(在你的情况下是V8)对象在内存中的位置。复制值时,您可以这样做:复制值。复制对象引用会复制引用(而不是对象),并且不会以任何方式将该值的目标与值的源相关联,而不是将b
绑定到{{ 1}}:
a
因此,您的代码会记录它记录的内容,并且不会更改var a = 5;
var b = a;
a = 6;
console.log(b, a); // 5, 6
的原型,原因与此代码记录同样的内容并且不会更改价值instance1
:
instance1.p
&#13;
让它抛出一些ASCII艺术(好吧,Unicode艺术),它也会回答:
- V8引擎在此代码中一步一步地做什么?
醇>
运行之后:
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
结果。
- 编辑:我将如何更改所有实例的原型?
醇>
如果您想要实际更改他们用作原型的对象,只有在您想要更改的实例的引用时才能执行此操作,并且只能在支持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
成功更改所有现有对象。所以答案显然是你不应该将新对象分配为原型,而只是修改当前的原型。