JavaScript Style Guide
When editing existing script you may have to modify these guidelines to
harmonize with existing code, although you should consider reformatting
the existing code if you are already making substantial changes.
Whitespace
- The basic indentaion is two spaces. Tabs are not to be used at
all.
- Try to keep lines to 80 characters or less. When wrapping lines,
try to indent to line up with a related item on the previous line.
Examples:
var result = prompt(aMessage,
aInitialValue,
aCaption);
var IOService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
- Lines should not contain trailing spaces, even after binary operators, commas or semicolons.
- Separate binary operators with spaces.
- Spaces after commas and semicolons, but not before.
- Spaces after keywords, e.g.
if (x > 0). - One (or two) blank lines between block definitions. Also consider
breaking up large code blocks with blank lines.
- End the file with a newline. (This applies mainly to emacs users.)
Symbols
- Spaces around braces used for in-line functions or objects, except before commas or semicolons, e.g.
function valueObject(aValue) { return { value: aValue }; };
- Otherwise function braces must always be on their own line, i.e.
function toOpenWindow(aWindow)
{
aWindow.document.commandDispatcher.focusedWindow.focus();
}
- Otherwise spaces are not necessary inside brackets e.g. parameter
lists, array subscripts. This includes wrapping an in-line JavaScript
object in parentheses, or the
for (;;) construct - the
space normally required after the first semicolon is inhibited by the
second semicolon, the space after the second semicolon is inhibited by
the close parenthesis.
- Prefer double quotes, except in in-line event handlers or when quoting double quotes.
- Braces
are not indented relative to their parent statement. Stick to the style
used in existing files, but when creating new files you may choose your
favourite of the following acceptable constructs:
if (dlmgrWindow)
dlmgrWindow.focus();
else
dlmgr.open(window, null);
if (dlmgrWindow) {
dlmgrWindow.focus();
} else {
dlmgr.open(window, null);
}
if (dlmgrWindow) {
dlmgrWindow.focus();
}
else {
dlmgr.open(window, null);
}
if (dlmgrWindow)
{
dlmgrWindow.focus();
}
else
{
dlmgr.open(window, null);
}
- Use \uXXXX unicode constants for non-ASCII characters. The
character set for XUL, DTD, script and properties files is UTF-8 which
is not easily readable.
Code style
- Always put else on its own line, as shown above.
- Don't use else after return, i.e.
if (x < y)
return -1;
if (x > y)
return 1;
return 0;
- Both
i++ and ++i are acceptable.
- Name inline functions, this makes it easier to debug them. Just
assigning a function to a property doesn't name the function, you
should to do this:
var offlineObserver = {
observe: function offlineObserve(aSubject, aTopic, aState)
{
if (aTopic == "network:offline-status-changed")
setOfflineUI(aState == "offline");
}
}
Function and variable naming
- Use interCaps for names and enumeration values; other contstants should be in
UPPER_CASE.
- Convenience constants for interface names should be prefixed with
nsI, e.g.
const nsISupports = Components.interfaces.nsISupports;
const nsIWBN = Components.interfaces.nsIWebBrowserNavigation;
- Enumeration values should be prefixed with the letter
k, e.g. const kDisplayModeNormal = 0;.
- Global variables should be prefixed with the letter
g, e.g. var gFormatToolbar;.
- Arguments (parameter names) should be prefixed with the letter
a.
- Event handler functions should be prefixed with the word
on,
in particular try to use the names onLoad, onDialogAccept, onDialogCancel
etc. where this is unambiguous.
- Function names, local variables and object members have no prefix.
- Try to declare local variables as near to their use as possible;
try to initialize every variable.
JavaScript features
- Make sure that your code doesn't generate any strict JavaScript
warnings, such as:
- Duplicate variable declaration
- Mixing
return; with return value;
- Trailing comma in JavaScript object declarations
- Undeclared variables or members. If you are unsure if an
array value exists, compare the index to the array's length. If you are
unsure if an object member exists, use
"name" in aObject,
or if you are expecting a particular type you may use typeof aObject.name == "function" (or whichever type you are expecting).
- Use
[value, ...] to create a
JavaScript array in preference to using new Array(value,
...) which can be confusing; new Array(length)
will actually create a physically empty array with the given logical
length, while [value] will always
create a 1-element array. You cannot actually guarantee to be able to preallocate memory for an
array.
- Use
{
member: value, ... } to create a JavaScript object; a
useful advantage over new Object()
is the ability to create initial properties and use extended JavaScript
syntax to define getters and setters. If having defined a
constructor you need to assign default properties it is preferred to
assign an object literal to the prototype property. For example,
function SupportsString(data)
{
this.data = data;
}
SupportsString.prototype = {
toString: function toString()
{
return data;
}
};
- Use regular expressions, but use them wisely. For instance, to
check that aString is not completely whitespace use
/\S/.test(aString);
only use aString.search if you need to know the
position of the result, or aString.match if you
need to collect matching substrings (delimited by parentheses in the
regular expression). Regular expressions are less useful if the match
is unknown in advance, or to extract substrings in known positions in
the string. For instance, aString.slice(-1)
returns the last letter in aString, or the empty string if aString
is empty. - Don't compare booleans to true or false. For example, write
if (ioService.offline). Compare objects to null, numbers to 0 or strings to "" if there is chance for confusion.
- It's always worth reading the JavaScript 1.5 reference. For
instance, don't forget that you can index a string as if it was an
array.
XPConnect
- Don't use object methods and properties more than you have to. It is often faster to store the result in a temporary variable.
- Don't call methods that you don't have to. For instance, for a single window,
windowManager.getEnumerator(aType).hasMoreElements() may be replaced with windowManager.getMostRecentWindow(aType) != null.
- Don't query interfaces unless you need to access methods and
properties of that interface. You do not have to query interfaces to
compare objects, nor to pass objects as parameters. (Both these are
required in C++).
- Don't call QueryInterface unless you expect to succeed. Instead, use instanceof, e,g,:
if (target instanceof nsIRDFResource)
return target.Value;
if (target instanceof nsIRDFLiteral)
return target.Value;
return null;
- Don't test the return value of QueryInterface, it always returns
the original variable if it succeeds. XPConnect knows all about
tearoffs and modifies the object that you QueryInterface or
instanceof to cache all its known interfaces.
- Often when passing an object to an XPCOM method it is helpful if
the object you pass is an XPCOM object, so that the C++ method access a
C++ object. However this is not always necessary or desirable. For
instance the offline observer declared above is a JavaScript object
that is registered with an XPCOM object, so that the call back from
XPCOM executes the JavaScript method. Some XPCOM methods expect an
object that implements several interfaces thus requiring you to write a
QueryInterface method. However in JavaScript this is quite simple even
in the case of a weak reference which in C++ requires a helper class:
var weakObserver = {
QueryInterface: function QueryInterface(aIID) {
if (aIID.equals(Components.interfaces.nsIObserver) ||
aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
aIID.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_NOINTERFACE;
},
observe: function observe(aSubject, aTopic, aState) {
}
}
- When declaring XPCOM methods try to use the same names for method parameters as are used in the interface definition.
DOM Elements
- DOM Elements are just XPCOM objects with some of the interfaces precached.
- Don't call
getAttribute to see if an attribute exists, call hasAttribute instead.
- Prefer to loop through
childNodes rather than using first/lastChild with next/previousSibling. But prefer hasChildNodes() to childNodes.length > 0. Similarly prefer document.getElementsByTagName(aTag).item(0) != null to document.getElementsByTagName(aTag).length > 0.
- Prefer to use
localName rather than tagName.
- XUL elements have many of the attributes mapped to properties. This was done for a reason, so use them! The properties are:
id
align
dir
flex
orient
pack
observes
contextMenu
tooltip
width
height
minWidth
minHeight
maxWidth
maxHeight
persist
left
top
datasources
ref
tooltipText
statusText
allowEvents
- XUL also maps the
ordinal attribute but this defaults to "1" if it is not present.
- XUL also maps the
class attribute, but unfortunately class is a reserved identifier, so the property is named className. (The property could have been implemented as ['class'] but that just looks silly.)
- XUL also maps the
hidden and collapsed attributes to properties, but note that these are boolean properties whereas the above list are all string properties. - XUL also maps other useful properties and methods using XBL bindings; these vary from element to element.
- For best performance give ids to all important elements. However
in addition to locating elements by tag name XUL also allows you to
locate an element by attribute, starting at any element in the document.
- Don't forget to use DOM constants such as
event.keyCode == KeyEvent.DOM_VK_RETURN rather than event.keyCode == 13.