Exploring the JavaScript Prototype Chain

Exploring the JavaScript Prototype Chain

·

4 min read

Say we have a class defined as below.

class Person {
    constructor(name, energy) {
        this.name = name;
        this.energy = energy;
    }

    eat(v) {
        this.energy += v;
    }

    sleep(v) {
        this.energy += v;
    }
}

const p1 = new Person("Jack", 1);
const p2 = new Person("Mary", 2);

It's a normal class, let's explore some of its details.

A class definition has a prototype property, which is just a normal object.

> Person.prototype
{constructor: ƒ, eat: ƒ, sleep: ƒ}

The eat and sleep property is just the function we defined inside the Person class, so we know now that these functions are stored in the prototype object.

The constructor property points to the whole class definition, so we have below equalness.

Person.prototype.constructor === Person; // true

Every object in JavaScript have a property __proto__ which points to the object's prototype, so does this prototype object. Now you may ask, what is the prototype of the prototype? The answer is Object's prototype.

Person.prototype.__proto__ === Object.prototype; // true

This Person class definition itself also have a __proto__ property, which points to the native code implementation.

> Person.__proto__
ƒ () { [native code] }

So, to sum it up, this Person class has some details like below.

Person
    - prototype
        - constructor => Person
        - eat
        - sleep
        - __proto__ => Object.prototype
    - __proto__ => ƒ () { [native code] }

After exploring this Person class, let see its instance p1 and p2. These 2 are just plain object, so they doesn't have the prototype property. But it also has __proto__ property, which points to the Person's prototype.

p1.__proto__ === Person.prototype; // true
p2.__proto__ === Person.prototype; // true

So when these instance call the class methods, they actually try the call methods stored in the class's prototype object.

Now let explore upward. We learn from above that the Person's __proto__ property points to Object.prototype. What is Object? Let's see some details.

First let's check the __proto__ property in Object.

> Object.__proto__
ƒ () { [native code] }

Oh, now we know Object is just a built-in function. Function is just like class, has an prototype property.

Screen Shot 2022-09-24 at 23.17.56.png

Wow, we can see there are a lot going on in the Object's prototype, such as toString. The object we normally see are all in the Object prototype chain, that's why we can always run the toString function.

The Object's prototype object also have a __proto__ property, let see what it is.

Object.prototype.__proto__ === null; // true

Oh, now we know the root of this chain is null.

So to sum it up, from bottom to top, we have details like below.

p1
    - __proto__ => Person.prototype

p2
    - __proto__ => Person.prototype

Person
    - prototype
        - constructor => Person
        - eat
        - sleep
        - __proto__ => Object.prototype
    - __proto__ => ƒ () { [native code] }

Object
    - prototype
        - __proto__ => null
        - toString
        - ...
    - __proto__  => ƒ () { [native code] }

null

Actually, the es6 class syntax is just a syntax sugar above the function style. So the Person class is almost the same as below function implementation.

function Person(name, energy) {
    this.name = name;
    this.energy = energy;
}

Person.prototype.eat = function(v) {
    this.energy += v;
}

Person.prototype.sleep = function(v) {
    this.energy += v;
}

const p1 = new Person("Jack", 1);
const p2 = new Person("Mary", 2);

In above example, we walk through the normal class object with the prototype chain. Now let's take the array as an example to see how prototype chain works in built-in function.

Normally, if we want to create an array, we will do below.

const arr = [];

This is actually a syntax sugar for below code.

const arr = new Array();

Now we have this array instance, let check its __proto__.

arr.__proto__ === Array.prototype; // true

Just as expected, this arr instance's __proto__ property points to the Array definition's prototype property.

Now let's see this Array definition.

> Array.__proto__
ƒ () { [native code] }

As you can see, this Array is just a function. Let's see its prototype.

Screen Shot 2022-09-24 at 23.33.25.png

A lot of things going on here, that's why we have a lot of methods to call with array instance.

Let's explore upward to see its prototype's prototype.

Array.prototype.__proto__ === Object.prototype; // true

Yes, Array is also under the Object's prototype.

So with all this information, from bottom to up, we can have below information.

arr
    - __proto__ => Array.prototype

Array
    - prototype
        - constructor => Array
        - push
        - pop
        - ...
        - __proto__ => Object.prototype
    - __proto__ => ƒ () { [native code] }

Object
    - prototype
        - __proto__ => null
        - toString
        - ...
    - __proto__  => ƒ () { [native code] }

null