JS Tip of the Day: The Deal With Prototypes

The Deal With Prototypes
Level: Beginner

Prototypes in JavaScript are objects that other objects look to when they can’t find properties that are being requested of them. If the object doesn’t have the property itself, it can instead use the value of that property in its associated prototype.

You can set the prototype for an object using Object.setPrototypeOf().

let me = {};
let friend = { answer: 42 };
console.log(me.answer); // undefined

Object.setPrototypeOf(me, friend);
// (me's prototype is now friend)

console.log(me.answer); // 42 (taken from friend)

Prototypes are the mechanism by which JavaScript handles object inheritance. In the example above, the me object was set to inherit from the friend object. By inheriting from friend, me has access to all of the properties in friend.

Whenever you create a new object normally, that object will already inherit values from the Object type. This includes methods like toString().

let empty = {};
console.log(empty.toString()); // [object Object]
// (toString is inherited from empty's default prototype)

When an object inherits properties from its prototype, it also inherits the properties in its prototype’s prototype since prototype objects will also look to their own prototypes if a property is being accessed that they don’t have. This creates the prototype chain. In the earlier example where me's prototype was set to friend, me still had access to all of the values inherited from the Object type because friend inherits from it by default.

let me = {};
let friend = { answer: 42 };
Object.setPrototypeOf(me, friend);
console.log(me.toString()); // [object Object]
// (me's toString inherited from friend
// which inherited it from the Object type)

New objects can be created with a specific prototype already set using Object.create().

let friend = { answer: 42 };
let me = Object.create(friend);
console.log(me.answer); // 42

To get the prototype associated with an object, use Object.getPrototypeOf().

let friend = { answer: 42 };
let me = Object.create(friend);
console.log(Object.getPrototypeOf(me) === friend); // true

There’s also an __proto__ accessor property (inherited from the Object type), but it’s use is not recommended. However, when digging through object hierarchies in debuggers like the one used in chrome, you’ll see this identifier used to provide access to the prototype of an object so it’s good to know.

let me = {};
let friend = { answer: 42 };
me.__proto__ = friend;
console.log(me.answer); // 42
console.log(Object.getPrototypeOf(me) === me.__proto__); // true

Object prototypes must be an object or null. Non-null primitive values are not allowed. If you create an object with a null prototype (or set it to null through Object.setPrototypeOf()) it will not have access to any of its normally inherited properties.

let reallyEmpty = Object.create(null);
console.log(reallyEmpty.toString()); // TypeError: not a function

In this particular case, __proto__ would also fail to work since it is inherited much like toString() is.

let reallyEmpty = Object.create(null);
console.log(reallyEmpty.__proto__); // undefined (should be null)
let lessEmpty = { glassHalfFull: true };
reallyEmpty.__proto__ = lessEmpty; // (does not set prototype)
console.log(Object.getPrototypeOf(reallyEmpty)); // null

The actual prototype of an object is stored in an internal slot referred to as [[Prototype]] which is not accessible directly. Some objects have a prototype property, but it does not represent the prototype of its owner. The prototype property exists on constructor functions (or any function that can be a constructor) and is the object used as the prototype for the instances that constructor creates.

let arr = new Array(); // arr prototype set to Array.prototype
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true

For objects created with literal syntax, the same applies even though you’re not using new explicitly. For example, {} creates a new Object(), [] creates a new Array(), and /pattern/ creates a new RegExp(pattern).

let arr = []; // same as new Array()
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true

So when we say an object inherits from the Object type, it’s inheriting from Object.prototype.

let obj = {}; // same as new Object()
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true

In creating your own constructor function, it is automatically given a prototype property which is a normal object inheriting from Object.prototype.

function Person () {} // (constructor function)
console.log(typeof Person.prototype); // object
console.log(Object.getPrototypeOf(Person.prototype) === Object.prototype);
// true

Using a constructor to create a new object creates a new object that inherits from the constructor’s prototype which in turn inherits from Object.prototype setting up the inheritance chain for that object.

function Person () {}
let me = new Person();
console.log(Object.getPrototypeOf(me) === Person.prototype); // true
console.log(Object.getPrototypeOf(Person.prototype) === Object.prototype);
// true
console.log(me.toString()); // [object Object]
// (me's toString inherited from Person.prototype
// which inherited it from Object.prototype)

A constructor’s prototype is where you’d store shared methods. Each instance created by the constructor would have access to the methods defined there because each instance inherits from the same prototype object that’s defined in the constructor.

function Person () {}
Person.prototype.getAnswer = function () {
    return 42;
}
let me = new Person();
console.log(me.getAnswer()); // 42
let myEvilClone = new Person();
console.log(friend.getAnswer()); // 42

The class syntax allows you to do the same thing in a more concise manner. The result is the same: a constructor function that creates object’s that inherits from the class’s (constructor’s) prototype object.

class Person {
    getAnswer () { // defined on prototype
        return 42;
    }
}
console.log(typeof Person.prototype.getAnswer); // function

let me = new Person();
console.log(Object.getPrototypeOf(me) === Person.prototype); // true
console.log(me.getAnswer()); // 42

With classes, you can also easily extend other classes. This will add the extended class’s prototype to the prototype chain of the instances it creates.

class Person {
    getAnswer () {
        return 42;
    }
}
class SmartPerson extends Person {
    getSmartAnswer () {
        return 'Two score and two';
    }
}

let me = new SmartPerson();
console.log(Object.getPrototypeOf(me) === SmartPerson.prototype);
// true
console.log(Object.getPrototypeOf(SmartPerson.prototype) === Person.prototype);
// true
console.log(me.getAnswer()); // 42 (from Person.prototype)
console.log(me.getSmartAnswer());
// Two score and two (from SmartPerson.prototype)

Classes are likely going to be the primary means by which you take advantage of prototypes in JavaScript. While you can use Object.setPrototypeOf() to change an object’s prototype, it’s not common to do so and can even have a negative impact on performance. You generally want to create new objects with their prototype already in place and constructors created with class are an easy and convenient way to do so.

While this has been a pretty quick and basic overview of prototypes in JavaScript, it can be a difficult concept to get your head wrapped around. If you don’t get it right away, that’s ok. It may take some time.

More info: