JavaScript - Inheritance




JavaScript - Inheritance

JavaScript is unique in a lot of ways. It’s the major browser-supported language that powers most user interfaces online. It has its own quirks in syntax and equality that can range from endearing to maddeningly annoying. However, JavaScript’s biggest unique point is object inheritance and the prototype chain. In nearly any interview for a front-end or JavaScript developer role, you’re likely to hear a question about prototypal inheritance, and for good reason. Inheritance is a critical concept in computer programming, and JavaScript inheritance works unlike any other programming language. Even experienced developers can get tripped up by the JavaScript prototype chain, its mechanics, and how it differs from other programming languages. In this post, we’ll cover the essentials on prototypal inheritance in JavaScript.

JavaScript is an object-oriented language. However, it’s different from many other OO languages in that it uses prototype-based inheritance instead of class-based inheritance. Prototype-based inheritance means that objects inherit items from its prototype. A prototype is just another object, which can be inherited by other objects. This is different from class-based inheritance in that classes are templates for creating new objects. Classes can inherit from other classes to reuse code from the class it’s inheriting from.

Syntax of Inheritance

Before ES6, we only have constructor functions to serve as templates to create new objects which are instances of the constructor. For example, we can define a constructor function as follows −

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

Then we can create a new instance of Person by writing −

let person = new Person('Joe', 10);

To inherit items from other constructor functions in a constructor function, we have to call the parent constructor function that we want to inherit from with the call method, and then set our constructor’s prototype’s constructor property to the parent constructor function that we want to inherit from. For example, if we want a Employee constructor function to inherit the properties of the Person constructor, we can write −

function Person(name, age) {
  this.name = name;
  this.age = age;
}
function Employee(name, age, title) {
  this.title = title;
  Person.call(this, name, age);
  this.__proto__.constructor = Person;
}
let employee = new Employee('Joe', 20, 'waiter');
console.log(employee);

The call method takes the value of this we want to set, and the rest are arguments we pass into the function that the call method is called on. If we look at the __proto__ property of the employee object, which has the prototype for it, we should get that __proto__.constructor of it should be the Person constructor like we set it to. The properties and the values of the employee object should be what we passed into the Employee constructor when we called it.

Object.create()

The Object.create() method is another way to inherit from a prototype when we create an object. The argument that it takes is the prototype object that we want the object returned from it to inherit from. For example, we can use Object.create to create an object with a prototype as follows −

const person = {
  name: 'Joe',
  age: 20
}
let employee = Object.create(person);
employee.title = 'waiter';
console.log(employee);

If we look at the employee object, we’ll see that the __proto__ property will have the age and name properties set with values.

Setting the __proto__ Property Directly

Setting the __proto__ property directly has been officially supported since ES6 and it’s an undocumented way to set the prototype of an object in various browsers before it like Firefox. We can set an object to the __proto__ property directly, by writing something like -

const person = {
  name: 'Joe',
  age: 20
}
let employee = {
  title: 'waiter'
};
employee.__proto__ = person;
console.log(employee);

We should get the exact structure of the properties and values as we did when we created an object with the Object.create() method. One thing we have to be careful about is that we don’t want to accidentally set it if we don’t want to change an object’s prototype. This may happen if we use JavaScript objects as maps. With ES6, we can use the Map object for this purpose.

Object.defineProperty

We can also use the defineProperty method to set the prototype of an object. For example, we can write -

const person = {
  name: 'Joe',
  age: 20
}
let employee = {
  title: 'waiter'
};
Object.defineProperty(employee, '__proto__', {
  value: person
});
console.log(employee.__proto__);

When we log the value of employee.__proto__ , we get back the person object. Note that the prototype is in the value property of the 3rd argument of the defineProperty method call.

What is JavaScript Inheritance

As all of us know that JavaScript also follows object-oriented concepts, we should be able to implement inheritance in JavaScript also. This implementation is different from other object-oriented languages like C#, Java etc. As there is no class keyword (which recently got introduced with the latest version), it is different to inherit from one class (or constructor) to other class.

Example

var rectangle = function(len, wid)  
{  
    this.type = "rectangle";  
    this.len = len;  
    this.wid = wid;  
}  
var square = function(len)  
{  
    rectangle.call(this, len, len);  
}  
var rectangleObj = new rectangle(2, 3);  
console.log(rectangleObj.len);  
console.log(rectangleObj.wid);  
console.log(rectangleObj.type);  
var squareObj = new square(2);  
console.log(squareObj.len);  
console.log(squareObj.wid);  
console.log(squareObj.type);

