15

So I've been getting up to speed on some of the newer features of JavaScript and have been reading about Object.setPrototypeOf(). I ran across this bit of code from MDN which deals with inheriting from regular objects. But I'm confused at how they use Object.setPrototypeOf() here. I expected them to write

Object.setPrototypeOf(Dog, Animal) 

as opposed to what the do below. Why do they write it this way?

var Animal = {
  speak() {
    console.log(this.name + ' makes a noise.');
  }
};

class Dog {
  constructor(name) {
    this.name = name;
  }
}

// If you do not do this you will get a TypeError when you invoke speak
Object.setPrototypeOf(Dog.prototype, Animal);

var d = new Dog('Mitzie');
d.speak(); // Mitzie makes a noise.
user3840170
  • 26,597
  • 4
  • 30
  • 62
Al R.
  • 2,430
  • 4
  • 28
  • 40
  • 2
    Notice that `Dog` is a constructor function while `Animal` is an object (like `Dog.prototype` is an object). – Bergi Jul 11 '17 at 20:21
  • Yes, that did contribute to some confusion. I was thinking of Animal as a constructor... – Al R. Jul 11 '17 at 20:22

3 Answers3

10

The reason for calling Object.setPrototypeOf is to make sure that any objects created by the Dog constructor will get the Animal object in their prototype chain. It would be wrong to set a prototype of the constructor itself (not to be confused with the constructor's prototype property which really is a misnomer), since the constructor has no place in d's prototype chain.

A created Dog object does not get Dog in its prototype chain, but Dog.prototype. Dog is just the vehicle by which objects are created, it is not supposed itself to become part of the prototype chain.

You could instead do this in the Dog constructor:

Object.setPrototypeOf(this, Animal)

That makes the length of the prototype chain one step shorter, but the downside is that now d instanceof Dog will no longer be true. It will only be an Animal. This is a pity, and it explains why it is good to keep the original Dog.prototype object, while setting its prototype to Animal, so that now d is both a Dog and an Animal.

Read about this subject here. I would promote my own answer to that Q&A.

daotoad
  • 26,689
  • 7
  • 59
  • 100
trincot
  • 317,000
  • 35
  • 244
  • 286
  • 1
    *It would be wrong to set a prototype of the constructor itself*. This confuses me because if I change `Dog` to be a constructor function instead of a class then this works: `function Dog() { this.name = name; } Dog.prototype = Animal;` What's different? Aren't I setting a prototype on the constructor itself? – Frank Modica Jul 11 '17 at 20:12
  • 1
    Thanks for these examples. It took a couple re-reads, but makes sense now. My confusion stemmed from misunderstanding the purpose of Object.setPrototypeOf(). I'm used to the old way of JS class inheritance, where you'd assign to the prototype property of the constructor, e.g. Dog.prototype = new Animal(). So I imagined Object.setPrototypeOf() as shorthand for that (of course, here Animal is not a constructor at all). It's now apparent that Object.setPrototypeOf needs to be applied to instances of Dog, or better yet (as you note) further up the Dog instance prototype chain. – Al R. Jul 11 '17 at 20:13
  • 2
    Indeed, the way the word `prototype` is used in the language is confusing: `setPrototypeOf` relates more to `__proto__` then to `prototype`. The latter could better have been named differently, although I cannot think of a short name. Something like `prototypeForObjectsCreatedByThisConstructor`, but that is maybe a tad too verbose ;-) So when we use the word prototype in an English sentence we most often refer to what `__proto__` is, not `prototype`. – trincot Jul 11 '17 at 20:19
  • I shared Frank's confusion. Trincot's own answer to a previous question was helpful to me: https://stackoverflow.com/questions/572897/how-does-javascript-prototype-work/42880438#42880438. You want to get Animal on the prototype chain of Dog instances, which Dog.prototype = Animal does. Object.setPrototypeOf() is not shorthand for this, which was the source of my confusion. – Al R. Jul 11 '17 at 20:20
  • 1
    Right: `Object.setPrototypeOf(a, b)` corresponds to (the discouraged) `a.__proto__ = b`, and has nothing to do with the `prototype` property. – trincot Jul 11 '17 at 20:28
3

1) Animal is an Object literal
2) Object literal doesn't have prototype property
3) the syntax is

Object.setPrototypeOf(targetObj, sourceObj);
Object.setPrototypeOf(Dog.prototype,Animal);

4) By doing this we are inheriting the properties of
object literal ( Animal) to another literal or constructor(Dog)

5) here the Dog 's prototype is being set from Animal.
this method (Object.setPrototypOf()) sets a reference to Animal's methods to Dog's prototype

Abhishek D K
  • 2,257
  • 20
  • 28
0

We can do the same using {__proto__:...}:

var Animal = {
   speak() {
     console.log(this.name + ' makes a noise.');
   }
};

var Dog={
  __proto__: Animal
}

Dog.name='Mitzie'
Dog.speak(); // Mitzie makes a noise.

The {__proto__: Animal} is a way of defining prototype chain. Here, this is {__proto__: {speak:function(){...}} as defined in Animal.

To understand this let's see the chain in steps. When we did Dog.speak() js can't find function speak inside Dog. So it goes up the chain once and finds speak inside Animal. As expected this outputs "Mitzie makes a noise.".

Conceptually, Object.setPrototypeOf(Dog.prototype, Animal) is the same as Dog.prototype.__proto__=Animal. We call this setting Dog.prototype.[[Prototype]] to Animal. The [[Prototype]] is a link pointing to an object one step up the chain.

var Animal=function() {}
Animal.prototype.speak=function() {
    console.log(this.name + ' makes a noise.');
}

var Dog=function(name) {
    this.name=name
}

// set up prototype chain
Dog.prototype.__proto__=Animal.prototype

var d = new Dog('Mitzie');
d.speak(); // Mitzie makes a noise.

Here is your example replaced with using __proto__ to define the prototype chain.

var Animal = {
   speak() {
     console.log(this.name + ' makes a noise.');
   }
};

class Dog {
   constructor(name) {
   this.name = name;
  }
}

//Object.setPrototypeOf(Dog.prototype, Animal);// If you do not do this you will get a TypeError when you invoke speak
Dog.prototype.__proto__=Animal

var d = new Dog('Mitzie');
d.speak(); // Mitzie makes a noise.

Lastly, you could just use class extends...

var Animal = class {
    constructor(name) {
    this.name=name
  }
   speak() {
     console.log(this.name + ' makes a noise.');
   }
};

class Dog extends Animal {
   constructor(name) {
     super(name)
  }
}

var d = new Dog('Mitzie');
d.speak(); // Mitzie makes a noise.

As you can see this approach is the simplest so recommended ;)

Logan Lee
  • 807
  • 9
  • 21