Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Writing Object-Oriented JavaScript Part 1

0.00/5 (No votes)
8 Dec 2003 1  
Using Cfx to develop a JavaScript control class library.

Introduction

ASP.NET and Visual Studio 7.0 are making important contributions to the improvement of the web development experience. Unfortunately, there is also a tendency created among developers to limit their interaction with JavaScript. Clearly JavaScript is valuable for adding client-side functionality to web pages. However ASP.NET programming models suggest that developers produce page layout while emitting client-side JavaScript from ASP.NET controls. As a consequence, this model tends to limit JavaScript to procedural adjuncts. This is rather unfortunate because it severely limits the power of an object-oriented scripting language that developers can use to write rich and reusable client-side components.

There are several reasons why JavaScript object-oriented capabilities are not fully utilized:

  • The tendency of client-side operations to be discrete favoring procedures.
  • ASP.NET programming model for its controls suggests limiting JavaScript to functional adjuncts.
  • Legacy JavaScript lacked key features such as exception handling and inner functions.
  • Text books on JavaScript often de-emphasize its support for object-oriented programming.
  • JavaScript does not formally support class inheritance like conventional OOP languages.

But writing JavaScript procedurally creates another set of undesirable conditions:

  • Task duplication due to the decoupling of data and functions.
  • Tendency to use complex array patterns to simulate abstract data types.
  • Increases the impact risk and side effects of code modifications.
  • Complicates auditing of functions thereby diminishing reusability.
  • The consequential effect is reduced performance.

We can enhance our scripts and eliminate these problems by writing JavaScript in an object-oriented fashion. JavaScript possess all the familiar object-oriented programming tenets, by its ability to define new object types and extend them through its prototype property. Prototypes serve as templates for an entire class of abstract data types. Therefore prototype properties are shared by all instantiated objects of the class. While this is a powerful and expressive type of inheritance model, what's missing is the customary class inheritance support that is familiar to programmers of conventional object-oriented languages.

Writing object-oriented JavaScript will be presented in a series of three articles. The first installment provides background on how JavaScript supports the main principles of object-oriented programming. Part two demonstrates how JavaScript constructs can be used to build a class inheritance framework and write scripts supporting a JavaScript class hierarchy. The third and final installment shows how to use the JavaScript class framework to build object-oriented client-side abstractions of ASP.NET user controls.

It is the aim of this series to introduce an alternate programming strategy and style that fully utilities JavaScript capabilities, and then elevates JavaScript from its typical junior partner role (reinforced by the ASP.NET programming model), into full domain partnership with ASP.NET. The result will enable developers to write rich client-side scripts.

Principles of Object-Oriented Programming in JavaScript

Abstract Data Types

Abstract data types are descriptions and representations of a real-world construct. Many languages support aggregation of disparate intrinsic data types to represent some real world construct. For example in "C" the struct keyword denotes such an aggregation. However an abstract data type is more than just an aggregation of disparate data types. An abstract data type also defines behaviors represented by the abstraction. In many object-oriented languages the combination of data and its associated behaviors denotes a class. Languages such as C++, Java, and C# provide the class keyword to identify abstract data types.

While JavaScript reserves the class keyword it does not support defining classes in the same manner as conventional OOP languages. In JavaScript functions are used as object descriptors and all functions are actually JavaScript objects. Thus in JavaScript, classes are function definitions. The code below illustrates how the simplest class is defined in JavaScript -- the empty class MyClass:

function MyClass() {}
Figure 1. Empty class definition

Using the JavaScript new operator defines myClassObj as an object instance of the type MyClass:

var myClassObj = new MyClass();
Figure 2. Object instantiation

Notice that the function keyword is overloaded and serves as both the constructor function for objects as well as identifying procedural functions:

var result = MyClass();
Figure 3. MyClass used as a procedural function

The difference between MyClass being interpreted as a constructor or as a procedure is the new operator. The new operator instantiates an object of class MyClass calling the constructor function, while in the second call a procedural call is made to the function MyClass expecting a return result.

MyClass defined in figure 1 is an empty class having no assigned data or behavior. JavaScript can dynamically add properties to the instantiated objects of MyClass:

var myClassObj = new MyClass();
myClassObj.myData = 5;
myClassObj.myString = "Hello World";
alert( myClassObj.myData );     // displays: 5

alert( myClassObj.myString );   // displays: "Hello World"

Figure 4. Dynamically assigning object properties

The problem however is that only the instance of MyClass referenced by myClassObj possesses the additional data properties. Subsequent instances will not have any properties. What is needed is a way to defined properties to all instances of MyClass. Using the this keyword in the constructor function data properties are now defined on all instances of MyClass:

