Skip to main content

Prototype

TL;DR

Prototype is a feature of Javascript objects' that can mimic inheritence concept in object-oriented languages but is instead just simple behavior delegations.

Below images visualizes how prototype concept forms the relationship between JavaScript objects.

JavaScript Prototype Layout (Source: mollypages)

Train of Thoughts

At the time when Brendan Eich was designing the language, object-oriented programming languages such as C++ and Java were gaining it's popularity.Though affected by OOP and everything were treated as object, JavaScript, however, was intended to be a script language to provide interactive abilities on the web.

Implementing concepts such as class and inheritance might be too complicated and makes it less easier to pick up. Then how was JavaScript designed to mimic the inheritance concept?

note

JavaScript does have a class syntax introduced in ECMAScript (ES6), but is essentially a syntax sugar over the prototype-based model.

Creating object with object literal

To create an object in simplest term, we can use the object literal syntax to create an instance and encapuslate properties related to it:

var borderoCollie = {}
borderCollie.type = 'Dog'
borderCollie.breed = 'Border Collie'

var pug = {}
pug.type = 'Dog'
pug.breed = 'Pug'

The down side is obvious, it gets tedious if multiple instances need to be instantiated, nor do the instances provide information indicating their reltationship with their prototype (in this case, Dog, since both border collie and pug are different breeds of dog).

We can reduce the duplicated codes by creating an instance through function:

function Dog (breed) {
return {
type: 'Dog'
breed: breed
}
}

var borderCollie = Dog('Border Collie')
var pug = Dog('Pug')

Instantiate with new keyword

Although we may infer through code that both instances might resemble to each other since they are both created through same function, it doesn't implies the relationship that both instances represent a breed of dog.

Taking page of OOP langugaes, new keyword and function constructor are introduced into JavaScript. Meaning we can instantiate an instance with following snippet, where the this keyword binds to the created instance.

One important thing to notice is that a constructor property is automatically added to the created instances, which points to the function Dog, indicating that instances are created by the constructor function.

An operator, instanceof, is added to test whether if the prototype property of a constructor appears anywhere in the prototype chain of an object.

// A function constructor
function Dog(breed){
this.type = 'Dog'
this.breed = breed
}

var border = new Dog('Border Collie');
console.log(border.breed) // Border Collie
console.log(border.constructor === Dog) //true
console.log(border instanceof Dog) //true

var pug = new Dog('Pug');
console.log(pug.breed) // Pug
console.log(pug.constructor === Dog) //true
console.log(pug instanceof Dog) //true

Introducing prototype

Desipte the syntax resembles other OOP language, its hard to share mutual properties of funtionalities between instances through this approach. As shown in below example, each instance will have its own memory space for each method, which increases memory usage and is slower when instantiating a new instance.

function Dog(breed){
this.type = 'Dog'
this.breed = breed

this.Bark = function(){
console.log(`I'm a ${this.breed}`)
}
}

var border = new Dog('Border Collie')
var pug = new Dog('Pug')

console.log(border.type) // Dog
console.log(pug.type) // Dog
console.log(border.Bark === pug.Bark) //false

To solve this problem, a property prototype is automatically added to the function which points to another object which instances will inherite all of its properties from.

tip

prototype is a function-specific property, object.prototype returns undefined.

let a = { b: 1 }
a.prototype // undefined

Defined in ECMA-262, function objects (e.g function a () {}) are created by by calling a CreateDynamicFunction operation on the Function object, which defines a prototype property on any function it creates whose kind is not ASYNC to provide for the possibility that the function will be used as a constructor.

function Dog(breed){
this.breed = breed
}

console.log(Dog.prototype) // {constructor: ƒ}

Dog.prototype.type = 'Dog'
Dog.prototype.Bark = function () {
console.log(`I'm a ${this.breed}`)
}

console.log(Dog.prototype) // {type: 'Dog', Bark: ƒ, constructor: ƒ}

var border = new Dog('Border Collie')
var pug = new Dog('Pug')

console.log(`${border.breed} is a ${border.type} breed`) // Border Collie is a Dog breed
console.log(`${pug.breed} is a ${pug.type} breed`) // Pug is a Dog breed

console.log(border.constructor) // ƒ Dog(breed){ this.breed = breed }
console.log(border.constructor === Dog) // true

console.log(pug.constructor) // ƒ Dog(breed){ this.breed = breed }
console.log(pug.constructor === Dog) // true

console.log(border.constructor === pug.constructor) // true

console.log(border.Bark === pug.Bark) // true

border.Bark() // I'm a Border Collie
pug.Bark() // I'm a Pug

Dog.prototype.type = 'Mammal'
Dog.prototype.Bark = function () {
console.log(`I'm a ${this.type} that don't bark`)
}

