JavaScript Tips

  • Revision slug: JavaScript_Tips
  • Revision title: JavaScript Tips
  • Revision id: 77655
  • Created:
  • Creator: Dietrich
  • Is current revision? No
  • Comment /* Code style */

Revision Content

Introduction

This guide is initially intended for code in {{wiki.template('Named-source', [ "browser", "mozilla/browser" ])}}.

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.

This is based on Neil's guide, and should be updated by code reviewers to match contemporary styles.

Whitespace

  • The basic indentation 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. See {{template.Anch("Indentation examples")}}.
  • 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 a function's opening brace must be on the same line as the declaration, and the closing brace on it's 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();

if (dlmgrWindow) {
  dlmgrWindow.focus();
}
else {
  dlmgr.open(window, null);
}
  • Use \uXXXX unicode escapes 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 OO_observe(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. 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 {{mediawiki.external('value1, value2')}} to create a JavaScript array in preference to using new Array(value1, value2) which can be confusing, as new Array(length) will actually create a physically empty array with the given logical length, while {{mediawiki.external('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 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 of 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 {{mediawiki.external('\'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.

Indentation examples

Examples of valid indentation:

var result = prompt(aMessage,
                    aInitialValue,
                    aCaption);

var IOService = Components.classes["@mozilla.org/network/io-service;1"]
                          .getService(Components.interfaces.nsIIOService);

Valid styles of XPCOM component initialization:

var IOService = Components.classes["@mozilla.org/network/io-service;1"]
                          .getService(Components.interfaces.nsIIOService);

const Ci = Components.interfaces;
const Cc = Components.classes;

var IOService = Cc["@mozilla.org/network/io-service;1"].
                getService(Ci.nsIIOService);

var IOService = Cc["@mozilla.org/network/io-service;1"].
                  getService(Ci.nsIIOService);

Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService)
                                   .logStringMessage(aMsg);

Tools

  • JST Review - While intended for C++, this provides coverage for some basic JS rules (tabs, line-length, etc.)

References

Revision Source

<p>
</p>
<h2 name="Introduction"> Introduction </h2>
<p>This guide is initially intended for code in {{wiki.template('Named-source', [ "browser", "mozilla/browser" ])}}.
</p><p>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.
</p><p>This is based on <a class="external" href="http://neil.rashbrook.org/Js.htm">Neil's guide</a>, and should be updated by code reviewers to match contemporary styles.
</p>
<h2 name="Whitespace"> Whitespace </h2>
<ul><li> The basic indentation is two spaces. Tabs are not to be used at all.
</li><li> 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. See {{template.Anch("Indentation examples")}}.
</li><li> Lines should not contain trailing spaces, even after binary operators, commas or semicolons.
</li><li> Separate binary operators with spaces.
</li><li> Spaces after commas and semicolons, but not before.
</li><li> Spaces after keywords, e.g. <code>if (x &gt; 0)</code>.
</li><li> One (or two) blank lines between block definitions. Also consider breaking up large code blocks with blank lines.
</li><li> End the file with a newline. (This applies mainly to emacs users.)
</li></ul>
<h2 name="Symbols"> Symbols </h2>
<ul><li> Spaces around braces used for in-line functions or objects, except before commas or semicolons, e.g.
</li></ul>
<pre class="eval">function valueObject(aValue) { return { value: aValue }; }
</pre>
<ul><li> Otherwise a function's opening brace must be on the same line as the declaration, and the closing brace on it's own line, i.e.
</li></ul>
<pre>function toOpenWindow(aWindow) {
  aWindow.document.commandDispatcher.focusedWindow.focus();
}
</pre>
<ul><li> 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 <code>for (;;)</code> 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.
</li><li> Prefer double quotes, except in in-line event handlers or when quoting double quotes.
</li><li> 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:
</li></ul>
<pre>if (dlmgrWindow)
  dlmgrWindow.focus();

if (dlmgrWindow) {
  dlmgrWindow.focus();
}
else {
  dlmgr.open(window, null);
}
</pre>
<ul><li> Use <a href="en/Core_JavaScript_1.5_Guide/Unicode"><code>\uXXXX</code> unicode escapes</a> for non-ASCII characters. The character set for XUL, DTD, script, and properties files is UTF-8 which is not easily readable.
</li></ul>
<h2 name="Code_style"> Code style </h2>
<ul><li> Always put else on its own line, as shown above.
</li><li> Don't use <code>else</code> after <code>return</code>, i.e.
</li></ul>
<pre>if (x &lt; y)
  return -1;
if (x &gt; y)
  return 1;
return 0;
</pre>
<ul><li> Both <code>i++</code> and <code>++i</code> are acceptable.
</li><li> 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:
</li></ul>
<pre>var offlineObserver = {
  observe: function OO_observe(aSubject, aTopic, aState)
  {
    if (aTopic == "network:offline-status-changed")
      setOfflineUI(aState == "offline");
  }
};
</pre>
<h2 name="Function_and_variable_naming"> Function and variable naming </h2>
<ul><li> Use interCaps for names and enumeration values; other contstants should be in UPPER_CASE.
</li><li> Convenience constants for interface names should be prefixed with nsI, e.g. 
</li></ul>
<pre class="eval">const nsISupports = Components.interfaces.nsISupports;
const nsIWBN = Components.interfaces.nsIWebBrowserNavigation;
</pre>
<ul><li> Enumeration values should be prefixed with the letter k, e.g. <code>const kDisplayModeNormal = 0;</code>.
</li><li> Global variables should be prefixed with the letter g, e.g. <code>gFormatToolbar</code>.
</li><li> Arguments (parameter names) should be prefixed with the letter a.
</li><li> Event handler functions should be prefixed with the word on, in particular try to use the names <code>onLoad</code>, <code>onDialogAccept</code>, <code>onDialogCancel</code>, etc., where this is unambiguous.
</li><li> Function names, local variables, and object members have no prefix.
</li><li> Try to declare local variables as near to their use as possible; try to initialize every variable.
</li></ul>
<h2 name="JavaScript_features"> JavaScript features </h2>
<ul><li> Make sure that your code doesn't generate any strict JavaScript warnings, such as:
<ul><li> Duplicate variable declaration
</li><li> Mixing <code>return;</code> with <code>return value;</code>
</li><li> Trailing comma in JavaScript object declarations
</li><li> 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 <code>"name" in aObject</code>, or if you are expecting a particular type you may use <code>typeof aObject.name == "function"</code> (or whichever type you are expecting).
</li></ul>
</li><li> Use <code>{{mediawiki.external('value1, value2')}}</code> to create a JavaScript array in preference to using <code>new Array(value1, value2)</code> which can be confusing, as <code>new Array(length)</code> will actually create a physically empty array with the given logical length, while <code>{{mediawiki.external('value')}}</code> will always create a 1-element array. You cannot actually guarantee to be able to preallocate memory for an array.
</li><li> Use <code>{ member: value, ... }</code> to create a JavaScript object; a useful advantage over <code>new Object()</code> 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,
</li></ul>
<pre>function SupportsString(data)
{
  this.data = data;
}
SupportsString.prototype = {
  toString: function toString()
  {
    return data;
  }
};
</pre>
<ul><li> Use regular expressions, but use them wisely. For instance, to check that <code>aString</code> is not completely whitespace use <code>/\S/.test(aString);</code> only use <code>aString.search</code> if you need to know the position of the result, or <code>aString.match</code> 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, <code>aString.slice(-1)</code> returns the last letter in <code>aString</code>, or the empty string if <code>aString</code> is empty.
</li><li> Don't compare booleans to <code>true</code> or <code>false</code>. For example, write <code>if (ioService.offline)</code>. Compare objects to <code>null</code>, numbers to <code>0</code> or strings to <code>""</code> if there is chance for confusion.
</li><li> It's always worth reading the <a href="en/Core_JavaScript_1.5_Reference">JavaScript reference</a>. For instance, don't forget that you can index a string as if it was an array.
</li></ul>
<h2 name="XPConnect"> XPConnect </h2>
<ul><li> Don't use object methods and properties more than you have to. It is often faster to store the result in a temporary variable.
</li><li> Don't call methods that you don't have to. For instance, for a single window, <code>windowManager.getEnumerator(aType).hasMoreElements()</code> may be replaced with <code>windowManager.getMostRecentWindow(aType) != null</code>.
</li><li> 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 of these are required in C++).
</li><li> Don't call <code>QueryInterface</code> unless you expect to succeed. Instead, use <code>instanceof</code>, e,g,:
</li></ul>
<pre>if (target instanceof nsIRDFResource)
  return target.Value;
if (target instanceof nsIRDFLiteral)
  return target.Value;
return null;
</pre>
<ul><li> Don't test the return value of <code>QueryInterface</code>, it always returns the original variable if it succeeds. XPConnect knows all about tearoffs and modifies the object that you <code>QueryInterface</code> or <code>instanceof</code> to cache all its known interfaces.
</li><li> 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 <code>QueryInterface</code> method. However in JavaScript this is quite simple even in the case of a weak reference which in C++ requires a helper class:
</li></ul>
<pre>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) {
  }
}
</pre>
<ul><li> When declaring XPCOM methods try to use the same names for method parameters as are used in the interface definition.
</li></ul>
<h2 name="DOM_elements"> DOM elements </h2>
<ul><li> DOM elements are just XPCOM objects with some of the interfaces precached.
</li><li> Don't call <a href="en/DOM/element.getAttribute">getAttribute</a> to see if an attribute exists, call <a href="en/DOM/element.hasAttribute">hasAttribute</a> instead.
</li><li> Prefer to loop through childNodes rather than using first/lastChild with next/previousSibling. But prefer hasChildNodes() to <code>childNodes.length &gt; 0</code>. Similarly prefer <code>document.getElementsByTagName(aTag).item(0) != null</code> to <code>document.getElementsByTagName(aTag).length &gt; 0</code>.
</li><li> Prefer to use localName rather than tagName.
</li><li> XUL elements have many of the attributes mapped to properties. This was done for a reason, so use them! The properties are:
<ul><li> id
</li><li> align
</li><li> dir
</li><li> flex
</li><li> orient
</li><li> pack
</li><li> observes
</li><li> contextMenu
</li><li> tooltip
</li><li> width
</li><li> height
</li><li> minWidth
</li><li> minHeight
</li><li> maxWidth
</li><li> maxHeight
</li><li> persist
</li><li> left
</li><li> top
</li><li> datasources
</li><li> ref
</li><li> tooltipText
</li><li> statusText
</li><li> allowEvents
</li></ul>
</li><li> XUL also maps the <code>ordinal</code> attribute but this defaults to "1" if it is not present.
</li><li> XUL also maps the <code>class</code> attribute, but unfortunately <code>class</code> is a reserved identifier, so the property is named <code>className</code>. (The property could have been implemented as <code>{{mediawiki.external('\'class\'')}}</code> but that just looks silly.)
</li><li> XUL also maps the <code>hidden</code> and <code>collapsed</code> attributes to properties, but note that these are boolean properties whereas the above list are all string properties.
</li><li> XUL also maps other useful properties and methods using XBL bindings; these vary from element to element.
</li><li> 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.
</li><li> Don't forget to use DOM constants such as <code>event.keyCode == KeyEvent.DOM_VK_RETURN</code> rather than <code>event.keyCode == 13</code>.
</li></ul>
<h2 name="Indentation_examples"> Indentation examples </h2>
<p>Examples of valid indentation:
</p>
<pre>var result = prompt(aMessage,
                    aInitialValue,
                    aCaption);

var IOService = Components.classes["@mozilla.org/network/io-service;1"]
                          .getService(Components.interfaces.nsIIOService);
</pre>
<p>Valid styles of XPCOM component initialization:
</p>
<pre>var IOService = Components.classes["@mozilla.org/network/io-service;1"]
                          .getService(Components.interfaces.nsIIOService);

const Ci = Components.interfaces;
const Cc = Components.classes;

var IOService = Cc["@mozilla.org/network/io-service;1"].
                getService(Ci.nsIIOService);

var IOService = Cc["@mozilla.org/network/io-service;1"].
                  getService(Ci.nsIIOService);

Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService)
                                   .logStringMessage(aMsg);
</pre>
<h2 name="Tools"> Tools </h2>
<ul><li> <a class="external" href="http://beaufour.dk/jst-review/">JST Review</a> - While intended for C++, this provides coverage for some basic JS rules (tabs, line-length, etc.)
</li></ul>
<h2 name="References"> References </h2>
<ul><li> This was started as a reprint of <a class="external" href="http://neil.rashbrook.org/Js.htm">Neil's guide</a>
</li><li> Some more current info on this <a class="external" href="http://dietrich.ganx4.com/blog/?p=226">blog post</a>
</li></ul>
Revert to this revision