In the first installment of this series, we discussed objects and functions in JavaScript. In this second, and final, installment in this series, we will go a bit further and see how functions are used as “instance factories” or constructors to create objects. We will see how we can use these “constructors” to not only instantiate new instances, but also endow new objects with a predefined set of properties applicable to that “class” of objects (much like Java classes). Finally, we will put it all together to build our own hierarchy. We will also see some potential potholes and how to avoid them in your code.
This is an article I wrote for NFJS, The Magazine’s May, 2012 issue. This is a 2-part series, this being the second one. You can find the first one here.
In our previous discussion, we learned about JavaScript objects and that they have a few magical properties adorned upon them, specifically the constructor
property and the __proto__
property. We also noted that all objects (except Object.prototype
) participate in a hierarchy – by having their __proto__
property point (directly, or indirectly) to Object.prototype
.
We also learned that JavaScript functions are objects themselves and have an additional property called prototype
, which points to a plain old JavaScript object. This prototype
object, just like any other JavaScript object, has a constructor
property that points back to the function itself and it’s __proto__
property points to Object.prototype
.
Before proceeding, I encourage you to fire up Google Chrome’s Console (or Firebug’s console) so that you can experiment with the code (See Part I to see how you can do this).
Also, I would like to include our last code snippet Listing 1 and its accompanying visualization Figure 1 from Part 1 just for reference purposes – we are going to need them for our discussion.
1
2
3
4
5
6
7
function Person(name) {
// ignore the argument for this listing
}
console.log(Person.prototype); // evaluates to Person { }
console.log(typeof Person.prototype); // evaluates to "object"
Just like we discussed in Part 1, we are defining a function called Person
. This function object has a prototype
property that points to a simple JavaScript object, referred to as Person.prototype
. This prototype
has its own __proto__
property, which in turn points to Object.prototype
, and a constructor
property that points back to the Person
function object. This hierarchy is visualized in Figure 1.
Object.prototype visualized
So that was our recap of Part 1. Ready to begin? Let’s go …
The Constructor Invocation Pattern
Let’s say we wanted to create a “class” of Person
objects. These objects are to have a name, and should be polite to one another. Listing 2 is how we would declare and use the Person
function to achieve this.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Person(name) {
this.name = name;
this.sayHello = function(other) {
return "Hello " + other.name + "! My name is " + this.name;
}
}
// Using the 'Person' function as a "constructor"
var brendan = new Person("Brendan");
var douglas = new Person("Douglas");
console.log(brendan.name); // returns "Brendan"
console.log(brendan.sayHello(douglas)); // returns "Hello Douglas! My name is Brendan"
Some subtle, yet important things to note here. Our Person
function object does not have an explicit return
. Furthermore, within Person
we are referring to some object as this
. Lastly, we invoke the Person
function with the new
keyword. So what’s going on here?
JavaScript overloads the role of a function to act as a constructor when invoked with a new
– this is called the “Constructor Invocation pattern”. When you do this, a new object pops into existence within the scope of the constructor, which you can refer to as this
. Finally, the constructor evaluates to (that is “returns”) this newly created object.
Armed with that information, you can see what’s going on in Listing 2. When we invoke Person
with the new
keyword, JavaScript creates a new object. This object, which we can get a handle to using the this
keyword within the scope of the Person
constructor, is then adorned with the name
property, and a sayHello
method. sayHello
can refer to that object’s name
using this.name
. Finally, Person
returns this newly created object, which is assigned to the variable brendan
. Essentially, brendan
is an “instantiation” of the Person
function. Another way to look at Person
is to think of it as the “constructor” and the “class” all rolled into one.
I would like to point out that both brendan
and douglas
are JavaScript objects like any other – they both have __proto__
pointers but with one big difference – this.__proto__
(where this
is the newly created object, so either brendan
or douglas
– See Figure 2) pointer points to Person.prototype
, and not Object.prototype
. Conceptually after evaluation, Listing 2 looks as shown in Figure 2.
Objects created using the new operator
Compare Figure 2 with Figure 1 and see how we have managed to intercept the __proto__
lookup chain. If we were to introduce sayGoodbye
as a method in Person.prototype
only brendan
and douglas
would see that method as part of the method lookup – all other objects that directly (or indirectly) inherit from Object.prototype
are not affected! This allows us to define methods that apply only to a specific (in this case Person
) “class” of objects. Pretty neat, right?
Let’s take it up another notch. We have the Person
function defined, and consequently we get a Person.prototype
. But suppose we realize we need to model Mammal
, from which Person
(and subsequently) brendan
and douglas
are to inherit from. How do we go about doing this? Think about this for a few minutes before taking a peek at Listing 3 (Hint: You have to figure out a way to make Person
’s prototype point to Mammal
’s prototype)
1
2
3
4
5
6
7
8
9
10
11
// Tack this code on after Listing GAN-2 in your console window
function Mammal() {}
Mammal.prototype.nurture = function() {
return "Care";
};
// this is where we rearrange the pointer
Person.prototype.__proto__ = Mammal.prototype;
console.log(brendan.nurture); // evaluates to "Care"
Here, Mammal
is just another function, and has its own prototype
. This prototype
object is no different than Person.prototype
in that it has a __proto__
property that points to Object.prototype
. We then change Person.prototype
to point to Mammal.prototype
. See Figure 3 to see how this lays itself out (I have left out Object.prototype
, but Mammal.prototype.__proto__
points to Object.prototype
as indicated by the dashed arrow).
Intercepting the prototype chain
The key thing to understand and remember here is that the constructor functions are mere facilitators allowing us to create objects with their __proto__
property set correctly. It’s the objects themselves that intrinsically know who their “prototypes” are and enable function (and property) lookup.
There is another way to set the inheritance chain properly without resorting to what we did in Listing 3. In Part I of this series, we used Object.create
to create a new object, but we passed null
as an argument. Now that we know how we can use prototypes to enrich the inheritance chain, we will see how to use it. This argument, if supplied, tells JavaScript what the newly created object’s prototype should be. Consider Listing 4 to see how you can use this.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var person = {
sayHello: function(other) {
return "Hello " + other.name + "! My name is " + this.name;
}
};
// here 'person' becomes the 'prototype' for 'brendan'
var brendan = Object.create(person);
brendan.name = "Brendan";
var douglas = Object.create(person);
douglas.name = "Douglas";
brendan.sayHello(douglas); // evaluates to "Hello Douglas! My name is Brendan"
We create a person
object that happens to have a sayHello
method. We then use this object (Note: Not a function) as an argument to Object.create
. Fundamentally, this is the same as Listing 2. The newly created person
object serves as brendan
’s __proto__
and everything works just as we saw earlier. This just gives us another way to correctly set the prototype chain up of any object.
Putting It To Good Use
Let’s consider two distinct problems and see if we can apply our new found knowledge to solve them. In functional programming, the map
function is amongst the most used functions, not only in and of itself, but also as a building block for other higher-order functions. map
allows you to apply a function to every element in a collection, thereby transforming each element in the collection, and returns a new collection with the transformed elements. Needless to say, the returned collection has the same size as the one supplied to map
. We do this all the time in our Java code, albeit in an iterative fashion. Consider the Java code listed in Listing 5.
1
2
3
4
5
6
7
8
9
10
11
12
List<String> names = new ArrayList<String>(3) { {
add("brendan");
add("douglas");
add("john");
} };
List<String> transformedNames = new ArrayList<String>(names.size());
// perform the map
for(String name: names) {
transformedNames.add(name.toUpperCase());
}
As you can see from Listing 5, we apply the toUpperCase
method to every element in the names
list and add it to transformedNames
. After this code executes, transformedNames
will contain exactly three names, all uppercase.
If we were to do this in JavaScript, one approach would be to do as shown in Listing 6.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function map(coll, f) {
var ret = [];
for(var i = 0; i < coll.length; i++) {
ret.push(f(coll[i]));
}
return ret;
}
// just wrap toUpperCase. You can use apply instead
// takes a single arg and uppercases it
function myToUpperCase(str) { return str.toUpperCase(); }
map(["brendan", "douglas", "john"], myToUpperCase); // return ["BRENDAN", "DOUGLAS", "JOHN"]
Notice that our map
function takes another function as its argument, and for each element calls f
passing it the nth element of the array and pushes it on to the return array.
If you were to deliberate on this for a moment, it does seem a little smelly. The fact that map
has to iterate over the collection seems like feature envy. What if we could have a map
method on Array
that would accept the transforming function and we could invoke that on any array? That would get rid of the smell. Think about what we discussed and see if you can figure out how to pull that off (before taking a look at Listing 7).
1
2
3
4
5
6
7
8
9
10
11
12
Array.prototype.map = function(f) {
var ret = [];
for(var i = 0; i < this.length; i++) {
ret.push(f(this[i]));
}
return ret;
}
function myToUpperCase(str) { return str.toUpperCase(); }
["brendan", "douglas", "john"].map(myToUpperCase); // return ["BRENDAN", "DOUGLAS", "JOHN"]
We know that when we invoke a method on any object, JavaScript will go through the prototype chain until it finds the method or reaches Object.prototype
and bails. All we have to do is get a handle to the __proto__
object of Array
, or alternatively ask the Array
function for its prototype
(which is what we are doing in Listing 7) and tack on the map
method. Done! Now, all arrays are blessed with a map
method. Note that within the for
loop in Listing 7, we refer to the array itself as this
– since JavaScript is invoking the map
method on an instance of an Array
.
Consider another scenario. Let’s say that you wanted to keep tabs on how many Person
objects were created during the execution of a program. The way we would do this in Java would be to create a static
variable – essentially a variable that is the same across all instances of a class and increment it every time the Person
constructor was invoked. How can we do that in JavaScript? Well, we know we have constructor functions that would be invoked with the new
keyword, so that would be a good place to increment the counter. But where do we store the variable itself? Doing a this.counter++
won’t work because this
within the context of a constructor function refers to the newly created object, making counter
an instance variable.
But we also know that functions themselves are objects and can have properties of their own. Can we somehow make use of that? Take a look at Listing 8.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person(name) {
// equivalent to saying Person.counter
// but more idiomatic
this.constructor.counter++;
this.name = name;
}
// init 'static' variable
Person.counter = 0;
brendan = new Person("Brendan");
douglas = new Person("Douglas");
console.log(Person.counter); // returns 2
// alternatively
console.log(brendan.constructor.counter); // also returns 2
If we were to glance back at Figure 3 you will notice that Person.prototype
has a constructor
property that points back to the its function (Person
in this case). Within the context of the constructor function, this
is the newly created object, which has its __proto__
property set to Person.prototype
. When we ask this
(for e.g brendan
) for its constructor
property, JavaScript looks within the newly created object and does not find it. So it does what we expect it to do – follow the __proto__
property up to Person.prototype
which does have it, and hands us back a pointer to the Person
function. We then ask it for its counter
property (which we have initialized to 0) and increment it. Figure 4 illustrates the state of the system after Listing 8 finishes execution (note that I am not showing Object
only to avoid clutter – that hierarchy remains the same in previous illustrations).
Intercepting the prototype chain
Of course, we could have just said Person.counter
and get the same desired effect, but there might be times when you don’t know the constructor function of an object, so having the constructor
property can prove to be really handy.
Words of Caution
Before we conclude this article, I would like to emphasize a few key points. Although __proto__
is a handy property for testing and illustrative purposes (in FireFox and Chrome), by no means should you rely on using (or even assume the existence of) this property in production code. The idiomatic way to getting the prototype of any object is to use the getPrototypeOf
method supplied on Object
(e.g. Object.getPrototypeOf(brendan)
).
We have been naming our constructor functions (like Person
and Mammal
) with a capital “P” (and “M”) vs. lowercase letters as is the norm for function names in JavaScript. The reason for this is to highlight the fact that they are, in fact, constructors, and should be invoked with the new
keyword. Not doing so will result in bad things happening. Constructor functions, as we noted earlier, do not have an explicit “return”, and often refer to this
in their function bodies. What does it mean if we were to invoke a constructor function without the new keyword? Let’s take a quick look in Listing 9.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function BadlyInvoked(villianiousArg) {
this.villianiousProperty = villianiousArg;
this.villianiousFunction = function() {
return "Muhhhaahaa!";
}
}
var villian = BadlyInvoked("The Joker");
console.log(villian); // evaluates to undefined
// global domination
console.log(villianiousProperty); // no object context
console.log(villianiousFunction()); // nor here
Notice that you get no errors whatsoever. JavaScript happily chugs along executing the code, but villian
evaluates to undefined
. This is because BadlyInvoked
does not return
anything (and by default, JavaScript returns undefined
). Furthermore, you can get to villianiousProperty
and villianousFunction
without a reference to villian
. What happened was upon invocation, BadlyInvoked
, did not create a new object, rather it acted as any other function would (remember that if you use the ‘Function Invocation’ pattern, that is invoke a function as a standalone, this
is implicitly the “global object”). Therefore, this
refers to the global object, which in the browser environment happens to be the window
object. We just accidentally introduced two new properties on the window
object. If these had been named anything less sinister (e.g. name
) you would have just clobbered the global name
property! Not to mention that your code will begin to throw all kinds of errors if you attempt to use villian
in any way, because it is undefined
!
Naming constructor functions with capital letters is idiomatic JavaScript, and the preferred convention as a way to alert fellow developers that this function should be invoked with the new
keyword.
Conclusion
Phew! We have reached the end of our whirlwind tour inside JavaScript’s inheritance strategy and implementation. Although some of this may seem a little overwhelming, and perhaps even a tad confusing, hopefully I have given you a clear guide to a better “understanding” of the language and implementing your own inheritance hierarchies within JavaScript.
JavaScript is truly a powerful language. Couple prototypal inheritance with JavaScript’s dynamic nature (adding, removing and changing behavior at runtime), and you are one step closer to unleashing the power to write idiomatic, clean and most importantly, re-usable code within your applications.