function MyClass()
{
    this.myData = 5; 
    this.myString = "Hello World";
}

var myClassObj1 = new MyClass();
var myClassObj2 = new MyClass();
myClassObj1.myData = 10;
myClassObj1.myString = "Obj1:  Hello World";
myClassObj2.myData = 20;
myClassObj2.myString = "Obj2:  Hello World"; 
alert( myClassObj1.myData );    // displays: 10

alert( myClassObj1.myString );  // displays: "Obj1:  Hello World"

alert( myClassObj2.myData );    // displays: 20

alert( myClassObj2.myString );  // displays: "Obj2:  Hello World"

Figure 5. Defining data properties to all instances of the class

MyClass is still incomplete because there are no behaviors assigned to it. To add methods to MyClass, properties that reference functions are added to MyClass:

function MyClass()
{
    this.myData = 5;
    this.myString = "Hello World";
    this.ShowData = DisplayData;
    this.ShowString = DisplayString;
}

function DisplayData()
{
    alert( this.myData );
}

function DisplayString()
{
    alert( this.myString ); 
}

var myClassObj1 = new MyClass();
var myClassObj2 = new MyClass();
myClassObj1.myData = 10;
myClassObj1.myString = "Obj1:  Hello World";
myClassObj2.myData = 20;
myClassObj2.myString = "Obj2:  Hello World";
myClassObj1.ShowData();     // displays: 10

myClassObj1.ShowString();   // displays: "Obj1:  Hello World"

myClassObj2.ShowData();     // displays: 20

myClassObj2.ShowString();   // displays: "Obj2:  Hello World"

Figure 6. Defining data and methods to all instances of the class

The figure above defines a complete abstract data types or class in JavaScript. MyClass now defines a concrete type possessing data and associated behaviors.

Encapsulation

Using MyClass as defined above permits accessibility of its internal data representation as well as having its methods and variable names global in scope increasing the risk of name collisions. Encapsulation supports data hiding and the concept of viewing objects as self-contained entities providing services to consumers.

function MyClass()
{
    var m_data = 5;
    var m_text = "Hello World";
    this.SetData = SetData;
    this.SetText = SetText;
    this.ShowData = DisplayData;
    this.ShowText = DisplayText; 

    function DisplayData()
    {
        alert( m_data );
    }

    function DisplayText()
    {
        alert( m_text );
        return;
    }

    function SetData( myVal ) 
    {
        m_data = myVal;
    }

    function SetText( myText ) 
    {
        m_text = myText;
    }
}

var myClassObj1 = new MyClass();
var myClassObj2 = new MyClass();
myClassObj1.SetData( 10 );
myClassObj1.SetText( "Obj1:  Hello World" );
myClassObj2.SetData( 20 );
myClassObj2.SetText( "Obj2:  Hello World" );
myClassObj1.ShowData();    // displays: 10

myClassObj1.ShowText();    // displays: "Obj1:  Hello World"

myClassObj2.ShowData();    // displays: 20

myClassObj2.ShowText();    // displays: "Obj2:  Hello World"

Figure 7. Dynamically assigning properties to all object instances

JavaScript treats a class definition as a function definition and uses the var keyword to define local variables. Therefore var indicates that m_data is a local or "private" variable of MyClass. In figure 6, var was not used and thus, MyData is global in scope or "public". The var keyword is how encapsulation is specified in JavaScript.

Inheritance

JavaScript supports inheritance through the use of object prototypes. A prototype is a template of properties that is shared by all instances of the object type. Thus instances of object types "inherit" the values of its prototype property. In JavaScript, all object types have a prototype property that can be both extended and inherited.

In the example below, the Shape prototype object defines three properties, GetArea, GetPerimeter, and Draw, that reference the functions, Shape_GetArea, Shape_GetParameter, and Shape_Draw. Every instance of Shape inherits the prototype allowing them to call the Shape functions through the properties.

To achieve inheritance in JavaScript, the prototype of a previously defined object type is copied into the prototype of the derived object type. The Shape prototype below is copied into prototype of the derived Circle and Rectangle object types. Then the Draw property is overridden supplying the proper the function reference for the new class.

Shape.prototype.GetArea = Shape_GetArea; 
Shape.prototype.GetParameter = Shape_GetParameter;
Shape.prototype.Draw = Shape_Draw;

function Shape()
{
}

function Shape_GetArea()
{
    return this.area; 
}

function Shape_GetParameter()
{
    return this.parameter;
}

