"Real" Classes in JavaScript
And why your JavaScript
classes probably aren't.
Bruce Wallace
http://www.PolyGlotInc.com/
bruce.wallace@acm.org
With the increasingly popular AJAX [1] and RIA[2] paradigms, the impetus to build
complicated dynamic user interfaces is driving developers to use the same design
patterns (e.g. Model-View-Controller) formerly tied to "fat"
GUI clients. All of these design patterns expect the consistency of class-based[3] objects as provided in languages like C++ and
Java. However, since JavaScript does
not use classes, Rich Internet Applications must emulate their functionality.
This article describes one such emulation technique and demonstrates why many
commonly used techniques fall short.
In order to develop non-trivial logic in JavaScript, and especially when using standard design patterns[4], a platform is needed that provides for programming with classes[3]. An implementation of such a platform is presented that is based on the techniques used in the Gravey framework[19]. Presented is a very small set of JavaScript code and programming conventions that together emulate classical classes[5] of the sort familiar to C++ and, in particular, Java programmers.
For those object-oriented developers whose experience
with JavaScript has been shallow or not particularly object-oriented (e.g.
the author a few years ago), it may not be obvious why such a platform is
needed. It is only when understanding how JavaScript actually works, and why simplistic
approaches to JavaScript "classes" fail, that it becomes apparent why
the particulars of the presented platform are needed.
JavaScript doesn’t already have Classes?!
OR, Prototype-based vs Class-based,
Dynamic vs Static, Weakly-typed vs Strongly-typed
Just as a person, born and confined to an entirely blue room, has a hard time conceiving of green, the typical OO programmer has a hard time imagining objects and inheritance without classes. JavaScript is a language with objects and inheritance but no classes. For casual JavaScript programmers, this fact is often not even known because (a) JavaScript has syntax that is deceptively similar to Java, even though something completely different is going on semantically, and (b) many web development texts talk as if JavaScript has merely a slightly different syntax from Java, rather than describing how they are fundamentally different categories of languages[17].
JavaScript is
generally a prototype-based
language[6] where each object is not constructed from
a predefined template of attributes and methods (i.e. a class), but is
effectively just a dynamic
associative array of attribute names and values[11]. In
other words, JavaScript allows each object's attributes to be dynamically
created and deleted at runtime. It also (not being strongly-typed[7])
allows the same attribute (or any variable for that matter) to be assigned
values of different data
types[8] over time. And finally, an attribute value can be a Function object
which can be invoked in a manner similar to a method[13]. JavaScript has syntactic sugar[9]
to make creating object instances, accessing their attributes, and invoking
their "methods", look like Java syntax.
Regarding subclasses, Java's classical
inheritance[10] defines one class as a subclass of another
thus inheriting its methods and attributes.
When a class instance (i.e. an object) is created, that object has all
the attributes and methods of the class' entire inheritance hierarchy. With
JavaScript's prototype inheritance, an object may point to another object
as its "prototype". When reading an object attribute, if that
attribute is not found, the object’s prototype will be searched. Since that prototype object may, in turn,
point to another prototype of its own, an entire inheritance chain may be built
(ala a class hierarchy).
A big difference, however, is that prototypes, being simple objects, may be changed at runtime and hence dynamically change what is being inherited. On the other hand, if multiple prototype objects of the same "class" are created, changes to one prototype object will not be seen by those pointing to different prototype objects. Further more, when assigning a value to an inherited attribute, the prototype object is not changed. Instead, a new attribute with the same name is created on the top-level object, SO, implementing "class" (aka static) attributes via simple prototype objects doesn't really work.
Note that I described JavaScript as “generally prototype-based”. This is because it has other features that look a lot like classes! JavaScript allows functions to be used as "class" constructors[12] and recognizes their names via the instanceof operator. JavaScript also has several predefined "classes" (e.g. Date, String, Function, etc.).
With the presented technique, it is a simple matter (as
shown in Listing 1) to declare a class along with its superclass, constructor,
and methods, as well as, to use “super”.
1 Class(A);
2 function
A()
3 {
4 this.konstructor
= function( arg1 ){ this.bar = arg1; }
5 this.aMethod = function( ... ){ ... }
6 this.anotherOne = function( ... ){ ... }
7 }
8
9 Class(B).Extends(A);
10 function B()
11 {
12 this.konstructor = function( arg1, arg2,
optionalArg3 )
13 {
14
this.souper(
arg2 ); //ie.
invoke "A.konstructor"
15
this.foo = arg1;
16 }
17
18 this.aMethod = function(...){ this.souper(42); ...
}
19 this.yetMore = function(...){ ... }
20 }
21
22 var x = new B(3,4,5);
Listing 1.
Simple example using the GrvObject technique
Normal looking JavaScript “constructor” functions are defined in
lines 2-7 and 10-20 except that any logic other than defining methods is
contained in the special “konstructor” method. Lines 1 and 9 invoke the utility
routines that set up the internal plumbing for A and B to be a “real” classes, and for B to be a
subclass of A. Line 22 shows how to create an instance of B which
implicitly executes the B
konstructor. The B
konstructor, in turn, invokes the A konstructor via “super”. Line 18 shows a method
invoking the version of the method that it overrode.
Listing 2 shows how “static” class methods and attributes can be defined and used. Unlike some traditional approaches that create them on the prototype objects (which doesn’t work except in the simplest of scenarios), the Function object is the container used. Lines 4-8 define “static” methods and variables. Line 11 demonstrates invoking a static method. Actually, this technique for Class properties does not depend on the GrvObject platform at all; it is just a good coding convention.
1 Class(A);
2 function
A()
3 {
4 if (A.Tar==null){ //only needed once
5 A.Tar = function(...){ ... }
6 A.Foo = new A(19);
7 A.Bar = 42;
8 }
9
10 this.konstructor = function( arg1 ){ this.bar = arg1; }
11 this.methodX = function( ... ){ return A.Tar(...); }
12 this.methodY = function( ... ){ ... }
13 }
Listing 2.
Simple example defining Class (aka “static”) properties
Note the following platform features along with their rationales.
· Keywords “class”, “extends”, “super” and “constructor” are spelled Class, Extends, souper and konstructor to avoid name collisions with reserved keywords and/or variables that JavaScript already uses.
· Base classes (e.g. Class A) are implicitly made subclasses of the root class GrvObject. In addition to providing the souper method, GrvObject also acts as a Design Marker[14] to aid in documentation, and, can be checked via instanceof to identify classes created with this platform. This design marker shows the intention[30] that the declared classes are not wildly dynamic as might be the case with unrestrained or undisciplined JavaScript.
· While this technique manipulates internal plumbing to accomplish its functionality, it preserves the ability to detect an object’s class, and all superclasses (including GrvObject), via the instanceof operator. Many class emulation techniques break instanceof[25].
· The new operator also works using this technique. Many emulation techniques break new [27].
· To allow traditional JavaScript techniques to coexist with this platform, no properties are added to any standard JavaScript classes[31] like Object, String, Function, etc.
· By having explicit constructor methods (i.e. this.konstructor), which can invoke “super” constructors, arguments can be passed to each superclass constructor on an instance by instance basis, as is the norm for Java and C++. Traditional approaches[24] either force one set of superclass constructor parameters to be used for all subclasses, or, for a new superclass prototype object to be created for every new object created!
· This technique makes object property lookup and storage more efficient since all data properties (and no method definitions) exist in only the topmost object in the prototype chain. Instance variables defined by even the deepest superclass methods are still created in the topmost object only. Many other techniques spread the variables around multiple prototype objects making runtime lookup slower and taking up extra memory with multiple obsolete copies of the variables.
· Java (rather than C++) source style is used such that all methods are defined in a single code block rather than spread around the source code. While using this style with normal JavaScript would replicate the method definitions on every object instance, the GrvObject platform insures that only one singleton copy of each method exists.
· This technique also insures that each class prototype object is a singleton[22]. Traditional approaches[24] create multiple copies of class prototypes which cause problems when trying to do fancy aspect-oriented[28] trickery via prototype object manipulation.
· The ability to invoke “super” works for arbitrary hierarchy depths and also allows the super being invoked to have been declared arbitrarily far up the inheritance chain. Not all techniques work for deep hierarchies[25].
· Since JavaScript doesn't police method signatures[18], one can't really separate method overloading[20] from method overriding[21]. Therefore in order to handle either case, souper allows arbitrary parameters to be passed to the “super” method or constructor. Even some second generation techniques do not allow this[25].
· This style of source code is Eclipse and IBM WSAD friendly in that each “class” is displayed in the IDE’s Outline View. Class emulation techniques that advocate passing in a class initializer object literal[23] cause those classes to be invisible in the outline view. In fact, if you can live with having to specify the method name twice, you can get all the methods to show up too (w/show hierarchy) underneath the proper classes, by replacing lines like “this.drawMe = function(){...}” with “this.drawMe = function drawMe(){...}”.
· This style of source code is also compatible with JSDoc[15], the open source Javadoc[16] tool for JavaScript. The Gravey framework and example apps are all documented with JsDoc. Other styles of class declaration often confuse JsDoc.
· The full version of the platform[29] adds separate utility libraries which enable constructor parameter validation. With it, each class may declare an array of required constructor parameter names, and if any are missing when the constructor is invoked, an exception will be thrown. E.G. Class(B,["bar","foo"]).Extends(A);
· The
full version also adds the optional ability to access to any class
"constructor" via the class name. E.G. “this.souper” could be
replaced by “this.A” in line 14 of Listing 1.
soapbox warning: While
this means that the name of the superclass is used explicitly, I’ve always
thought that merely invoking "super(...)", as Java does, was a bit
disingenuous. Because super(...) must be passed the proper arguments, you still
have to know what the superclass is anyway! Recall that, unlike overridding a
method (which requires the same calling signature for both super and sub
methods), the constructor invoked via super can have completely different
arguments (as well as the fact that there can be more than one super
constructor, each with a different signature). So, if you change a superclass,
you can't just leave the super(...) call as is (even though you aren't
mentioning the superclass by name) because you still have to make the super()
argument list match the new superclass constructor! Therefore, having the
superclass name explicitly in the code makes it obvious which arguments you had
in mind, and very obvious when they are out of sync with “extends”.
While the complete documented version of the platform[19]. is contained in a few JavaScript include files, the compact, standalone code in Listing 3 implements the basic technique. Lines 12-18 implement the “super” capability. Lines 27-31 replace the original class function with a wrapper function that, in turn, uses the original class as its prototype. Instances of the wrapper class will hold all the instance variables and only the singleton prototype objects contain method definitions.
1 Class(GrvObject); //special root class
2 function
GrvObject(){
3 this.souper
= function()
{
4 var superMethod
= this.souper.caller.souper;
5
return (superMethod) ? superMethod.apply( this, arguments ) : null;
6
}
7 }
8
9 function grvExtends(
superClass ){ //link super and sub classes
10 this.baseClass.prototype
= (this==GrvObject)
? null :
superClass.prototype;
11 this.prototype
= new this.baseClass();
12 if
(this.baseClass.prototype)
13 for
(var x in this.prototype) { // iterate over all properties
14
var m = this .prototype[x];
// the method
15
var s
= this.baseClass.prototype[x];
// its super
16
if (s
&& (s!=m) && (m instanceof Function) && (s instanceof Function))
17 m.souper = s;
18 }
19 }
20
21 function
grvFuncName(f){ //extract name from
function source
22
var s = f.toString().match(/function\s*(\S*)\s*\(/)[1];
23
return s ? s : "anonymous";
24 }
25
26 function Class( theClass ){
27 var originalClass =
theClass;
28 self[
grvFuncName(theClass) ] = theClass = function(){
29
arguments.callee.prototype.konstructor.apply( this, arguments );
30 }
31
theClass.baseClass = originalClass;
32 theClass.Extends = grvExtends;//so we can call Extends as a method
33 theClass.Extends(GrvObject);//required here even if overridden later
34 return theClass;
35 }
Listing 3. The
GrvObject technique’s basic implementation (grvObject.js)
The utility function grvFuncName (lines 21-24) takes a function object and returns the function name (made possible because, in JavaScript, the function object contains the function source code).
With the presented compact implementation. JavaScript developers can take advantage of most of the features of Java classes, thus enabling the use of robust design patterns needed for AJAX and RIA applications. Use of this technique does not preclude mixing in other techniques nor legacy code thus allowing the technique to be added to existing code.
The source code in
this article[32] is
available for viewing or download.
References
1. http://en.wikipedia.org/wiki/Ajax_%28programming%29
2. http://en.wikipedia.org/wiki/Rich_Internet_Application
3. http://en.wikipedia.org/wiki/Class_%28computer_science%29
4. http://en.wikipedia.org/wiki/Design_pattern_%28computer_science%29
5. http://www.crockford.com/JavaScript/inheritance.html
6. http://en.wikipedia.org/wiki/Prototype-based_programming
7. http://en.wikipedia.org/wiki/Strongly-typed_programming_language
8. http://en.wikipedia.org/wiki/Type_system
9. http://en.wikipedia.org/wiki/Syntactic_sugar
10. http://en.wikipedia.org/wiki/Inheritance_%28computer_science%29
11. http://en.wikipedia.org/wiki/Associative_array#JavaScript
12. http://en.wikipedia.org/wiki/JavaScript_syntax#Constructors
13. http://en.wikipedia.org/wiki/Method_%28computer_science%29
14. http://en.wikipedia.org/wiki/Design_marker
15. http://jsdoc.sourceforge.net/
16. http://en.wikipedia.org/wiki/Javadoc
17. http://en.wikipedia.org/wiki/Prototype-based_programming#Comparison_with_class-based_models
18. http://en.wikipedia.org/wiki/Method_signature
19. http://www.gravey.org/
20. http://en.wikipedia.org/wiki/Method_overloading
21. http://en.wikipedia.org/wiki/Method_overriding_%28programming%29
22. http://en.wikipedia.org/wiki/Singleton_pattern
23. http://manual.dojotoolkit.org/WikiHome/DojoDotBook/Book20
24. http://www.webreference.com/js/column79/4.html
25. http://www.davidflanagan.com/blog/2006_10.html#000115
26. http://www.uselesspickles.com/blog/the-class-library/
27. http://weblog.openlaszlo.org/archives/2006/02/class-based-oop-in-JavaScript-done-right/
28. http://en.wikipedia.org/wiki/Aspect-oriented_programming
29. http://www.polyglotinc.com/Gravey/Gravey/jsdoc/overview-summary-grvClass.js.html
30. http://www.onjava.com/pub/a/onjava/2003/03/26/design_markers.html
31. http://encytemedia.com/blog/articles/2006/05/23/prototype-inheritance-madness
32. http://www.polyglotinc.com/AJAXscratch/Gravey/examples/
About the Author
As principal consultant of PolyGlot, Inc., Bruce Wallace has provided consulting and custom computer software development services around the world. Projects have been completed in Sydney Australia, Perth West Australia, "Silicon Valley", "Route 128" MA, Austin TX, Atlanta GA, and Charlotte NC.
© Copyright, 2006-2007, PolyGlot, Inc.