Prototypes and inheritance

The object's prototype

JavaScript is a prototype-based language. Prototype-based programming is a style of object-oriented programming in which an object inherit properties from an other existing object (the prototype), rather than properties being derived from an explicitly defined class (an objects "template"). Classes will be explained later this tutorial.

An object's property can have an other object as value, as we have seen:


const a = { b: { prop: 2 } }
console.log(a.b.prop); // logs: 2

We can also define a property with an object value that makes its nested object properties directly available in the original object. Such object is called a prototype and one says that an object inherits the properties of its prototype. By using the reserved key __proto__ (pronounced as "dunder proto"), we can set the object's prototype in an object literal.


// Creating an object with a set prototype:
const a = { __proto__: { prop: 2 } }

// object "a" inherits property "prop" from its prototype:
console.log(a.prop); // logs: 2

In JavaScript every object has precisely one direct prototype. The default prototype of an object is Object.prototype, which is an anonymous object created by the built-in constructor function Object() (later more about this anonymous object).


const a = { b: 2 }; // no set prototype: default prototype is Object.prototype
console.log(Object.getPrototypeOf(a) === Object.prototype); // logs: true
// or (non-standard):
console.log(a.__proto__ ===  Object.prototype); // logs: true

PS: The property prototype does not reference the prototype of an object. It is a property of a constructor function (Object in the example above), which will be explained later. It may be tempting to write myObject.prototype instead of Object.getPrototypeOf(myObject) to access myObject's prototype, but this will not work (it will return undefined).

PS: If you log an object to the console, the console will show its prototype, usually as the last property in the expanded list of properties, probably using a "key" like [[Prototype]] or <prototype>.

Using the __proto__ key to set the prototype within an object literal ({ __proto__: "some object" }) is standardized, optimized and supported by all modern JS engines. However, using the __proto__ key to identify the object's prototype as a property (myObject.__proto__) is non-standard and not recommended! JS engines may (still) support it, but this is not guaranteed. To get an object's prototype, you can use the standardized method Object.getPrototypeOf(myObject) instead.


const myObject = { __proto__: { prop: 2 } }

console.log(myObject.prop); // logs: 2

Object.getPrototypeOf(myObject).prop = 20;
console.log(myObject.prop); // logs: 20

myObject.prop = 30;
console.log(myObject.prop); // logs: 30

const myObject = { prop: 1, __proto__: { prop: 2 } }

console.log(myObject.prop); // logs: 1
console.log(Object.getPrototypeOf(myObject).prop); // logs: 2

myObject.prop = 10;
Object.getPrototypeOf(myObject).prop = 20;

console.log(myObject.prop); // logs: 10
console.log(Object.getPrototypeOf(myObject).prop); // logs: 20

Note in the examples above that myObject.prop looks up property prop down the prototype chain of myObject, starting with myObject itself, whereas Object.getPrototypeOf(myObject).prop looks up property prop down the prototype chain, starting with the prototype of myObject. Both get the first property by the name prop they encounter. More about the prototype chain in the next section.

The prototype chain

A prototype is an object and since all objects have their own prototype, a prototype has a prototype. This creates an object's prototype chain. The last prototype in the prototype chain is always null; null has no prototype.

An object inherits the properties from all prototypes in the prototype chain. The direct, non-inherited, properties of an object are referred to as own properties. When a property is accessed, the engine first tries to find it among the own properties. If it can't be found, the prototype is searched. If the property still can't be found, the prototype's prototype is searched, and so on until either the property is found, or until the next prototype is null (end of property chain).


const myObject = {
  a: 1,
  b: 2,
  __proto__: {
    b: 3,
    c: 4,
    __proto__: {
      d: 5,
    },
  },
};

console.log(myObject.d); // logs: 5

console.log(myObject); // logs: { a: 1, b: 2 } 
console.log(myObject.__proto__); // logs: { b: 3, c: 4 }
console.log(myObject.__proto__.__proto__); // logs: { d: 5 }
console.log(myObject.__proto__.__proto__.__proto__); // logs the Object.prototype
console.log(myObject.__proto__.__proto__.__proto__.__proto__); // logs: null

