Please Name Your Functions Thursday, 5 July 2007

I have updated this entry to address the JScript FunctionExpression Identifier bug (see below).

Giving a name to a function allows anyone to inspect the stack trace in any debugger and see the function's name, not "anonymous" or "no name".

A named function has an identifier following the function keyword.

The ECMAScript production for a FunctionExpression is as follows:

function Identifieropt ( FormalParameterListopt ){ FunctionBody }

Example

    var foo = function foo() {
    
    };

It is just as important to name constructor functions, too.

Don't do this:

/** 
 * @class Board. Used for creating board games (like checkers).
 * @constructor
 * @param x {Number} - number of horizontal squares
 * @param y {Number} - number of vertical squares
 */
com.example.game.Board = function ( x, y ) {

};

Do this instead:

/** @constructor
 * @param x {Number} - number of horizontal squares
 * @param y {Number} - number of vertical squares
 */
com.example.game.Board = function Board( x, y ) {

};

It is just as important to name methods, too.

Object Literals

com.example.game.Board.prototype = {
    /** 
     * @return squares {Array} a copy of the game's squares Array.
     * Modifying the returned array will have no effect on the game itself.
     */
	getSquares : function getSquares() {
		return this.squares.concat();
	}
	
	// DO NOT DO THIS. getScore should point to a named function.
	,getScore : function /*name goes here*/() {
	
	}
	
	,toString : function boardToString() {
		return this.name + ", " + this.getScore();
	}
};

Browser Compatibility

Safari <= 2 cannot handle named functions in object literals and will fail on interpreting the perfectly valid JavaScript file (bug 4698). The Safari bug has been fixed.

Internet Explorer has the unwanted side effect also interpreting a FunctionExpression as a FunctionDeclaration to the containing scope.

Microsoft is violating the ECMA-262 spec (pdf) (Citation of ECMA Spec: 1).

JScript FunctionExpression Identifier bug

The problem can be explained with an example:

    var meth = function ident() {
  // The identifier 'ident' may be legally referenced from within the function.
       alert( ident == meth ); // true, (actually false in IE);
    };
  // The variable 'ident' must not be affected by the function identifier.
    alert( ident ); // must be undefined, but not in IE.

The above examples illustrate that IE will leak a function declaration in the containing scope.

This seems to be due to faulty lexical parsing on the first pass. This is further evidenced by the following example that illustrates JScript overriding the first function declaration with the second.
if (true) {
  function f() { return true; }
} else {
  function f() { return false; }
}
// IE will choose "false" returning function.
alert('Conditionally Delcared Function: ' + f()); 

However, a FunctionExpression is not parsed on the first pass, and is correctly parsed in JScript (and JavaScript, of course).

var f;
if (true) {
  f = function () { return true; };
} else {
  f = function () { return false; };
}
// IE chooses the 'true' returning function this time.
msg('Conditional FunctionExpression: ' + f()); 

How to handle the JScript FunctionExpression Identifier bug?

To avoid unwanted side effects, either:

  1. Do not use an identifier, or
  2. wrap the code that uses identifiers in anonymous function

Example

(function(){ // Enclosing scope.

com.example.game.Board = function Board( x, y ) {
  ...
};
com.example.game.Board.prototype = {
        getSquares : function getSquares() {
            return this.squares.concat(); // defensive copy.
        }
};
})();

The above example will cause a scope to be created to "catch" a FunctionDeclaration that can be created in Microsoft JScript.

Consider Other Developers

You might be thinking: "It's not that big a deal, I can figure my code out." But will someone else be able to figure out a stack trace of "no name" functions in Firebug?

If You're Writing Library Code

You should name your functions.

It can be very difficult to pinpoint the actual function in the source code from a stack trace that contains anonymous functions referring to external library code that you did not write. When the code uses subclassing, it is even harder to find the actual method that caused the problem.

I sometimes have to resort to calling toString() on the function object in the debugger, copying that text, then searching for that text in my IDE. It works, but nobody should have to resort to those types of tactics.

Wouldn't it make more sense to just name the function in the first place? Something about an ounce of prevention...

If You're Writing Implementation Code

You should name your functions.

Most errors will start out in implementation code. If the first error in the stack trace is anonymous, it will be harder to debug and take more time.

Summary

Naming your functions is an easy way to make your code more explicit and easier to debug. Due to problems in Microsoft's JScript engine, FunctionDeclarations and FunctionExpressions with an Identifier must be used with care.

References

  1. From the EMCA-262 specification.

    NOTE

    The Identifier in a FunctionExpression can be referenced from inside the FunctionExpression's FunctionBody to allow the function to call itself recursively. However, unlike in a FunctionDeclaration, the Identifier in a FunctionExpression cannot be referenced from and does not affect the scope enclosing the FunctionExpression.

  2. ECMAScript's FunctionDeclaration versus FunctionExpression, by Hallvord R. M. Steen.
  3. Core JavaScript 1.5 Reference:Functions

Technorati Tags:

Posted by default at 2:29 PM in Debugging

 

Comment: Frank Manno at Fri, 6 Jul 6:26 PM

Garrett,

Awesome article. I can't tell you how many times I've scratched my head trying to figure out where a problem was coming from. I'll keep this in mind and be sure to follow your suggestions.

What question I had was when naming your functions, which function do you use when calling that function? In the case of the "toString" function in the Board class:


toString : function boardToString()


Do you call toString() or boardToString() ?

Comment: Frank Manno at Fri, 6 Jul 6:27 PM

PS: Something's funky with the formatting... I think it happens right after the first code example at the start of the article.

Comment: Garrett at Fri, 6 Jul 6:49 PM

If I am stepping through code and I see a function that has a problem, I might want to see the caller.

I can insert a watch.


caller


If the function is anonymous, then I can use

caller.toString()



and then search in the IDE.

About the formatting: I had to disable smilies. that is too bad, but it appears that smilies plugin was causing a problem :(

 

*AnimTree
*Tabs
*GlideMenus
*DragLib