JavaScript

JavaScript Tutorial – Part 4: Object Creation and Prototype Chains

The term “Object-Oriented programming (OOP)” has been greatly overused, and JavaScript is not one of the exceptions. For someone who comes from many years programming in Java, learning JavaScript made me realize that the correct name for the paradigm used by programming languages like Java, C++, and C# (to name a few) is not Object-Oriented but Class-Oriented programming. Because in these languages what you are really defining are classes of objects and instantiating them.

In contrast, JavaScript is a real Object-Oriented language since you are always handling objects. Sadly time made its thing and there is no way to change the name of the paradigms… so the paradigm used by JavaScript (and other less known languages) is called Prototype-based programming.

So why am I saying all this? Because this is a very important concept that helps understand how the language works.

In tutorial 2, we saw two ways to create objects: inline or with the new keyword before a function name. The third way to do this is using the Object.create(). Unlike the two previous ways of creating object, this one really “feels” like we are copying the existing object when creating another object.

Let’s revisit them and investigate some more. First, objects defined inline:

// Inline object creation
var a = {
    field: "a"
}
console.info("a's constructor: "+a.constructor); // prints "function Object()..."
console.info("a instanceof Object?: " + (a instanceof Object)); // prints "true"

We are inspecting a by checking it’s constructor, and using the instanceof operator which checks the prototype chain of the object (more on this later). We can see that the constructor of a is the function Object() and that as expected, it is an instance of Object.

Objects defined using the new give us a consistent way to create objects, which are also identified as being of the same instance by the instanceof operator:

// Using a constructor function
function A() {
    this.field = "a";
}
// Inline object creation
var a = new A();
console.info("a's constructor: " + a.constructor) // prints "function A()..."
console.info("a instanceof Object?: " + (a instanceof Object)); // prints "true"
console.info("a instanceof A?: " + (a instanceof A)); // prints "true"

We have now objects of a specific “class”. This is not really a class but something called [[prototype]], which in an internal property of all JavaScript objects which can be accessed using the Object.getPrototypeOf() function. But how do we create prototype chains like we do in OO languages? This can be done in a number of ways. First, by cloning objects using the Object.create method:

// Object cloning using Object.create()
var a = {
    f1: 1
}
var b = Object.create(a);
b.f2 = 2;
var c = Object.create(b);
c.f3 = 3;
console.info(c.f1 + ", "+c.f2+", "+ c.f3); // prints "1, 2, 3"

And while this creates something like inheritance, it behaves really strange when using the instanceof operator:

console.info(b instanceof a); // prints an error because instanceof expect a function as its second argument

This is the place where JavaScript comes out as a very confusing and inconsistent language. The instanceof operator actually checks if the [[prototype]] chain of the first argument contains the prototype property of the second argument (a function). But since we didn’t use a function to create these objects, we can’t use the instanceof operator. Weird…
For this case we can use the Object.getPrototypeOf() function to check the prototype chain, like this:

console.info(Object.getPrototypeOf(c) === b); // prints "true"

But IMHO this is a broken experience. So then, how can we create inheritance in JavaScript while keeping the instanceof operator working? We do this by overriding the prototype property of the constructor function:

function A() {
    f1: 1
};
// no need to override prototype here since it is our base class
var a = new A();
console.info("a instanceof A?: " + (a instanceof A)); // true

function B() {
    this.f2 = 2;
};
B.prototype = new A();
var b = new B();
console.info("b instanceof B?: " + (b instanceof B)); // true
console.info("b instanceof A?: " + (b instanceof A)); // true

function C() {
    this.f3 = 3;
}
C.prototype = new A();
var c = new C();
console.info("c instanceof C?: " + (c instanceof C)); // true
console.info("c instanceof A?: " + (c instanceof A)); // true
console.info("c instanceof B?: " + (c instanceof B)); // false

This works as expected, and while it is not the clearest syntax in the world, it is understandable. And while this works, and it took me a while to get this syntax right, I found that this is not the correct way to do it, as explained by the MDN. Some reasons for this are :

  • Using new to initialize the prototype of the constructor is problematic when the constructor requires parameters, since there are none available when the prototype is set
  • We should replace the prototype.constructor of the constructor (which sets it for all objects created by it) so that if someone, for some strange reason, wants to create an object by referring the constructor from the prototype, he will get the correct constructor

So based on these guidelines, this is how we should create new objects and prototype chains (which is similar to class hierarchies, but not exactly the same):

var A = function() { };
var B = function() { };
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
var b = new B();
console.info("b instanceof B?: " + (b instanceof B)); // true
console.info("b instanceof A?: " + (b instanceof A)); // true
console.info("Object.getPrototypeOf(b) === B.prototype?: "+ (Object.getPrototypeOf(b) === B.prototype)); // true

And as you can see, both the instanceof operator and the Object.getPrototypeOf() function behave as expected.

Now that we have this settled, let’s do some properties and functions to our objects and see what happens:

var A = function (v1) {
    this.v1 = v1;
};
A.prototype.p1 = "a";
A.prototype.f1 = function () {
    console.info("In A.f1, p1="+this.p1);
}

var B = function () {
    A.call(this, 2);
};
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
B.prototype.p1 = "b";
B.prototype.f2 = function () {
    console.info("In B.f2, p1=" + this.p1);
}

var C = function () {
    B.call(this);
}
C.prototype = Object.create(B.prototype);
C.prototype.constructor = C;
C.prototype.f1 = function () {
    console.info("In C.f1, p1=" + this.p1);
}

var a = new A(1);
console.info("v1="+a.v1);
a.f1(); // prints "In A.f1, p1=a"
var b = new B(); 
b.f1(); // prints "In A.f1, p1=b"
b.f2(); // prints "In B.f2, p1=b"
console.info("v1="+b.v1);
var c = new C();
c.f1(); // prints "In C.f1, p1=b"
c.f2(); // prints "In B.f2, p1=b"
console.info("v1="+c.v1);

We first create the prototype A by defining its constructor (the A function) and adding two properties to its prototype and assign them values (remember, functions in JavaScript are also values). Then we define prototype B which is based on the prototype of A.

Of interest here is that we are replacing p1 with a new value, “masking” the value that was defined in A, and we call the parent constructor using the call function, which passes the this context. This is the way parent constructors are called… ugly, but that is how it is done. We then define C based on the prototype of B, and this time change the value of f1. The result of all this can be seen in the output that is printed to the console.

What happens internally is that when a value is referenced in an object, the interpreter first checks if it exists in the object. If not, it goes to the prototype, and then to the prototype of the prototype, until it either finds the value or gets an null prototype.

Continuing after we left of in the previous example, let’s now do something that can’t be done in regular compiled OO languages like C++/C#/Java. We’ll redefine f1 of prototype A, which changes its value for all objects that have A as its prototype (a and b):

A.prototype.f1 = function () {
    console.info("New A.f1, p1=" + this.p1);
}
a.f1(); // prints "New A.f1, p1=a"
b.f1(); // prints "New A.f1, p1=b"
c.f1(); // prints "C.f1, p1=b"

I guess that is enough for the moment. It took me a while to get all of this material inside, but now I think I understand what is happening here. Until next time, happy coding!

Arieh Bibliowicz

Arieh is a longtime programmer with more than 10 years of experience in enterprise grade software projects. He has worked as server-size programmer, team leader and system architect in a mission-critical high-availability systems. Arieh is currently a Program Manager (PM) in the Microsoft ILDC R&D center for the Azure Active Directory Application Proxy, and also a PhD student at the Technion where he is developing a Visual Programming Language based on the graphical language of the Object-Process Methodology, using Java and the Eclipse platform.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button