Object Oriented Programming in Javascript
I wrote some stuff in Javascript now, but during the recent days I made a big step on my personal learning curve. I learned about how to do Object Oriented Javascript programming correctly. Until recently, I coded OO Javascript "by the book", which, to be honest, is no real object oriented programming at all. While reading the code of X-lib and mootools I understood that "by the book" makes no real object orientation. In this little article I discuss what I have learned.
By-the-book Object Orientation
If you read the "Object oriented programming" chapters of best selling Javascript books, you will find a reference to the "prototype" property of Javascript objects. This property allows you to assign functions and other properties to objects before they get instantiated during runtime. That way you can assign functions and properties for all objects of a certain type. Internally to Javascript the prototype property contains an anonymous object, which properties are copied into the real object once that object is instantiated.
Using the prototype property is a technique that is commonly referred as closures. Most books on Javascript programming end here when it comes to object orientation. However, closures alone have little to do with object oriented programming in Javascript. In fact it is a technique that is somewhat against the spirit of object orientation: Instead of tightly binding variables, functions and methods to an object, you can make anything a closure of an object from the outside. Furthermore, everything that is part of an Javascript object's prototype is public. It is not possible to hide any logic to the outside.
Another drawback of the prototype solution is that you have to use the this object to access information of an object. Using this is problematic because it is an context sensitive object and does not always contain the instance of your object. This can be problematic if you want to access your object's variables from within a callback function. In these cases the this object commonly refers to the caller of the callback and not to your object. However, in object oriented programming your want to avoid these kind of problems.
Encapsulation
The tight coupling of variables, methods and functions is called encapsulation. The concept refers that certain aspects are integral parts of an object class. While implementing a class the programmer defines these aspects together the information if the information is public, private, or (in some languages) restricted.
With Javascript, as I learned from the x_lib code, at least public and private parts of a class can be implemented easily. Because classes in Javascript are specially used functions, it is possible to make use of 'nested' functions and local variables. These variables and functions are only visible from inside the function itself and cannot get accessed from the outside. This implies that these functions cannot be overwritten by some evil hacker that manipulates the prototype property of your function. Another cool thing with this approach is that all variables that are defined in the class are accessible from all nested functions as if they were global variables. The following code illustrates this.
function MyClass() { var a = 0;
function increment() { a++; } }
The increment() function will increment the variable a with every call. The only problem with the example is that the increment() function is only available from within our class - it is private and thus invisible to everything outside. Therefore, we cannot increment the variable a at all. A tiny extension makes the increment() function public.
function MyClass() { var a = 0; this.increment = increment; this.a = getA;
function increment() { a++; } function getA() { return a; } }
What I did is assigning the increment function as an object property. Assigning functions can be done anywhere in the function and do not depend on where the function is defined in the class. In order to make things public, we simply use the closure approach. Now we can increment the internal variable a by running the following code. You also have recognized that I added a second function getA() and made that public, too. This function is called an accessor. I assigned the function to the public name a(). So from internal we would call getA() but from the outside we have to call object.a() to access to that function. This accessor makes our internal variable somewhat protected (read only) to the outside.
var myobj = new MyClass(); myobj.increment(); alert( myobj.a() ); // alerts 1 myobj.increment(); alert( myobj.a() ); // alerts 2 myobj.a++; // causes an error
OK, but now there is little difference with a prototyped object. The power in this approach lies in giving you the freedom to make things public as you like, but keep things private where you need it, as I show with the following code.
function MyClass() { var a = 0; this.increment = incrementLimit; this.a = getA;
function increment() { a++; } function getA() { return a; } function incrementLimit() { if ( a < 2 ) increment(); } }
I added the incrementLimit function and made it public as a replacement of the old increment function, which is now a private function again. From all functions of our object we can still call the increment() function as normal, but calling increment() on a class' object will now result in a call of the incrementLimit() function. The results are of course as expected as the next code illustrates.
var myobj = new MyClass(); myobj.increment(); alert( myobj.a() ); // alerts 1 myobj.increment(); alert( myobj.a() ); // alerts 2 myobj.increment(); alert( myobj.a() ); // alerts still 2
As you have recognized our class hardly uses the this object anymore. For the little example this works fine, but what about public variables of an object? For these situations we want to avoid the this object, too. From what I learned from my experiences with callbacks in closures, is that you should use a copy of the this object instead of this itself.
function MyClass() { var xthis = this; var a = 0; xthis.increment = incrementLimit; xthis.a = getA; xthis.b = 0; return xthis;
function increment() { a++; } function getA() { return a; } function incrementLimit() { if ( a < 2 ) increment(); raiseB(); } function raiseB() { xthis.b += 20; } }
In this little extension I copy the this object into a private variable called xthis. Note that this is done only during the creation of the object. All internal function will use that local copy instead of the official this. It is always a good idea to do this as early as possible and avoid any calls to the real this object afterwards.
I also added a public variable b which can be accessed from outside just as normal.
What is interesting is the return statement just before the function definitions start. This statement is pretty helpful in order to indicate the end of the class' initialization code. We will see later that it has some other benefits, too.
We can run now the following code.
var myobj = new MyClass(); myobj.b++; myobj.increment(); alert( myobj.a() ); // alerts 1 alert( myobj.b ); // alerts 21
Oops, why does the public variable b is now set to 21, instead of 1? Well, careful readers might have recognized that I added the private function raiseB() to the class, which is called every time incrementLimit() is called. That means when the increment() function is called from the outside, both variables, the private a and the public b are incremented. Therefore, the public variable b is incremented twice in the example: first from outside the object and then by a private function of the object.
The important bit with the last class definition is that the this object is only used once during object initialization. This implies that there are no dangers from that side anymore.
Inheritance
The second thing I learned is how to do subclasses without the prototype mechanism as it is done by the current version (1.11) of mootools. In order to do the trick, I implemented a additional function called extends(). This function takes a class name as an input parameter.
function extends(classname) { return new classname(); }
Now I can write a second class that inherits from the MyClass which we have developed so far.
function MyOtherClass() { var xthis = extends(MyClass);
var c; xthis.incrementC = incrementC; xthis.c = getC;
return xthis; // THIS IS IMPORTANT!
function incrementC() { c++; } function getC() { return c; } }
You see that there is no use of the this object in MyOtherClass anymore. Instead, it takes the result of the extends() function as the internal instance reference. The important bit comes now with the return statement after the object initialization: if a object is created in Javascript with the new function the local this object is returned as an object instance. However, if a function has a return value, this return value is used instead of the internal object reference. For our code it implies we get us an object of the original class and add our public functions to it. The interesting bit here is that the private variables and functions of the new class are still available.
Compared to the inheritance approach of mootools this approach results in much cleaner code.
Conclusions
Object Oriented Javascript is relatively simple by hardly documented. In this article I discussed how to make real use of encapsulation and class inheritance in Javascript. The only missing part is function overloading, but I think that I will come up with some neat solution for that as well ;)
--------