border.Bark() // I'm a Mammal that don't bark
pug.Bark() // I'm a Mammal that don't bark

Let's break down above code a bit:

  1. Line #1~#3: A constructor function is declared.
  2. Line #5: A prototype property is automatically added to the constructor function, which is an object instance that only has a constructor property.
  3. Line #7~#12: Adding shared properties and methods to the prototype object directly.
  4. Line #14 ~ #15: Instantiate two Dog instances: border and pug.
  5. Line #17 ~ #31: Checking properties of two instances, what is worth noting is that:
    • Both instances immediately have accessible breed and type properties after instantiate.
    • Instances have a constructor property that points to the same construction function Dog.
    • Both instances have access to the same Bark function while able to access their own properties with this keyword.
  6. Line #33 ~ #39: Directly modifying the prototype object affects both instances' behaviour

This is the prototype model that JavaScript uses to mimic the inheritance behaviour of OOP languages.

Accessing object instances' prototype

Even though we now know both instances have some sort of connection with the constructor function, is there an explict way to check it? That's where static methods Object.getPrototypeOf() and Reflect.getPrototypeOf() come in, where both returns the prototype (i.e. the value of the internal [[Prototype]] property) of the specified object.

Warning

You're almost guaranteed to come across accessing the prototype of an object with the __proto__ syntax when browsing articles about JavaScript prototype, which points to the prototype object of the creator function (the function used to create any instance of that type).

Basically, it is equivalent to Object.getPrototypeOf() for object instances, meaning Object.getPrototypeOf(border) === border.__proto__.

Though browsers might still support it for legacy reasons, such syntax is deprecated and no longer recommended.

In this note, __proto__ will be used for simplicity and verifying concept. One should gradually remove such syntax in production code.

function Dog(breed){
this.breed = breed
}

Dog.prototype.type = 'Dog'
Dog.prototype.Bark = function(){
console.log(`I'm a ${this.breed}`)
}

var border = new Dog('Border Collie')
var pug = new Dog('Pug')

console.log(Reflect.getPrototypeOf(border)) // {type: 'Dog', Bark: ƒ, constructor: ƒ}
console.log(Object.getPrototypeOf(pug)) // {type: 'Dog', Bark: ƒ, constructor: ƒ}
console.log(Reflect.getPrototypeOf(border) === Object.getPrototypeOf(border)) // true
console.log(Object.getPrototypeOf(border) === Object.getPrototypeOf(pug)) // true
console.log(Object.getPrototypeOf(border) === Dog.prototype) // true

Note that both border and pug instance has a prototype object, that contains type property and Bark method defined through our code. More importantly, line 16 and 17 confirms what we concluded in previous section that both returned prototypes are actually the same object instance, which is the constructor function's prototype object !

And as expected, modifying the prototype object will change behaviours of all inherited

// Directly modify prototype properties
Object.getPrototypeOf(border).type = 'Mammal'
Object.getPrototypeOf(border).Bark = function () {
console.log(`${this.breed} is a ${this.type} that doesn\'t bark`)
}

console.log(Reflect.getPrototypeOf(border)) // {type: 'Mammal', Bark: ƒ, constructor: ƒ}
console.log(Object.getPrototypeOf(pug)) // {type: 'Mammal', Bark: ƒ, constructor: ƒ}

border.Bark() // Border Collie is a Mammal that doesn't bark
pug.Bark() // Pug is a Mammal that doesn't bark
tip

This is why it is strongly discouraged to directly modifying prototype of third-party object. It will affect globally and cause unexpected behaviour, making it hard to trace the source of modification in large codebase. Only do so with a good reason.

// Overrides Array.concat method with Array.pop method
Array.prototype.concat = Array.prototype.pop

let a = [1, 2, 3, 4, 5]
a.pop() // 5
a.concat() //4
console.log(a) // [1, 2, 3]

Understanding prototype chain

In previous code examples, we've called type and Bark function on instances without second thought, but we will find that both object instances itself does not include both properties if we log them directly.

Instead, both properties exist in the prototype object of the instance.

let border = new Dog('Border Collie')
console.log(border)
// Dog {
// breed: 'Border Collie'
// [[Prototype]]: Object
// }

console.log(border.__proto__) // {type: 'Dog', Bark: ƒ, constructor: ƒ}
console.log(border.type) // Dog
border.Bark() // I'm a Border Collie

What if we add a property type directly to the instance ?

border.type = 'Animal'
console.log(border)
// Dog {
// breed: 'Border Collie',
// type: 'Animal'
// [[Prototype]]: Object
// }

console.log(border.type) // Animal
border.Bark() // I'm a Border Collie

Calling border.type now returns Animal instead of Dog. This demonstrates how JavaScript works : it will first search for properties on the called instance itself, and continues to search into prototype object if not found in instance itself.

