Object Oriented Programming in JavaScript by Java2Script(Draft)

Zhou Renjian
March 6, 2006

Summary

It's well-known that JavaScript is a prototype based or object based scripting language not a complete Object Oriented Programming (OOP) language as Java, which is a well-known and most-succeeded OOP language. As AJAX applications boosting, lots of JavaScript inheritance systems are developing to make codes more readable, more manageable and more developer-friendly. However, polymorphism and super method calling, which are two most important features for OOP, are not implemented or not fully implemented. In this article, a new library will be introduced for JavaScript to simulate a complete Object Oriented Programming. And a new Eclipse JDT plugin, named "Java2Script Pacemaker", will also be introduced to help developers in converting Java codes into Object Oriented JavaScript codes directly.

Content

Prerequisite

In this article, we think that you know a little about JavaScript, and know a little about Java or other Object Oriented programming languages.

Background

JavaScript is getting hotter and hotter. And more and more web applications are written with tons of JavaScript. Writing, debugging, maintaining large size of JavaScript sources is not an easy job, especially for those not well-structured codes. Usually, it's OK for developer to handle lesser than 200K JavaScript sources (non-OOP) by normal text editor. But when sources exceed 200K, it would be nightmares for developers to manage those codes. Adding new functions, testing, debugging, fixing bugs would waste the developers' time in locating the JavaScript functions. As there is still lacking of mature tools to help developers developing JavaScript when comparing to varieties of OOP tools, such as Eclipse or Visual Studio.

Why large JavaScript is a nightmare to developers?

Usually, developers need to locate the source position and do refactors to get things done. In OOP language, such as Java or C++, there are mature IDEs which can perform the jobs for the developers. But currently there are no such tools doing the jobs for JavaScript. Developers have to remember all those method names or property names. If you want to rename some methods or remove some methods, you have to find out all method invocations, and then rename or delete them one by one! And you should test a lot when you make some changes. But when testing you will find that there is lacking of testing tools for JavaScript. And debugging JavaScript would be a challenging job for developer especially the project is very large. And once you want the API document, you need to write and update them yourself. As there are lacking document tools for JavaScript yet.

Actually, OOP does will increase the developers' efficiencies and help developers free from the nightmare. And there are lots of mature IDE tools for developing in OOP, which will increase the developers' efficiencies sharply.

Concept of OOP

As well known, Java is a most successful OOP language. So we can take the major features of Java as the characters of OOP language. Java has the following features, while comparing to those non-OOP languages, such as C or FORTRAN:

JavaScript Embedded Inheritance System

Is the current version of JavaScript a complete OOP language, while comparing to Java? Let take a look at the native supports of JavaScript.

Even though there are no concepts of classes and interfaces in JavaScript, in fact, every function in JavaScript can be considered as a Class. And every anonymous function can be considered as anonymous class. Once a method is defined, it can be considered as implementing an interface with that method.

And accessing of the fields or methods in JavaScript are not strictly controlled. And accessing by keyword "this" is not reliable. Because in different closures, "this" will reference to different hosts. And the concept of final variables is similar with variables using in different closures. But the concept of final variables with anonymous class in Java is different the concept of variables with closures.

So JavaScript can be considered as a weak object oriented language. Or we can say JavaScript is only an object based language.

JavaScript 2.0

JavaScript 2.0 is an experimental proposal maintained by Waldemar for future changes in the JavaScript language. And there were several JavaScript 2.0 drafts since it was first proposed in 1999. In the JavaScript 2.0 proposals, there are lots of new features included, such as keywords "class", "interface", "include", "package", "const", "super", and more primitive types. But it seems that JavaScript 2.0 was developing very slowly, and when or whether will it be released is uncertain.

Currently, JavaScript 1.5 is the major version supported by most of modern browsers. But JavaScript 1.5 does not support OOP completely; Lots of JavaScript inheritance tricks are developed.

Existed Enhancement of JavaScript Inheritance

As OOP is successful way to develop application, some early developers try to develop some tricks to bring OOP into JavaScript world. In Douglas Crockford's Classical Inheritance in JavaScript, a "sugar" to implement simple inheritance was introduced. It's a simple inheritance system, because it does not implement the polymorphism, which is a most important feature of OOP. And some existed successful JavaScript frameworks are also developing their own inheritance system.

