Introduction
JavaScript – It's beat up, put down, shrugged off and kicked
around. Cursed by the web browser's inconsistency yet blessed by a
pervasive ubiquity -it's a technology many try to disregard even when
its potential is something few can ignore. If you want to write an
interactive application for the web today, then you'll need
some JavaScript code on your side.
This article approaches JavaScript from the perspective of an
ASP.NET developer who is comfortable with the paradigms and patterns
of either C# or Visual Basic. The article doesn't look at how to use
JavaScript from ASP.NET exactly, but it does look at why JavaScript
is so different from the two languages we commonly use with the .NET
CLR. The article assumes you already know that JavaScript is a
loosely-typed language (because you don't have to declare the type of
data you store in a variable), and that the syntax is similar to the
C family of languages (with charming curly braces and stunningly
beautiful semi-colons).
What Is Wrong With Javascript
The introduction didn't paint a flattering picture of the
JavaScript language, but the truth is JavaScript is a good language.
The biggest sources of pain when programming with JavaScript aren't
because of the language. The biggest pains come from:
The tools
The implementations
The bad practices
The Tools
Most of the tools we use in a Visual Studio environment are geared
to languages targeting the CLR.
If you program in C# or Visual Basic, you'll be assisted by
Intellisense, class browsers, class diagrams, code snippets, code
analysis, and a world class debugger. The menus will list refactoring
commands, and unit testing is only a few keystrokes away.
Contrast the above with the experience of programming with
JavaScript. There is no Intellisense (although the feature is coming
in the next version of Visual Studio), the debugger is finicky, and
most of the other tools listed above are missing entirely. Of course,
the majority of the code we write in Visual Studio is not
JavaScript, but as the demands of the web have required more
scripting, we've started to need better tools. The lack of tools
makes JavaScript more difficult to work with.
The Implementations
JavaScript is hosted by many different types of web browsers, and
is generally our primary means to manipulate a browser's DOM.
While ostensibly governed by W3C
standards, we all know each browser contains variations and
idiosyncrasies. Script code that works on one version of a browser
might not work on a different version of the same browser. These
scenarios cause a lot of pain in testing and re-writing of JavaScript
code. This pain isn't the language's fault – we will never see the
day when all browsers implement web standards with 100% accuracy.
The Bad Practices
JavaScript is an accessible language. We don't need special tools
or compilers. We can view the source code of any page on the Internet
and copy the script code for our own purposes – and many people do.
Of course, not everyone who uses JavaScript is a software developer
with an eye for good code. All sorts of people use JavaScript, and
all sorts of ugly JavaScript code perpetuates itself on the Internet.
Even software developers (the author included) have taken a quick
and dirty approach to writing JavaScript. It's only script code,
after all, and just slapping the code into a text editor to get the
desired result is all we need. It's not until we have to untangle a
mess that we realize a more disciplined approach would have
ultimately saved us time.
With all of these problems in the JavaScript environment – why
do we still want to torture ourselves by writing JavaScript code?
What is Right with JavaScript?
Over the last few years, the amount of JavaScript code you'll find
in the typical web application has surged. There are a couple good
reasons for the surge:
JavaScript is ubiquitous
JavaScript is mature
Ubiquity
If you want to write an application that will reach as many users
as possible, then you'll be writing a web application. You can reach
users on Windows, Macintosh, Linux, and hundreds of other platforms
on devices both large and small.
How will you make a web application interactive? You'll use
JavaScript. Your users won't have to install a runtime, or an ActiveX
control, or download some interpreting engine. They'll install a web
browser that includes JavaScript support, as so many do, and they'll
happily use your application. JavaScript is the most ubiquitous
programming language on the planet.
Maturity
As the demand for JavaScript code has increased, the frameworks
and libraries of well-tested and robust JavaScript code have begun to
emerge. Many of these frameworks abstract away the browser
idiosyncrasies we discussed earlier, and can greatly reduce the
amount of time we invest in writing and debugging cross-platform
JavaScript code. Here are some of the most popular frameworks:
We've also begun to see the emergence of proven practices and
design patterns. The practices put the object oriented features of
JavaScript to good use. What's that? You didn't know JavaScript was
object oriented? We might not have applied OOP practices to
JavaScript code over the last few years, but the capability does
exist.
Object Oriented JavaScript
JavaScript does have an object data type – but these objects can
behave differently from the objects we create in C# and VB code. In
C# and VB we create new objects by telling the runtime which class we
want to instantiate. A class is a template for object creation. A
class defines the properties and methods an object will have, and
these properties and methods are forever fixed. We can't manipulate
an object by adding or removing properties and methods at runtime.
In JavaScript there are no classes, so we have no template for
object creation. Then how do properties and methods become part of an
object? One approach is to dynamically create new properties and
methods on an object after construction. To create a new property,
all we need to do is assign a new value for a property. The following
code creates a new object, then adds an x and y property to the
object. Finally, the script writes the values of the two new
properties into an area in the HTML document.
var p = new Object();
p.x = 3;
p.y = 5;
message.innerHTML = p.x + "," + p.y;
JavaScript objects are entirely different from C# and VB objects
because they are ultimately a collection of name and value pairs. We
can access an object's values using the dot operator "."
followed by the name of the value. A value isn't constrained to
holding simple integer types as we have shown in the first example,
but could hold an array, function, or even other object.
If you are thinking a JavaScript object sounds like a Dictionary
from the .NET framework class library, then you are heading in the
right conceptual direction.
JavaScript Objects Are Dictionaries
A JavaScript object is similar to a Dictionary<string, object>
in the sense we can associate any arbitrary piece of data with any
arbitrary string. In fact, there is an alternate syntax for accessing
the values inside an object which makes the object look exactly like
a dictionary. Instead of using the . operator to access a value, we
can use the [] operator. The [] operator is a common sight when we
work with collections like arrays, dictionaries, and hashtables.
Let's rewrite our first example using the [] operator.
var p = new Object();
p["x"] = 3;
p["y"] = 5;
message.innerHTML = p["x"] + "," + p.y;
The above piece of script produces the same result as our first
script. It creates a new object, then adds x and y properties to the
object, then writes out the values. Note that if we access a property
that does not exist, we'll get back a value of "undefined".
For instance, the line of code "alert(p.z);" would force a
dialog box to appear with the string "undefined" inside.
Creating Object Methods
We can also add functions into the collection of values inside an
object. Functions associated with an object become methods
of the object. The following code sample shows how to create and use
a method with the name of "print".
var p = new Object();
p["x"] = 3;
p.y = 5;
p["print"] = function()
{
message.innerHTML = p.x + "," + p.y;
}
p.print();
Notice we alternate use of the . operator and the [] operator. We
can use these two operators interchangeably, for the most part, to
create and access an object's properties and methods. Sometimes these
operators lead to confusion, because it's not clear if a particular
piece of code is trying to create new properties on an
object, or if it's trying to set existing properties to new
values. Fortunately, there is a third syntax available that makes our
intent explicitly clear.
Object Literals
The object literal syntax of JavaScript allows us to create an
object and specify its properties using shorthand. The syntax uses a
comma-separated list of name and value pairs, where the name and the
value themselves are separated by a colon. Let's rewrite our code and
create our object using this object initialization syntax.
var p =
{
x : 5,
y : 3,
print : function() { message.innerHTML = p.x + ',' + p.y; }
}
p.print();
In the above code it becomes clear where object initialization
begins and ends. Also note that we can nest object literals, and that
the property values inside the object literals do not need to be
constants – we can use any legal JavaScript expression. The
following code will contains a nested object (address), and assigns
the current date to a new createdDate property.
var person =
{
name: "Scott Allen", createdDate: new Date()
website: "OdeToCode.com",
address: { state: "MD", postalCode: "21044" },
};
alert(person.address.state);
alert(person.createdDate);
The object literal syntax is popular because of its explicit
intent and compact size. If you look at the source code for many of
today's popular JavaScript frameworks, you'll see they are using
object literals inside. Frameworks, however, aren't the ones using
object literals.
Object Literals and JSON
JavaScript Object Notation (JSON)
is a lightweight data-interchange format based on a subset of the
object literal syntax. Technically, JSON is a stricter version of the
object literal syntax. For example, string literals must be enclosed
in double quotes – no single quotes are allowed.
JSON allows JavaScript to exchange data over the network
(typically with the XmlHttpRequest
object) and interoperate with other applications. Many web service
providers offer JSON as a serialization format and as an alternative
to XML. When our JavaScript contacts the web service, the web service
will return its data in JSON. There is no need for our code to
manipulate XML data with an XML API - instead our code can use
JavaScript's eval
statement to convert JSON into an object graph.
var jsonString = "({ x : 3, y: 5 })";
var p = eval(jsonString);
alert(p.x + ',' + p.y);
JSON is becoming hugely popular on the web. JSON is human readable
and easily consumable in JavaScript. Also, exchanging data with JSON
typically results in smaller payloads than using XML. ASP.NET AJAX
includes a JavaScriptSerializer
class to use JSON on the server-side in managed code.
Where Are We With Object Oriented JavaScript?
With our brief diversion into JSON complete, let's return to the
topic of object oriented JavaScript. So far we've learned the
following:
Every JavaScript object is a dictionary.
This is useful information for constructing objects, but it's only
a starting point. To get to the next level of abstraction, we'll need
to add a second piece of knowledge:
Every JavaScript function is an object.
#2 is what we will discuss in the next topic.
JavaScript Functions
A JavaScript function is a chunk of executable code, but it's also
a first class object. This is fundamentally different from methods in
C# and Visual Basic. We can invoke methods in C# and VB, but
we can't treat those methods as datatypes (although delegates
and lamda
expressions in C# make this area a little bit fuzzy). In
JavaScript, we can manipulate functions using other JavaScript code,
assign functions to variables, store functions inside arrays, nest
functions inside other functions, and pass functions as a parameter
to other functions. This might sound strange, so let's walk through a
simple example.
function add(point1, point2)
{
var result = {
x : point1.x + point2.x,
y : point1.y + point2.y
}
return result;
}
The above code defines a function named "add". The
function expects two parameters, and expects that these two
parameters will both have x and y properties that it can add
together. It returns the result as a new object (created in object
notation) with x and y properties. We could use invoke this function
as in the following sample:
var p1 = { x: 1, y: 1 };
var p2 = { x: 1, y: 1 };
var p3 = add(p1, p2);
alert(p3.x + "," + p3.y);
The resulting dialog box will display "2,2".
Technically, what we've done with the add function is create a new
function object, and assigned the function object to a variable named
add. We could take the same function object and assign it to
different variables and invoke the function through those variables.
function add(point1, point2)
{
var result = {
x : point1.x + point2.x,
y : point1.y + point2.y
}
return result;
}
var foo = add
var bar = add
var p1 = { x: 1, y:1 };
var p2 = { x: 1, y:1 };
var p3 = foo(p1, p2);
p3 = foo(p3, p1);
alert(p3.x + "," + p3.y);
The resulting dialog box should now display "3,3".
Functions as Methods
We can also assign a function object to an object property. As we
noted before, this promotes the function to the status of "method".
var point1 =
{
x: 3,
y: 5,
add: function(otherPoint)
{
this.x = this.x + otherPoint.x;
this.y = this.y + otherPoint.y;
}
};
var point2 =
{
x: 1,
y: 1
};
point1.add(point2);
alert(point1.x + "," + point1.y);
The first part of the code uses object notation to create an
object with x, y, and add properties. The add property is a function
object, and inside we've introduced the "this" keyword.
Just as every instance method in C# has an implicit "this"
parameter (and every instance method in VB has "Me"
parameter), every JavaScript method has an implicit "this"
parameter that represents the object through which the method was
invoked. "this.x" will reference the x property of point1,
because we invoke the add method using point1.
What is a problem is that we have two "point" objects,
but one has an add method and one does not. Remember, we are not
defining classes like we would in C# or VB, we are simply creating
objects and adding properties and methods on the fly. If we wanted to
same add method in both point1 and point2, we could write the
following code.
function addPoints(otherPoint)
{
this.x = this.x + otherPoint.x;
this.y = this.y + otherPoint.y;
}
var point1 =
{
x: 3,
y: 5,
add: addPoints
};
var point2 =
{
x: 1,
y: 1,
add: addPoints
};
point1.add(point2);
alert(point1.x + "," + point1.y);
Now we've defined a function object and assigned the object to a
variable named addPoints. We use addPoints to create new add methods
in both the point1 and point2 objects. Does the "this"
reference still work in addPoints? Yes it does, because "this"
will still reference the object through which the method was invoked.
"this" is a bit ephemeral in JavaScript, as we see later
on, but we can can now invoke the add method on either the point1 or
point2 object.
This syntax is feeling uncomfortable, however. It looks as if we
are trying to create a Point class that will define the properties
and methods for all Point objects. But JavaScript doesn't have
classes, so that would be a dream, right? We'll forever need to
include all this object literal code every time we need a point
object, right? Let's hope we never move into 3 dimensions where the
definition of our point objects will change.
Fortunately, there is a better solution.
Constructor Functions
In JavaScript, a constructor function works in
conjunction with the new operator to initialize objects. A
constructor function can improve our previous code, because we can
use the function to initialize every object we want to use as a
Point.
function Point(x,y)
{
this.x = x;
this.y = y;
}
var p1 = new Point(3,5);
var p2 = new Point(4,6);
alert(p1.x, p1.y);
Constructor functions are just regular functions. It's just we've
designed the function to be used with the new operator. By
convention, we generally capitalize constructor functions to make
other programmers aware of their significance.
When we use the new operator with the Point function, the new
operator will first create a new object. The new operator then
invokes the Point function and passes a reference to the newly
created object in the implicit "this" parameter. Inside the
Point function we are creating new name/value pairs using the
parameter values passed to the function.
Constructor Functions and Object Methods
We can also create methods on an object inside a constructor
function.
function Point(x,y)
{
this.x = x;
this.y = y;
this.add = function(point2)
{
this.x += point2.x;
this.y += point2.y;
}
}
var p1 = new Point(3,5);
var p2 = new Point(4,6);
p1.add(p2);
alert(p1.x + ',' + p1.y);
This approach works well, but there is an alternate approach we
can use which is more in favor today. To understand this approach,
we'll need to introduce a new piece of knowledge. Let's review the
first two:
Every JavaScript object is a
dictionary.
Every JavaScript function is an object.
Now for number 3:
Every JavaScript object references a prototype object.
Let's talk about prototypes.
Object Prototypes
Prototypes are a distinguishing feature of the JavaScript
language. C#, Visual Basic, C++, and Java are all examples of
class-based programming languages. To create objects, we
must first write a class that defines fields, properties, methods,
and events. When we create a new object, we are creating an instance
of that class.
In JavaScript, there are no classes. JavaScript is a
prototype-based programming language. Every object has a prototype
property that references its prototype object. Any properties and
methods that are a part of an object's prototype will appear
as properties and methods of the object itself.
Remember that every function is an object, and every object
references a prototype object. That means every constructor function
references a prototype object. This is extremely useful when used in
conjunction with the "new" operator, because of steps taken
by the new operator:
Create an empty object.
Assign the value of the
constructor function's prototype property to the new object's
prototype property.
Invoke the constructor function, passing the new object as
the "this" reference.
The above 3 steps mean that all objects created by a
constructor function will have the same prototype – the
prototype object for the constructor function. If we can modify the
constructor function's prototype object, we will modify all
objects the constructor function ever creates (or has already
created). You can almost think of every object as inheriting
from it's prototype, because it will include all the properties and
methods defined by its prototype. I say "almost" because
this thinking can be dangerous in some edge cases.
Fortunately, there is an easy syntax we can use to add new
properties and methods into a prototype object.
Prototype Programming
Remember, all objects in JavaScript are dictionaries, and a
prototype object is no exception. We can modify an object' s
prototype simply by referencing it's prototype property, and we can
add properties and methods to that prototype object. Let's rewrite
our Point "class" one more time.
function Point(x,y)
{
this.x = x;
this.y = y;
}
Point.prototype.add = function(point2)
{
this.x += point2.x;
this.y += point2.y;
}
var p1 = new Point(3,5);
var p2 = new Point(4,6);
p1.add(p2);
alert(p1.x + ',' + p1.y);
The methods and properties we add to Point.prototype will be
shared by all objects that are constructed from the Point
constructor function. When we add methods to an object using the
constructor function – each object gets a new property referencing
a function object, so the prototype approach is more efficient
(shared function objects) as well as being a little easier to read.
This prototype approach is used by many of today's JavaScript
frameworks.
Putting It All Together
The topics we've discussed so far put us close to "simulating"
classes in JavaScript. There are just a couple more topics we need to
introduce before wrapping up.
One topic is encapsulation. In JavaScript, every name/value pair
we add to an object becomes a public property. There are no keywords
in JavaScript to restrict accessibility (like the internal,
protected, and private keywords in C#). Nevertheless, we can simulate
private members.
Private Members
Douglas Crockford published an article "Private
Members In JavaScript" that demonstrates how to add private
members to a JavaScript object. Information hiding is an important
technique in object oriented programming, and many JavaScript
toolkits use Crockford's approach to private members.
Private members have to be made in an object's constructor
function. Both local vars and parameters are eligible to become
private members using a closure.
A closure in JavaScript is an inner function that references a local
var or parameter in its outer function. Those local variables and
parameters, which typically go out of scope when the outer function
finishes execution are now "enclosed" by the inner
function, which can continue to reference and use those variables.
Let's re-write our sample once more, this time providing public
"get" and "set" accesors for our points.
function Point(x, y)
{
this.get_x = function() { return x; }
this.set_x = function(value) { x = value; }
this.get_y = function() { return y; }
this.set_y = function(value) { y = value; }
}
Point.prototype.print = function()
{
return this.get_x() + ',' + this.get_y();
}
var p = new Point(2,2);
p.set_x(4);
alert(p.print());
Client code can no longer access the x and y values of a point
object directly. Instead, the code has to go through the set_ and
get_ methods.
Namespaces
Namespaces are crucial for avoiding type name collisions, which
can be a bad thing in JavaScript. Unlike a compiled language like C#
or VB, where a type name collision will result in a compiler error
and an un-shippable product, in JavaScript you can still ship the
code and might not find out about the collision until it's too late.
JavaScript will happily overwrite one value with another. Since we
are now including JavaScript code from all over the place, the
practice of using namespace is important.
There is just one problem.
JavaScript doesn't support namespaces.
This is ok, because we can "simulate" namespace using
objects. Let's put our "Point class" into a Geometry
namespace.
var Geometry = {}
Geometry.Point = function(x,y)
{
this.x = x;
this.y = y;
}
Geometry.Point.prototype =
{
print: function()
{
return this.x + ',' + this.y;
}
}
var p1 = new Geometry.Point(5,2);
alert(p1.print());
Essentially, we are adding our constructor function to the
Geometry object. By adding other constructor functions (Rectangle,
Square, etc), we could keep all of our types inside Geometry and not
pollute the global namespace. Most JavaScript frameworks use a
similar technique.
In Conclusion
This article presented three key pieces of knowledge:
Every JavaScript object is a
dictionary.
Every JavaScript function is an
object.
Every JavaScript object references a prototype object.
Those are three fundamental facts about JavaScript that also make
JavaScript different from mainstream CLR languages like C# and
VB.NET. Embracing and internalizing these differences will put you
ahead of the game in understanding modern JavaScript frameworks,
toolkits, and libraries.
History
04-Jan-2010 Initial Post