Wednesday, April 13, 2016

Prototypal inheritance in Javascript

Overview

Javascript is not a classical language, meaning that it doesn’t have class. However, this doesn’t mean that object oriented programming is not supported in Javascript. In fact, all 3 major features of OOP, namely, inheritance, encapsulation and polymorphism, are all possible in Javascript via prototypal inheritance which is built into the language instead of class. Before detailing them further into this article, let’s first list how the 3 major features of OOP are supported in Javascript:

  • Inheritance in Javascript is supported by defining an object as the prototype of another object.
  • Encapsulation in Javascript is supported by its extensible object definition by defining related data and methods on the same prototype object, the latter possible because of first-class function.
  • Polymorphism in Javascript is supported by prototype chain lookup: an object’s behavior is determined at runtime by what is on its prototype chain.

However, the above is only one of the many possible ways to achieve OOP in Javascript, which is coined as pseudoclassical by Douglas Crockford. In his famous book, Javascript: The Good Parts, he listed other ways of doing OOP, such as pure prototypal, functional and building objects via parts, thanks to the flexibility of object in Javascript. On contrary, an object-oriented languages that relies on class to achieve OOP, such as Java and C++, as well as this way of doing OOP, are called classical.

Even though Javascript supports pure prototypal OOP and it seems to be the original design idea, pseudoclassical usage of Javascript is still the main stream. Many Javascript frameworks, such as Node.js and Google Closure are all promoting psuedoclassical pattern as the paradigm. There are even syntactical sugar that makes Javascript looks more classical. Therefore, this article focuses on the pseudoclassical usage of Javascript based on its prototypal nature. Without further ado, let’s go to some code examples.

Constructor Function

In Javascript, a constructor function roughly maps to class definition** in classical language such as Java. A constructor function is not different from any other function. The only difference is that a constructor function is called with the new keyword. Since there is no language feature to distinguish a constructor function from non-constructor function, it’s conventional to capitalize the first latter of a constructor function’s name, making it look even more like Java.

var Employee = function(firstName, lastName, id) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.id = id;
};

Employee.prototype.printInfo = function() {
    var name = this.firstName + " " + this.lastName;
    console.log("Name: " + name);
    console.log("Id: " + this.id);
};

var securityPerson = new Employee("Jack", "Lee", "0001");

// Name: Jack Lee
// Id: 0001
securityPerson.printInfo();

The above code defines an Employee class which 3 fields assigned via the constructor. It also adds a printInfo method to the Employee class by defining it on the prototype object of the constructor function. Finally, we created an instance of Employee, securityPerson, and calls the printInfo method on it.

Note: The above paragraph uses classical notions such as class, field, instance, etc. which are not accurate in Javascript. However, because most people are most familiar with classical notions, we’ll keep using them to avoid the verbose differentiating explanation every time.

As shown in the above example, the prototype object of the constructor function, Employee.prototype above, is the place where methods of a class are defined. Where as fields are defined directly on the this object inside the constructor function.

When defining a functionin Javascript, the function object runs some code like this:

this.prototype = {constructor: this};

which creates an empty object as the prototype object of the constructor function. This object is not entirely empty as it has a constructor attribute pointing back to the constructor function. The constructor property is not very important, but it makes it easier to find the constructor of an object.

// This will print the constructor function, something like:
// function (firstName, lastName, id) {
//    this.firstName = firstName;
//    this.lastName = lastName;
//    this.id = id;
// }
console.log(securityPerson.constructor);

When a constructor function is called with the new keyword, the Javascript runtime does more then calling the function itself. The following is done in sequence under the hood:

  1. A new object is created that inherits from the constructor’s prototype object:

    var newObj = Object.create(Employee.prototype);
    

    Notice that the Object.create() method is used to assign the prototype to illustrate the process. In reality, the Javascript runtime does something lower level.

  2. The constructor is invoked with this bound to the new object:

    var ret = Employee.apply(newObj, "Jack", "Lee", "0001");
    
  3. The new object is returned. However, if the constructor function returns an object itself, then that is returned instead:

    var securityPerson = (typeof ret === 'object' && ret) || newOjb;
    

Prototype Chain

It’s mentioned in step 1 of how new works that the prototype of the newly created object is set to the prototype object of the constructor function. It’s crucial that we use two slightly different terms here. The prototype of an object is its prototype in the sense of prototypal inheritance. The prototype object of the constructor function is simply an attribute on the function object whose name is “prototype”. The prototype object is used in Javascript to enable the pseudoclassical new syntax, so that the newly created object has its prototype set to it. After an object is created by new, the prototype object of its constructor is no longer relevant.

What matters is the prototype of an object. The prototype is an internal property of an object and should not be modified, although modern browsers all implements the __proto__ pseudo-property that points to an object’s prototype. The __proto__ property should not be directly used by application code, as indicated by its __ prefix. Nonetheless, it’s useful for illustrating prototype chain machenism, so I’ll use it in code samples.

In Javascript, each object has an prototype, which is in turn an object. That object has its own prototype, which is in turn an object again. Following this chain of prototypes, it eventually leads to Object.prototype, which is the prototype object of the Object() constructor and thus the immediate prototype of all objects created via object literal or new Object(). The prototype of Object.prototype is null. For any object, its prototype, its prototype’s prototype, …, all the way to Object.prototype forms a chain called its prototype chain. The prototype chains of all objects in Javascript runtime forms a tree structure.

