0

I am having some confusion about implementing inheritance through prototyping and 'this' keyword in JavaScript.

let person = {
  stomach: [],

  eat(food) {
    this.stomach.push(food);
  }
};

let tony = {
  __proto__: person
};

let peter = {
  __proto__: person
};

tony.eat("shawarma");
alert( tony.stomach ); // shawarma


alert( peter.stomach ); // shawarma

In the above example, why does the last line gives the answer 'shawarma' even if nothing was pushed ?

Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
JSnow
  • 929
  • 2
  • 11
  • 24

4 Answers4

1

Because both tony and peter share the array, which is on person. There's only one array, you're just changing its state.

After you've created tony and peter, you have this in memory (omitting details):

                               +−−−−−−−−−−+                   
person−−−−−−−−−−−−−−−−−−−+−+−−>| (Object) |                   
                        / /    +−−−−−−−−−−+      +−−−−−−−−−−−+ 
                        | |    | stomach  |−−−−−>|  (Array)  | 
                        | |    +−−−−−−−−−−+      +−−−−−−−−−−−+ 
                        | |                      | length: 0 |
                        | |                      +−−−−−−−−−−−+ 
         +−−−−−−−−−−−+  | |
tony−−−−>| (Object)  |  | |
         +−−−−−−−−−−−+  | |
         | __proto__ |−−+ |
         +−−−−−−−−−−−+    |
                          |
         +−−−−−−−−−−−+    |
peter−−−>| (Object)  |    |
         +−−−−−−−−−−−+    |
         | __proto__ |−−−−+
         +−−−−−−−−−−−+

Whether you access that array via tony.__proto__.stomach or peter.__proto__.stomach (via the prototype chain), you're accessing just that one array. When you push "shawarma" on it via eat, that one array's state is modifed, and visible regardless of which path you take to get to it:

                               +−−−−−−−−−−+                   
person−−−−−−−−−−−−−−−−−−−+−+−−>| (Object) |                   
                        / /    +−−−−−−−−−−+      +−−−−−−−−−−−−−−−+ 
                        | |    | stomach  |−−−−−>|    (Array)    | 
                        | |    +−−−−−−−−−−+      +−−−−−−−−−−−−−−−+ 
                        | |                      | length: 1     |
                        | |                      | 0: "shawarma" |
         +−−−−−−−−−−−+  | |                      +−−−−−−−−−−−−−−−+
tony−−−−>| (Object)  |  | |
         +−−−−−−−−−−−+  | |
         | __proto__ |−−+ |
         +−−−−−−−−−−−+    |
                          |
         +−−−−−−−−−−−+    |
peter−−−>| (Object)  |    |
         +−−−−−−−−−−−+    |
         | __proto__ |−−−−+
         +−−−−−−−−−−−+

You'd solve this by giving tony and peter their own stomachs, and probably removing stomach from person (though you could leave it if you want to use person directly as well as using it as a prototype):

let person = {
  stomach: [], // You may or may not want to remove this, depending
  eat(food) {
    this.stomach.push(food);
  }
};

let tony = {
  __proto__: person,
  stomach: []
};

let peter = {
  __proto__: person,
  stomach: []
};

tony.eat("shawarma");
console.log(tony.stomach);  // shawarma

console.log(peter.stomach); // empty
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • I need to become _a lot faster_ to answer in crowded tags :( – Suraj Rao Sep 02 '17 at 10:56
  • @suraj: In this case, we both should have gone looking for duplicates rather than answering; I realized I was being silly and it didn't take long to find a dupetarget. – T.J. Crowder Sep 02 '17 at 11:02
0

tony.hasOwnProperty("stomach") will return false (since it is not what other OO languages call a property) and is now part of the prototype (like a class property or static member). A prototype is common to each object that uses it.

Therefore any object which has person as its __proto__ or prototype will share the same stomach.

You can, with ES6, use this instead:

class Person{
  constructor(){
    this.stomach = [];
  }

  eat(food){
    this.stomach.push(food);
  }
}

const tony = new Person();
const peter = new Person();

class RandomPelo extends Person{
  constructor(){
    super();
    this.randomness = true;
  }

  eat(food){
    super.eat(food);
    this.weird_shared_stomach.push(food);  
  }
}

const pelo = new RandomPelo();

NB:
the class keyword is just syntactic sugar around the concept of constructor function, therefore you can also modify its prototype to add "static" properties:

RandomPelo.prototype.weid_shared_stomach = [];

Vivick
  • 3,434
  • 2
  • 12
  • 25
0

Because "stomach" is a property of prototype that is exactly the same for tony and peter objects. You need to reinitialize "stomach" property in constructor for every newly created object.

Also try to avoid reference to proto https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto

class Person {
    constructor() {
        // created separately for every new object
        this.stomach = [];
    }

    eat(food) {
        this.stomach.push(food);
    }
}

var tony = new Person();
var peter = new Person();
wookieb
  • 4,099
  • 1
  • 14
  • 17
0

That's because both tony and peter share the same person prototype which contains stomach property.

When you trigger tony.eat('shawarma') the eat method is being launched with this pointing to the person object. So the person's stomach is being changed.

Everything will work well if you give peter and tony their own stomachs ;).

let person = {
  eat(food) {
    this.stomach.push(food);
  }
};

let tony = {
  stomach: [],
  __proto__: person
};

let peter = {
  stomach: [],
  __proto__: person
};

EDIT: Just saw @wookieb answer - it's even better than mine.