JavaScript is the only language a web browser speaks; it is a given fact.
It's not a matter of choice if you are going to work with it, whenever you build
a browser app it is there. All script code might be hidden to you but there is
no ASP.NET, let alone Atlas (AJAX) without JavaScript. Recently I've been doing
a project which involved loads and loads of JavaScript. Being a C# addict myself I had
to get used to it but quite soon I found it had more power than I believed at
first. The first thing I wanted to know about my new programming environment is
how it supports OOP. When browsing for information on JavaScript and OOP the
problem seems to be that two disciplines have such a different background. The
majority of JS is produced by web-designers who have to write some code to get
their site working. The majority of OOP information is provided by "real"
programmers who look down on JS. And there are people like
Douglas Crockford to whom JavaScript is
the language where almost every way of programming meets. I don't share his
enthusiasm but his site is a must read and did provide me the best basis for
workable patterns.
As promised here is my, C# based, way of
OOP in JavaScript.
Getting started with JavaScript
Getting started is no big deal. At first sight you just start
typing ahead pretending to write in C#. But there is no compiler or unit testing
to validate your work; not until the code is interpreted at run time. As you can
do horrible things in JavaScript it does take a lot of discipline to write good
code. Also because the language itself is not as strict as C#. The following snippet of
code will run
function
CheckId()
{
if (text1.value !=
"")
{
Id = ++text1.value;
}
else
{
id = -1;
}
return id;
}
An undeclared variables is created on the fly with global visibility. In this
case two, Id and id, are introduced. All script code on the webpage will see an Id a
an id variable (the different casing is a typing error). As JavaScript is
case-sensitive these are two distinct members. Both will keep their value over
calls to the function. But this id does not make much sense outside of the
function.
You limit the visibility of a member using the var keyword. This code
does the same without any of the side effects.
function
CheckId2()
{
var id = -1;
if (text1.value !=
"")
id = ++text1.value;
return id;
}
Note that variables and functions in JS do not have a type. The
value of the function result or the variable's values is formed by the
context. Which can lead to confusing things. These three expressions are
all true
1 + 2 == 3;
1 + '2' == '12';
1 + '2' == 12;
The last one looks funny at first sight. But it makes sense when a
ToString() is applied first to every member of the expression.
Objects in JavaScript
In C# an object is described by the class it's instantiated from. The class
is the type of the object. We just saw that the JavaScript language does not
have types. Objects in JS are created by a constructor function. An object in JS
is a dynamic collection of variables and functions. All methods in JavaScript
are functions, a procedure is in JS a function without a return statement. The constructor
function below creates a new
object with a Name and a Counter member.
function
MyObject()
{
this.Name =
'A Javascript object';
this.Counter = 0;
}
The this keyword marks the members as public accessible. You
instantiate an object using the new keyword.
<script>
var myObj = new MyObject();
function CountOnMyObject()
{
text1.value = myObj.Name + ' ' + (++myObj.Counter);
}
</script>
<input type="text" size=90 id ="text1" />
<input type="button" value = "Debug" onclick="CountOnMyObject()">
When the page is loaded the new object is created. On every click of the
button the count is raised and the object returns it's name and count. Here we
have the base of object orientation, the possibility to bundle data and
functionality at work.
There are several ways to add methods to the objects. What you will find most
is a declaration which adds a function to the so called prototype of the object. These functions can
access the other members of
the object..
function
MyObject()
{
this.Name =
'A Javascript object';
this.Counter = 0;
}
function
MyObject.prototype.DoubleCount()
{
return
this.Counter * 2;
}
The DoubleCount() function returns twice the current value of the
Counter member.
As said, JavaScript objects are a dynamic collection of members. These
members can be variables or functions. Any piece of
code can add a member on the fly. This can be done inside an existing member of the
object.
function
MyObject.prototype.DoubleCount()
{
this.OnTheFlyMember =
'Set';
return
this.Counter * 2;
}
The result is that myObj.OnTheFlyMember is undefined until the first
time myObj.DoubleCount() is called.
But it can even get greasier than that. Also a snippet of script consuming
the object can add a member on the fly
<script>
var myObj = new MyObject();
function AddMemberToMyObject()
{
myObj.MemberFromScript = 'Hello from
a snippet of script';
}
</script>
We will see later the not just member variables but also member functions can
be added or altered. To some people this dynamic nature of JavaScript is a bliss which opens the
doors to great ways of programming. I'm not sure about that, there are other
languages in case I would dive into that. Me and other mortals just need plain
simple, maintainable and easy to understand code. Object orientation is a step
towards that but the OO we have seen so far is no more than a bundling of
variables and functions with a public visibility.
<Update>Part 2 further discusses prototype</update>
What makes objects far easier to
work with is encapsulation. Not just hiding the implementation of the method but
also hiding all internal data inside the object and all helper methods without
any relevance to the object's consumer. An object should only expose those
members which are of relevance to the consumer. This is done in
JavaScript using a slightly different syntax.
Now all functions are defined in the constructor function.
function
MyObject()
{
var name =
'A Javascript object';
var counter = 0;
this.Count
= function()
{
counter++;
}
this.Name
= function()
{
return
name + ' with a count of ' + counter;
}
}
Using the var keyword the name and counter's visibility
are limited to the constructor function. As the exposed members this.Count
and this.Name are also declared inside the constructor function they can
reach the private vars. They are named privileged members. The members
declared on the prototype are named public. To the script consuming the
objects there is no difference except when it comes to working with the
prototype, something we will meet when taking a look at inheritance. The object exposes public and
privileged methods and encapsulates
the private vars.
Also a helper method can be a private member. It is used by the object's
members but is not visible by the object's consumer.
function
MyObject()
{
var name =
'A Javascript object';
var counter = 0;
function
capFirst(aString)
{
var str1
= aString.substr(0,1);
var str2
= aString.substr(1);
return
str1.toUpperCase() + str2.toLowerCase();
}
this.Count
= function()
{
counter++;
}
this.Name
= function()
{
return
capFirst(name + ' with a count of ' + counter);
}
this.AnotherName
= function(anotherString)
{
return
name + ' ' + capFirst(anotherString);
}
}
Now we have objects which can hide data and implementation and expose just
that what is of interest to the consumer. Just like the .net object you're used
to.
Methods, overloads and properties
In a language like C# you have overloads, that is multiple versions of a
method each with a distinct parameter list. There is no such thing in
JavaScript. The function with a matching name is called passing it all parameters. Even when the
parameters are not specified in the function declaration they are available in
the function's implementation via the arguments keyword. This AnotherName method declares only 1 parameter.
function
MyObject()
{
this.AnotherName =
function(anotherString)
{
return
arguments.length;
}
}
When you use it with a longer parameter list, like this:
text1.value =myObj.AnotherName(text2.value, 'Boe', 'Blub',
9);
the AnotherName method will still be hit and it will return 4 .
You can use something like overloads in JavaScript but all implementation has to
be done within one method. It has to inspect the number and content of the arguments actually passed and
act accordingly.
To have the kind of object you're used to, where methods can reach private
data, requires privileged members. What the JavaScript syntax in such a
declaration actually does is
assign a function variable to a member of the object
this.MemberName =
function(params)
This dynamic way JS mixes code and data can hit back when you look at
properties. The constructor function below initiates a property Name and a
privileged method AnotherName.
function
MyObject()
{
this.Name =
'A Javascript object';
this.AnotherName
= function(anotherString)
{
return
'Another ' + anotherString;
}
}
The consumer can read and set the Name property and call the
AnotherName method.
text1.value = myObj.Name;
text2.value = myObj.AnotherName(text2.value);
text3.value = myObj.AnotherName;
The third statement is also valid. The object has besides the method
AnotherName() also a property AnotherName (without
parenthesis). The property will return the implementation of the AnotherName
method. The first time you meet this, for instance when you forget the
parenthesis in a method call it will make you scratch your head. The bad thing
is that you can also set this method property from code. Needless to say that
this is the way to have your carefully crafted code wrecked by consuming code.
Or altered, which is a next subject.
Static members
A static member is a member which is available without first creating an
object instance. JavaScript does have static members. They are declared outside of the
constructor function.
function
MyObject()
{
this.Name =
'A Javascript object';
}
MyObject.MyStaticProp = 'This
is a static property';
function
MyObject.MyStaticFunction()
{
return
'This is a static function';
}
You use them in your code by using the constructor function's name as a
prefix.
text2.value = MyObject.MyStaticFunction();
The usability of static members is limited as they cannot be used by the
privileged members. But they are a nice
way to organize a bunch of related functions into a class.
Inheritance
Inheritance is the weak spot in JavaScript's OOP implementation. The language
itself does not include the idea. Scattered over the web you will find a wide
variety of hacks to implement it. Harry Fuecks tries to give
an overview
and
does provide an, at first sight, elegant solution himself. The main problem with
all the implementations, including his, is that they work with the prototype and
thus only work for public members. The big problem we just saw with public
members is that they cannot work with encapsulated private members. The way to
keep your objects clean is using privileged members, that was having all code in
the constructor function. The implementations found on the web don't work with these
privileged members.
A reason for inheriting from a base class is polymorphism, where you pass
an object of a derived class to code expecting an object of the base class. As
JavaScript just tries at runtime if the object has a member with a matching
name, polymorphism will always work as intended. Another reason for inheritance is
changing the behavior by overriding members. We have seen that you can add or
replace any member of a JavaScript object on the fly. Instead of inheriting you
can aggregate an object, using a base class to implement things. Take these
creatures
function
Animal(named)
{
var name = named ? named :
'Unknown';
this.Name =
function()
{
return
name;
}
this.MakeSound
= function()
{
return
'That\'s the ' + name + ' sound';
}
}
function
Cat()
{
var base =
new Animal('Cat');
this.Name
= base.Name;
this.MakeSound =
function()
{
alert('Mew');
return
base.MakeSound();
}
}
The Animal constructor function provides an object with basic
functionality. The Cat constructor creates an Animal object,
passing in a value for the name. The Cat constructor exposes the animal's
Name function and provides an own implementation of
the MakeSound function which augments the base implementation.
<Update>Part2 discusses a variation</update>
Most hacks
on the web are able to publish all public
methods in one go using the prototype. I have to explicitly re-publish every
privileged member. But the members of my object can keep working with shared
private var's; provided the members are in the same class, the Cat constructor
cannot see the Animal name var. I need some extra lines but this is a
price I'm quite willing to pay for a better encapsulation.
Conclusion
The four tenants of OOP programming are abstraction, encapsulation,
inheritance and polymorphism. Judging JavaScript on each of them
- Abstraction is met. The object consumer only sees published members, not
the implementation.
- Encapsulation is met. Not only the code but also private data can be
hidden from the object consumer
- Inheritance is not met, but thanks to JavaScript objects being dynamic a
simple and pragmatic work-around is possible
- Polymorphism is in the core of the language.
The main point of JavaScript to me is being able to work with it in a
pragmatic manner and have simple well structured code. Which is possible as long
as I restrict myself.
Posted
Tue, Oct 24 2006 3:48 PM
by
pvanooijen