1

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

并遇到以下解释:

对于使用 new fun 创建的对象,其中 fun 是在脚本中定义的函数,此值 [ __proto__ ] 是 fun.prototype在评估 new fun 时的值。 (也就是说,如果为 fun.prototype 分配了一个新值,之前创建的 fun 实例将继续将之前的值作为它们的 [[Prototype]],并且后续新的 fun 调用将使用新分配的值作为它们的 [ [原型]]。)

注意: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);

在此之后,如果我输入孩子。__proto__和 second_child。__proto__进入控制台,我得到相同的值,即 Person {strength: "your value here"}

根据 MDN,不应该是孩子。__proto__ “继续拥有以前的值” Person.prototype 作为他们的内部原型?

4

2 回答 2

2

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"));

你可能认为这会导致 Ebenezer 有一个只包含 Child A 的数组,而 Noah 有一个只包含 Child B 的数组,但实际上父母双方现在都有一个包含 BOTH Child A 和 Child 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数组 forparent1仍然引用children原型上的属性,所以如果你要创建一个新的 Person 对象,那么它仍然会children与 Ebenezer 共享:

var parent3 = new Person("Eve");
console.log(parent3.children); //array containing Child A

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

于 2014-08-30T14:00:24.080 回答
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'

在创建大量实例后取消引用构造函数的原型会对性能产生负面影响(请参见此处)。这就是为什么您通常在创建实例后不引用 constructor.protoype 的原因。

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

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

于 2014-08-30T16:17:50.033 回答