Part 1: Javascript Classes
While the Javascript language offers many of the
constructs required for object-oriented programming, they remain largely
unused. Today we�ll take a look at how to start with object-oriented
programing in Javascript. by defining a class in Javascript. We�ll use
the simple HTML file to call our script file,
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Hello Javascript!</title>
<script type="text/javascript" src="MyScript.js"></script>
</head>
<body>
Hello Javascript!
</body>
</html>
Now let�s create a class definition (in MyScript.js). Note that there is no �class� keyword in Javascript, the class definition is just a function definition,
function MyClass() {
// Public field
this.aPublicField = "This is a public field of the type MyClass";
// Private field
var aPrivateField = "This is a private field of the type MyClass";
// Public method
this.aPublicMethod = function() {
// Use the private method
if (aPrivateMethod()) { return this.aPublicField; }
else { return aPrivateField; }
}
// Private method
function aPrivateMethod() {
return true;
}
// Oops! We can expose the private field
this.exposePrivateField = aPrivateField;
// and the private method
this.exposePrivateMethod = aPrivateMethod;
}
// Create an instance of MyClass using the �new� keyword
var myclass = new MyClass();
// Get the public field
alert(myclass.aPublicField);
// Call the public method
alert(myclass.aPublicMethod());
// Call the private field � can�t get to them directly
alert(myclass.exposePrivateField);
alert(myclass.exposePrivateMethod());
So it�s pretty simple to create a class in Javascript. We can also create a runtime field for the class,
// Create an instance of MyClass
var myclass = new MyClass();
// Create a field at runtime
myclass.aRuntimeField = "This is a runtime field of the type MyClass";
// View the field value
alert(myclass.aRuntimeField);
Part 2: Javascript Inheritance
Now that we created a base Javascript class in the first post, let�s inherit from it to get a derived class.
// Define the derived function (remember there is no 'class' keyword in Javascript)
function MyDerivedClass() { }
// Derive from MyClass, equivalent to �> public class MyDerivedClass : MyClass
MyDerivedClass.prototype = new MyClass();
// Create an instance of the derived class
var myderivedClass = new MyDerivedClass();
// Get the public field
alert(myderivedClass.aPublicField);
// Call the public method
alert(myderivedClass.aPublicMethod());
Woah! That was simple! Notice that MyClass�s private methods and the runtime field (aRuntimeField) we gave to MyClass in the last post is not available to MyDerivedClass.
Let�s override the base classe�s public fields and methods and see what happens,
// Define the derived function (remember there is no 'class' keyword in Javascript
function MyDerivedClass() {
// Override the base's public field
this.aPublicField = "This is a public field of the type MyDerivedClass";
// Override the base's public method
this.aPublicMethod = function() { return this.aPublicField; }
}
// Derive from MyClass, equivalent to �> public class MyDerivedClass : MyClass
MyDerivedClass.prototype = new MyClass();
// Create an instance of the derived class
var myderivedClass = new MyDerivedClass();
// Get the public field
alert(myderivedClass.aPublicField);
// Call the public method
alert(myderivedClass.aPublicMethod());
You�ll see that the base classes public fields and methods have been overridden in the derived class.
We can obviously derive again,
// Derive from MyDerivedClass
function MyDerivedDerivedClass() {
// Override the base's public field
this.aPublicField = "This is a public field of the type MyDerivedDerivedClass";
// Override the base's public method
this.aPublicMethod = function() { return this.aPublicField; }
}
// Derive from MyDerivedClass, equivalent to �> public class MyDerivedDerivedClass : MyDerivedClass
MyDerivedDerivedClass.prototype = new MyDerivedClass();
// Create an instance of the derived class
var myderivedderivedClass = new MyDerivedDerivedClass();
// Call the public method
alert(myderivedderivedClass.aPublicField);
Part 3: Javascript and JSON
JSON stands for Javascript Object Notation. In the first post we saw how to define a class in Javascript so that an object instance can be created. Now let�s create an object using the object literal notation. In Javascript, there is the concept of a literal notation to define an object,
// A string literal
var stringLiteral = "A string literal";
// An array literal
var arrayLiteral = [1,2,3];
Extending this concept to objects, we create an anonymous type in Javascript using an object literal,
// Create an anonymous type by using the object literal notation,
var myObjectUsingJSON = {
// Public field � a name/value pair
aPublicField : "This is a public field of an anonymous type",
// Public method � another name/value pair
aPublicMethod : function() { return this.aPublicField; }
}
// Call the anonymous type�s public field or method
alert(myObjectUsingJSON.aPublicMethod());
We created an instance (myObjectUsingJSON) of the the anonymous type using the object literal notation and the anonymous object has two members which are name-value pairs. This way of representing (and creating) an object is called the Javascript Object Notation or JSON.
It�s just a way of creating an anonymous type using name-value pairs.
For comparison, recall how we similarly create anonymous types in C#,
// Anonymous type in C#
var anonType = new { Name = "John", Age = 50 };
Now let�s create a more complicated class using this object notation,
// Create an anonymous type...
var myObjectUsingJSON = {
// ...with a key whose value is a simple array...
anArray : ["This", "array", "has", 5, "items"],
// ...and another key whose value is an array with a nested object...
anotherArray : ["This", "array", "has", 6, { X: 1 }, "items"],
// ...and another key whose value happens to be another anonymous object...
anObject : {
key1 : "A string", /* ...with one key whose value is a string... */
key2 : function() { return "Hello"; }, /* ...and another key whose value is a function... */
key3 : { /* ...and another key whose value happens to be another anonymous object */
key31 : true, /* ...with a key whose value is a boolean... */
key32 : function() { return { Name: "World!" }; }, /* ...and a key whose value is a function that returns an object */
key33 : { /* This key's value happens to get yet another anonymous object */
key331 : function() { return "That�s deep!"; }
}
}
}
}
// Access an array element
alert(myObjectUsingJSON.anArray[2]);
// Access the inner object
alert(myObjectUsingJSON.anotherArray[4].X);
// Get a value from a name
alert(myObjectUsingJSON.anObject.key3.key31);
// Access the object returned by the function
var funcReturn = myObjectUsingJSON.anObject.key3.key32();
alert(funcReturn.Name);
// Call the deepest function
alert(myObjectUsingJSON.anObject.key3.key33.key331());
As you can see it�s pretty easy to nest anonymous objects to create
a class structure. This representation of a class is more compact than
XML, in terms of raw serialized bytes sent over the wire. So,
Javascript prefers using this object notation when sending/receiving
data. A string can easily be eval-ed into an object,
// The JSON string
var jsonString = "{ anotherArray: ['This', 'array', 'has', 6, { X: 1 }, 'items'] }";
// Eval the JSON string into an object
var myEvaledJSONObject = eval('(' + jsonString + ')');
// Access the object
alert(myEvaledJSONObject.anotherArray[4].X);
For more information about JSON see here.
Part 4: The Prototype Property
All Javascript objects have a property called �prototype�
which can contain an object reference. Javascript uses this property to
implement it�s inheritance hierarchy � by putting a reference to the
parent object via the �prototype� property. To see this try the
following code,
// The base class
function Base() {
// Public property
this.aPublicProperty = "This is a public property of the type Base";
}
This initializes the prototype property (internally) to an empty class.
// 'prototype' is not null, it is initialized internally like this - Base.prototype = { }
alert(Base.prototype); // [object Object]
Now we can add fields/properties to the (empty) object that the prototype refers,
// Add a new field/property
Base.prototype.newField = "This is a new field of the type Base's prototype";
Base.prototype.aPublicProperty = "This is a public property of the type Base's prototype";
Now here�s the trick that transforms this innocuous looking property
into the basis of Javascript�s object oriented implementation: when you
access a property on a class, if the class doesn�t have the property,
Javascript will look for the property on the object the prototype refers to.
Now we can see how this might lead to inheritance � the derived class
(the object) can access the base class�s (the object�s prototype�s
object) methods. Also if both the object and the object�s prototype
referred object have the same property, the object�s property overrides
the prototype referred object�s property.
var baseInstance = new Base();
alert(baseInstance.newField); // Access the prototype's property
alert(baseInstance.constructor.prototype.aPublicProperty); // Access the prototype's property - will see later
alert(Base.prototype.aPublicProperty); // Access the prototype's property via Base
alert(baseInstance.aPublicProperty); // Override the prototype's property
We are starting to see object-oriented behaviors implemented via the
prototype property. The prototype property refer�s to the object�s base
class. Let�s take this further,
// Derived from the base class
function Derived() { }
Derived.prototype = new Base();
// Another class derived from the base class
function AnotherDerived() { }
AnotherDerived.prototype = new Base();
// A class derived from the derived class
function DerivedDerived() { }
DerivedDerived.prototype = new Derived();
This results in a class hierarchy that looks like this,
Now that we have the class structure, let�s create some instances,
// Create some instances
var derivedInstance = new Derived();
var anotherDerivedInstance = new AnotherDerived();
var derivedDerivedInstance = new DerivedDerived();
Let�s add a new property to the Base�s prototype.
// Add a new property to the Base's prototype
Base.prototype.testInheritance = "Every object that derives from Base now has this property";
alert(derivedDerivedInstance.testInheritance);
And add a new property to the Derived�s prototype,
// Add a new property to the Derived's prototype
Derived.prototype.testInheritance = "Every object that derives from Derived now has this property";
// Does that mean the Base now has the property? No.
alert(baseInstance.testInheritance); // undefined
alert(derivedDerivedInstance.testInheritance);
For more information about Javascript prototypes see here.
Part 5: Javascript Closures
Closures are a tricky concept in Javascript. They
seem deceptively simple but there are a lot of intricacies that can
trip up developers. To start understanding closures, we need to start
with scope � in Javascript, a function has access to it�s parent�s
context, grandparent�s context, etc. up the scope chain. To see
this try the code,
var X = "window";
function outermost() {
var Y = "outermost";
function inner() {
// Knows about the parent function's Y
alert(Y);
function innermost() {
// Knows about the grandparent function's Y
alert(Y);
// Knows about the greatgrandparent's X
alert(X);
}
innermost();
}
inner();
}
outermost();
This seems simple enough, even expected. Now here�s the trick, let�s return some function pointers,
var X = "window";
function outermost() {
var Y = "outermost";
function inner() {
// Knows about the parent function's Y
alert(Y);
function innermost() {
// Knows about the grandparent function's Y
alert(Y);
// Knows about the greatgrandparent's X
alert(X);
}
// 'inner' returns a reference to the 'innermost' function
return innermost;
}
// 'outermost' returns a reference to the 'inner' function
return inner;
}
Now let�s call these functions,
// Get the reference to the 'inner' function
var inr = outermost();
// Call the 'inner' function
inr(); // alerts 'outermost'
// Get the reference to the innermost function
var inmost = inr(); // alerts 'outermost'
// Call the 'innermost' function
inmost(); // alerts 'outermost' and 'window'
As you can see, when we call the �innermost� function, using the
reference returned by the call to �inner�, it still has access to the X and Y values as they were in scope when the �innermost� function was defined.
This is Javascript�s closure � capturing information at the point of
definition, so that the information can be used at the point of
execution. Also the variables are not copies � they are references.
A very common problem that occurs is in the unintended creation of closure,
for (var i = 0; i < 5; i++) {
// This creates a closure!
elements[i].onclick = function() { alert(i); };
}
Now clicking on any element will alert �4�, why? Because all of the
functions we created have a reference (not a copy) to the outer var i, probably not the effect you want. To fix this,
for (var i = 0; i < 5; i++) {
// Don't create a function - no closure
elements[i].onclick = Handle(i);
}
function Handle(i) {
return function() { alert(i); };
}
You can read up more about closures here.
|