简体   繁体   中英

Possible error in MDN explanation of __proto__ property?

So, in working to further solidify my understanding of Object-Oriented JavaScript, I have been voraciously reading, and then testing things that I don't understand. I was reading the Mozilla Developer Network (MDN) article titled "Object.prototype. proto " at: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto

and came across the following explanation:

For objects created using new fun, where fun is a function defined in a script, this value [ __proto__ ] is the value of fun.prototype at the time new fun is evaluated. (That is, if a new value is assigned to fun.prototype, previously-created fun instances will continue to have the previous value as their [[Prototype]], and subsequent new fun calls will use the newly-assigned value as their [[Prototype]].)

Note: MDN is using [[Prototype]] to refer to the "internal" Prototype of an object, which is referenced as __proto__ in JavaScript code.

So I opened up my Chrome console, and wrote some simple JavaScript as such:

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

After this, if I type in child. __proto__ and second_child. __proto__ into the console, I get the same value, which is Person {strength: "your value here"}

According to MDN, shouldn't child. __proto__ "continue to have the previous value" of Person.prototype as their internal Prototype?

The MDN docs are talking about completely replacing the prototype, not adding new properties or methods to it (which will be added to all objects sharing that prototype since the internal [[Prototype]] property is shared). Consider this example:

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

In the above example, the [[Prototype]] properties of the instances refer to two different prototype objects, since I replaced the prototype using .prototype = before creating the second object.

It's important to understand that the internal prototype property is shared among all instances created with the same prototype. That's why in your example, the strength property gets added to both objects - the internal [[Prototype]] property of both objects is still a reference to the same shared prototype object. It's also important to recognize that object and array properties of the prototype are shared as well. So for example, suppose you added a children array to your Person prototype:

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

You might expect that this would cause Ebeneezer to have an array containing only Child A, and Noah to have an array containing only Child B, but in fact both parents will now have an array containing BOTH Child A and Child B, because children actually refers to the same array belonging to the internal [[Prototype]] object.

That's why I consider it a best practice to always declare data properties in the constructor, and only methods on the prototype. For example:

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

Note: The modification vs. replacement concept applies to prototype properties in addition to the prototypes themselves. If you set a property directly on an object, it will be used instead of the property on the prototype. So if I were to change my above example to 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 = [];
//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"));

...then the two parents would have separate children arrays, but of course I'm mentioning this just for illustrative purposes, and you should declare array or object properties in the constructor as I showed above. In this example, the children array for parent1 is still referring to the children property on the prototype, so if you were to create a new Person object then it would still share children with Ebeneezer:

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

This article may also be helpful for understanding this: http://www.bennadel.com/blog/1566-using-super-constructors-is-critical-in-prototypal-inheritance-in-javascript.htm

Just adding an answer because this behavior does not only apply to prototype and it should be made clear what the difference between de referencing and mutating is.

I think the correct terms are de reference versus mutate. You're mutating:

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.

Here is what MDN is talking about (de reference):

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'

De referencing a constructor's prototype after crating a lot of instances has a negative affect on performance ( see here ). Which is why you usually don't de reference constructor.protoype after creating instances.

Mutating prototype members or prototype can have unexpected results as shown here (under More about prototype). It can be useful as well as long as you know why you're doing it and what is actually happening.

Matt has mentioned this in his answer but distinguishes between data and behavior where it should be between shared and instance specific. There can be shared data that is deliberately modified on instances although using a static member would usually be better (Person.static=...). Even in cases where you use a factory pattern and can't hard code the constructor name you could use someInstance.constructor.static (assuming you didn't wreck prototype.constructor when setting the prototype).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM