10

I've seen that in many cases inheritance in js can be implemented like so

function Organism(age) {
    this.age = age;
}

Organism.prototype.growOlder = function(){ this.age = this.age + 1}
var org = new Organism("1000");

function Human(name,age){
     Organism.call(this,age); //this sets up the properties on the human object
     this.name = name;
}

Human.prototype = Object.create(Organism)
Human.prototype.constructor = Human; //sets the constructor
Human.prototype.run = function(){ console.log(this.name + "run")}

My question is if someone can explain in detail why Object.create(Organism) is necessary and simply doing this

Human.prototype = Organism.prototype

is not enough.Respectively the prototype chain looks like

without object.create : Human.__proto__ -> Organism __proto__-> Object

with object.create: Human.__proto__-> IntermediateObject.__proto__ -> Organism.__proto__-> Object

Thank you for your time.

EDIT: I am aware of the es6 class and inherits syntactic sugar. Im just curious how things work on the lower levels though.

Return-1
  • 2,329
  • 3
  • 21
  • 56
  • 1
    `Object.create` takes prototype object as an argument. `Organism` is a constructor whose prototype you want to use as a base. So you want `Organism.__proto` not `Organism` itself. – dfsq Mar 13 '18 at 13:18
  • you're right, thats what i meant in the first place, i just missed the .__proto__ in all the chaos. edited it. But the question still remains, why use Object.create at all – Return-1 Mar 13 '18 at 13:21
  • 2
    I guess you also mean `Organism.call(this);`. – dfsq Mar 13 '18 at 13:24
  • ES 6 classes are not just syntactic sugar. They do things the pattern you mention above cannot do, i.e. correctly extend built-ins. – Jared Smith Mar 13 '18 at 13:26
  • `Organism.call(this)` is used to make the properties of `Organism` to be the `own` properties of `Human`, as they should be. – karthikaruna Mar 13 '18 at 13:26
  • 1
    @dfsq actually you want `Object.create(Organism.prototype)` – Patrick Roberts Mar 13 '18 at 13:32
  • @JaredSmith: for cases like this, though, and for most uses, they are merely syntactic sugar. They simply offer a more convenient way to set up the prototype chain and constructor, and sometimes obscure the fact that the underlying prototype system is the same as always. – Scott Sauyet Mar 13 '18 at 13:33
  • 1
    @ScottSauyet fair enough. I just spend my days writing web components, so I literally live in the space where the difference matters. – Jared Smith Mar 13 '18 at 13:42
  • So nice to see core javascript question getting so many upvotes! Even in 2018 when everyone just uses `class` sugar knowing roots is important. – dfsq Mar 13 '18 at 13:49
  • It seems you are definitely confused about the [difference between `__proto__` and `prototype`](https://stackoverflow.com/q/9959727/1048572) – Bergi Mar 13 '18 at 14:43

3 Answers3

4

My question is if someone can explain in detail why Object.create(Organism) is necessary and simply doing this Human.prototype = Organism.prototype is not enough.

You can totally do this, and it will even work.

However there is subtle problem. If you assign Organism.prototype directly it will cause a nasty side effect when Organism will inherit all the methods you create on Human.prototype. Not what you want.

For example if you do Human.prototype = Organism.prototype then org will have run, however it should probably not have methods of Human.

dfsq
  • 191,768
  • 25
  • 236
  • 258
  • But it sounds more logical that `Human` should inherit even the new methods added at a later point to `Organism`, right? – karthikaruna Mar 13 '18 at 13:38
  • 1
    @karthikaruna Yes, and it will. But if you add methods to Human, Organism should not receive those new methods from Human. – dfsq Mar 13 '18 at 13:39
  • 1
    @GeorgeAvgoustis You are welcome. Before `Object.create` people used to use intermediate dummy constructor to avoid direct referencing between child.prototype-parent.prototype. If you want to learn more about possible approaches to inheritance, I suggest you checking excellent book Javascript Patterns (by Stoayn Stefanov). You will lean all the gotchas of all possible implementations. – dfsq Mar 13 '18 at 13:47
  • thanks > Number.MAX_VALUE //true – Return-1 Mar 13 '18 at 13:51
1

Organism is not an organism. It's a constructor function that creates organisms. The prototype chain should point to an object of the right type.

The reason you shouldn't do the following is more subtle:

Human.prototype = new Organism()

That's because calling new Organism sets properties of the object in a way you may not want. For instance, it sets this.age to undefined. It might also have global effects such as updating a counter, launching the nuclear weapons, stealing your boyfriend, or some such. When you use Object.create, you avoid this issue; it simply sets up the correct prototype chain for your new object.

One note on your technique: by not passing age in the Human constructor and then passing it along to the Organism one, you again end up with undefined. This could be problematic.

Update

An edit changed Human.prototype = Organism to Human.prototype = Organism.prototype. The objection to this is that then any changes to Human.prototype updates all organisms. That's unlikely to be what you want.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • I guess OP is asking about why not just doing `Human.prototype = Organism.prototype` :) – dfsq Mar 13 '18 at 13:36
  • Okay but i am not ( as far as i understand) calling new Organism() and assigning the object created by that constructor to Human.prototype. What i am doing instead is `Human.prototype = Organism.prototype.` which essentially is pointing the prototype of the Human constructor to the prototype of the Organism constructor which has methods like growOlder. I am trying to understand how Object.assign() changes anything instead of introducing an intermediate object in the prototype chain. Thank you for replying – Return-1 Mar 13 '18 at 13:37
  • @GeorgeAvgoustis: I tried to answer the question as posted and then described a problem with one possible adjustment to it. Your edit made a different adjustment, and I updated my answer. – Scott Sauyet Mar 13 '18 at 13:43
1

I am aware of the es6 class and inherits syntactic sugar. Im just curious how things work on the lower levels though.

So let's start with the modern approach and work our way backwards:

class Organism {
  constructor (age) {
    this.age = age
  }

  growOlder () {
    this.age++ 
  }
}

class Human extends Organism {
  constructor (name, age) {
    super(age)

    this.name = name
  }

  run () {
    console.log(`${this.name} run`) 
  }
}

would be the ES6 canonical approach to prototypal inheritance. Using Babel and simplifying a few of the type checks that don't apply to properly declared classes, we can see how this transpiles to ES5:

function Organism(age) {
  this.age = age
}

Organism.prototype.growOlder = function growOlder() {
  this.age++
}

function Human(name, age) {
  Organism.call(this, age)

  this.name = name
}

Human.prototype = Object.create(Organism.prototype)
Human.prototype.constructor = Organism

if (Object.setPrototypeOf)
  Object.setPrototypeOf(Human, Organism)
else
  Human.__proto__ = Organism

Human.prototype.run = function run() {
  console.log(this.name + " run")
}

Transpiled using es2015-loose for simplicity, otherwise things get a bit more involved in order to make prototype methods non-enumerable and other details

Human.prototype = Object.create(Organism.prototype) creates a new empty object with __proto__ set to Organism.prototype, without invoking the constructor for side-effects, and sets that as the new prototype for Human.

It's a more correct approach for prototypal inheritance supported starting in ES5 since ES3 did not have Object.create() and so required Human.prototype = new Organism(). Unfortunately, this creates an object with the property age = undefined, which may cause undesired behavior for edge-cases since that causes 'age' in Human.prototype === true. Other constructors may have further undesired side-effects.

As such, it is necessary to have Object.create() in order to properly transpile ES6 class syntax.

Another note is that Object.setPrototypeOf(Human, Organism) or Human.__proto__ = Organism causes static prototypal inheritance, in case Organism contains static methods. This is not the case here, but if you were inheriting from Array for example, that statement would cause the sub-class to also have the methods isArray() and from(). The former is preferred over the latter because __proto__ is an implementation-specific hack targeted to V8 for prototypal inheritance that is not part of the official specification for ECMAScript.

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153