Output

2
3
rectangle
2
2
rectangle

What Is Inheritance?

In most object-oriented programming languages, there is a mechanism for child objects to inherit methods and attributes from their parents. Popular languages like Java, C#, C++, and Python (among many others) all support this type of inheritance. Defining these objects and their inheritance structures is a matter of creating classes that extend one another. A class is a definition of attributes and behaviors, an object is an instance of a given class. For instance, you might have a base Animal class with methods that allow animals to eat() and sleep(). Then, your Dog class inherits the eat() and sleep() methods from its Animal parent class while also defining its own methods like bark() and be_undyingly_loyal().

Inheritance is one of the core principles of object-oriented design. It helps build reusable and modular components of software applications because we can inherit from and extend the parent class in many different ways.

Let's see how we can achieve inheritance like functionality in JavaScript using prototype object. Let's start with the Person class which includes FirstName & LastName property as shown below.

function Person(firstName, lastName) {
    this.FirstName = firstName || "unknown";
    this.LastName = lastName || "unknown";
};

Person.prototype.getFullName = function () {
    return this.FirstName + " " + this.LastName;
}

Inheritance in JavaScript

It’s possible to do object-oriented programming in JavaScript. Indeed, JavaScript has built-in support for inheritance between objects. However, JavaScript is not a class-based language. While there is a class keyword in JavaScript since 2015, it’s only syntactic sugar. Under the hood, JavaScript has its own mechanism for defining and resolving inheritance relationships. This mechanism is known as the prototype chain. There are two key things to know about the JavaScript prototype chain and how it differs from class-based languages. If you take nothing else from this article, these two points will get you pretty far in understanding JavaScript inheritance.

Prototypes are objects themselves

In class-based languages, the classes act as blueprints. No object of that class actually exists until you instantiate it in your program. So, you define the class, then you create an instance of that class. In JavaScript, prototypes are objects themselves. In fact, every function in JavaScript has a __proto__ attribute and a special property called prototype that allows you to access the __proto__ attributes. If you open your console in your browser (ctrl+shift+I in Chrome & Firefox), you can see this for yourself. Let’s create a new function that does absolutely nothing. Just leave it empty −

function doSomething(){}

Then, let’s check out the default prototype property that JavaScript created for us -

console.log( doSomething.prototype );

You can see the constructor for the doSomething function is indeed doSomething(). However, every JavaScript function also inherits certain attributes and qualities from JavaScript’s default Object() prototype. So, you can see all those default methods as well.

{
constructor: ƒ doSomething(),
    __proto__: {
        constructor: ƒ Object(),
        hasOwnProperty: ƒ hasOwnProperty(),
        isPrototypeOf: ƒ isPrototypeOf(),
        propertyIsEnumerable: ƒ propertyIsEnumerable(),
        toLocaleString: ƒ toLocaleString(),
        toString: ƒ toString(),
        valueOf: ƒ valueOf()
    }
}

We can add data and methods to our doSomething prototype by setting them using .prototype -

doSomething.prototype.foo = "bar";

Then, if we look at the results of console.log( doSomething.prototype ); again we can see that new value -

{
foo: "bar",
    constructor: ƒ doSomething(),
    __proto__: {
        constructor: ƒ Object(),
        ... // other default methods here
    }
}

Cool! So we see doSomething is an object itself. We can define methods/data on doSomething’s prototype. We can also see the methods and data from doSomething’s parent prototype – Object().

Prototypal Inheritance in JavaScript

For JavaScript developers, it’s critical to know what your language is doing behind the scenes when you’re inheriting attributes and methods from one object to another. For developers who are familiar with other languages and new to JavaScript, understanding the prototype chain is going to be essential to getting stuff done.

Getting started

First of all, make yourself a local copy of our oojs-class-inheritance-start.html file (see it running live also). Inside here you'll find the same Person() constructor example that we've been using all the way through the module, with a slight difference — we've defined only the properties inside the constructor -

function Person(first, last, age, gender, interests) {
  this.name = {
    first,
    last
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
};

The methods are all defined on the constructor's prototype. For example -

Person.prototype.greeting = function() {
  alert('Hi! I\'m ' + this.name.first + '.');
};

Note - In the source code, you'll also see bio() and farewell() methods defined. Later you'll see how these can be inherited by other constructors.