// Prototype chain:
// { a: 1, b: 2 } ---> { b: 3, c: 4 } ---> { d: 5 } ---> Object.prototype ---> null

PS: Note in the above example that myObject.__proto__ was used to get the prototype. This is shorter code, but using Object.getPrototypeOf(myObject) has better support, as mentioned before.

this and the prototype chain

As mentioned before, a property can have a primitive or an object as a value. A property with a function value is called a "method", which, in JavaScript, is merely a name for this kind of properties. In JavaScript, a method is a property that acts just as any other property, including inheritance and property shadowing (see next section).

If a prototype object has an own property, a method, in which keyword this is used, then the value of this points to the inheriting object, not to the prototype object:


const obj1 = { addOne() { return this.value + 1; } };
const obj2 = { value: 2, __proto__: obj1 };

// "this" in obj1 method "addOne()" refers to obj2.
console.log(obj2.addOne()); // logs: 3

const obj1 = { value: 2, addOne() { return this.value + 1; } };
const obj2 = { __proto__: obj1 };

// "this.value" in obj1 method "addOne()" refers to
// inherited property "value" because
// "obj2" does not have an own property "value":
console.log(obj2.addOne()); // logs: 3

Shadowing properties

Creating a property with the same name as an inherited property creates an own property for the object, but the property with the same name earlier in the prototype chain remains existing, although it will not be accessible anymore through the object. The object has so called "shadowed" properties.

In the example below myObj.prop has value "c". Inherited properties prop with values "b" and "a" are still in the prototype chain of myObj, but are not directly accessible through myObj. They are accessible through Object.getPrototypeOf().


const myObj = {
  prop: "c",
  __proto__: {
    prop: "b",
	__proto__: {
	  prop: "a"
	},	
  },
}

function listAllPrototypes(myObj) {
  let objectToInspect = myObj;
  let result = [objectToInspect];
  while(objectToInspect !== null) {
    objectToInspect = Object.getPrototypeOf(objectToInspect);
    result = result.concat(objectToInspect);
  }
  return result;
}

console.log(myObj.prop); // logs: "c" // logs the own property
// prototype chain of myObj:
console.log(listAllPrototypes(myObj)); // logs: [ { prop: "c" }, { prop: "b" }, { prop: "a" }, Object { … }, null ]

console.log(myObj.prop); // logs: "c"
console.log(Object.getPrototypeOf(myObj).prop); // logs: "b"
console.log(Object.getPrototypeOf(Object.getPrototypeOf(myObj)).prop); // logs: "a"

Setting the prototype

Four options

As mentioned earlier, to create an object with a desired prototype we can use one of the next four options:

Using Object.create()

We can use Object.create() to create an object with a desired prototype.


const obj1 = { prop: "a" };
const obj2 = Object.create(obj1);
obj2.prop = 'b';
const obj3 = Object.create(obj2);
obj3.prop = 'c';

function listAllPrototypes(myObj) {
  // same function as in the previous example
}

console.log(obj3.prop); // logs: "c" // logs the own property
console.log(listAllPrototypes(obj3));
// logs: [ { prop: "c" }, { prop: "b" }, { prop: "a" }, Object { … }, null ]

// prototype chain of obj3:
// obj3 ---> obj2 ---> obj1 ---> Object.prototype ---> null

The example above is equivalent to:


const obj1 = { prop: "a" };
const obj2 = { prop: "b", __proto__: obj1 };
const obj3 = { prop: "c", __proto__: obj2 };

function listAllPrototypes(myObj) {
  // same function as in the previous example
}

console.log(obj3.prop); // logs: "c"
console.log(listAllPrototypes(obj3));
// logs: [ { prop: "c" }, { prop: "b" }, { prop: "a" }, Object { … }, null ]