Dojo

Dojo provides ways to define packages and a simple way to define class inheritance. But Dojo has no concepts of interface. And it seems that Dojo does not care much about types. So it is still weak typed.

Prototype

The library Prototype is just providing a simple way to define class inheritance, without further inheritance tricks.

Altas

Altas core does implement about class name for every class (function). So it's somewhat strong typed. And Altas knows concepts of base class and inherited class and abstract class and interface. And Altas also keeps methods for base class so super calling may be implemented already. It seems that Altas does not extend the abilities of anonymous class and does not implement polymorphism and constructors. And it seems that final variables are not seriously considered. And Altas does not provide ways to define namespaces.

It seems that the current existed frameworks do not extend the JavaScript's inheritance abilities fully. So JavaScript programming in these frameworks is still considered as weak object oriented programming.

Now it seems that the JavaScript world needs OOP and need a complete OOP inheritance helper to help JavaScript developers into the decent OOP world.

J2S Clazz

J2S Clazz is a script library that provides APIs to define classical inheritance system in JavaScript for Object Oriented Programming. It is small, about 15k without comments and indents, and about 8k after compressed by dean.edwards.name/packer/. You can download J2S Clazz from j2s.sourceforge.net/j2sclazz/.

Following I will present a demo of OOP in JavaScript. Here is the source:

<html>
    <head>
        <title>Tests on Object Oriented Programming in JavaScript</title>
    </head>
<body>
<script type="text/javascript" src="j2sclazz.js"></script>
<script type="text/javascript">
// Writing string into the page instead of alert dialog when testing
window.alert = function (str) {
    var line = document.createElement ("DIV");
    document.body.appendChild (line);
    line.appendChild (document.createTextNode (str));
}
...
</script>
</body>
</html>

In the above codes, j2sclazz.js is included, so classical inheritance may be simulated.

The following codes will define an interface with two three final constants. The API Clazz.defineType is used to define interfaces or classes.

var pkgPrefix = "net.sf.j2s.hello";

// Interface ISex
var ISex = Clazz.defineType (pkgPrefix + ".ISex", function () {});
ISex.UNKNOWN = 0;
ISex.MALE = 1;
ISex.FEMALE = 2;

The API of Clazz.defineType is as following:
Clazz.defineType (typeQualifiedName, typeFunction, parentType, parentInterfaces);

The following codes will define a class instead of an interface. It will define member fields and member methods and also static methods.

// Class ExObject implements ISex
var ExObject = Clazz.defineType (pkgPrefix + ".ExObject", 
        function () {
            this.sex = ISex.UNKNOWN;
        }, null, ISex);
// Implementing interface ISex
ExObject.defineMethod ("getSex", function () {
            return this.sex;
        });
// Implementing interface ISex
ExObject.defineMethod ("setSex", function (sex) {
            this.sex = sex;
        }, "Number");
ExObject.defineMethod ("getAdjective", function () {
            if (this.sex == ISex.FEMALE) {
                return "her";
            } else if (this.sex == ISex.MALE) {
                return "his";
            } else {
                return "its";
            }
        });
ExObject.defineMethod ("getPronoun", function () {
            if (this.sex == ISex.FEMALE) {
                return "she";
            } else if (this.sex == ISex.MALE) {
                return "he";
            } else {
                return "it";
            }
        });
ExObject.defineStaticMethod ("capitalize", function (str) {
            if (str != null && str.length != 0) {
                str = str.substring (0, 1).toUpperCase () + str.substring (1);
            }
            return str;
        }, "String");

The API of (class).defineMethod/defineStaticMethod is as following:
*.defineMethod/defineStaticMethod (methodName, methodFunctionBody, parametersType);

And you can pass the defined class as parameter, but you must keep the parameter type name with a full qualified name. For example, for ExObject, you should write "net.sf.j2s.hello.ExObject".
This method has no return.

Following is defining class type with constructors.

// Class User extends ExObject
var User = Clazz.defineType (pkgPrefix + ".User", function () {
            this.name = null;
            this.email = null;
            // Make sure that constructors are to be evaluated
            Clazz.instantialize (this, arguments);
            }, ExObject);
User.makeConstructor (function () {
            this.name = "missing";
            this.email = "unknown";
        });
User.makeConstructor (function (name) {
            this.name = name;
            this.email = "unknown";
        }, "String");
User.makeConstructor (function (name, email, sex) {
            this.name = name;
            this.email = email;
            this.setSex (sex);
        }, "String, String, Number");
User.defineMethod ("getName", function () {
            return this.name;
        });
User.defineMethod ("setName", function (name) {
            this.name = name;
        }, "String");
User.defineMethod ("getEmail", function () {
            return this.email;
        });
User.defineMethod ("setEmail", function (email) {
            this.email = email;
        }, "String");
User.defineMethod ("toString", function () {
            return "This is " + this.name + ". " + 
                    ExObject.capitalize (this.getAdjective ()) + 
                    " email is " + this.email + ".";
        });

The API of (class).makeConstructor is as following:
*.makeConstructor (methodFunctionBody, parametersType);

This method has no return.

The API of (class).makeConstructor is similar to (class).defineMethod/defineStaticMethod. In fact, concept of constructors by J2S Clazz is just a method named "construct". When you define multiple constructors with different parameter types, you are defining polymorphisms on methodology.

As constructors should be called when creating a new instance of the defined type, so the line:

Clazz.instantialize (this, arguments);
must be the last line of the type function body.
The usage of this API is fixed as the above line.

Now, let test whether the J2S Clazz inheritance works or not and whether the constructors with different parameter types works or not.

var u1 = new User ();
alert (u1); // u1.toString ();
var u2 = new User ("Kate");
alert (u2); // u2.toString ();
u2.setSex (ISex.FEMALE);
u2.setEmail ("kate@gmail.com");
alert (u2); // u2.toString ();
var u3 = new User ("John", "john@gmail.com", ISex.MALE);
alert (u3); // u3.toString ();

And the result (You can take the online test here) is:

This is missing. Its email is unknown.
This is Kate. Its email is unknown.
This is Kate. Her email is kate@gmail.com.
This is John. His email is john@gmail.com.

It seems that J2S Clazz works.

Following we will show you:

First let's define two more interfaces:

// Interface IEndorsed
var IEndorsed = Clazz.defineType (pkgPrefix + ".IEndorsed", function () {});
// Interface INickNamed
var INickNamed = Clazz.defineType (pkgPrefix + ".INickNamed", function () {});

Now let's extend the class User:

// Class ExtendedUser extends User
var ExtendedUser = Clazz.defineType (pkgPrefix + ".ExtendedUser",
        function () {
            this.endorsedBy = null;
            // Make sure that constructors are to be evaluated
            Clazz.instantialize (this, arguments);
            }, User, [IEndorsed, INickNamed]); // Multiple interfaces
ExtendedUser.makeConstructor (function (name, email, sex, byUser) {
            // Call super constructor
            Clazz.superConstructor (this, ExtendedUser, [name, email, sex]);
            this.endorsedBy = byUser;
        }, "String, String, Number, User");

// Implementing interface IEndorsed
ExtendedUser.defineMethod ("getEndorsedBy", function () {
            return this.endorsedBy;
        });
// Implementing interface IEndorsed
ExtendedUser.defineMethod ("setEndorsedBy", function (byUser) {
            this.endorsedBy = byUser;
        }, "User");
// Implementing interface INickNamed
ExtendedUser.defineMethod ("getNickName", function () {
            return this.getNickName (null);
        });
// Implementing interface INickNamed
ExtendedUser.defineMethod ("getNickName", function (leading) {
            if (leading != null && leading.length != 0) {
                return leading + " " + this.getName ();
            } else {
                return this.getName ();
            }
        }, "String");
ExtendedUser.defineMethod ("toString", function () {
            // Call super.toString ()
            var desc = Clazz.superCall (this, ExtendedUser, "toString", []);
            if (this.endorsedBy != null) {
                return desc + " " + ExObject.capitalize (this.getPronoun ()) + 
                        " is endorsed by " + this.endorsedBy.getName () + ".";
            } else {
                return desc;
            }
        });

You should notice that there are two "getNickName" methods but with different parameters. And the "getNickName ()" method is calling "getNickName (String)". And it will be proved to be working! This proves the polymorphism implementation in JavaScript by J2S Clazz.

