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.