function Shape_Draw()
{
    alert( "Drawing generic shape" );
}

Circle.prototype = new Shape();
Circle.prototype.constructor = Circle;
Circle.prototype.baseClass = Shape.prototype.constructor;
Circle.prototype.Draw = Circle_Draw; 

function Circle( r )

{
    this.area = Math.PI * r * r;
    this.parameter = 2 * Math.PI * r;
}

function Circle_Draw()
{
    alert( "Drawing circle" );
}

Rectangle.prototype = new Shape();
Rectangle.prototype.constructor = Rectangle;

function Rectangle( x, y )

{
    this.area = x * y;
    this.parameter = 2 * x + 2 * y;
}

Rectangle.prototype = new Shape();
Rectangle.prototype.constructor = Rectangle;
Rectangle.prototype.baseClass = Shape.prototype.constructor;
Rectangle.prototype.Draw = Rectangle_Draw;

function Rectangle( x, y )

{
    this.area = x * y; 
    this.parameter = 2 * x + 2 * y; 
}

function Rectangle_Draw()
{
    alert( "Drawing rectangle" );
}

var circle = new Circle( 10 );
var rectangle = new Rectangle( 10, 20 );

alert( "Circle base class = " + circle.baseClass );
alert( "Circle area = " + circle.GetArea() );
alert( "Circle parameter = " + circle.GetParameter() );
circle.Draw();

alert( "Rectangle base class = " + rectangle.baseClass );
alert( "Rectangle area = " + rectangle.GetArea() );
alert( " Rectangle parameter = " + rectangle.GetParameter() );
rectangle.Draw();
Figure 8. JavaScript Inheritance

Circle and Rectangle prototypes are assigned to the prototype of the Shape class through an object instance thereby "inheriting" the methods assigned to the prototype array of the Shape class. An instance of the Shape class is necessary to create a copy of the Shape prototype array. This permits overriding of the Shape methods yet preserving the original prototype array associated to all Shape objects.

Both the Circle and Shape classes extend the Shape class by overriding Draw method supplying its own respective implementation while inheriting the Shape implementation of the GetArea and GetParameter methods:

var circle = new Circle( 10 );
var rectandle = new Rectangle( 10, 20 );
alert( "Circle base class = " + circle.baseClass );
    // Alert:  Circle base class = <STRING constructor Shape of output>

alert( "Circle area = " + circle.GetArea() );
    // Alert: Circle area = 314.1592653589793

alert( "Circle parameter = " + circle.GetParameter() );
    // Alert: Circle parameter = 62.83185307179586

circle.Draw();
    // Alert: Drawing circle


alert( "Rectangle base class = " + rectangle.baseClass );
    // Alert:  Rectangle base class = <STRING constructor Shape of output>

alert( "Rectangle area = " + rectangle.GetArea() );
    // Alert: Rectangle area = 200

alert( "Rectangle parameter = " + rectangle.GetParameter() );
    // Alert: Rectangle parameter = 60

rectangle.Draw();
    // Alert: Drawing rectangle

Figure 9. Results of JavaScript Inheritance

Polymorphism

Polymorphism defines disparate behaviors and actions by objects to the same function invocation. The Draw property of Shape, Circle, and Rectangle object types is polymorphic. The Draw property invokes a different function depending upon its object type.

var shape = new Shape();
var circle = new Circle( 10 );
var rectandle = new Rectangle( 10, 20 );
shape.Draw();
    // Alert: Drawing generic shape

circle.Draw();
    // Alert: Drawing circle

rectangle.Draw();
    // Alert: Drawing rectangle

Figure 10. JavaScript polymorphism

Demo

The demo program, JsOOPDemo, is a standard ASP.NET application running only JavaScript with a blank web page as its UI. The URL for this site is http://localhost/JsOOP/JsOOPDemo/WebForm1.aspx. This means you should copy the source code into the JsOOP/JsOOPDemo subdirectory of your http://localhost home directory. You can use Visual Studio to create the ASP.NET project or use the IIS management tool found in Administrative Tools of the Control Panel. This demo has been tested on both version 1.0 and 1.1 of the runtime.

The demo contains examples of the JavaScript found in the article and running the demo will help you explore writing object-oriented code in JavaScript.

Conclusion

JavaScript prototypes and function objects provide object-oriented features customary to object-oriented programming language. What is confusing is that JavaScript does not intrinsically support class inheritance and clumsily supports it through prototype inheritance. Part 2 of this series introduces a framework that simplifies JavaScript class inheritance and demonstrates writing a class hierarchy of JavaScript object types.

History

  • Version 1.0

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here