There are many routes into becoming a JavaScript programmer, ranging from graphic design to a serious programmer coming up from the business tiers.
This appendix won’t aim to teach you how to program in JavaScript—there are already many good books and articles to help you do that. What I intend to record here are a few core concepts that will help Java and C# programmers make the leap to JavaScript programming in a relatively painless way. (The same is true to a lesser extent of C++ programmers, but C++ inherits a lot of strange flexibility from C, so that JavaScript should prove less of a shock to the system.) If you are a serious enterprise programmer with a grounding in OO design principles, then your first approaches to JavaScript may be overly influenced by your experience with languages such as Java and C#, and you may find yourself fighting against the language rather than working with it. I certainly did, and I’ve based this on my own experience as a programmer and in mentoring others along the same route.
JavaScript can do a lot
of clever things that Java and C# can’t. Some of these can help you to write better code, and some
can only help you to shoot yourself in the foot more accurately! It’s worth
knowing about both, either to make use of the techniques or to avoid doing them
unwittingly. If you are coming to
JavaScript is not Java
What’s in a name? In the case of Java and JavaScript, a lot of marketing and relatively little substance. JavaScript was renamed from “livescript” at the last minute by Netscape’s marketing department, and now the name has stuck. Contrary to popular perception, JavaScript is not a descendent of the C family of languages. It owes a lot more to functional languages such as Scheme and Self, and it has quite a lot in common with Python, too. Unfortunately, it’s been named after Java and syntactically styled to look like Java. In places, it will behave like Java, but in many places, it just plain won’t.
Table B.1 summarizes the
key differences.
|
Key features of JavaScript and their implications |
|
|
Feature |
Implications |
|
Variables are loosely typed. |
Variables are just declared as variables, not as integers, strings, or objects of a specific class. In JavaScript, it is legal to assign values of different types to the same variable. |
|
Code is dynamically interpreted . |
At runtime, code is stored as text and interpreted into machine
instructions as the program runs, in contrast to precompiled languages such
as Java, C, and C#. Users of your website can generally see the source code
of your |
|
JavaScript functions are |
A Java object’s methods are tied to the object that owns them and can be invoked only via that object. JavaScript functions can be attached to objects so that they behave like methods, but they can also be invoked in other contexts and/or reattached to other objects at runtime. |
|
JavaScript objects are |
A Java, C++, or C# object has a defined type, with superclasses and virtual superclasses or interfaces. This strictly defines its functionality. Any JavaScript object is just an object, which is just an associative array in disguise. Prototypes can be used to emulate Java-style types in JavaScript, but the similarity is only skin deep. |
These differences allow the language to be used in different ways and open up the possibility of a number of weird tricks worthy of a seasoned Lisp hacker. If you’re a really clever, disciplined coder, you can take advantage of these tricks to do marvelous things, and you might even do so beyond a few hundred lines of code. If, on the other hand, you only think you’re really clever and disciplined, you can quickly end up flat on your face.
I’ve tried it a few times and come to the conclusion that keeping things simple is generally a good thing. If you’re working with a team, coding standards or guidelines should address these issues if the technical manager feels it is appropriate.
However, there is a second reason for knowing about these differences and tricks: the browser will use some of them internally, so understanding what is going on can save you much time and pain in debugging a badly behaved application. In particular, I’ve found it helpful to know where the code is not behaving like a Java object would, given that much of the apparent similarity is only apparent.
So read on, and find out what JavaScript objects really look like when the lights are out, how they are composed of member fields and functions, and what a JavaScript function is really capable of.
Objects in JavaScript
JavaScript doesn’t require the use of objects or even functions. It is possible to write a JavaScript program as a single stream of text that is executed directly as it is read by the interpreter. As a program gets bigger, though, functions and objects become a tremendously useful way of organizing your code, and we recommend you use both.
The simplest way to
create a new JavaScript object is to invoke the built-in constructor for the Object class:
var myObject=new Object();
We’ll look at other approaches, and what the new keyword really does, in section B.2.2. Our object myObject is initially “empty,” that is, it has no properties or methods. Adding them in is quite simple, so let’s see how to do it now.
Building ad hoc objects
As already noted, the JavaScript object is essentially just an associative array, with fields and methods keyed by name. A C-like syntax is slapped on top to make it look familiar to C-family programmers, but the underlying implementation can be exploited in other ways, too. We can build up complex objects line by line, adding new variables and functions as we think of them.
There are two ways of building up objects in this ad hoc fashion. The first of these is to simply use JavaScript to create the object. The second is to use a special notation known as JSON. Let’s start with the plain old JavaScript technique.
Using JavaScript statements
In the middle of a complicated piece of code, we may want to assign a value to some object’s property. JavaScript object properties are read/write and can be assigned by the = operator. Let’s add a property to our simple object:
myObject.shoeSize="12";
In a structured OO language, we would need to define a class that declared a property shoeSize or else suffer a compiler error. Not so with JavaScript. In fact, just to emphasize the array-like nature, we can also reference properties using array syntax:
myObject['shoeSize']="12";
This notation is clumsy for ordinary use but has the advantage that the array index is a JavaScript expression, offering a form of runtime reflection, which we’ll return to in section B.2.4.
We can also add a new function to our object dynamically:
myObject.speakYourShoeSize=function(){
alert("shoe size : "+this.shoeSize);
}
Or borrow a predefined function:
function sayHello(){
alert('hello, my shoeSize
is '+this.shoeSize);
}
...
myObject.sayHello=sayHello;
Note that in assigning the predefined function, we omit the parentheses. If we were to write
myObject.sayHello=sayHello();
then we would execute the sayHello function and assign the return value, in this case null, to the sayHello property of myObject.
We can attach objects to other objects in order to build up complex data models and so on:
var myLibrary=new
Object();
myLibrary.books=new Array();
myLibrary.books[0]=new Object();
myLibrary.books[0].title="Turnip Cultivation
through the Ages";
myLibrary.books[0].authors=new Array();
var jim=new
Object();
jim.name="Jim Brown";
jim.age=9;
myLibrary.books[0].authors[0]=jim;
This can quickly become tedious (often the case where turnips are involved, I’m afraid), and JavaScript offers a compact notation that we can use to assemble object graphs more quickly, known as JSON. Let’s have a look at it now.
Using JSON
The JavaScript Object Notation (JSON) is a core feature of the language. It provides a concise mechanism for creating arrays and object graphs. In order to understand JSON, we need to know how JavaScript arrays work, so let’s cover the basics of them first.
JavaScript has a built-in Array class that can be instantiated using the new keyword:
myLibrary.books=new Array();
Arrays can have values assigned to them by number, much like a conventional C or Java array:
myLibrary.books[4]=somePredefinedBook;
Or they can be associated with a key value, like a Java Map or Python Dictionary, or, indeed, any JavaScript Object:
myLibrary.books["BestSeller"]=somePredefinedBook;
This syntax is good for fine-tuning, but building a large array or object in the first place can be tedious. The shorthand for creating a numerically indexed array is to use square braces, with the entries being written as a comma-separated list of values, thus:
myLibrary.books=[predefinedBook1,predefinedBook2,predefinedBook3];
And to build a JavaScript Object, we use curly braces, with each value written as a key:value pair:
myLibrary.books={
bestSeller : predefinedBook1,
cookbook : predefinedBook2,
spaceFiller : predefinedBook3
};
In both notations, extra white space is ignored, allowing us to pretty-print for clarity. Keys can also have spaces in them, and can be quoted in the JSON notation, for example:
"Best Seller"
: predefinedBook1,
We can nest JSON notations to create one-line definitions of complex object hierarchies (albeit rather a long line):
var myLibrary={
location : "my house",
keywords : [ "root vegetables",
"turnip", "tedium" ],
books: [
{
title :
"Turnip Cultivation through the Ages",
authors :
[
{ name:
"Jim Brown", age: 9 },
{ name:
"Dick Turnip", age: 312 }
],
publicationDate : "long
ago"
},
{
title :
"Turnip Cultivation through the Ages, vol. 2",
authors :
[
{ name:
"Jim Brown", age: 35 }
],
publicationDate : new
Date(1605,11,05)
}
]
};
I have assigned three properties to the myLibrary object here: location is a simple string, keywords is a numerical list of strings, and books a numerically indexed list of objects, each with a title (a string), a publication date (a JavaScript Date object in one case and a string in the other), and a list of authors (an array). Each author is represented by a name and age parameter. JSON has provided us with a concise mechanism for creating this information in a single pass, something that would otherwise have taken many lines of code (and greater bandwidth).
Sharp-eyed readers will have noted that we populated the publication date for the second book using a JavaScript Date object. In assigning the value we can use any JavaScript code, in fact, even a function that we defined ourselves:
function gunpowderPlot(){
return new Date(1605,11,05);
}
var volNum=2;
var turnipVol2={
title : "Turnip Cultivation through the
Ages, vol. "
+volNum,
authors : [
{ name: "Jim Brown", age: 35 }
],
publicationDate : gunpowderPlot()
}
]
};
Here the title of the book is calculated dynamically by an inline expression, and the publicationDate is set to the return value from a predefined function.
In the previous example, we defined a function gunpowderPlot() that was evaluated at the time the object was created. We can also define member functions for our JSON-invoked objects, which can be invoked later by the object:
var turnipVol2={
title : "Turnip Cultivation through the
Ages, vol. "+volNum,
authors : [
{ name: "Jim Brown", age: 35 }
],
publicationDate : gunpowderPlot()
}
],
summarize:function(len){
if (!len){ len=7; }
var summary=this.title+"
by "
+this.authors[0].name
+" and his cronies is very
boring. Z";
for (var i=0;i<len;i++){
summary+="z";
}
alert(summary);
}
};
...
turnipVol2.summarize(6);
The summarize() function has all the features of a standard JavaScript function, such as parameters and a context object identified by the keyword this. Indeed, once the object is created, it is just another JavaScript object, and we can mix and match the JavaScript and JSON notations as we please. We can use JavaScript to fine-tune an object declared in JSON:
var numbers={ one:1, two:2, three:3 };
numbers.five=5;
We initially define an object using JSON syntax and then add to it using plain JavaScript. Equally, we can extend our JavaScript-created objects using JSON:
var cookbook=new Object();
cookbook.pageCount=321;
cookbook.author={
firstName:
"Harry",
secondName:
"Christmas",
birthdate: new
Date(1900,2,29),
interests: ["cheese","whistling",
"history of lighthouse keeping"]
};
With the built-in JavaScript Object and Array classes and the JSON notation, we can build object hierarchies as complicated as we like, and we could get by with nothing else. JavaScript also offers a means for creating objects that provides a comforting resemblance to class definitions for OO programmers, so let’s look at this next and see what it can offer us.
Constructor functions, classes, and prototypes
In OO programming, we generally create objects by stating the class from which we want them to be instantiated. Both Java and JavaScript support the new keyword, allowing us to create instances of a predefined kind of object. Here the similarity between the two ends.
In Java, everything (bar a few primitives) is an object, ultimately descended from the java.lang.Object class. The Java virtual machine has a built-in understanding of classes, fields, and methods, and when we declare in Java
MyObject myObj=new MyObject(arg1,arg2);
we first declare the type of the variable and then instantiate it using the relevant constructor. The prerequisite for success is that the class MyObject has been declared and offers a suitable constructor.
JavaScript, too, has a concept of objects and classes but no built-in concept of inheritance. In fact, every JavaScript object is really an instance of the same base class, a class that is capable of binding member fields and functions to itself at runtime. So, it is possible to assign arbitrary properties to an object on the fly:
MyJavaScriptObject.completelyNewProperty="something";
This free-for-all can be organized into something more familiar to the poor OO developer by using a prototype, which defines properties and functions that will automatically be bound to an object when it is constructed using a particular function. It is possible to write object-based JavaScript without the use of prototypes, but they offer a degree of regularity and familiarity to OO developers that is highly desirable when coding complex rich-client applications.
In JavaScript, then, we can write something that looks similar to the Java declaration
var myObj=new MyObject();
but we do not define a class MyObject, but rather a function with the same name. Here is a simple constructor:
function MyObject(name,size){
this.name=name;
this.size=size;
}
We can subsequently invoke it as follows:
var myObj=new MyObject("tiddles","7.5 meters");
alert("size of "+myObj.name+"
is "+myObj.size);
Anything set as a property of this in the constructor is subsequently available as a member of the object. We might want to internalize the call to alert() as well, so that tiddles can take responsibility for telling us how big it is. One common idiom is to declare the function inside the constructor:
function MyObject(name,size){
this.name=name;
this.size=size;
this.tellSize=function(){
alert("size of "+this.name+"
is "+this.size);
}
}
var myObj=new
Object("tiddles","7.5 meters");
myObj.tellSize();
This works, but is less
than ideal in two respects. First, for every instance of MyObject that we create, we
create a new function. As responsible
A prototype is a property of JavaScript objects, for which no real equivalent exists in OO languages. Functions and properties can be associated with a constructor’s prototype. The prototype and new keyword will then work together, and, when a function is invoked by new, all properties and methods of the prototype for the function are attached to the resulting object. That sounds a bit strange, but it’s simple enough in action:
function MyObject(name,size){
this.name=name;
this.size=size;
}
MyObject.prototype.tellSize=function(){
alert("size of "+this.name+"
is "+this.size);
}
var myObj=new MyObject("tiddles","7.5 meters");
myObj.tellSize();
First, we declare the constructor as before, and then we add functions to the prototype. When we create an instance of the object, the function is attached. The keyword this resolves to the object instance at runtime, and all is well.
Note the ordering of events here. We can refer to the prototype only after the constructor function is declared, and objects will inherit from the prototype only what has already been added to it before the constructor is invoked. The prototype can be altered between invocations to the constructor, and we can attach anything to the prototype, not just a function:
MyObject.prototype.color="red";
var obj1=new MyObject();
MyObject.prototype.color="blue";
MyObject.prototype.soundEffect="boOOOoing!!";
var obj2=new MyObject();
obj1 will be red, with no sound effect, and obj2 will be blue with an annoyingly cheerful sound effect! There is generally little value in altering prototypes on the fly in this way. It’s useful to know that such things can happen, but using the prototype to define class-like behavior for JavaScript objects is the safe and sure route.
Interestingly, the prototype of certain built-in classes (that is, those implemented by the browser and exposed through JavaScript, also known as host objects) can be extended, too. Let’s have a look at how that works now.
Extending built-in classes
JavaScript is designed to be embedded in programs that can expose their own native objects, typically written in C++ or Java, to the scripting environment. These objects are usually described as built-in or host objects, and they differ in some regards to the user-defined objects that we have discussed so far. Nonetheless, the prototype mechanism can work with built-in classes, too. Within the web browser, DOM nodes cannot be extended in the Internet Explorer browser, but other core classes work across all major browsers. Let’s take the Array class as an example and define a few useful helper functions:
Array.prototype.indexOf=function(obj){
var result=-1;
for (var i=0;i<this.length;i++){
if (this[i]==obj){
result=i;
break;
}
}
return result;
}
This provides an extra function to the Array object that returns the numerical index of an object in a given array, or -1 if the array doesn’t contain the object. We can build on this further, writing a convenience method to check whether an array contains an object:
Array.prototype.contains=function(obj){
return (this.indexOf(obj)>=0);
}
and then add another function for appending new members after optionally checking for duplicates:
Array.prototype.append=function(obj,nodup){
if (!(nodup
&& this.contains(obj))){
this[this.length]=obj;
}
}
Any Array objects created after the declaration of these functions, whether by the new operator or as part of a JSON expression, will be able to use these functions:
var
numbers=[1,2,3,4,5];
var
got8=numbers.contains(8);
numbers.append("cheese",true);
As with the prototypes of user-defined objects, these can be manipulated in the midst of object creation, but I generally recommend that the prototype be modified once only at the outset of a program, to avoid unnecessary confusion, particularly if you’re working with a team of programmers.
Prototypes can offer us
a lot, then, when developing client-side object models for our
Inheritance of prototypes
Object orientation provides not only support for distinct object classes but also a structured hierarchy of inheritance between them. The classic example is the Shape object, which defines methods for computing perimeter and area, on top of which we build concrete implementations for rectangles, squares, triangles, and circles.
With inheritance comes the concept of scope. The scope of an object’s methods or properties determines who can use it—that is, whether it is public, private, or protected.
Scope and inheritance can be useful features when defining a domain model. Unfortunately, JavaScript doesn’t support either natively. That hasn’t stopped people from trying, however, and some fairly elegant solutions have developed.
Doug Crockford (see the Resources section at the end of this appendix) has developed some ingenious workarounds that enable both inheritance and scope in JavaScript objects. What he has accomplished is undoubtedly impressive and, unfortunately, too involved to merit a detailed treatment here. The syntax that his techniques require can be somewhat impenetrable to the casual reader, and in a team-based project, adopting such techniques should be considered similar to adopting a Java framework of the size and complexity of Struts or Tapestry—that is, either everybody uses it or nobody does. I urge anyone with an interest in this area to read the essays on Crockford’s website.
Within the world of object orientation, there has been a gradual move away from complex use of inheritance and toward composition. With composition, common functionality is moved out into a helper class, which can be attached as a member of any class that needs it. In many scenarios, composition can provide similar benefits to inheritance, and JavaScript supports composition perfectly adequately.
The next stop in our brief tour of JavaScript objects is to look at reflection.
Reflecting on JavaScript objects
In the normal course of writing code, the programmer has a clear understanding of how the objects he is dealing with are composed, that is, what their properties and methods do. In some cases, though, we need to be able to deal with completely unknown objects and discover the nature of their properties and methods before dealing with them. For example, if we are writing a logging or debugging system, we may be required to handle arbitrary objects dumped on us from the outside world. This discovery process is known as reflection, and it should be familiar to most Java and .NET programmers.
If we want to find out whether a JavaScript object supports a certain property or method, we can simply test for it:
if
(MyObject.someProperty){
...
}
This will fail, however, if MyObject.someProperty has been assigned the boolean value false, or a numerical 0, or the special value null. A more rigorous test would be to write
if
(typeof(MyObject.someProperty)
!= "undefined"){
If we are concerned about the type of the property, we can also use the instanceof operator. This recognizes a few basic built-in types:
if
(myObj instanceof Array){
...
}else
if (myObj instanceof
Object){
...
}
as well as any class definitions that we define ourselves through constructors:
if
(myObj instanceof MyObject){
...
}
If you do like using instanceof to test for custom classes, be aware of a couple of “gotchas.” First, JSON doesn’t support it—anything created with JSON is either a JavaScript Object or an Array. Second, built-in objects do support inheritance among themselves. Function and Array, for example, both inherit from Object, so the order of testing matters. If we write
function
testType(myObj){
if (myObj instanceof Array){
alert("it's an array");
}else if (myObj instanceof Object){
alert("it's an object");
}
}
testType([1,2,3,4]);
and pass an Array through the code, we will be told—correctly—that we have an Array. If, on the other hand, we write
function
testType(myObj){
if (myObj instanceof Object){
alert("it's an object");
}else if (myObj instanceof Array){
alert("it's an array");
}
}
testType([1,2,3,4]);
then we will be told that we have an Object, which is also technically correct but probably not what we intended.
Finally, there are times when we may want to exhaustively discover all of an object’s properties and functions. We can do this using the simple for loop:
function
MyObject(){
this.color='red';
this.flavor='strawberry';
this.azimuth='45 degrees';
this.favoriteDog='collie';
}
var
myObj=new MyObject();
var
debug="discovering...\n";
for
(var i in myObj){
debug+=i+" -> "+myObj[i]+"\n";
}
alert(debug);
This loop will execute four times, returning all the values set in the constructor. The for loop syntax works on built-in objects, too—the simple debug loop above produces very big alert boxes when pointed at DOM nodes! A more developed version of this technique is used in the examples in chapters 5 and 6 to develop the recursive ObjectViewer user interface.
There is one more feature of the conventional object-oriented language that we need to address—the virtual class or interface. Let’s look at that now.
Interfaces and duck typing
There are many times in software development when we will want to specify how something behaves without providing a concrete implementation. In the case of our Shape object being subclassed by squares, circles, and so on, for example, we know that we will never hold a shape in our hands that is not a specific type of shape. The base concept of the Shape object is a convenient abstraction of common properties, without a real-world equivalent.
A C++ virtual class or a Java interface provides us with the necessary mechanism to define these concepts in code. We often speak of the interface defining a contract between the various components of the software. With the contract in place, the author of a Shape-processing library doesn’t need to consider the specific implementations, and the author of a new implementation of Shape doesn’t need to consider the internals of any library code or any other existing implementations of the interface.
Interfaces provide good
separation of concerns and underpin many design patterns. If we’re using design
patterns in
The simplest approach is to define the contract informally and simply rely on the developers at each side of the interface to know what they are doing. Dave Thomas has given this approach the engaging name of “duck typing”—if it walks like a duck and it quacks like a duck, then it is a duck. Similarly with our Shape interface, if it can compute an area and a perimeter, then it is a shape.
Let’s suppose that we want to add the area of two shapes together. In Java, we could write
public
double addAreas(Shape s1, Shape s2){
return s1.getArea()+s2.getArea();
}
The method signature specifically forbids us from passing in anything other than a shape, so inside the method body, we know we’re following the contract. In JavaScript, our method arguments aren’t typed, so we have no such guarantees:
function
addAreas(s1,s2){
return s1.getArea()+s2.getArea();
}
If either object doesn’t have a function getArea() attached to it, then we will get a JavaScript error. We can check for the presence of the function before we call it:
function
hasArea(obj){
return obj &&
obj.getArea && obj.getArea
instanceof Function;
}
and modify our function to make use of the check:
function
addAreas(s1,s2){
var total=null;
if (hasArea(s1)
&& hasArea(s2)){
total=s1.getArea()+s2.getArea();
}
return total;
}
Using JavaScript reflection, in fact, we can write a generic function to check that an object has a function of a specific name:
function
implements(obj,funcName){
return obj &&
obj[funcName] && obj[funcName] instanceof
Function;