The behaviour of local property will prevent searching for same property in the prototype object is called property shadowing. And the mechanism will continue to find the target property through object instance's prototype, prototype of object instance's prototype, and so on. It continues to search and stop ultimately when a certain prototype object has a null prototype, meaning the search has comes to end.

The searching path, essentially a chain of prototypes, is what is commonly known as the prototype chain.

What exactly is the prototype object that has a null prototype ({someObject}.proto === null) then? Since everything is treated as object in JavaScript, prototype chains of all objects eventually points to a same object: Object.prototype.

Since Object.prototype servers as the inheritance root of all object, it has a null prototype.

function Animal () {}

Animal.prototype.kingdom = 'Animalia'

function Dog(breed){
this.breed = breed
}

Dog.prototype.Bark = function () {
console.log(`I'm a ${this.breed}`)
}

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

let border = new Dog('Border Collie')

console.log(border.kingdom) // Animalia
border.Bark() // I'm a Border Collie

console.log(border.__proto__ === Dog.prototype) // true
console.log(border.__proto__.__proto__ === Animal.prototype) // true
console.log(border.__proto__.__proto__.__proto__ === Object.prototype) // true
console.log(border.__proto__.__proto__.__proto__.__proto__ === null) // true
console.log(Object.prototype.__proto__ === null) // true
// prototype chain:
// border -.__proto__-> Dog.prototype -.__proto__-> Animal.prototype -.__proto__-> Object.prototype -.__proto__-> null

Verifying prototype of an object

We now know what prototype chain is, how can we verify if a certain object involes in a prototype chain ? A few approaches can come in handy.

  • instanceof
  • instanceof operator is the earliest approach introduced to check the prototype chaining.

    The instanceof operator tests the presence of constructor.prototype in object's prototype chain. This usually (though not always) means object was constructed with constructor.

    -- Mdn Web Docs

    function Animal () {}
    Animal.prototype.kingdom = 'Animalia'

    function Dog(breed){
    this.breed = breed
    }
    Dog.prototype.Bark = function () {
    console.log(`I'm a ${this.breed}`)
    }

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

    let border = new Dog('Border Collie');
    console.log(border.kingdom) // Aminalia
    border.Bark() // I'm a Border Collie

    console.log(border instanceof Dog) // true, because Object.getPrototypeOf(border) === Dog.prototype
    console.log(border instanceof Animal) // true, because Object.getPrototypeOf(Dog.prototype) === Animal.prototype

    However, instanceof operator might fail on some circumstances, such as when an object is generated through Object.create with a particular prototype. Some edge case examples can be found on Mdn Web Docs. In short, instanceof operator is somewhat limited.

    let animalProto = {
    kingdom: 'Animalia'
    }

    let dog = Object.create(animalProto)
    dog.breed = 'Border Collie'
    dog.Bark = function () {
    console.log(`I'm a ${this.breed}`)
    }

    dog.Bark() // I'm a Border Collie
    console.log(dog.kingdom) // Aminalia
    console.log(dog instanceof animalProto) // Uncaught TypeError: Right-hand side of 'instanceof' is not callable

  • Object.isPrototypeOf
  • Contray to the instanceof operator, Object.isPrototypeOf is a built-in method available on the Object.prototype object and is much more generally used.

    isPrototypeOf() differs from the instanceof operator. In the expression object instanceof AFunction, object's prototype chain is checked against AFunction.prototype, not against AFunction itself.

    -- Mdn Web Docs

    function Animal () {}
    Animal.prototype.kingdom = 'Animalia'

    function Dog(breed){
    this.breed = breed
    }
    Dog.prototype.Bark = function () {
    console.log(`I'm a ${this.breed}`)
    }

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

    let border = new Dog('Border Collie');
    console.log(border.kingdom) // Aminalia
    border.Bark() // I'm a Border Collie

    console.log(Dog.isPrototypeOf(border)) // false
    console.log(Dog.prototype.isPrototypeOf(border)) // true
    console.log(Animal.isPrototypeOf(border)) // false
    console.log(Animal.prototype.isPrototypeOf(border)) // true

    Deep Dive

    Object instanceof Function === true ?

    If you run below code in browser console, you'll find the result a bit surprising. Is this a chicken-or-egg problem ?

    Object instanceof Function // true
    Function instanceof Object // true

    In fact, both Object and Function are constructor functions. And according ECMA-262 Specification:

    • The Object constructor has a internal slot [[Prototype]] whose value is Function.prototype. (link)

    • The Function constructor is itself a built-in function object, which has a internal slot [[Prototype]] whose value is Function.prototype. (link)

    That is to say: Function.__proto__ points to Function.prototype. Function is it's own constructor!

    Function.constructor === Function // true
    Object.__proto__ === Function.prototype // true

    References