// prototype chain of obj3:
// obj3 ---> obj2 ---> obj1 ---> Object.prototype ---> null

Using constructors

Another way to create an object with a desired prototype is to use a constructor function. In JavaScript all functions (except arrow functions) have a property named prototype. When a function is used as a constructor, this property sets the prototype for the constructed object.

MyConstructorFunction.prototype designates an anonymous object that serves as the prototype of any object instance constructed from this constructor function by using the new operator.


// define a constructor function:
function User(name, avatar, nComments) {
  this.name = name;
  this.avatar = avatar;
  this.nComments = nComments;
};

// add a method to the anonymous object associated with the User constructor function:
User.prototype.infoUser = function () {
  return this.name+", aka "+this.avatar+", posted "+this.nComments+" comments.";
};

// construct an object instance:
const user1 = new User('John Doe', 'John1986', 321);

// The constructed new object inherited the infoUser method from the User.prototype anonymous object. 
console.log(user1.infoUser()); // logs: "John Doe, aka John1986, posted 321 comments."

console.log(Object.getPrototypeOf(new User()) === User.prototype); // log: true

If the infoUser method in the example above would have been added directly to the constructor function, this method would have become a separate own property of each object instance created from this constructor. If you want to change this method for all instances, you will need to change each infoUser method individually for each object instance.

Assigning method infoUser to the User.prototype, as done in the example above, makes the method infoUser a property in the prototype chain of each object instance that is created from this constructor. Each object instance inherits this same method. Changing this method User.prototype.infoUser, changes the method for all object instances.

The flip side of the coin is that ConstructorFunction.prototype properties do not have access to possible local variables in the constructor, while direct methods in the constructor do.


function User(name, avatar) {
  let commentsCount = 0; // local variable
  this.name = name;
  this.avatar = avatar;
  // next method uses local variable:
  this.nComments = function () {
    commentsCount++;
    return this.name+", aka "+this.avatar+", posted "+commentsCount+" comments.";
  };
};

// next method does not use local variables defined in the constructor 
User.prototype.getUser = function () {
  return this.name+", aka "+this.avatar+".";
};

// construct object instances:
const user1 = new User('John Doe', 'John1986');
const user2 = new User('Jane Roe', 'notmyrealname');

console.log(user1.nComments()); // logs: "John Doe, aka John1986, posted 1 comments."
console.log(user1.nComments()); // logs: "John Doe, aka John1986, posted 2 comments."
console.log(user2.nComments()); // logs: "Jane Roe, aka notmyrealname, posted 1 comments."
console.log(user1.getUser()); // logs: "John Doe, aka John1986."
console.log(user2.getUser()); // logs: "Jane Roe, aka notmyrealname."

console.log("nComments" in user1); // logs: true
console.log('getUser' in user1); // logs: true

console.log(Object.hasOwn(user1, "nComments")); // logs: true
console.log(Object.hasOwn(user1, "getUser")); // logs: false

Constructor functions will typically build a prototype chain as follows:

obj ---> ConstructorFunction.prototype ---> Object.prototype ---> null

We can build longer prototype chains by using Object.setPrototypeOf() to set the prototype of ConstructorFunction.prototype.


function A(){}
function B(){}

const obj = new A();

// obj ---> A.prototype ---> Object.prototype ---> null

Object.setPrototypeOf(A.prototype, B.prototype)

// obj ---> A.prototype ---> B.prototype ---> Object.prototype ---> null

The constructor property

In JavaScript all objects have inherited a constructor property that references the constructor function that the object was created from. A MyConstructorFunction.prototype anonymous object has, by default, one own property: constructor, that references the constructor function itself again.


function MyConstructorFunction() {};
myObject = new MyConstructorFunction();

console.log(myObject.constructor === MyConstructorFunction); // logs: true
console.log(MyConstructorFunction.prototype === Object.getPrototypeOf(myObject)); // logs: true

console.log(MyConstructorFunction.prototype.constructor === MyConstructorFunction); // logs: true

