Inheritance and the prototype chain Redirect 1

by 1 contributor:

JavaScript is a bit confusing for developers experienced in class-based languages (like Java or C++), as it is dynamic and does not provide a class implementation (although the keyword class is a reserved keyword and cannot be used as a variable name).

When it comes to inheritance, JavaScript only has one construct: objects. Each object has an internal link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with null as its prototype. null, by definition, has no prototype, and acts as the final link in this prototype chain.

While this is often considered to be one of JavaScript's weaknesses, the prototypal inheritance model is in fact more powerful than the classic model. It is, for example, fairly trivial to build a classic model on top of a prototypal model, while the other way around is a far more difficult task.

Inheritance with the prototype chain

Inheriting properties

JavaScript objects are dynamic "bags" of properties (referred to as own properties) and each one has a link to a prototype object. Here is what happens when trying to access a property:

// Let's assume we have an object o with its prototype chain looking like:
// {a:1, b:2} ---> {b:3, c:4} ---> null
// 'a' and 'b' are o own properties.

// In this example, someObject.[[Prototype]] will designate the prototype of someObject.
// This is a pure notation (based on the one used in the ECMAScript standard) and cannot be used in scripts

console.log(o.a); // 1
// Is there an 'a' own property on o? Yes and its value is 1

console.log(o.b); // 2
// Is there a 'b' own property on o? Yes and its value is 2
// The prototype also has a 'b' property, but it's not visited. This is called "property shadowing"

console.log(o.c); // 4
// Is there a 'c' own property on o? No, check its prototype
// Is there a 'c' own property on o.[[Prototype]]? Yes, its value is 4

console.log(o.d); // undefined
// Is there a 'd' own property on o? No, check its prototype
// Is there a 'd' own property on o.[[Prototype]]? No, check its prototype
// o.[[Prototype]].[[Prototype]] is null, stop searching, no property found, return undefined

Setting a property to an object creates an own property. The only exception to the getting and setting behavior rules is when there is an inherited property with a getter or a setter.

Inheriting "methods"

JavaScript does not have "methods" in the form that class-based languages define them. In JavaScript, any function can be added to an object in the form of a property. An inherited function acts just as any other property, including property shadowing as shown above (in this case, a form of method overriding).

When an inherited function is executed, the value of this points to the inheriting object, not to the prototype object where the function is an own property.

var o = {
  a: 2,
  m: function(b){
    return this.a + 1;
  }
};

console.log(o.m()); // 3
// When calling o.m in this case, 'this' refers to o

var p = Object.create(o);
// p is an object that inherits from o

p.a = 12; // creates an own property 'a' on p
console.log(p.m()); // 13
// when p.m is called, 'this' refers to p.
// So when p inherits the function m of o, 'this.a' means p.a, the own property 'a' of p

Different ways to create objects and the resulting prototype chain

Objects created with syntax constructs

var o = {a: 1};

// The newly created object o has Object.prototype as its [[Prototype]]
// o has no own property named 'hasOwnProperty'
// hasOwnProperty is an own property of Object.prototype. So o inherits hasOwnProperty from Object.prototype
// Object.prototype has null as its prototype.
// o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];

// Arrays inherit from Array.prototype (which has methods like indexOf, forEach, etc.)
// The prototype chain looks like:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}

// Functions inherit from Function.prototype (which has methods like call, bind, etc.)
// f ---> Function.prototype ---> Object.prototype ---> null

With a constructor

A "constructor" in JavaScript is "just" a function that happens to be called with the new operator.

function Graph() {
  this.vertexes = [];
  this.edges = [];
}

Graph.prototype = {
  addVertex: function(v){
    this.vertexes.push(v);
  }
};

var g = new Graph();
// g is an object with own properties 'vertexes' and 'edges'.
// g.[[Prototype]] is the value of Graph.prototype when new Graph() is executed.

With Object.create

ECMAScript 5 introduced a new method: Object.create. Calling this method creates a new object. The prototype of this object is the first argument of the function:

var a = {a: 1}; 
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (inherited)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, because d doesn't inherit from Object.prototype

Performance

The lookup time for properties that are high up on the prototype chain can have a negative impact on performance, and this may be significant in code where performance is critical. Additionally, trying to access nonexistent properties will always traverse the full prototype chain.

Also, when iterating over the properties of an object, every property that is on the prototype chain will be enumerated.

To check whether an object has a property defined on itself and not somewhere on its prototype chain, it is necessary to use the hasOwnProperty method which all objects inherit from Object.prototype.

hasOwnProperty is the only thing in JavaScript which deals with properties and does not traverse the prototype chain.


Note: It is not enough to check whether a property is undefined. The property might very well exist, but its value just happens to be set to undefined.

Bad practice: Extension of native prototypes

One mis-feature that is often used is to extend Object.prototype or one of the other built-in prototypes.

This technique is called monkey patching and breaks encapsulation. While used by popular frameworks such as Prototype.js, there is still no good reason for cluttering built-in types with additional non-standard functionality.

The only good reason for extending a built-in prototype is to backport the features of newer JavaScript engines; for example Array.forEach, etc.

In conclusion

It is essential to understand the prototypal inheritance model before writing complex code that makes use of it. Also, be aware of the length of the prototype chains in your code and break them up if necessary to avoid possible performance problems. Further, the native prototypes should never be extended unless it is for the sake of compatibility with newer JavaScript features.

 

Document Tags and Contributors

Contributors to this page: Sheppy
Last updated by: Sheppy,