In JavaScript, every object has a [[Prototype]] internal slot which refers to the object it inherits from. In other words, prototypes are how JavaScript objects inherit from each other. There are a number of ways to set the prototype of an object in JavaScript. Let’s review four of them.

Constructor functions (and classes)

When invoked with the new operator, most functions (those with an internal [[Construct]] method) act as a constructor. The object assigned to the function’s .prototype property becomes the prototype of the object under construction.

Before the class syntax was introduced, constructor functions were probably the most common way to set the prototype of an object. They’re great when you you want to use the object-oriented programming (OOP) paradigm and are supported all the way back to IE 5.5.

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

Person.prototype.species = "human";

Person.prototype.greet = function () {
return `hi, my name is ${this.name}`;
};

let ironMan = new Person("tony stark");

ironMan.species; // "human"
ironMan.greet(); // "hi, my name is tony stark"

Object.getPrototypeOf(ironMan) === Person.prototype; // true

Introduced in ECMAScript 2015 (also known as ES2015 or ES6), classes are syntactic sugar for constructor functions. They’re more familiar to developers who come from class-based languages, but they still use prototypes internally. However, a major benefit of classes is that they support private members. Unfortunately, private members are not trivial to mimic with constructor functions.

Note the static initialization block in the following class declaration. It’s impossible to add non-function properties to the object assigned to a class’ .prototype property, so you have to do it manually. This is one minor limitation of classes.

class Person {
static {
Person.prototype.species = "human";
}

constructor(name) {
this.name = name;
}

greet() {
return `hi, my name is ${this.name}`;
}
}

let ironMan = new Person("tony stark");

ironMan.species; // "human"
ironMan.greet(); // "hi, my name is tony stark"

Object.getPrototypeOf(ironMan) === Person.prototype; // true

The __proto__ key

When writing an object initializer, you can specify a __proto__ key to set the prototype of the object being initialized. This does not add a __proto__ property to the object, although you can do this using the Object.defineProperty() method.

Unlike the deprecated Object.prototype.__proto__ property, the __proto__ key in an object initializer is both standardized and optimized. Setting the prototype of the object while it’s being initialized is fast, and it’s very ergonomic to declare the object’s own properties alongside its prototype. You can also use the __proto__ key to create an object with a null prototype.

let person = {
species: "human",
greet() {
return `hi, my name is ${this.name}`;
},
};

let ironMan = {
name: "tony stark",
__proto__: person,
};

ironMan.species; // "human"
ironMan.greet(); // "hi, my name is tony stark"

Object.getPrototypeOf(ironMan) === person; // true

The Object.create() method

Like the __proto__ key in an object initializer, the static Object.create() method lets you set the prototype of an object while it’s being initialized. It also lets you create an object with a null prototype. The ability to declare property descriptors while an object is being initialized could be useful too. The alternative is to invoke the Object.defineProperty() or Object.defineProperties() method after an object has been created.

The downside is that the second parameter, an object of property descriptors, is neither ergonomic nor performant. Declaring an extra object to describe each property could be an issue if you have a large number of properties to describe. However, you would need to declare hundreds of thousands of property descriptors before this would become an issue.

let person = {
species: "human",
greet() {
return `hi, my name is ${this.name}`;
},
};

let ironMan = Object.create(person, {
name: {
value: "tony stark",
configurable: true,
enumerable: true,
writable: true,
},
});

ironMan.species; // "human"
ironMan.greet(); // "hi, my name is tony stark"

Object.getPrototypeOf(ironMan) === person; // true

The Object.setPrototypeOf() method

You can use the static Object.setPrototypeOf() method to manipulate an object’s prototype dynamically. However, it’s better to set the prototype while the object is being initialized if possible. Because of the way JavaScript engines optimize prototypes, dynamically manipulating an object’s prototype is very slow.

let person = {
species: "human",
greet() {
return `hi, my name is ${this.name}`;
},
};

let ironMan = {
name: "tony stark",
};

Object.setPrototypeOf(ironMan, person);

ironMan.species; // "human"
ironMan.greet(); // "hi, my name is tony stark"

Object.getPrototypeOf(ironMan) === person; // true

Summary

  • Constructor functions (and classes) are great when you want to use the object-oriented programming (OOP) paradigm in JavaScript. Although classes were introduced in ECMAScript 2015, constructor functions are supported all the way back to IE 5.5.

  • The __proto__ key in an object initializer lets you set the prototype of an object alongside its own properties. This is very ergonomic and potentially useful as a one-off when a constructor function might be overkill.

  • The Object.create() method achieves the same as the __proto__ key but is less ergonomic and less performant. However, the ability to declare property descriptors while an object is being initialized could be useful.

  • The Object.setPrototypeOf() method lets you manipulate an object’s prototype dynamically. While potentially useful, you should avoid doing this because of the way JavaScript engines optimize prototypes.