Javascript and Firefox

From UIDWiki

Jump to: navigation, search

Contents

Books

Javascript: the Definitive Guide by David Flanagan is the best reference source about Javascript. It covers the core language, using Javascript in HTML, and DOM and CSS. We have a copy floating around the lab.

Articles

Javascript closures explains in more detail how Javascript resolves name references, and closures (functions that carry their static environment with them) are actually implemented in Javascript.

Using XPCOM in JavaScript without leaking explains some important details of Firefox internals that help you prevent Firefox from consuming more and more and more memory as you use it.

Mozilla XPath Documentation explains, briefly, the ins and outs of using XPath in Firefox which you can think of like a hybrid between css selectors and javascript treeWalkers.

New in JavaScript 1.7 describes the new features of Javascript that will be available in Firefox 2.0, which is already (as of July 2006) in beta. New features include iterators (with a for...each construct), destructuring assignments (e.g. var [a,b,c] = array), and real block scoping for variables.

Traps and Pitfalls

Undeclared variables are put into the global namespace by default. If you assign to a variable without declaring it locally with var, then you're making it a global variable. For example:

 function f() {
   x = 5; // x wasn't declared locally, so this sets a global variable
 }
 f();
 output(x); // f() affected the global namespace


The scope of a local variable is the entire function in which it's declared. That's a big difference from Java and C++, in which the scope of a variable runs from its declaration to the next terminating curly brace. Here are some instructive examples you can try in Chickenfoot:

 // first, here's what happens when a variable isn't in scope, just so you can compare
 function f() {
   output(x); // throws an error complaining that x is not defined
 }
 f();
 // here's an example showing that local variables are in scope before their declaration too
 function f() {
   output(x);  // displays "undefined", since x is in scope here 
               // (it wouldn't be in Java), but it hasn't been assigned yet
   var x = 5;
 }
 f();
 // and here's one showing that a local variable in scope AFTER the block containing its declaration
 function f() {
   {
     var x = 5;
   }
   output(x); // displays 5, since x is still in scope here. (wouldn't be in Java)
 }
 f();

Multiple declarations of the same variable or function are quietly accepted. For example:

 // all the x's in the code below represent the same variable
 function f() {
   var x = 5;
   var x; // no complaint
 
   for (var x = 0; x < 10; ++x) {} // no complaint
   for (var x = 0; x < 20; ++x) {} // no complaint
   output(x); // displays 20
 }

This is even worse when the variables or functions are in different source files:

 // A.js
 function f() {
   ...
 }
 // B.js
 function f() {
   ...
 }

Either A.js or B.js will win, whichever is loaded last. Name your functions carefully.


Using nested functions and native C++ XPCOM objects at the same time can lead to memory leaks. Here's a seemingly harmless example from the article on memory leaks linked above:

     function findRadioButtons(/*Node*/ root) {

          // nested filter function
          function _filterRadioGroup(aNode) {
            switch (aNode.localName) {
              case "radio": return NodeFilter.FILTER_ACCEPT;
              case "template":
              case "radiogroup": return NodeFilter.FILTER_REJECT;
              default: return NodeFilter.FILTER_SKIP;
            }
          }

          // make a TreeWalker, which happens to be a C++ XPCOM object
          var iterator = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, _filterRadioGroup, true);
          while (iterator.nextNode())
            radioChildren.push(iterator.currentNode);
 
          return this.mRadioChildren = radioChildren;
     }

Here's the problem: the _filterRadioGroup function is a closure, which means it has references to all the local variables in the function alive, including iterator. The iterator variable is a reference to a TreeWalker object, which is a (reference-counted) C++ XPCOM object. And that TreeWalker object has a reference to the _filterRadioGroup function that it needs to filter the iteration. So we've created a reference cycle -- which wouldn't hurt if we had garbage collection (as Java and Javascript do), but C++ XPCOM does not -- it uses reference counting instead. With reference counting, a reference cycle like this will never be freed, so it will consume more memory each time findRadioButtons is called.

The solution is to move _filterRadioGroup outside findRadioButtons.

Tips and Tricks

Use local functions as utility functions. Javascript allows functions to be nested inside other functions, with static scoping just like Scheme or Pascal, so the nested function can access any of its parent's local variables or functions. This is very handy when you want to need a utility function in only one place. (It's also used for creating closures -- see the article linked above.) Here's an example:

 function prettyPrint(/*Node*/ node) {
   printIndented(node, 0);

   function printIndented(/*Node*/ node, /*int*/ indent) {
     output(getIndent(indent) + node.tagName);
     for (var i = 0; i < node.childNodes.length; ++i) {
       printIndented(node.childNodes[i], indent+2);
     }
   }

   function getIndent(/*int*/ indent) {
     var s = "";
     while (indent) {
       s+=' ';
       --indent; 
     }
   }
 }
Personal tools