JavaScript Resources
JavaScript is an interesting language. Although it lacks some important features, like library support, the core language is surprisingly elegant. In general it's the ugliness of APIs like the DOM, the frustration of cross-browser variations, and the difficulty debugging code that give JavaScript a bad name.
It's worthwhile to read the first part of JavaScript: The Definitive Guide to explore the design of the core language. Here is a brief, biased, incomplete, and probably incorrect overview. The idea here is not to give examples of syntax, but to explain some of the inner workings which are often overlooked.
JavaScript is an object-based language, but not object-oriented in the same way that Java is. It uses a C-style syntax which looks a fair but like Java. But JavaScript is not Java, and not even "Java-lite".
JavaScript is a dynamically-typed interpreted language. It supports UTF-8 and is case sensitive. JavaScript code in web pages is run by the browser inside a sandbox, and so it does not have any access outside the document and browser window.
The basic data types are null
, undefined
, Boolean
, Number
(a double), String
, and Object
. All of these data types are passed by value, except for Object
which is passed by reference. Object
is the most interesting type. There are many kinds of objects, including Array
, Function
, and Error
. Objects work like dictionaries in other languages, with keys and values. The values can be any other data type, including other objects and functions. When we talk about an object foo
and its "method" foo.bar()
we mean that foo
has a key named "bar" which has a function as a value. When we talk about foo
's "property" foo.baz
we mean that foo
has a key "baz" with a value of some other data type (not a function).
So JavaScript has objects that look a lot like objects in other languages. It also has object inheritance, although it works differently than Java and C++. JavaScript uses prototype-based inheritance. Here's how it works. We have an object foo
, and we set foo.a = "A"
. If we try to access foo.b
or foo.c
we will get undefined
, because the object foo
does not have a key "b" or "c". When there is no key the value will be undefined
. However, we can create an object fud
and set fud.a = "X"
and fud.b = "B"
. Then we can set the prototype
of foo
to fud
like this: foo.prototype = fud
. Now we find that foo.a
is "A"
, foo.b
is "B"
, and foo.c
is still undefined
.
What's happening in this example? JavaScript checks foo
's dictionary first, and if the desired key is found then the value is returned. If the key is not found, then JavaScript checks the foo.prototype
for the key. JavaScript will keep searching the fud.prototype
, and so on along the chain of prototypes, until it finds a matching key or the chain ends. This is how inheritance works in JavaScript.
There are several built-in objects in JavaScript, all of which inherit from Object
: Array
, Boolean
, Date
, Function
, Math
, Number
, RegExp
, and String
. Array
uses numeric indexes instead of keys, and works like you expect an array to work. Boolean
, Number
, and String
can be used to create the primitive data types, and they also provide the properties and methods for the primitive types. Every function in JavaScript is a Function
object.
We've seen objects and inheritance, but we haven't yet seen anything like a Java or C++ class. JavaScript doesn't have classes, but it does have "constructor" functions. Here's an example:
Example 3.3. JavaScript constructor
function Rectangle(width, height) { this.width = width; this.height = height; } Rectangle.prototype.getArea = function() { return this.width * this.height; } var r = new Rectangle(2, 3); if(r.width * r.height == r.getArea()) { alert("Test passed."); }
First we define a function named "Rectangle". The this
keyword is special; it refers to the object in the current scope (more on scope in a moment). We also add another function named "getArea" to Rectangle.prototype
. Then with the line var r = new Rectangle(2, 3);
we create a new object with width
and height
properties, and we assign it to r
. We can access r.width
to get 2 and r.height
to get 3, and we can call r.getArea()
to get 6.
In many ways, Rectangle
behaves like a class, and we use the new
keyword to create instances of the class. The next step is to show how sub-classes work. We continue the example above, adding the following code:
Example 3.4. JavaScript constructor inheritance
function Square(width) { this.width = width; this.height = width; } Square.prototype = new Rectangle; Square.prototype.constructor = Square; var s = new Square(4); if(s.width * s.height == s.getArea()) { alert("Test passed."); }
Again we define a constructor function, this time one named "Square". Then we set Square.prototype = new Rectangle;
, which establishes the chain of prototype-based inheritance. We also set Square.prototype.constructor = Square;
, to make it clear that the special constructor
key, which is supposed to refer to the constructor function, refers to Square
instead of Rectangle
. After that we can create a new square and access the getArea()
method using inheritance.
There are other ways to make JavaScript objects work like classes; this is the one we generally use in Mozile. But be careful! JavaScript is not Java or C++, and these "classes" are only similar and not the same.
One last important topic is JavaScript's rules for handling variable scope. The global scope in JavaScript can be considered a function like any other. When working in a browser the global object will have DOM properties, like window
and document
. When you refer to a variable inside a function, JavaScript will check a chain of scopes which works like the chain of prototypes. First it checks the current function for a variable with the matching name, then it checks the next function and the next until it finds the variable or reaches the global scope. If you declare a variable with the same name as one higher in the scope chain, JavaScript will think you want to use the original variable from the outer scope and overwrite it.
JavaScript does not include a notion of public, private, and protected visibility for variables and methods. We rely on the scoping rules, and the convention that names starting with an underscore, like foo._bar()
, are intended to be private and should not be used outside the object that defines them.
Debugging JavaScript is notoriously difficult. Although there are very few differences in the JavaScript language implementation between browsers, there are lots of places where incompatibilities and bugs can slip in. The fact that Mozile uses eval()
to load much of the code aggravates the situation, because it means that the line numbers in most error messages are useless. There's no silver bullet, but there are some things that will make life easier.
Test-driven development can help. See below for Mozile's JsUnit test system.
If you are using a Mozilla browser, set the javascript.options.strict
and javascript.options.showInConsole
preferences to true
. This tells the JavaScript engine to send warning messages when it notices undefined variables, or an =
where ==
is expected, or useless expressions. It might help you catch a problem early. You can access the browser preferences by typing about:config
into the address bar of the browser.
Mozile provides some debugging tools in the mozile.debug
module (in src/core.js
). The mozile.debug.warn()
, mozile.debug.inform()
, and mozile.debug.debug()
functions will log debugging messages and store them in mozile.debug.messages
. By changing the mozile.debug.alertLevel
and mozile.debug.logLevel
you can decide which kinds of messages are displayed using alerts, which are silently logged, and which are ignored. The "Debug" command on the Mozile toolbar (shortcut "Command-D") will open a new window to display logged messages.
Mozilla's Venkman JavaScript Debugger is a very powerful tool for tracking down errors. By setting the "Debug - Error Trigger" and "Debug - Throw Trigger" you can catch problems as they occur, look at the values of variables, and step through the code. Be sure to use the "Pretty Print" mode to display the JavaScript code as it appears to Mozilla. This is important for code executed using eval()
which includes almost all the Mozile code. One difficulty is that most of Mozile's functions are "anonymous" and the name of the object the function belongs to won't be displayed in "Pretty Print" mode. Tracking down the function in the source code may require some clever use of text search like the Unix grep
command.
The Microsoft Script Debugger does many of the same things as Venkman, but does them in different ways.
The best method when writing new Mozile code is to use just one browser until the code is working, make sure you have good tests written and helpful debugging messages ready, and then "port" the code to other browsers. Just make sure that the changes you make while porting don't break the code in the original browser.
The Dojo JavaScript Style Guide is a pretty good place to start. For the sake of the next person to read the code it's nice if there's consistency. But we aren't going to be too strict about style.
Here's what we would like to see: Please make an effort to use tabs, Unix file endings (LF), and UTF-8 encoded files. Include the license block at the top of all code files. And please make use of the JSDoc system of documentation in comments for all methods and properties.