Note: The tree structure is guaranteed because there can not be loops in the prototype chain, a fundamental invariant enforced by Javascript runtime. One can try to force a prototype chain and the Javascript runtime will certainly throw:
var a = {}, b = {};
a.__proto__ = b;
b.__proto__ = a;
Try the above lines in Chrome results in Uncaught TypeError: Cyclic __proto__ value(…). By the way, the above code runs because Chrome happens to define __proto__ property and make it writable. One should never try to modify __proto__ in application code, as its behavior is only recently standardized and its usage is discouraged.

Here is an example showing the prototype chains:

var securityPerson = new Employee("Jack", "Lee", "0001");
var janitor = new Employee("Robert", "Douglas", "0002");

var printPrototypeChain = function(obj) {
    var chain = 'this';
    var prototype = obj.__proto__;
    while (true) {
        if (prototype === Employee.prototype) {
            chain += " -> Employee.prototype";
        } else if (prototype === Object.prototype) {
            chain += " -> Object.prototype";
        } else if (prototype === Function.prototype) {
            chain += " -> Function.prototype";
        } else if (prototype === null) {
            chain += " -> null";
            break;
        } else {
            chain += " -> ?";
        }
        prototype = prototype.__proto__;
    }
    console.log(chain);
};

// this -> Employee.prototype -> Object.prototype -> null
printPrototypeChain(securityPerson);

// this -> Employee.prototype -> Object.prototype -> null
printPrototypeChain(janitor);

// this -> Object.prototype -> null
printPrototypeChain({x:1, y:2});

// this -> Function.prototype -> Object.prototype -> null
printPrototypeChain(function() { return true; });

// this -> ? -> Object.prototype -> null
// The '?' stands for Array.prototype
printPrototypeChain(new Array(1, 2, 3));

Property Lookup

So why does prototype chain matters? The prototype chain is used when looking up a property on an object. For example, when evaluating an expression such as obj.someProperty, the Javascript rumtime will look for someProperty on obj itself, then following it’s prototype chain to try to find it on obj.__proto__, obj.__proto__.__proto__, until it reaches Object.prototype. If there is no someProperty on obj nor its prototype chain, undefined is returned. This allows an object to inherit its prototypes, thus achieving inheritance and code reuse.

Prototype chain is looked up when getting property but not when setting property. Otherwise, manipulating one object has the side effect of modifying the behavior of other objects with overlapping prototype chain. This is illustrated below:

var securityPerson = new Employee("Jack", "Lee", "0001");
var janitor = new Employee("Robert", "Douglas", "0002");

// Name: Jack Lee
// Id: 0001
securityPerson.printInfo();

// Name: Robert Douglas
// Id: 0002
janitor.printInfo();

janitor.printInfo = function() {
    console.log("I am the janitor!");
};

// Name: Jack Lee
// Id: 0001
securityPerson.printInfo();

// I am the janitor!
janitor.printInfo();

delete janitor.printInfo;

// Name: Jack Lee
// Id: 0001
securityPerson.printInfo();

// Name: Robert Douglas
// Id: 0002
janitor.printInfo();

To summarize, getting a property is done via the prototype chain where as setting/deleting a property is always directly on the object itself.

An Example of Psuedoclassical OOP in Action

Finally, let’s see an example of psuedoclassical OOP in action, illustrating inheritence, encapsulation and polymorphism.

var Employee = function(firstName, lastName, id) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.id = id;
};

// Encapsulation is achieved by bundling related logic into the prototype object
// of the constructor function.
Employee.prototype.printInfo = function() {
    var name = this.firstName + " " + this.lastName;
    console.log("Name: " + name);
    console.log("Id: " + this.id);
    console.log("Annual income: " + this.getAnnualIncome());
};

Employee.prototype.getAnnualIncome = function() {
    // This is to mimic abstract method in Java.
    throw new Error("getAnnualIncome must be implemented by subclass.");
}

var Contractor = function(firstName, lastName, id, hourlyPay) {
    // The way to call super constructor in Javascript is to call it explicitly.
    Employee.call(this, firstName, lastName, id);
    this.hourlyPay = hourlyPay;
};
var tmpCtor = function() {};
tmpCtor.prototype = Employee.prototype;
Contractor.prototyye = new tmpCtor();

// This shows inheritance. The super method is reused with some subclass specific
// customization.
Contractor.prototype.printInfo = function() {
    // The way to call super method in Javascript is to call it explicitly.
    console.log("[Contractor]");
    Employee.prototype.printInfo.call(this);
}

// This shows polymorphism: when getAnnualIncome is called in
// Employee.prototype.printInfo, the exact behavior is determined by the subclass
// at runtime.
Contractor.prototype.getAnnualIncome = function() {
    return 12 * 20 * 8 * this.hourlyPay;
}

var FullTimeEmployee = function(firstName, lastName, id, salary) {
    // The way to call super constructor in Javascript is to call it explicitly.
    Employee.call(this, firstName, lastName, id);
    this.salary = salary;
}
tmpCtor.prototype = Employee.prototype;
FullTimeEmployee.prototyye = new tmpCtor();

FullTimeEmployee.prototype.printInfo = function() {
    console.log("[Full-time Employee]");
    // The way to call super method in Javascript is to call it explicitly.
    Employee.prototype.printInfo.call(this);
}

FullTimeEmployee.prototype.getAnnualIncome = function() {
    return 12 * this.salary;
}

var newMeetingRoomBuilder = new Contractor("Victor", "Johnson", "0003", 20);
// [Contractor]
// Name: Victor Johnson
// Id: 0003
// Annual income: 38400
newMeetingRoomBuilder.printInfo();

var systemAdmin = new FullTimeEmployee("Peter", "Huffman", "0004", 6000);
// [Full-time Employee]
// Name: Peter Huffman
// Id: 0004
// Annual income: 72000
systemAdmin.printInfo();

No comments:

Post a Comment