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!
Reference: | JavaScript Tutorial – Part 4: Object Creation and Prototype Chains from our WCG partner Arieh Bibliowicz at the Vainolo’s Blog blog. |