DEV Community

loading...
Cover image for Understanding JavaScript Prototypes

Understanding JavaScript Prototypes

Zach Snoek
Software Engineer
・4 min read

Introduction

When you first learned JavaScript, you might have started by writing something simple like creating a string primitive:

const hello = "Hello, world!";
Enter fullscreen mode Exit fullscreen mode

You likely even learned how to use split to turn that string into an array of substrings:

const parts = hello.split(",");
console.log(parts); // output: ["Hello", " world!"]
Enter fullscreen mode Exit fullscreen mode

You didn't implement split yourself, though. Instead, split is defined on hello's prototype object, which comes from String. Prototypes are JavaScript's method of inheritance and it allows properties to be shared across all object instances.

Prototypes

All JavaScript objects have a prototype, which is an object that it inherits properties from. This prototype object is a property on the constructor function that the inheriting object was created from, and the inheriting object links to it.

An object's prototype can have its own prototype, and that prototype can have its own prototype; this prototype chain continues until a prototype points to null, which is the end of the chain. Most objects are instances of Object, so the prototype chain will eventually link back to Object's prototype property, which is null.

This diagram, modified from MDN and created with Excalidraw, shows one way you can think about the prototypal inheritance of hello:

prototypes2

The prototype property and an object's prototype

A constructor function defines the prototype object on its prototype property; this is the object that all inheriting objects will link to. For example, to see all of the properties inherited by instances of String, we can log String.prototype:

console.log(String.prototype);
Enter fullscreen mode Exit fullscreen mode

Output:

{
    anchor: ƒ anchor()
    big: ƒ big(),
    ...
    split: ƒ split()
    ...
    __proto__: Object
}
Enter fullscreen mode Exit fullscreen mode

To access the prototype of an object, we can call Object.getPrototypeOf(obj) or use the __proto__ property of the object in many web browsers. Since hello is an instance of String (or, coerced to String at runtime), we should expect to see it linked to the prototype object defined by the String constructor function:

console.log(Object.getPrototypeOf(hello));
Enter fullscreen mode Exit fullscreen mode

Output:

{
    anchor: ƒ anchor()
    big: ƒ big(),
    ...
    split: ƒ split()
    ...
    __proto__: Object
}
Enter fullscreen mode Exit fullscreen mode

The prototype chain

We've discussed what prototypes are and how instances link to them, but how does this allow objects to inherit properties? To find the property of an object, JavaScript will "walk up" the prototype chain. First, it will look at the calling object's properties. If the property is not found there, it will look at its prototype's properties. This continues until the property is found or the end of the prototype chain is reached.

An instance of String is an object that inherits from Object, so String's prototype is the prototype defined on Object's constructor function. Because of this, we can access the properties defined on Object's prototype such as toLocaleString:

console.log(hello.toLocaleString()); // output: "Hello, world!"
Enter fullscreen mode Exit fullscreen mode

When we called hello.toLocaleString(), JavaScript:

  1. Checked for the property on hello and did not find it
  2. Checked hello's prototype, the prototype object defined by String, and did not find it
  3. Checked String's prototype, the prototype object defined by Object, and did find it

Note: MDN is a handy way to tell which properties are defined on the prototype of built-in objects. For instance, the Array page links to documentation for all of the different properties, such as map and pop, that are defined on Array.prototype.

Walking the prototype chain in JavaScript

We briefly saw a simple graphical representation of hello's prototype chain earlier. Now that we know how to access an object's prototype, we can write our own function to show the chain programmatically:

function walkPrototypeChain(obj) {
    let current = Object.getPrototypeOf(obj);

    while (current) {
        console.log("Inherits from:", current.constructor.name);
        console.dir(current);

        const next = Object.getPrototypeOf(current);
        current = next;
    }

    console.log("Reached of prototype chain:", current);
}
Enter fullscreen mode Exit fullscreen mode

Note: current.constructor.name is the name of the constructor function that defines the prototype.

If we run this in the browser with hello, we get the following output:

prototype-chain

Extending a prototype

We can easily define our own properties on a constructor function's prototype property. Let's say we have a program that creates many arrays that we commonly want to ensure only contain truthy values. We can define a whereNotFalsy property on Array's prototype to make this available on every array we create:

Array.prototype.whereNotFalsy = function () {
    return this.filter((x) => x);
};
Enter fullscreen mode Exit fullscreen mode

Now we can call whereNotFalsy on the subsequent arrays we create:

const hasFalsyValues = ["", "Hello, world!", null];

console.log(hasFalsyValues.whereNotFalsy()); // output: ["Hello, world!"]
Enter fullscreen mode Exit fullscreen mode

Conclusion

Prototypes allow objects to inherit shared properties. An object's prototype refers to the object that it inherits properties from. This prototype object is defined on the prototype property of the constructor function that
creates it. Inheriting objects contain a link to the prototype object and it can be accessed through the __proto__ property in web browsers or by calling Object.getPrototypeOf in other contexts.

When an object's property is accessed, JavaScript first checks its own properties, then walks its prototype chain to find the property––this is how objects are able to inherit properties through prototypes. Lastly, we can directly modify the prototype of a constructor function by accessing its prototype property, which will affect all inheriting objects.

References

Cover photo by Daniel McCullough on Unsplash

Discussion (1)

Collapse
z00md profile image
z00md

Not just constructor function, every function in JS has prototype object. It depends on us if we call it with new keyword or not.

And one more point to add - There is a static method Object.create() through which we can create new.objects without any prototype.

Forem Open with the Forem app