Object-oriented JavaScript
June 7, 2006
JavaScript is not generally considered a robust programming language, especially when compared to languages such as Java or C#: it is interpreted, rather than compiled; it is dynamically, rather than statically, typed; and it is commonly considered a procedural, rather than an object-oriented, language.
However, the demands on JavaScript as a development platform are growing with the increasing popularity of so-called AJAX applications. The procedural development model commonly used to add basic client-side interactivity to web pages today will not scale to support the level of UI complexity required by these applications. Fortunately, and contrary to popular belief, it is possible to apply object-oriented (OO) design principles in JavaScript, which can help manage this complexity. The next several sections explain how.
Encapsulation
In OO programming, a class is used to define a type of object that will be used by an application. The type encapsulates the data used by the object and may optionally expose methods to allow callers (other objects) to interact with the data.
Because JavaScript is not a statically typed language, it does not provide a keyword for defining a class or object-type definition. Additionally, because JavaScript is not compiled, there would be no way to enforce the proper use of such types. However, it is still possible to define custom objects in JavaScript that behave, in many ways, like classes in C# or Java.
For example, in a C# program, we might define a class to represent a family pet as follows:
// C# Pet class public class Pet { private string name; public Pet(string name) { this.name = name; } public string GetName() { return name; } }
Our program might create an instance of the Pet
class and invoke the
GetName()
method as follows:
Pet p = new Pet("Max"); System.Console.WriteLine(p.GetName());
In JavaScript, we would define the Pet
class as follows:
// JavaScript Pet class function Pet(name) { this._name = name; } Pet.prototype._name; Pet.prototype.getName = function() { return this._name; }
Our JavaScript program (most likely a web page) could create an instance of
Pet
and invoke the getName()
method as follows:
var p = new Pet("Max"); alert(p.getName());
The result of running the above JavaScript code should be an alert that looks like this:
Figure 1. JavaScript alert
The following list compares the JavaScript version to the C# version:
-
In C#, a constructor is defined using this syntax:
public class Pet() { // ...
In JavaScript, class constructors are defined as functions:
function Pet(name) { ... }
However, as in C#, class instances are created using the
new
keyword:var p = new Pet("Max");
-
Methods and properties in JavaScript are attached to a class via the
prototype
keyword. For example, the class defines a prototype property called_name
that will contain the name of thePet
, and a prototype method namedgetName()
that returns the value of_name
.A complete description of prototype-based object modeling is beyond the scope of this article; suffice it to say that this is the recommended syntax for defining the properties and methods that your JavaScript class will expose.
-
Unlike C#, JavaScript properties and methods are untyped: the
_name
property is not declared as a string, and thegetName()
function is not declared to return astring
. There is no compile-time check for proper type usage. The burden of ensuring proper type usage is placed entirely on the developer. -
A JavaScript class must always refer to its own properties and methods using the
this
keyword; unlike Java or C#, JavaScript objects do not provide an implicitthis
scope. -
JavaScript does not support any concept of method or property visibility: every property and method is always public. The developer is responsible for ensuring proper usage of a JavaScript class's members. As a result, it is a common convention to tag member variables that should be considered private with a leading underscore, as in the
_name
property in the example. -
C# method names typically use the upper camel case naming convention, in which the first letter of each word is capitalized, including the first word; JavaScript (and Java) methods are commonly named using lower camel case, in which the first letter of every word except for the first is capitalized.
Inheritance
Inheritance in object-oriented programming allows developers to define an "is a"
relationship between classes. For example, we might want to extend our object model
to
define slightly more specialized versions of the Pet
class: Dog
and Cat
. The base class, Pet
, will contain any properties or
methods shared by all Pet
s, but Dog
and Cat
may
define additional properties or methods applicable only to instances of those classes.
For
example, our Dog
class will provide a wag tail
method, and the
Cat
class will provide a purr
method.
In C#:
// C# Dog class public class Dog : Pet { public Dog(string name) : base(name) { } public void WagTail() { // Wagging } } // C# Cat class public class Cat : Pet { public Cat(string name) : base(name) { } public void Purr() { // Purring } }
In JavaScript:
// JavaScript Dog class function Dog(name) { Pet.call(this, name); } Dog.prototype = new Pet(); Dog.prototype.wagTail = function() { // Wagging } // JavaScript Cat class function Cat(name) { Pet.call(this, name); } Cat.prototype = new Pet(); Cat.prototype.purr = function() { // Purring }
Comparison:
-
In C#, the syntax for declaring a class hierarchy is as follows:
-
public class Dog : Pet
This declares the
Dog
class as a subclass ofPet
. In JavaScript, the syntax is:Dog.prototype = new Pet();
This defines the
Pet
class as the prototype object for allDog
instances. Note that a class's constructor function must always be defined before assigning the class's parent prototype, as shown above. -
In C#, the
Dog
andCat
classes pass the name constructor argument to the base class using the following syntax:public Dog(string name) : base(name) { // ...
In JavaScript, the syntax is:
Pet.call(this, name);
call()
is a built-in JavaScript function that is used to invoke a specific target function in the context of a specific object. In this case, we are invoking thePet
constructor function in the context of theCat
orDog
instance. In other words, whenPet()
is called, the implicit JavaScript variable will refer to the instance ofCat
orDog
that is being constructed.
We can use our new classes and methods as follows. (The only major difference is the
declaration of the object types: in C#, Dog
and Cat
, and in
JavaScript, var
):
C#:
Dog d = new Dog("Max"); d.WagTail(); Cat c = new Cat("Fluffy"); c.Purr();
JavaScript:
var d = new Dog("Max"); d.wagTail(); var c = new Cat("Fluffy"); c.purr();
This program wouldn't generate any output. However, if we call the
GetName()
/getName()
function from our earlier example, applying
it to our Dog instance...
C#:
System.Console.WriteLine(d.GetName());
JavaScript:
alert(d.getName());
...the output is the same as when the method was called on the original Pet
instance (see Figure 2):
Figure 2. The output is the same alert.
Polymorphism
Polymorphism refers to the ability of a caller to invoke a particular method or
set of methods on an object without regard to the object's type. For example, we might
want
to add a speak()
method to our Pet
class, to allow our
Pet
s to answer the phone when we are not at home; the caller on the other end
of the line will not necessarily know or care which Pet
is answering the phone,
as long as it is able to speak()
:
In C#:
// C# Pet class public class Pet { // ... public virtual void Speak() { System.Console.WriteLine(GetName() + " says..."); } } // C# Dog class public class Dog : Pet { // ... public override void Speak() { base.Speak(); System.Console.WriteLine("woof"); } } // C# Cat class public class Cat : Pet { // ... public override void Speak() { base.Speak(); System.Console.WriteLine("meow"); } }
In JavaScript:
// JavaScript Pet class // ... Pet.prototype.speak = function() { alert(this.getName() + " says..."); } // JavaScript Dog class // ... Dog.prototype.speak = function() { Pet.prototype.speak.call(this); alert("woof"); } // JavaScript Cat class // ... Cat.prototype.speak = function() { Pet.prototype.speak.call(this); alert("meow"); }
Note that the same call()
function used to invoke the base class constructor
functions is also used to invoke method on the base class:
Pet.prototype.speak.call(this);
In this case, however, the target function name is the fully qualified name of the
base
class method, Pet.prototype.speak
.
Invoking the methods is the same as before:
C#:
p = new Dog("Max"); p.Speak(); p = new Cat("Fluffy"); p.Speak();
JavaScript:
p = new Dog("Max"); p.speak(); p = new Cat("Fluffy"); p.speak();
The output of this program should be the following (see Figures 3-6):
Figure 3. Output is "Max says..."
Figure 4. Output is "woof"
Figure 5. Output is "Fluffy says..."
Figure 6. Output is "meow"
Although it may not offer features as powerful as C# or Java, JavaScript is more capable than many web developers may know, and it can be used to provide the structure of object-oriented development to the growing number of AJAX applications currently being deployed on the web.