Inheritance
Inheritance is tricky in JavaScript because there's no clearly defined method to accomplish it. This is why many JavaScript libraries such as Ext, jQuery, and YUI have their own inheritance structure. Here I'll show you a basic technique to implement inheritance in JavaScript in much the same way that these libraries do. Even though there's not really such a thing as a "class" in JavaScript (everything, including functions, is an object), I use the term "class" here to refer to objects that are constructor functions and have prototype properties since they're somewhat analogous to classes in VB.NET.
Using the previous Automobile/Truck example, here's how the inheritance structure might look in VB.NET:
Public Class Automobile
Public Overridable Sub Drive()
MsgBox("Driving the automobile...")
End Sub
Public Overridable Sub Park()
MsgBox("Parking the automobile.")
End Sub
End Class
Public Class Truck
Inherits Automobile
Public HasYosemiteSamMudflaps As Boolean = True
Public Overrides Sub Drive()
MsgBox("Driving the truck...")
End Sub
End Class
Here is an Automobile class with Drive() and Park() methods along with a Truck class that inherits Automobile, overrides the Drive() method and adds a HasYosemiteSamMudflaps property. If I were to instantiate the Truck class and call its Drive() method, I'd see a message box that reads "Driving the truck…" instead of the base class's implementation of "Driving the automobile…"
To override one of Automobile's methods but also execute its logic, I can use the MyBase keyword:
Public Class Truck
Inherits Automobile
Public HasYosemiteSamMudflaps As Boolean = True
Public Overrides Sub Park()
MyBase.Park()
MsgBox("But I'm really a truck.")
End Sub
End Class
To do something similar in JavaScript, I'll either use an inheritance mechanism offered by one of the previously mentioned libraries or implement my own. I'll implement my own for this example so you can see how it works.
The first thing I'll do is create a function that will extend a given base class by creating a new class and copying the base class prototype's properties to the new class. The function also supports an "overrides" object to override the base class prototype's properties and methods and to define new properties and methods.
// Create the function that will create a subclass of the specified base class and overrides.
function extend(base, overrides) {
var p,
subclass;
// Create the subclass's constructor.
subclass = function () {
};
// Copy each of the base class prototype's properties to the subclass's prototype.
for (p in base.prototype) {
if (base.prototype.hasOwnProperty(p)) {
subclass.prototype[p] = base.prototype[p];
}
}
// Apply any overrides if specified.
if (overrides) {
for (p in overrides) {
if (overrides.hasOwnProperty(p)) {
subclass.prototype[p] = overrides[p];
}
}
}
// Return the constructor.
return subclass;
}
If you're not familiar with the "prototype" property of a class, here's a quick explanation: a class's prototype defines a set of properties and methods that can be referenced on any instance of that class and whose scope will be set to the instance of the class by default. When a property or method on a JavaScript object is referenced, the object is first inspected to see if it has that property or method (remember that objects can have their own arbitrary properties and methods regardless of which class it was instantiated from), and if it is not defined on the object itself, the JavaScript runtime will look for the property or method on the class's prototype. By copying the base class prototype's properties to the subclass prototype's properties, the subclass's prototype essentially "inherits" the properties and methods of the base class's prototype, and each object created from the subclass's constructor will inherit the subclass prototype's properties and methods.
Next I'll create the Automobile’s constructor and prototype. Note that a constructor in JavaScript is simply a function, and a prototype can either be assigned an object literal (as in my example), or the prototype's properties can be assigned one-by-one (a function always has a prototype, so you won't get a null reference error using the latter technique):
// Declare the Automobile constructor.
var Automobile = function () {
};
// Add methods to the Automobile's prototype.
Automobile.prototype = {
// Provide a default implementation for drive().
drive: function () {
alert("Driving the automobile...");
},
// Provide a default implementation for park().
park: function () {
alert("Parking the automobile.");
}
};
Now if I create an instance of Automobile, I can call its drive() and park() methods:
var a = new Automobile();
// This will show a message box saying "Driving the automobile..."
a.drive();
// This will show a message box saying "Parking the automobile."
a.park();
Next I'll create a new class called Truck that inherits Automobile. To do this, I create a Truck variable and assign it to the result of the extend() function I defined earlier:
// Create the Truck constructor, override the drive() function,
// and add a new property.
var Truck = extend(Automobile, {
// Override the drive() method.
drive: function () {
alert("Driving the truck...");
},
// Add a new property.
hasYosemiteSamMudflaps: true
});
Notice how I defined a drive() function and a hasYosemiteSamMudflaps property on the "overrides" object. These properties are assigned to the Truck's prototype object, essentially overriding the Automobile's drive() function and adding a new property to Truck. Now I'll create an instance of Truck and call its drive() and park() methods as well as inspect its hasYosemiteSamMudflaps property:
var t = new Truck();
// This will show a message box saying "Driving the truck.." because
// the drive() method was overridden in the Truck class.
t.drive();
// This will show a message box saying "Parking the automobile." because
// the park() method was not overridden.
t.park();
// This will show a message box saying "true" because the hasYosemiteSamMudflaps
// property was added to the Truck object's prototype.
alert(t.hasYosemiteSamMudflaps);
To override the Automobile's park() function, inject my own logic, then call the base class's park() function as is common in many inheritance implementations, I would do this:
// Create the Truck constructor, override the drive() function,
// and add a new property.
var Truck = extend(Automobile, {
// Override the drive() method.
park: function () {
Automobile.prototype.park.apply(this, arguments);
alert("But I'm really a truck.");
}
});
var t = new Truck();
// This will show a message box saying "Parking the automobile." from the
// base class implementation, then another message box saying "But I'm really
// a truck." from the subclass's implementation.
t.park();
Notice how I called the Automobile prototype's park() method from within the Truck's park() method. Instead of calling the function directly, I called the function's apply() method (since functions are objects in JavaScript, functions can also have methods). The first parameter, the keyword "this," is the instance of Truck, and the second parameter, "arguments," is a special object that represents all the arguments that were passed to the function it belongs to. By passing "this" as the first parameter to apply(), I adjust the scope of the drive() function to the instance of Truck. If the Automobile prototype's drive() method referenced "this" inside itself, "this" would refer to the instance of Truck. This has the same effect as referencing the "Me" keyword in a VB.NET method, where it refers to the instance of the subclass when referenced from the base class. Some inheritance libraries add a "superclass" property to a subclass so its prototype's methods can be called in a similar way to Java or .NET languages, but I left that out here to show you what actually happens behind the scenes of those types of implementations.