The test codes:

var eu1 = new ExtendedUser ("Kate");
var eu2 = new ExtendedUser ("John", "john@gmail.com", ISex.MALE, eu1);
alert (eu1); // eu1.toString ();
alert (eu2); // eu2.toString ();
alert ("Nicknames:");
alert (eu1.getNickName ());
alert (eu2.getNickName ("Little"));

alert (Clazz.getClassName (eu1));
alert (Clazz.instanceOf (eu1, ISex));
alert (Clazz.instanceOf (eu1, IEndorsed));
alert (Clazz.instanceOf (eu1, INickNamed));
alert (Clazz.instanceOf (eu1, ExObject));
alert (Clazz.instanceOf (eu1, User));
alert (Clazz.instanceOf (eu1, ExtendedUser));

alert (Clazz.getClassName (u1));
alert (Clazz.instanceOf (u1, ISex));
alert (Clazz.instanceOf (u1, IEndorsed));
alert (Clazz.instanceOf (u1, INickNamed));
alert (Clazz.instanceOf (u1, ExObject));
alert (Clazz.instanceOf (u1, User));
alert (Clazz.instanceOf (u1, ExtendedUser));

And the test result (You can take the online test here):

This is Kate. Its email is unknown.
This is John. His email is john@gmail.com. He is endorsed by Kate.
Nicknames:
Kate
Little John
net.sf.j2s.hello.ExtendedUser
true
true
true
true
true
true
net.sf.j2s.hello.User
true
false
false
true
true
false

From the results above, you would see that J2S Clazz do provide an almost complete OOP inheritance system for JavaScript. Now you may wonder what is inside j2sclazz.js, and how does it work?

Details of J2S Clazz

It's somewhat complicated for explaining J2S Clazz inheritance system. You may need to read the source of J2S Clazz to get a complete understanding. Here following the main point of J2S Clazz inheritance.

First, J2S Clazz is implementing multiple interfaces besides the singly rooted inheritance hierarchies. When implementing an interface, the interfaces are kept in an array, so when checking by "Clazz.instanceOf" method, these interfaces and also their parent will be checked recursively.

For example, when in the above JavaScript codes, method Clazz.defineType will result in executing codes similar to the following:

ExtendedUser.prototype = new User ();
ExtendedUser.superClazz = User;
ExtendedUser.implementz = [ISex, IEndorsed, INickNamed];

Constructors are defined as method "construct" to the class. So constructors with different parameters are just methodology polymorphism.

In order keep all the methods of different inheritance hierarchies with different parameters, proxy method is created. The methods are kept in extended properties of a proxy method with the same name. For example, when defining method getNickName with no parameter and method getNickName with "String" in ExtendedUser, a proxy method will generate:

ExtendedUser.prototype["getNickName"] = function () {
    var r = arguments;
    return Clazz.searchAndRunMethod (this, r.callee.claxxRefrence, 
            r.callee.methodName, r);
};

The method "Clazz.searchAndRunMethod" will try to find the correct defined method for the given parameters and then execute the method.

And the class hierarchies will also be recorded, in the proxy method. For example:

ExtendedUser.prototype["getNickName"].stacks = [ExtendedUser];

And the parameterized methods are kept in the proxy method's properties, such as:

	
// "void" stands for empty parameters
ExtendedUser.prototype["getNickName"]["\\void"] = function () {
    return this.getNickName (null);
};
ExtendedUser.prototype["getNickName"]["\\String"] = function () {
    if (leading != null && leading.length != 0) {
        return leading + " " + this.getName ();
    } else {
        return this.getName ();
    }
};

When calling the method, the class stacks of the proxy method will be checked. And then the methods in the right inheritance level will be picked up into an array. This will be called as "Round One" methods.

The parameters type string from the "Round One" methods will be separated into array, and compared with the given parameters' type. When parameters types fitted, these methods will be in "Round Two" method array.

And a most fitted method will be chosen by comparing the parameters' inheritance levels. This will be final methods to be executed.

When making a super calling, the super class of the current class will be checked in the "Round One" procedure, so it's sure a super calling for the current given class.

This is a rough implementation of Java Language Specification's Method Invocation by JavaScript, for more details, you should try to read the whole Java Language Specification.

Discussion on OOP in JavaScript

What you may say is "Hey, the syntax is too complicated! Writing those syntaxes will frustrate me ..."

Actually, JavaScript is not an Object Oriented Programming language. But we create context so we can do OOP. And once the syntax is too complicated, we can simplify them. But I consider borrowing the existed mature Java language would be a better choice. That is, we can write in the style of Java, which is sure an OOP style language, and then we converts the Java codes into JavaScript codes, which is somewhat syntactic complicated. And then we have a great thing.

Writing OO JavaScript by Writing Java

If you are familiar with Java, you may find the above JavaScript codes are doing the same thing as the following Java codes:

package net.sf.j2s.hello;
interface ISex {
    public static final int UNKNOWN = 0;
    public static final int MALE = 1;
    public static final int FEMALE = 2;
    public int getSex();
    public void setSex(int sex);
}
class ExObject implements ISex {
    private int sex = UNKNOWN;
    public int getSex() {
        return sex;
    }
    public void setSex(int sex) {
        this.sex = sex;
    }
    public String getAdjective() {
        if (sex == FEMALE) {
            return "her";
        } else if (sex == MALE) {
            return "his";
        } else {
            return "its";
        }
    }
    public String getPronoun() {
        if (sex == FEMALE) {
            return "she";
        } else if (sex == MALE) {
            return "he";
        } else {
            return "it";
        }
    }
    public static String capitalize(String str) {
        if (str != null && str.length() != 0) {
            str = str.substring (0, 1).toUpperCase () + str.substring (1);
        }
        return str;
    }
}
class User extends ExObject {
    private String name;
    private String email;
    public User() {
        super();
        this.name = "missing";
        this.email = "unknown";
    }
    public User(String name) {
        super();
        this.name = name;
        this.email = "unknown";
    }
    public User(String name, String email, int sex) {
        super();
        this.name = name;
        this.email = email;
        this.setSex(sex);
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String toString() {
        return "This is " + this.name + ". " + 
                ExObject.capitalize (this.getAdjective ()) + 
                " email is " + this.email + ".";
    }
}
interface IEndorsed {
    public User getEndorsedBy();
    public void setEndorsedBy(User endorsedBy);
}
interface INickNamed {
    public String getNickName();
    public String getNickName(String leading);
}
class ExtendedUser extends User implements IEndorsed, INickNamed {
    private User endorsedBy;
    public ExtendedUser(String name) {
        super(name);
    }
    public ExtendedUser(String name, String email, int sex, User byUser) {
        super(name, email, sex);
        endorsedBy = byUser;
    }
    public User getEndorsedBy() {
        return endorsedBy;
    }
    public void setEndorsedBy(User endorsedBy) {
        this.endorsedBy = endorsedBy;
    }
    public String getNickName() {
        return this.getNickName(null);
    }
    public String getNickName(String leading) {
        if (leading != null && leading.length() != 0) {
            return leading + " " + this.getName ();
        } else {
            return this.getName();
        }
    }
    public String toString() {
        String desc = super.toString();
        if (this.endorsedBy != null) {
            return desc + " " + ExObject.capitalize (this.getPronoun ()) + 
                " is endorsed by " + this.endorsedBy.getName () + ".";
        } else {
            return desc;
        }
    }
}
public class J2SOOPTest {
    public static void main(String[] args) {
        User u1 = new User();
        System.out.println(u1); // u1.toString ();
        User u2 = new User("Kate");
        System.out.println(u2); // u2.toString ();
        u2.setSex(ISex.FEMALE);
        u2.setEmail("kate@gmail.com");
        System.out.println(u2); // u2.toString ();
        User u3 = new User("John", "john@gmail.com", ISex.MALE);
        System.out.println(u3); // u3.toString ();
        
        ExtendedUser eu1 = new ExtendedUser("Kate");
        ExtendedUser eu2 = new ExtendedUser("John", "john@gmail.com", ISex.MALE, eu1);
        System.out.println(eu1); // eu1.toString ();
        System.out.println(eu2); // eu2.toString ();
        
        System.out.println("Nicknames:");
        System.out.println(eu1.getNickName());
        System.out.println(eu2.getNickName("Little"));

        System.out.println(eu1.getClass().getName());
        System.out.println(eu1 instanceof ISex);
        System.out.println(eu1 instanceof IEndorsed);
        System.out.println(eu1 instanceof INickNamed);
        System.out.println(eu1 instanceof ExObject);
        System.out.println(eu1 instanceof User);
        System.out.println(eu1 instanceof ExtendedUser);

        System.out.println(u1.getClass().getName());
        System.out.println(u1 instanceof ISex);
        System.out.println(u1 instanceof IEndorsed);
        System.out.println(u1 instanceof INickNamed);
        System.out.println(u1 instanceof ExObject);
        System.out.println(u1 instanceof User);
        System.out.println(u1 instanceof ExtendedUser);
    }
}

The Java language is a decent language, and it's quite easy to write these Java codes both by hand or by IDE. If writing or managing Java codes by IDE, you may save a lot of your times.

Now, If there are tools help converting the Java codes into OOP JavaScript, it will free developers from JavaScript syntax and from those unfamiliar J2S Clazz APIs. And now there is an already existed one. It's JavaScript Pacemaker, an Eclipse JDT plugin. Java2Script Pacemaker compiles Java sources into JavaScript automatically. Java2Script also provides a way to run those converted JavaScript codes in browser. Here is the example of the above codes: OOP Test

About Java2Script

Java2Script Pacemaker is an Eclipse JDT plugin that provides a way of converting Java codes into JavaScript. And environment of running generated JavaScript codes are also provided.
You can design or debug your Java codes and then generate JavaScript codes from the Java codes and test it inside Eclipse.

Features of Java2Script

Besides some common and easily implemented features of JavaScript's prototypal inheritance, Java2Script also

By standing on the shoulder of the Eclipse JDT giant, Java2Script can translate almost 90% cases of the Java sources correctly.

More about Java2Script

The above OOP test is just a simple example to test things about converting Java codes into JavaScript. There are others examples that show the feasibility of converting Java codes into JavaScript. It tested things on Sun's java.util.*, which are packed as useful utilities for developing applications. You may tested the generated java.util.* in the tutorial of J2S: How to Use java.util.* -- Resuing Java Codes. And besides, J2S also provide a JavaScript version Eclipse SWT by converting Java codes. Here is the tutorial of J2S: How to Use org.eclipse.swt.* -- Tour to UI. And AJAX from Java perspective can also be converted into JavaScript. Please read tutorial J2S: How to Use ajax.* -- A Simple RSS Reader.

Here is the architecture of Java2Script Pacemaker:
Java2Script Pacemaker Architecture

It's proved that converting Java codes into JavaScript is a feasible way of developing JavaScript applications. And developing in such a way may benefit a lot. The most import benefit would be the "Reusing" existed codes and tools.

Reusing is a Thing

One of the most important factors in the software development is reusing. Using Java2Script, you are sure that you can reuse lots of existed codes. And besides the level of reusing codes, you are also able to reuse tools, libraries, design patterns, frameworks or platforms.

And reusing by Java2Script is another example of Java's "Writing once, run anywhere" slogan.

When you are developing application under MVC framework in Java, you can reuse the Model codes in JavaScript now. For example, you have developed some validation tests on models in the server side. Now you can convert these validation codes into JavaScript and test the models before submitting data to server (so invalid data will be notified to the user), and the server will still do the validation test once more for the reason of security.

Discussion on Java2Script

What will benefit from writing JavaScript from Java perspective?

Besides Java2Script saving your time from typing those complex syntax, writing JavaScript from Java perspective will benefit you from lots of tools for all development periods of programming, debugging, and testing, documenting, maintaining. You can reuse codes from existed old codes or your current codes may be reused in the server side some days later.

Can Java codes 100% completely converted into JavaScript?

Under most of circumstance, the generated JavaScript will work. But there some must-known defects:

In normal cases, 90%+ may be correctly generated. You can test the generated JavaScript codes to see whether things work. And in some cases, you may need to modify the Java sources a little, without affecting the original functions, so that the generated JavaScript will work as the same of Java codes.

Reference

About

Zhou Renjian, independent developer, is dedicating these days in the open source project Java2Script Pacemaker at http://j2s.sourceforge.net, which is Eclipse Public License based.