So, in the example above MyConstructorFunction creates both myObject and myObject's prototype, i.e. an anonymous object, designated by MyConstructorFunction.prototype. The anonymous object is created only once, even if MyConstructorFunction is used multiple times to create an object instance.

The "built-in" Object() constructor function is the default constructor for all objects, which makes Object.prototype the default prototype for all objects. The default prototype of Object.prototype is null. The default prototype of any object can be overwritten by an other object, but eventually the last object (before null) in the prototype chain must be Object.prototype, unless you directly set the prototype of an object to null. However, setting the object's prototype to null, e.g. by using expression Object.create(null), can result in unexpected behavior, because the object will not inherit any "built-in" properties or methods from Object.prototype.


const o1 = { prop: 1 }
console.log(o1.constructor === Object); // logs: true // Object is the default constructor function!
console.log(Object.getPrototypeOf(o1).constructor === Object); // logs: true
console.log(Object.getPrototypeOf(o1) === Object.prototype); // logs: true
console.log(Object.prototype.constructor === Object); // logs: true

const o2 = Object.create(o1);
console.log(o2.constructor === Object); // logs: true // Object is the default constructor function!
console.log(Object.getPrototypeOf(o2) === o1); // logs: true

function User(name) {
  this.name = name;
};
const user1 = new User('John Doe');

console.log(user1.constructor === User); //logs: true
console.log(Object.getPrototypeOf(user1).constructor === User); //logs: true

console.log(Object.getPrototypeOf(user1) === User.prototype); // log: true
console.log(Object.getPrototypeOf(User.prototype) === Object.prototype); // log: true
console.log(Object.getPrototypeOf(Object.prototype) === null); // logs: true

const o3 = Object.create(null); // create an object with "null" prototype
console.log(`o2: ${o2}`); // logs: "o2: [object Object]"
console.log(`o3: ${o3}`); // logs: TypeError: can't convert o3 to string

An object literal, as used to create o1 in the example above, implicitly used the Object constructor function, equivalent to using const o1 = new Object();. An array literal, for instance, implicitly uses the Array constructor function, equivalent to using const array = new Array();.

Getting the prototype of a primitive value creates an instance of a corresponding primitive wrapper object.


const myArray = [1, 2, 3];
console.log(myArray.constructor === Array); // logs: true
console.log(Object.getPrototypeOf(myArray) === Array.prototype); // logs: true
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // logs: true

const myString = "hello";
console.log(myString.constructor === myString); // logs: false
console.log(Object.getPrototypeOf(myString) === String.prototype); // logs: true
console.log(Object.getPrototypeOf(String.prototype) === Object.prototype); // logs: true

Note that both creating primitives by explicitly using a constructor, like const myString = new String();, and extending a built-in prototype, like Array.prototype.myMethod = function () {...} (monkey patching) are both not recommended practices.

So, objects inherit from Object.prototype and arrays inherit from Array.prototype and Object.prototype, but also functions, even arrow functions, inherit from Function.prototype. Function.prototype is not to be confused with myFunction.prototype. Function.prototype is the prototype of function myFunction, while myFunction.prototype is the prototype of object instances constructed from myFunction when myFunction is used as a constructor function. Function.prototype will not be in the prototype chain of these object instances.


function myFunction() { return 2 }
console.log(myFunction.constructor === Function); // logs: true
console.log(Object.getPrototypeOf(myFunction) === Function.prototype); // logs: true
console.log(Object.getPrototypeOf(Function.prototype) === Object.prototype); // logs: true

console.log(typeof myFunction.prototype); // logs: object
console.log(myFunction.prototype === Function.prototype); // logs: false

const myArrowFunction = () => {}
console.log(Object.getPrototypeOf(myArrowFunction) === Function.prototype); // logs: true
console.log(myArrowFunction.prototype); // logs: undefined

// Arrow functions do not have a .prototype property;
// they cannot be used as constructors.
const myObject = new myArrowFunction(); // logs: TypeError: myArrowFunction is not a constructor