http://www.developer.com/services/article.php/3657486/Implementing-Real-Classes-in-JavaScript.htm
The AJAX from Scratch series of articles describes fundamental techniques needed to develop AJAX Rich Internet Applications in JavaScript from scratch. Each article focuses on a particular (usually little-covered) aspect of developing browser-side functionality without the use of commercial or server-based frameworks. The techniques described are illustrated in Gravey, a small, but real-world, JavaScript framework for AJAX and RIAs. With the increasingly popular AJAX and RIA 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 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, a platform is needed that provides for programming with classes. An implementation of such a platform is presented that is based on the techniques used in the Gravey framework. Presented is a very small set of JavaScript code and programming conventions that together emulate classical classes 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.
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
JavaScript is generally a prototype-based language; 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. In other words, JavaScript allows each object's attributes to be dynamically created and deleted at runtime. It also (not being strongly-typed) allows the same attribute (or any variable for that matter) to be assigned values of different data types over time. And finally, an attribute value can be a Function object which can be invoked in a manner similar to a method. JavaScript has syntactic sugar; to make creating object instances, accessing their attributes, and invoking their "methods", look like Java syntax.
Regarding subclasses, Java's classical inheritance; 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; 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".
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.
Listing 2. Simple example defining Class (aka "static") properties Note the following platform features along with their rationales.
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 overriding 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. 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.
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 or legacy code thus allowing the technique to be added to existing code.
The source code in this article is available for viewing or download at Polyglotinc.com or here at Developer.com.
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.
Implementing "Real" Classes in JavaScript
February 1, 2007
Series Description
Abstract
JavaScript doesn't already have Classes?!
Or, Prototype-based vs Class-based, Dynamic vs Static, Weakly-typed vs Strongly-typed
(a) JavaScript has syntax that is deceptively similar to Java, even though something completely different is going on semantically
(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.
The GrvObject Approach
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);
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 }
SOAPBOX WARNING: The Platform Implementation
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 }
Conclusion
Examples
About the Author
Copyright