As mentioned earlier, JavaScript doesn't (currently) provided built-in support for classes. Since classes are a familiar and useful design mechanism, it would be convenient to add them into the language. JavaScript's higher-order programming features can be used to implement class-based inheritance in terms of prototype-base inheritance.
Here's an example of how we might use classes in Java. Let's examine how to translate this into a similarly-structured JavaScript program.
class Base {
private String name;
public Base(String name) {
this.name = name;
}
public void greet() {
System.out.println("Hello, my name is " + name);
}
}
class Derived extends Base {
public Derived(int num) {
super("Derived object #" + Integer.toString(num));
}
public static void main(String[] args) {
Derived dobj = new Derived(17);
dobj.greet();
}
}
The above code uses few Java features not present in JavaScript:
We'll ignore static typing, and solve the other two problems. First, let's consider inheritance. We can write the above code in JavaScript as follows:
var Base = _class_(Object, {
_new_: function (name) {
this.name = name;
},
greet: function () {
alert('Hello, my name is ' + this.name);
}
});
var Derived = _class_(Base, {
_new_: function (num) {
Base.call(this, 'Derived object #' + num);
}
});
var dobj = new Derived(17);
dobj.greet() // displays "Hello, my name is Derived object #17"
There are several differences between this code and the Java code:
How do we implement the _class_ function? Let's first consider the types of its inputs and its result:
Recall that JavaScript object constructors are simply functions, called using the new keyword to initialize this to a new object, with a prototype object as specified by the constructor. As such, the base class constructor and new class constructor are both functions.
Hence _class_ needs to produce a new constructor which:
Here's a way to implement these ideas (download: class.js).
_class_ = function (base, methods) {
// Create an object with the base class's prototype,
// without calling the base class constructor.
function f() { return this; }
f.prototype = base.prototype;
var proto = new f();
// Add the new class's methods to the prototype.
for (var m in methods) {
proto[m] = methods[m];
}
// Set the prototype of the constructor.
methods._new_.prototype = proto;
return methods._new_;
};
As in the example above, the _new_ method can use the base class constructor's call() method to invoke the base class constructor.
The above example of the Base class in JavaScript differs from the Java implementation in that the name field is public rather than private. This is inconvenient, because it means we cannot add a new private field to a base class without potentially interfering with a derived class.
The simplest way to solve the naming conflict problem with public fields is to introduce a naming convention for fields that are considered private or protected. For example, we can store the private fields associated with the class Base in another object stored in the $Base field:
var Base = _class_(Object, {
_new_: function (name) {
this.$Base = {
name: name;
}
},
greet: function () {
var private = this.$Base;
alert('Hello, my name is ' + private.name);
}
});
If we agree to use the above convention for each class, then we have essentially accomplished the goal of private fields. Since this.$Base is not actually hidden in other scopes, this convention is not a defense against malicious code. On the positive side, this means it's easy for a debugger to inspect private fields.
The above convention can be slightly extended to include a this.$protected sub-object which contains all fields that are internal to an object, but accessible to subclasses.
While it is useful for a debugger to be able to inspect private fields of an object, it is not as important to be able to access private methods. In fact, it is more useful to only expose public methods, so that other developers can use the debugger to inspect what methods are available from an object.
A simple way to hide a private method of an object is to make it a standard function, and use the makeGreeting.call() method to explicitly invoke the makeGreeting function as a method:
var Base; // Forward declaration (function () { Base = _class_(Object, { _new_: function (name) { this.$Base = { name: name; } }, greet: function () { alert(makeGreeting.call(this)); } }); // This function is private to the enclosing function scope. function makeGreeting() { var private = this.$Base; return 'Hello, my name is ' + private.name; } })();
In the above example, we additionally used an anonymous function to encapsulate the makeGreeting() function, hiding it from other scopes. Later, we will see how this technique can be used for managing package scope.
In the previous section, we used a closure to encapsulate private methods of a class. We can also use closures to encapsulate private fields of an object, as follows:
var Base = _class_(Object, {
_new_: function (name) {
var numTimesGreeted = 0;
this.greet = function () {
alert('Hello, my name is ' + name);
numTimesGreeted++;
}
}
});
The critical difference between this code and the earlier definitions of the Base class is that it no longer uses JavaScript's prototype mechanism. Instead, a new closure is created for each method of an object. The local variables of the constructor (the _new_ method) are used as truly private fields of an object. Note that local variables referenced in methods are mutable.
While this technique is arguably more secure than by-convention private and protected fields, the by-convention approach is arguably more practical for several reasons: