如果实例本身尚未设置属性,为什么在更新原型时更新存储原始值而不是引用的实例的属性?
让我举个例子:
var Obj = function () {};
Obj.prototype.num = 1;
var myObj = new Obj();
var myOtherObj = new Obj();
console.log(myObj.num); //logs 1
console.log(myOtherObj.num); //logs 1
//After instances are created they still share the value (which is strange):
Obj.prototype.num = 3;
console.log(myObj.num); //logs 3
console.log(myOtherObj.num); //logs 3
//Update one of the instances property
myObj.num += 2;
console.log(myObj.num); //logs 5
console.log(myOtherObj.num); //logs 3
//Here it gets weird:
Obj.prototype.num = 4;
console.log(myObj.num); //logs 5 not updated
console.log(myOtherObj.num); //logs 4 updated
这里有几件奇怪的事情:
创建实例后,当且仅当实例本身从未更新实例值时,更新类定义才会更新实例值。
如果我试图推断它,似乎最初实例没有自己的num
属性,并且在原型上发现它的地方发生了查找,但是一旦你实际设置了属性(内部与<{1}}或外部this.num
)您在实例上创建了一个属性。
这似乎与ECMA5规范所说的不相符:
在将Function对象作为新创建的对象的构造函数调用之前,prototype属性的值用于初始化新创建的对象的[[Prototype]]内部属性。
听起来它应该填充内部[[Prototype]]的实例,但是它仍然可以通过更新构造函数的prototype属性来修改。我得到的是,如果我将类定义的属性设置为类似于数组的对象,实际上将引用传递给实例并在任何地方更新数组将为每个人修改属性,但这些是原始值,它似乎不是很有意义。
答案 0 :(得分:4)
这样做的方法是首先在对象上查找属性,然后通过在原型中查找它们来查找属性。
如果我试图推论它,那就是 看起来像最初的实例 没有自己的num属性和 查找发生在它被发现的地方 在原型上,但一旦你 实际设置属性(内部 与this.num或外部 instanceName.num)你创建一个 实例上instance.property的属性。
这正是它的工作原理。如果要删除在对象上设置的属性,可以使用delete:
delete myOtherObj.num;
这将删除myOtherObj的num属性并允许原型版本显示。
您引用的规范部分试图解释如何为新创建的实例设置内部[[Prototype]]属性。除了它在属性查找中的作用,[[Prototype]]就像任何其他Javascript对象引用一样。
因此,当构造函数的“prototype”属性被“复制”到新创建的对象的[[Prototype]]中时,它实际上会获得对构造函数上原始prototype属性的引用,这可以在以后修改
另一个例子:
function Obj() {};
Obj.prototype.num=1;
var myObj = new Obj();
console.log(myObj.num); // logs 1
Obj.prototype = {num:3}; // replaces Obj.prototype
console.log(myObj.num); // logs 1
var myOtherObj = new Obj();
console.log(myOtherObj.num); // logs 3
console.log(myObj.num); // still logs 1
请注意,一旦构造了对象,它就会在构造函数被调用时保留对构造函数原型所设置的任何对象的引用。更改该对象的属性仍将影响对同一原型的任何其他引用。
答案 1 :(得分:3)
这是完全正常的,不奇怪。 ;)
当您尝试访问某个对象的属性时,该对象没有在该对象中设置,JS引擎会启动原型链并尝试在那里读取它。只要未在该对象中显式设置该属性,就是这样。
当你更改对象继承的类的原型时,对象本身的属性不会改变,它仍然没有被设置,类的属性仍然被读取。
只有在设置对象的某个属性时,才会在该对象中定义该属性。当您更改对象的原型时,这也是已声明对象的某个属性发生更改的原因。
澄清:
Obj.prototype.num = 3;
console.log(myObj.num); //logs 3
console.log(myOtherObj.num); //logs 3
在这里,您实际上仍然阅读Obj.prototype.num
。
myObj.num += 2;
在这里,您实际上阅读 Obj.prototype.num
并将写入myObj.num
。
Obj.prototype.num = 4;
console.log(myObj.num); //logs 5 not updated
console.log(myOtherObj.num); //logs 4 updated
现在分别阅读myObj.num
和Obj.prototype.num
。
答案 2 :(得分:2)
其他人会比我更好地解释这一点,所以SO用户也可以随意批评我的回答。如果它非常恶劣,我会删除。
将其视为继承。如果从构造函数创建的对象没有自己的num
属性,则它会查找其原型(或父级或超级或其他)的值。
无论那个价值是什么,都是你得到的价值。更新它,任何查找它的对象都会找到更新的值。
我认为原型属性不是为每个实例复制的,而是由实例“查找”。因此,prototyped属性的值并没有真正添加到对象的大小,因为all共享相同的集合。
编辑:这也可能有所帮助。规范第5版中的定义下的第4.3.4和4.3.5节。
<强> 4.3.4 强>
构造强>
创建和初始化对象的函数对象。 注意构造函数的“prototype”属性的值是一个原型对象,用于实现继承和共享属性。
4.3.5
<强>原型强>
为其他对象提供共享属性的对象。
注意当构造函数创建对象时,该对象隐式引用构造函数的“prototype”属性以解析属性引用。构造函数的“prototype”属性可以由程序表达式constructor.prototype引用,添加到对象原型的属性通过继承由共享原型的所有对象共享。或者,可以使用Object.create内置函数,使用显式指定的原型创建新对象。