Skip to content

Instantly share code, notes, and snippets.

@rcanepa
Last active August 16, 2019 20:04
Show Gist options
  • Save rcanepa/1a310ac0314dbb04e407f6f6caa2605c to your computer and use it in GitHub Desktop.
Save rcanepa/1a310ac0314dbb04e407f6f6caa2605c to your computer and use it in GitHub Desktop.
JavaScript's prototype based inheritance

What is a prototype?

A prototype is a property functions have. This property points points to an object.

/*
No prototype inheritance is being used here. All cats, created with the factory
function, have a copy of the `sayName` function. What could go wrong if we had to
created thousands or millions of cats?
We can avoid having copies of the `sayName` function by using inheritance.
*/
function catFactory(name, birthday) {
const cat = {}
cat.name = name
cat.birthday = birthday
cat.sayName = function () {
return cat.name
}
return cat
}
const walter = catFactory('Walter', '01/23/86')
console.log(walter.name)
// => 'Walter'
console.log(walter.birthday)
// => '01/23/86'
console.log(walter.sayName())
// => 'Walter'
const gus = catFactory('Gus', '02/11/91')
console.log(gus.name)
// => 'Gus'
console.log(gus.birthday)
// => '02/11/91'
console.log(gus.sayName())
// => 'Gus'
/*
We extracted out the method and put it inside a separated object. Inside `catFactory`
we link to every new cat instance the `sayName` method. Now, there is only one instance
of the method/function that gets shared across all cat instances.
The downside of this approach is every time we add a new method to the `catMethods`
object, we need to update the `catFactory` function as well.
*/
function catFactory(name, birthday) {
const cat = {}
cat.name = name
cat.birthday = birthday
cat.sayName = catMethods.sayName
return cat
}
const catMethods = {
sayName: function () {
return this.name
}
}
const walter = catFactory('Walter', '01/23/86')
console.log(walter.name)
// => 'Walter'
console.log(walter.birthday)
// => '01/23/86'
console.log(walter.sayName())
// => 'Walter'
const gus = catFactory('Gus', '02/11/91')
console.log(gus.name)
// => 'Gus'
console.log(gus.birthday)
// => '02/11/91'
console.log(gus.sayName())
// => 'Gus'
/*
We use Object.create() to set the `catMethods` object as the prototype of
any cat created with the `catFactory` function.
From MDN web docs:
The Object.create() method creates a new object, using an existing object as
the prototype of the newly created object.
*/
function catFactory(name, birthday) {
// Make the prototype of a cat the `catMethods` object.
const cat = Object.create(catMethods)
cat.name = name
cat.birthday = birthday
return cat
}
const catMethods = {
sayName: function () {
return this.name
}
}
const walter = catFactory('Walter', '01/23/86')
console.log(walter.name)
// => 'Walter'
console.log(walter.birthday)
// => '01/23/86'
console.log(walter.sayName())
// => 'Walter'
const gus = catFactory('Gus', '02/11/91')
console.log(gus.name)
// => 'Gus'
console.log(gus.birthday)
// => '02/11/91'
console.log(gus.sayName())
// => 'Gus'
/*
Instead of using the `catMethods` object as the prototype, we add the `sayName` method
to the `catFactory` function prototype. Then, we pass that prototype as argument in the
Object.create() call. The result is the same.
*/
function catFactory(name, birthday) {
const cat = Object.create(catFactory.prototype)
cat.name = name
cat.birthday = birthday
return cat
}
catFactory.prototype.sayName = function () {
return this.name
}
const walter = catFactory('Walter', '01/23/86')
console.log(walter.name)
// => 'Walter'
console.log(walter.birthday)
// => '01/23/86'
console.log(walter.sayName())
// => 'Walter'
const gus = catFactory('Gus', '02/11/91')
console.log(gus.name)
// => 'Gus'
console.log(gus.birthday)
// => '02/11/91'
console.log(gus.sayName())
// => 'Gus'
/*
We use the `new` keyword to create cat instances. This makes using Object.create()
unnecessary because it automatically set the `Cat` function prototype to each object
created by that function. We don't need to explicitely create an object and return it,
the `new` keyword approach does that for us.
*/
function Cat(name, birthday) {
this.name = name
this.birthday = birthday
}
Cat.prototype.sayName = function () {
return this.name
}
const walter = new Cat('Walter', '01/23/86')
console.log(walter.name)
// => 'Walter'
console.log(walter.birthday)
// => '01/23/86'
console.log(walter.sayName())
// => 'Walter'
const gus = new Cat('Gus', '02/11/91')
console.log(gus.name)
// => 'Gus'
console.log(gus.birthday)
// => '02/11/91'
console.log(gus.sayName())
// => 'Gus'
/*
ES6 includes the `class` keyword (which is syntactic sugar on top of what we saw
in the previous example). It makes the whole process simple.
*/
class Cat {
constructor (name, birthday) {
this.name = name
this.birthday = birthday
}
sayName () {
return this.name
}
}
const walter = new Cat('Walter', '01/23/86')
console.log(walter.name)
// => 'Walter'
console.log(walter.birthday)
// => '01/23/86'
console.log(walter.sayName())
// => 'Walter'
const gus = new Cat('Gus', '02/11/91')
console.log(gus.name)
// => 'Gus'
console.log(gus.birthday)
// => '02/11/91'
console.log(gus.sayName())
// => 'Gus'
function Animal(name) {
this.name = name
}
Animal.prototype.sayName = function () {
return this.name
}
function Cat(name, birthday) {
// Call the Animal constructor
Animal.call(this, name)
this.birthday = birthday
}
Cat.prototype = Object.create(Animal.prototype)
Cat.prototype.constructor = Cat
// This is not the same as
// Cat.prototype = Animal.prototype
// This would make both classes use the same prototype objedt
Cat.prototype.eat = function () {
console.log(this.name, 'is eating now')
}
const walter = new Cat('Walter', '01/23/86')
console.log(walter.name)
// => 'Walter'
console.log(walter.birthday)
// => '01/23/86'
console.log(walter.sayName())
// => 'Walter'
const gus = new Cat('Gus', '02/11/91')
console.log(gus.name)
// => 'Gus'
console.log(gus.birthday)
// => '02/11/91'
console.log(gus.sayName())
// => 'Gus'
gus.eat()
function Animal(name) {
this.name = name
}
Animal.prototype.sayName = function () {
return this.name
}
function Cat(name, birthday) {
// Call the Animal constructor
Animal.call(this, name)
this.birthday = birthday
}
Cat.prototype = Object.create(Animal.prototype)
Cat.prototype.constructor = Cat
// This is not the same as
// Cat.prototype = Animal.prototype
// This would make both classes use the same prototype objedt
Cat.prototype.eat = function () {
console.log(this.name, 'is eating now')
}
const walter = new Cat('Walter', '01/23/86')
console.log(walter.name)
// => 'Walter'
console.log(walter.birthday)
// => '01/23/86'
console.log(walter.sayName())
// => 'Walter'
const gus = new Cat('Gus', '02/11/91')
console.log(gus.name)
// => 'Gus'
console.log(gus.birthday)
// => '02/11/91'
console.log(gus.sayName())
// => 'Gus'
gus.eat()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment