1

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?

Bryan Chen
  • 45,816
  • 18
  • 112
  • 143
papiro
  • 2,158
  • 1
  • 20
  • 29
  • 1
    you can read "previous value" as "previous reference" – Bryan Chen Aug 30 '14 at 13:29
  • You know, your comment actually helped me out in making the first step to understanding the answer to my inquiry, which I ultimately found below. I appreciate it! – papiro Aug 30 '14 at 14:22

2 Answers2

2

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

Matt Browne
  • 12,169
  • 4
  • 59
  • 75
  • Well thank you, Matt. I would vote you up, but I don't yet have a reputation of 15! This was a very clear explanation and a definite step for me in my journey of understanding prototypal inheritance :D – papiro Aug 30 '14 at 14:19
  • 1
    BTW I just added a link to the end of my answer that may be helpful. – Matt Browne Aug 30 '14 at 14:20
  • Nice link but it's not good practice to crate in instance of Parent to set prototype of Child. Parent can have instance specific members that should not be on Child.prototype or needs parameters passed (that are checked) that are not available when declaring the Child type. Better to use `Child.prototpe=Object.create(Parent.prototype)` and polyfil Object.create if needed for very old browsers. – HMR Aug 30 '14 at 16:31
  • @HMR I think this is a miscommunication....my parent/child example isn't intended to be an example of inheritance, but of child objects that *belong* to a parent object (composition, not inheritance). Perhaps I should have just used an array of ints to avoid confusion - I was simply pointing out that if you put the array on the prototype, it's shared by all instances of that prototype. – Matt Browne Aug 30 '14 at 18:28
  • For inheritance, I agree with you, it would be better to use Object.create(Parent.prototype) – Matt Browne Aug 30 '14 at 18:36
  • The article you refer to creates instance of Parent to set prototype of Child. Your answer is good but the article is flawed in that way. – HMR Aug 31 '14 at 01:25
  • Ah ok, didn't realize you were talking only about the article and not my answer; thanks for the clarification. – Matt Browne Aug 31 '14 at 04:39
1

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

Community
  • 1
  • 1
HMR
  • 37,593
  • 24
  • 91
  • 160
  • 1
    +1 For the distinction about shared vs instance as opposed to data vs behavior. Usually data is per-instance (except for static properties, and as you mentioned it's usually better to make those properties of the constructor function) and the methods are shared, but I should have been more clear about that. – Matt Browne Aug 30 '14 at 18:35
  • It'd be nice if I could "merge" the two answers provided as they both represent pieces of the complete answer for me. I won't change the accepted answer because I got "out of the woods" with Matt's answer. Thanks, HMR, for the clarification on this matter! – papiro Sep 01 '14 at 14:35