在__proto__属性的MDN解释中可能出现错误?

时间:2014-08-30 13:16:41

标签: javascript inheritance prototypal-inheritance proto

因此,在努力进一步巩固我对面向对象JavaScript的理解时,我一直在贪婪地阅读,然后测试我不理解的东西。我正在阅读Mozilla开发者网络(MDN)文章,标题为" Object.prototype。 proto "在:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto

并且发现了以下解释:

  

对于使用新乐趣创建的对象,fun是定义的函数   一个脚本,这个值[ __ proto __ ]是fun.prototype 在新乐趣时的价值   评估。(也就是说,如果为fun.prototype分配了一个新值,   以前创建的有趣实例将继续拥有前一个   作为[[Prototype]]的值,以及随后的新趣味调用将使用   新指定的值为[[Prototype]]。)

注意:MDN使用[[Prototype]]来引用"内部"对象的原型,在JavaScript代码中引用为 __ proto __

所以我打开了我的Chrome控制台,并编写了一些简单的JavaScript:

function Person(name, age)
{
    this.name = name?name:"Parent Function";
    this.age = age?age:"Old as Time";
}

var parent = new Person("Ebeneezer", 42);    

//new Person evaluated before strength is added to Person.prototype
var child = new Person("Aluiscious", 12);

console.log(child.strength);

Person.prototype.strength = "your value here";
console.log(child.strength);

var second_child = new Person('Sprout', 5);
console.log(second_child.strength);

在此之后,如果我输入child。 __ proto __ 和second_child。 __ proto __ 进入控制台,我会得到相同的值,即Person {strength:&#34 ;你的价值在这里"}

根据MDN,不应该是孩子。 __ proto __ "继续拥有之前的价值" Person.prototype作为其内部原型?

2 个答案:

答案 0 :(得分:1)

MDN文档正在讨论完全替换原型,而不是向其添加新属性或方法(由于共享内部[[Prototype]]属性,因此将添加到共享该原型的所有对象)。考虑这个例子:

function Person(name, age)
{
    this.name = name?name:"Parent Function";
    this.age = age?age:"Old as Time";
}

Person.prototype.strength = "some strength";
var parent = new Person("Ebeneezer", 42);

console.log(parent.strength); //"some strength"

//Replace `Person.prototype` with a completely new prototype object
Person.prototype = {
    //setting the 'constructor' property correctly when replacing a prototype object
    //is a best practice, but it will work without this too
    constructor: Person
};

console.log(parent.strength); //still "some strength"

var child = new Person("Aluiscious", 12);

//This will be undefined, because the object was created after the prototype was changed
console.log(child.strength);

在上面的例子中,实例的[[Prototype]]属性引用了两个不同的原型对象,因为我在创建第二个对象之前使用.prototype =替换了原型。

了解内部原型属性在使用相同原型创建的所有实例之间共享非常重要。这就是为什么在您的示例中,strength属性被添加到两个对象中 - 两个对象的内部[[Prototype]]属性仍然是对同一共享原型对象的引用。认识到原型的对象和数组属性也是共享的也很重要。例如,假设您在children原型中添加了Person数组:

//Don't do this!
Person.prototype.children = [];
var parent1 = new Person("Ebeneezer", 42);
parent1.children.push(new Person("Child A"));

var parent2 = new Person("Noah", 35);
parent2.children.push(new Person("Child B"));

你可能会认为这会导致Ebeneezer有一个只包含Child A的数组,而Noah有一个只包含Child B的数组,但实际上两个父亲现在都有一个包含B和子B的子数组,因为children实际上是指属于内部[[Prototype]]对象的相同数组。

这就是为什么我认为在构造函数中始终声明数据属性以及原型上只有方法的最佳实践。例如:

function Person(name, age)
{
    this.name = name?name:"Parent Function";
    this.age = age?age:"Old as Time";
    this.children = [];
}

//it's fine to declare methods on the prototype - in fact it's good, because it saves
//memory, whereas if you defined them in the constructor there would be a separate copy
//of the method for each instance
Person.prototype.addChild = function(child) {
    if (!child instanceof Person) {
        throw new Error("child must be a Person object");
    }
    //Note: in a real system you would probably also want to check that the passed child
    //object isn't already in the array
    this.children.push(child);
}

注意:除原型本身外,修改与替换概念适用于原型属性。如果直接在对象上设置属性,则将使用它来代替原型上的属性。所以,如果我要将上面的例子改为:

Person.prototype.children = [];
var parent1 = new Person("Ebeneezer", 42);
parent1.children.push(new Person("Child A"));

var parent2 = new Person("Noah", 35);
parent2.children = [];
//now `parent2` has its own `children` array, and Javascript will use that
//instead of the `children` property on the prototype.
parent2.children.push(new Person("Child B"));

...然后两个父母将有单独的children数组,但当然我只是为了说明目的而提到这一点,你应该在构造函数中声明数组或对象属性,如上所示。在此示例中,children的{​​{1}}数组仍然引用原型上的parent1属性,因此如果要创建新的Person对象,那么它仍将共享{{1}与Ebeneezer:

children

本文也可能有助于理解这一点:http://www.bennadel.com/blog/1566-using-super-constructors-is-critical-in-prototypal-inheritance-in-javascript.htm

答案 1 :(得分:1)

只是添加一个答案,因为这种行为不仅适用于原型,而且应该明确说明引用和变异之间的区别是什么。

我认为正确的术语是de reference与mutate。你在改变:

var org = {};
var copy1 = org;//copy1 is a reference to org
var copy2 = org;//copy2 is a reference to org
org.mutate=1;
console.log(copy1===org);//true
console.log(copy1===copy2);//true
console.log(copy2===org);//true
//basically copy1, copy2 and org all point to the same object
//so they all have a member called mutate with a value of 1
//because there is only one object with 3 variables referencing it.

以下是MDN正在谈论的内容(参考):

var org = {orgVal:22};
var copy1 = org;//copy1 is a reference to org
var copy2 = org;//copy2 is a reference to org
//de reference copy1 and copy2
org={mutate:1};
console.log(copy1===org);//false
console.log(copy1===copy2);//true
console.log(copy2===org);//false
console.log(copy1.orgVal);//=22
//since copy1 and copy2 both still reference the same object
//  mutating copy1 will affect copy2
copy1.orgVal='changed';
console.log(copy2.orgVal);//='changed'

在装箱很多实例后引用构造函数的原型会对性能产生负面影响(see here)。这就是为什么在创建实例后通常不会引用constructor.protoype。

变异原型成员或原型可能会产生意外结果,如here所示(在更多关于原型的情况下)。只要您知道为什么要这样做以及实际发生了什么,它就会很有用。

Matt在他的回答中提到了这一点,但区分了数据和行为,它应该在共享和特定于实例之间。尽管使用静态成员通常会更好(Person.static = ...),但可以在实例上有意修改共享数据。即使在您使用工厂模式且无法对构造函数名称进行硬编码的情况下,您也可以使用someInstance.constructor.static(假设您在设置原型时没有破坏prototype.constructor)。