Introduction

Surprisingly, a topic of named function expressions doesn’t seem to be covered well enough on the web. This is probably why there are so many misconceptions floating around. In this article, I’ll try to summarize both — theoretical and practical aspects of these wonderful Javascript constructs; the good, bad and ugly parts of them.

In a nutshell, named function expressions are useful for one thing only — descriptive function names in debuggers and profilers. Well, there is also a possibility of using function names for recursion, but you will soon see that this is often impractical nowadays. If you don’t care about debugging experience, you have nothing to worry about. Otherwise, read on to see some of the cross-browser glitches you would have to deal with and tips on how work around them.

I’ll start with a general explanation of what function expressions are how modern debuggers handle them. Feel free to skip to a final solution, which explains how to use these constructs safely.

Function expressions vs. Function declarations

One of the two most common ways to create a function object in ECMAScript is by means of either Function Expression or Function Declaration. The difference between two is rather confusing. At least it was to me. The only thing ECMA specs make clear is that Function Declaration must always have an Identifier (or a function name, if you prefer), and Function Expression may omit it:

FunctionDeclaration :
function Identifier ( FormalParameterList opt ){ FunctionBody }

FunctionExpression :
function Identifier opt ( FormalParameterList opt ){ FunctionBody }

We can see that when identifier is omitted, that “something” can only be an expression. But what if identifier is present? How can one tell whether it is a function declaration or a function expression — they look identical after all? It appears that ECMAScript differentiates between two based on a context. If a function foo(){} is part of, say, assignment expression, it is considered a function expression. If, on the other hand, function foo(){} is contained in a function body or in a (top level of) program itself — it is parsed as a function declaration.

function foo(){} // declaration, since it's part of a <em>Program</em>
var bar = function foo(){}; // expression, since it's part of an <em>AssignmentExpression</em> new function bar(){}; // expression, since it's part of a <em>NewExpression</em> (function(){
function bar(){} // declaration, since it's part of a <em>FunctionBody</em>
})();

  

A somewhat less obvious example of function expression is the one where function is wrapped with parenthesis — (function foo(){}). The reason it is an expression is again due to a context: "(" and ")" constitute a grouping operator and grouping operator can only contain an expression:

To demonstrate with examples:

function foo(){} // function declaration
(function foo(){}); // function expression: due to grouping operator try {
(var x = 5); // grouping operator can only contain expression, not a statement (which `var` is)
} catch(err) {
// SyntaxError
}

  

You might also recall that when evaluating JSON with eval, the string is usually wrapped with parenthesis — eval('(' + json + ')'). This is of course done for the same reason — grouping operator, which parenthesis are, forces JSON brackets to be parsed as expression rather than as a block:

try {
{ "x": 5 }; // "{" and "}" are parsed as a block
} catch(err) {
// SyntaxError
} ({ "x": 5 }); // grouping operator forces "{" and "}" to be parsed as object literal

  

There’s a subtle difference in behavior of declarations and expressions.

First of all, function declarations are parsed and evaluated before any other expressions are. Even if declaration is positioned last in a source, it will be evaluated foremost any other expressions contained in a scope. The following example demonstrates how fn function is already defined by the time alert is executed, even though it’s being declared right after it:

alert(fn());

function fn() {
return 'Hello world!';
}

  

Another important trait of function declarations is that declaring them conditionally is non-standardized and varies across different environments. You should never rely on functions being declared conditionally and use function expressions instead.

// Never do this!
// Some browsers will declare `foo` as the one returning 'first',
// while others — returning 'second' if (true) {
function foo() {
return 'first';
}
}
else {
function foo() {
return 'second';
}
}
foo(); // Instead, use function expressions:
var foo;
if (true) {
foo = function() {
return 'first';
};
}
else {
foo = function() {
return 'second';
};
}
foo();

  

If you're curious about actual production rules of function declarations, read on. Otherwise, feel free to skip the following excerpt.

FunctionDeclarations are only allowed to appear in Program or FunctionBody. Syntactically, they can not appear in Block ({ ... }) — such as that of ifwhile or forstatements. This is because Blocks can only contain Statements, not SourceElements, which FunctionDeclaration is. If we look at production rules carefully, we can see that the only way Expression is allowed directly within Block is when it is part of ExpressionStatement. However, ExpressionStatement is explicitly defined to not begin with "function" keyword, and this is exactly why FunctionDeclaration cannot appear directly within a Statement or Block (note that Block is merely a list of Statements).

Because of these restrictions, whenever function appears directly in a block (such as in the previous example) it should actually be considered a syntax error, not function declaration or expression. The problem is that almost none of the implementations I've seen parse these functions strictly per rules (exceptions are BESEN and DMDScript). They interpret them in proprietary ways instead.

It's worth mentioning that as per specification, implementations are allowed to introduce syntax extensions (see section 16), yet still be fully conforming. This is exactly what happens in so many clients these days. Some of them interpret function declarations in blocks as any other function declarations — simply hoisting them to the top of the enclosing scope; Others — introduce different semantics and follow slightly more complex rules.

Function statements

One of such syntax extensions to ECMAScript is Function Statements, currently implemented in Gecko-based browsers (tested in Firefox 1-3.7a1pre on Mac OS X). Somehow, this extension doesn't seem to be widely known, either for good or bad (MDC mentions them, but very briefly). Please remember, that we are discussing it here only for learning purposes and to satisfy our curiosity; unless you're writing scripts for specific Gecko-based environment, I do not recommend relying on this extension.

So, here are some of the traits of these non-standard constructs:

Function statements are allowed to be anywhere where plain Statements are allowed. This, of course, includes Blocks:

if (true) {
function f(){ }
}
else {
function f(){ }
}
  1. Function statements are interpreted as any other statements, including conditional execution:

    if (true) {
    function foo(){ return 1; }
    }
    else {
    function foo(){ return 2; }
    }
    foo(); // 1
    // Note that other clients interpet `foo` as function declaration here,
    // overwriting first `foo` with the second one, and producing "2", not "1" as a result
  2. Function statements are NOT declared during variable instantiation. They are declared at run time, just like function expressions. However, once declared, function statement's identifier becomes available to the entire scope of the function. This identifier availability is what makes function statements different from function expressions (you will see exact behavior of named function expressions in next chapter).
    // at this point, `foo` is not yet declared
    typeof foo; // "undefined"
    if (true) {
    // once block is entered, `foo` becomes declared and available to the entire scope
    function foo(){ return 1; }
    }
    else {
    // this block is never entered, and `foo` is never redeclared
    function foo(){ return 2; }
    }
    typeof foo; // "function"

    Generally, we can emulate function statements behavior from the previous example with this standards-compliant (and unfortunately, more verbose) code:

    var foo;
    if (true) {
    foo = function foo(){ return 1; };
    }
    else {
    foo = function foo() { return 2; };
    }
  3. String representation of functions statements is similar to that of function declarations or named function expressions (and includes identifier — "foo" in this example):
    if (true) {
    function foo(){ return 1; }
    }
    String(foo); // function foo() { return 1; }
  4. Finally, what appears to be a bug in earlier Gecko-based implementations (present in <= Firefox 3), is the way function statements overwrite function declarations. Earlier versions were somehow failing to overwrite function declarations with function statements:
    // function declaration
    function foo(){ return 1; }
    if (true) {
    // overwritting with function statement
    function foo(){ return 2; }
    }
    foo(); // 1 in FF<= 3, 2 in FF3.5 and later // however, this doesn't happen when overwriting function expression
    var foo = function(){ return 1; };
    if (true) {
    function foo(){ return 2; }
    }
    foo(); // 2 in all versions

Note that older Safari (at least 1.2.3, 2.0 - 2.0.4 and 3.0.4, and possibly earlier versions too) implement function statements identically to SpiderMonkey. All examples from this chapter, except the last "bug" one, produce same results in those versions of Safari as they do in, say, Firefox. Another browser that seems to follow same semantics is Blackberry one (at least 8230, 9000 and 9530 models). This diversity in behavior demonstrates once again what a bad idea it is to rely on these extensions.

Named function expressions

Function expressions can actually be seen quite often. A common pattern in web development is to “fork” function definitions based on some kind of a feature test, allowing for the best performance. Since such forking usually happens in the same scope, it is almost always necessary to use function expressions. After all, as we know by now, function declarations should not be executed conditionally:

// `contains` is part of "APE Javascript library" (http://dhtmlkitchen.com/ape/) by Garrett Smith
var contains = (function() {
var docEl = document.documentElement; if (typeof docEl.compareDocumentPosition != 'undefined') {
return function(el, b) {
return (el.compareDocumentPosition(b) & 16) !== 0;
};
}
else if (typeof docEl.contains != 'undefined') {
return function(el, b) {
return el !== b && el.contains(b);
};
}
return function(el, b) {
if (el === b) return false;
while (el != b && (b = b.parentNode) != null);
return el === b;
};
})();

Quite obviously, when a function expression has a name (technically — Identifier), it is called a named function expression. What you’ve seen in the very first example — var bar = function foo(){}; — was exactly that — a named function expression with foo being a function name. An important detail to remember is that this name is only available in the scope of a newly-defined function; specs mandate that an identifier should not be available to an enclosing scope:

var f = function foo(){
return typeof foo; // "foo" is available in this inner scope
};
// `foo` is never visible "outside"
typeof foo; // "undefined"
f(); // "function"

So what’s so special about these named function expressions? Why would we want to give them names at all?

It appears that named functions make for a much more pleasant debugging experience. When debugging an application, having a call stack with descriptive items makes a huge difference.

Function names in debuggers

When a function has a corresponding identifier, debuggers show that identifier as a function name, when inspecting call stack. Some debuggers (e.g. Firebug) helpfully show names of even anonymous functions — making them identical to names of variables that functions are assigned to. Unfortunately, these debuggers usually rely on simple parsing rules; Such extraction is usually quite fragile and often produces false results.

Let’s look at a simple example:

function foo(){
return bar();
}
function bar(){
return baz();
}
function baz(){
debugger;
}
foo(); // Here, we used function declarations when defining all of 3 functions
// When debugger stops at the `debugger` statement,
// the call stack (in Firebug) looks quite descriptive:
baz
bar
foo
expr_test.html()

We can see that foo called bar which in its turn called baz (and that foo itself was called from the global scope of expr_test.html document). What’s really nice, is that Firebug manages to parse the “name” of a function even when an anonymous expression is used:

function foo(){
return bar();
}
var bar = function(){
return baz();
}
function baz(){
debugger;
}
foo(); // Call stack
baz
bar()
foo
expr_test.html()

What’s not very nice, though, is that if a function expression gets any more complex (which, in real life, it almost always is) all of the debugger’s efforts turn out to be pretty useless; we end up with a shiny question mark in place of a function name:

function foo(){
return bar();
}
var bar = (function(){
if (window.addEventListener) {
return function(){
return baz();
};
}
else if (window.attachEvent) {
return function() {
return baz();
};
}
})();
function baz(){
debugger;
}
foo(); // Call stack
baz
(?)()
foo
expr_test.html()

Another confusion appears when a function is being assigned to more than one variable:

function foo(){
return baz();
}
var bar = function(){
debugger;
};
var baz = bar;
bar = function() {
alert('spoofed');
};
foo(); // Call stack:
bar()
foo
expr_test.html()

You can see call stack showing that foo invoked bar. Clearly, that’s not what has happened. The confusion is due to the fact that baz was “exchanged” references with another function — the one alerting “spoofed”. As you can see, such parsing — while great in simple cases — is often useless in any non-trivial script.

What it all boils down to is the fact that named function expressions is the only way to get a truly robust stack inspection. Let’s rewrite our previous example with named functions in mind. Notice how both of the functions returning from self-executing wrapper, are named as bar:

function foo(){
return bar();
}
var bar = (function(){
if (window.addEventListener) {
return function bar(){
return baz();
};
}
else if (window.attachEvent) {
return function bar() {
return baz();
};
}
})();
function baz(){
debugger;
}
foo(); // And, once again, we have a descriptive call stack!
baz
bar
foo
expr_test.html()

Before we start dancing happily celebrating this holy grail finding, I’d like to bring a beloved JScript into the picture.

JScript bugs

Unfortunately, JScript (i.e. Internet Explorer’s ECMAScript implementation) seriously messed up named function expressions. JScript is responsible for named function expressions being recommended against by many people these days. It's also quite sad that even last version of JScript — 5.8 — used in Internet Explorer 8, still exhibits every single quirk described below

Let’s look at what exactly is wrong with its broken implementation. Understanding all of its issues will allow us to work around them safely. Note that I broke these discrepancies into few examples — for clarity — even though all of them are most likely a consequence of one major bug.

Example #1: Function expression identifier leaks into an enclosing scope

var f = function g(){};
typeof g; // "function"

Remember how I mentioned that an identifier of named function expression is not available in an enclosing scope? Well, JScript doesn’t agree with specs on this one — g in the above example resolves to a function object. This is a most widely observed discrepancy. It’s dangerous in that it inadvertedly pollutes an enclosing scope — a scope that might as well be a global one — with an extra identifier. Such pollution can, of course, be a source of hard-to-track bugs.

Example #2: Named function expression is treated as BOTH — function declaration AND function expression

typeof g; // "function"
var f = function g(){};

As I explained before, function declarations are parsed foremost any other expressions in a particular execution context. The above example demonstrates how JScript actually treats named function expressions as function declarations. You can see that it parses g before an “actual declaration” takes place.

This brings us to a next example:

Example #3: Named function expression creates TWO DISTINCT function objects!

var f = function g(){};
f === g; // false f.expando = 'foo';
g.expando; // undefined

This is where things are getting interesting. Or rather — completely nuts. Here we are seeing the dangers of having to deal with two distinct objects — augmenting one of them obviously does not modify the other one; This could be quite troublesome if you decided to employ, say, caching mechanism and store something in a property of f, then tried accessing it as a property of g, thinking that it is the same object you’re working with.

Let’s look at something a bit more complex.

Example #4: Function declarations are parsed sequentially and are not affected by conditional blocks

var f = function g() {
return 1;
};
if (false) {
f = function g(){
return 2;
};
}
g(); // 2

An example like this could cause even harder to track bugs. What happens here is actually quite simple. First, g is being parsed as a function declaration, and since declarations in JScript are independent of conditional blocks, g is being declared as a function from the “dead” if branch — function g(){ return 2 }. Then all of the “regular” expressions are being evaluated and f is being assigned another, newly created function object to. “dead” if branch is never entered when evaluating expressions, so f keeps referencing first function — function g(){ return 1 }. It should be clear by now, that if you’re not careful enough, and call g from within f, you’ll end up calling a completely unrelated gfunction object.

You might be wondering how all this mess with different function objects compares to arguments.callee. Does callee reference f or g? Let’s take a look:

var f = function g(){
return [
arguments.callee == f,
arguments.callee == g
];
};
f(); // [true, false]
g(); // [false, true]

As you can see, arguments.callee references whatever function is being invoked. This is actually good news, as you will see later on.

Another interesting example of "unexpected behavior" can be observed when using named function expression in undeclared assignment, but only when function is "named" the same way as identifier it's being assigned to:

(function(){
f = function f(){};
})();

As you might know, undeclared assignment (which is not recommended and is only used here for demonstration purposes) should result in creation of global f property. This is exactly what happens in conforming implementations. However, JScript bug makes things a bit more confusing. Since named function expression is parsed as function declaration (see example #2), what happens here is that f becomes declared as a local variable during the phase of variable declarations. Later on, when function execution begins, assignment is no longer undeclared, so function f(){} on the right hand side is simply assigned to this newly created local f variable. Global f is never created.

This demonstrates how failing to understand JScript peculiarities can lead to drastically different behavior in code.

Looking at JScript deficiencies, it becomes pretty clear what exactly we need to avoid. First, we need to be aware of a leaking identifier (so that it doesn’t pollute enclosing scope). Second, we should never reference identifier used as a function name; A troublesome identifier is g from the previous examples. Notice how many ambiguities could have been avoided if we were to forget about g’s existance. Always referencing function via f or arguments.callee is the key here. If you use named expression, think of that name as something that’s only being used for debugging purposes. And finally, a bonus point is to always clean up an extraneous function created erroneously during NFE declaration.

I think last point needs a bit of an explanation:

JScript memory management

Being familiar with JScript discrepancies, we can now see a potential problem with memory consumption when using these buggy constructs. Let’s look at a simple example:

var f = (function(){
if (true) {
return function g(){};
}
return function g(){};
})();

We know that a function returned from within this anonymous invocation — the one that has g identifier — is being assigned to outer f. We also know that named function expressions produce superfluous function object, and that this object is not the same as returned function. The memory issue here is caused by this extraneous g function being literally “trapped” in a closure of returning function. This happens because inner function is declared in the same scope as that pesky g one. Unless we explicitly break reference tog function it will keep consuming memory.

var f = (function(){
var f, g;
if (true) {
f = function g(){};
}
else {
f = function g(){};
}
// null `g`, so that it doesn't reference extraneous function any longer
g = null;
return f;
})();

Note that we explicitly declare g as well, so that g = null assignment wouldn’t create a global g variable in conforming clients (i.e. non-JScript ones). By nulling reference to g, we allow garbage collector to wipe off this implicitly created function object that g refers to.

When taking care of JScript NFE memory leak, I decided to run a simple series of tests to confirm that nulling g actually does free memory.

Tests

The test was simple. It would simply create 10000 functions via named function expressions and store them in an array. I would then wait for about a minute and check how high the memory consumption is. After that I would null-out the reference and repeat the procedure again. Here’s a test case I used:

function createFn(){
return (function(){
var f;
if (true) {
f = function F(){
return 'standard';
};
}
else if (false) {
f = function F(){
return 'alternative';
};
}
else {
f = function F(){
return 'fallback';
};
}
// var F = null;
return f;
})();
} var arr = [ ];
for (var i=0; i<10000; i++) {
arr[i] = createFn();
}

Results as seen in Process Explorer on Windows XP SP2 were:

  IE6:

    without `null`:   7.6K -> 20.3K
with `null`: 7.6K -> 18K IE7: without `null`: 14K -> 29.7K
with `null`: 14K -> 27K

The results somewhat confirmed my assumptions — explicitly nulling superfluous reference did free memory, but the difference in consumption was relatively insignificant. For 10000 function objects, there would be a ~3MB difference. This is definitely something that should be kept in mind when designing large-scale applications, applications that will run for either long time or on devices with limited memory (such as mobile devices). For any small script, the difference probably doesn’t matter.

You might think that it’s all finally over, but we are not just quite there yet :) There’s a tiny little detail that I’d like to mention and that detail is Safari 2.x

Safari bug

Even less widely known bug with NFE is present in older versions of Safari; namely, Safari 2.x series. I’ve seen some claims on the web that Safari 2.x does not support NFE at all. This is not true. Safari does support it, but has bugs in its implementation which you will see shortly.

When encountering function expression in a certain context, Safari 2.x fails to parse the program entirely. It doesn’t throw any errors (such as SyntaxError ones). It simply bails out:

(function f(){})(); // <== NFE
alert(1); // this line is never reached, since previous expression fails the entire program

After fiddling with various test cases, I came to conclusion that Safari 2.x fails to parse named function expressions, if those are not part of assignment expressions. Some examples of assignment expressions are:

// part of variable declaration
var f = 1; // part of simple assignment
f = 2, g = 3; // part of return statement
(function(){
return (f = 2);
})();

This means that putting named function expression into an assignment makes Safari “happy”:

(function f(){}); // fails

var f = function f(){}; // works

(function(){
return function f(){}; // fails
})(); (function(){
return (f = function f(){}); // works
})(); setTimeout(function f(){ }, 100); // fails Person.prototype = {
say: function say() { ... } // fails
} Person.prototype.say = function say(){ ... }; // works

It also means that we can’t use such common pattern as returning named function expression without an assignment:

// Instead of this non-Safari-2x-compatible syntax:
(function(){
if (featureTest) {
return function f(){};
}
return function f(){};
})(); // we should use this slightly more verbose alternative:
(function(){
var f;
if (featureTest) {
f = function f(){};
}
else {
f = function f(){};
}
return f;
})(); // or another variation of it:
(function(){
var f;
if (featureTest) {
return (f = function f(){});
}
return (f = function f(){});
})(); /*
Unfortunately, by doing so, we introduce an extra reference to a function
which gets trapped in a closure of returning function. To prevent extra memory usage,
we can assign all named function expressions to one single variable.
*/ var __temp; (function(){
if (featureTest) {
return (__temp = function f(){});
}
return (__temp = function f(){});
})(); ... (function(){
if (featureTest2) {
return (__temp = function g(){});
}
return (__temp = function g(){});
})(); /*
Note that subsequent assignments destroy previous references,
preventing any excessive memory usage.
*/

If Safari 2.x compatibility is important, we need to make sure “incompatible” constructs do not even appear in the source. This is of course quite irritating, but is definitely possible to achieve, especially when knowing the root of the problem.

It’s also worth mentioning that declaring a function as NFE in Safari 2.x exhibits another minor glitch, where function representation does not contain function identifier:

var f = function g(){};

// Notice how function representation is lacking `g` identifier
String(f); // function () { }

This is not really a big deal. As I have already mentioned before, function decompilation is something that should not be relied upon anyway.

SpiderMonkey peculiarity

We know that identifier of named function expression is only available to the local scope of a function. But how does this "magic" scoping actually happen? It appears to be very simple. When named function expression is evaluated, a special object is created. The sole purpose of that object is to hold a property with the name corresponding to function identifier, and value corresponding to function itself. That object is then injected into the front of the current scope chain, and this "augmented" scope chain is then used to initialize a function.

The interesting part here, however, is the way ECMA-262 defines this "special" object — the one that holds function identifier. Spec says that an object is created "as if by expression new Object()" which, when interpreted literally, makes this object an instance of built-in Object constructor. However, only one implementation — SpiderMonkey — followed this specification requirement literally. In SpiderMonkey, it is possible to interfere with function local variables by augmenting Object.prototype:

Object.prototype.x = 'outer';

(function(){

  var x = 'inner';

  /*
`foo` function here has a special object in its scope chain — to hold an identifier. That object is practically a —
`{ foo: <function object> }`. When `x` is being resolved through the scope chain, it is first searched for in
`foo`'s local context. When not found, it is searched in the next object from the scope chain. That object turns out
to be the one that holds identifier — { foo: <function object> } and since it inherits from `Object.prototype`,
`x` is found right here, and is the one that's `Object.prototype.x` (with value of 'outer'). Outer function's scope
(with x === 'inner') is never even reached.
*/ (function foo(){ alert(x); // alerts `outer` })();
})();

Note that later versions of SpiderMonkey actually changed this behavior, as it was probably considered a security hole. A "special" object no longer inherits from Object.prototype. You can, however, still see it in Firefox <=3.

Another environment implementing internal object as an instance of global Object is Blackberry browser. Only this time, it's Activation Object that inherits from Object.prototype. Note that specification actually doesn't codify Activation Object to be created "as if by expression new Object()" (as is the case with NFE's identifier holder object). It states that Activation Object is merely a specification mechanism.

So, let's see what happens in Blackberry browser:

Object.prototype.x = 'outer';

(function(){

  var x = 'inner';

  (function(){

    /*
When `x` is being resolved against scope chain, this local function's Activation Object is searched first.
There's no `x` in it, of course. However, since Activation Object inherits from `Object.prototype`, it is
`Object.prototype` that's being searched for `x` next. `Object.prototype.x` does in fact exist and so `x`
resolves to its value — 'outer'. As in the previous example, outer function's scope (Activation Object)
with its own x === 'inner' is never even reached.
*/ alert(x); // alerts 'outer' })();
})();

This might look bizarre, but what's really disturbing is that there's even more chance of conflict with already existing Object.prototype members:

(function(){

  var constructor = function(){ return 1; };

  (function(){

    constructor(); // evaluates to an object `{ }`, not `1`

    constructor === Object.prototype.constructor; // true
toString === Object.prototype.toString; // true // etc. })();
})();

Solution to this Blackberry discrepancy is obvious: avoid naming variables as Object.prototype properties — toStringvalueOfhasOwnProperty, and so on.

JScript solution

var fn = (function(){

  // declare a variable to assign function object to
var f; // conditionally create a named function
// and assign its reference to `f`
if (true) {
f = function F(){ };
}
else if (false) {
f = function F(){ };
}
else {
f = function F(){ };
} // Assign `null` to a variable corresponding to a function name
// This marks the function object (referred to by that identifier)
// available for garbage collection
var F = null; // return a conditionally defined function
return f;
})();

Finally, here’s how we would apply this “techinque” in real life, when writing something like a cross-browser addEvent function:

// 1) enclose declaration with a separate scope
var addEvent = (function(){ var docEl = document.documentElement; // 2) declare a variable to assign function to
var fn; if (docEl.addEventListener) { // 3) make sure to give function a descriptive identifier
fn = function addEvent(element, eventName, callback) {
element.addEventListener(eventName, callback, false);
};
}
else if (docEl.attachEvent) {
fn = function addEvent(element, eventName, callback) {
element.attachEvent('on' + eventName, callback);
};
}
else {
fn = function addEvent(element, eventName, callback) {
element['on' + eventName] = callback;
};
} // 4) clean up `addEvent` function created by JScript
// make sure to either prepend assignment with `var`,
// or declare `addEvent` at the top of the function
var addEvent = null; // 5) finally return function referenced by `fn`
return fn;
})();

Alternative solution

It’s worth mentioning that there actually exist alternative ways of having descriptive names in call stacks. Ways that don’t require one to use named function expressions. First of all, it is often possible to define function via declaration, rather than via expression. This option is only viable when you don’t need to create more than one function:

var hasClassName = (function(){

  // define some private variables
var cache = { }; // use function declaration
function hasClassName(element, className) {
var _className = '(?:^|\\s+)' + className + '(?:\\s+|$)';
var re = cache[_className] || (cache[_className] = new RegExp(_className));
return re.test(element.className);
} // return function
return hasClassName;
})();

This obviously wouldn’t work when forking function definitions. Nevertheless, there’s an interesting pattern that I first seen used by Tobie Langel. The way it works is by defining all functions upfront using function declarations, but giving them slightly different identifiers:

var addEvent = (function(){

  var docEl = document.documentElement;

  function addEventListener(){
/* ... */
}
function attachEvent(){
/* ... */
}
function addEventAsProperty(){
/* ... */
} if (typeof docEl.addEventListener != 'undefined') {
return addEventListener;
}
else if (typeof docEl.attachEvent != 'undefined') {
return attachEvent;
}
return addEventAsProperty;
})();

While it’s an elegant approach, it has its own drawbacks. First, by using different identifiers, you lose naming consistency. Whether it’s good or bad thing is not very clear. Some might prefer to have identical names, while others wouldn’t mind varying ones; after all, different names can often “speak” about implementation used. For example, seeing “attachEvent” in debugger, would let you know that it is an attachEvent-based implementation of addEvent. On the other hand, implementation-related name might not be meaningful at all. If you’re providing an API and name “inner” functions in such way, the user of API could easily get lost in all of these implementation details.

A solution to this problem might be to employ different naming convention. Just be careful not to introduce extra verbosity. Some alternatives that come to mind are:

  `addEvent`, `altAddEvent` and `fallbackAddEvent`
// or
`addEvent`, `addEvent2`, `addEvent3`
// or
`addEvent_addEventListener`, `addEvent_attachEvent`, `addEvent_asProperty`

Another minor issue with this pattern is increased memory consumption. By defining all of the function variations upfront, you implicitly create N-1 unused functions. As you can see, if attachEvent is found in document.documentElement, then neither addEventListener nor addEventAsProperty are ever really used. Yet, they already consume memory; memory which is never deallocated for the same reason as with JScript’s buggy named expressions — both functions are “trapped” in a closure of returning one.

This increased consumption is of course hardly an issue. If a library such as Prototype.js was to use this pattern, there would be not more than 100-200 extra function objects created. As long as functions are not created in such way repeatedly (at runtime) but only once (at load time), you probably shouldn’t worry about it.

WebKit's displayName

A somewhat different approach was taken by WebKit team. Frustrated with poor representation of functions — both, anonymous and named — WebKit introduced "special" displayName property (essentially a string) that when assigned to a function is displayed in debugger/profiler in place of that function's "name". Francisco Tolmasky explains in details the rationale and implementation of this solution.

Future considerations

Upcoming version of ECMAScript — ECMA-262, 5th edition — introduces so-called strict mode. The purpose of strict mode is to disallow certain parts of the language which are considered to be fragile, unreliable or dangerous. One of such parts is arguments.callee, "banned" presumably due to security concerns. When in strict mode, access to arguments.callee results in TypeError (see section 10.6). The reason I'm bringing up strict mode is because inability to use arguments.callee for recursion in 5th edition will most likely result in increased use of named function expressions. Understanding their semantics and bugs will become even more important.

// Before, you could use arguments.callee
(function(x) {
if (x <= 1) return 1;
return x * arguments.callee(x - 1);
})(10); // In strict mode, an alternative solution is to use named function expression
(function factorial(x) {
if (x <= 1) return 1;
return x * factorial(x - 1);
})(10); // or just fall back to slightly less flexible function declaration
function factorial(x) {
if (x <= 1) return 1;
return x * factorial(x - 1);
}
factorial(10);

 

转至 http://kangax.github.io/nfe/

Named function expressions demystified的更多相关文章

  1. IE6 IE7 IE8 的函数声明和函数表达式的实现与其他浏览器有差异

    标准参考 函数声明和函数表达式 定义一个函数有两种途径:函数声明和函数表达式. 函数声明: function Identifier ( FormalParameterList opt ) { Func ...

  2. 我希望我知道的七个JavaScript技巧

    如果你是一个JavaScript新手或仅仅最近才在你的开发工作中接触它,你可能感到沮丧.所有的语言都有自己的怪癖(quirks)——但从基于强类型的服务器端语言转移过来的开发人员可能会感到困惑.我就曾 ...

  3. IIFE-js中(function(){…})()立即执行函数写法理解

    介绍IIFE IIFE的性能 使用IIFE的好处 IIFE最佳实践 jQuery优化 在Bootstrap源码(具体请看<Bootstrap源码解析>)和其他jQuery插件经常看到如下的 ...

  4. 立即执行函数表达式(IIFE)

    原文地址:benalman.com/news/2010/11/immediately-invoked-function-expression/ 译者:nzbin 也许你还没有注意到,我是一个对术语比较 ...

  5. 【repost】JavaScript Scoping and Hoisting

    JavaScript Scoping and Hoisting Do you know what value will be alerted if the following is executed ...

  6. js作用域和变量提升

    Function declarations and variable declarations are always moved (“hoisted”) invisibly to the top of ...

  7. (译)详解javascript立即执行函数表达式(IIFE)

    写在前面 这是一篇译文,原文:Immediately-Invoked Function Expression (IIFE) 原文是一篇很经典的讲解IIFE的文章,很适合收藏.本文虽然是译文,但是直译的 ...

  8. [转]Javascript中的自执行函数表达式

    [转]Javascript中的自执行函数表达式 本文转载自:http://www.ghugo.com/javascript-auto-run-function/ 以下是正文: Posted on 20 ...

  9. [JS]深入理解JavaScript系列(4):立即调用的函数表达式

    转自:汤姆大叔的博客 前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行.在详细了解这个之前,我们来谈了解一下"自执行"这个叫法 ...

随机推荐

  1. 【DOORS】如何基于DOORS实施需求管理

    引言 IBM Rational DOORS,简称DOORS,是被业界广泛认可的需求管理工具,在国内外需求管理领域具有较高的市场占有率.需求管理作为传统的工程领域,理论发展相对成熟和健全.随着越来越多的 ...

  2. C++ 11 学习2:空指针(nullptr) 和 基于范围的for循环(Range-based for loops)

    3.空指针(nullptr) 早在 1972 年,C语言诞生的初期,常数0带有常数及空指针的双重身分. C 使用 preprocessor macroNULL 表示空指针, 让 NULL 及 0 分别 ...

  3. CSS揭秘 技巧(五):条纹背景

    条纹背景 https://github.com/FannieGirl/ifannie/问题:条纹背景 在设觉设计中无处不在,我们真的可以用css 创建图案吗? 这一章相对还是比较复杂的哦!一起get. ...

  4. 1660: [Usaco2006 Nov]Bad Hair Day 乱发节

    1660: [Usaco2006 Nov]Bad Hair Day 乱发节 Time Limit: 2 Sec  Memory Limit: 64 MB Submit: 665  Solved: 31 ...

  5. Raft算法,从学习到忘记

    Raft算法,从学习到忘记 --Raft算法阅读笔记. --Github 概述 说到分布式一致性算法,可能大多数人的第一反应是paxos算法.但是paxos算法一直以来都被认为是难以理解,难以实现.S ...

  6. HTML5初步了解

        一.使用HTML5的十大原因 你难道还没有考虑使用HTML5? 当然我猜想你可能有自己的原因:它现在还没有被广泛的支持,在IE中不好使,或者你就是喜欢写比较严格的XHTML代码.HTML5是w ...

  7. android:layout_gravity和android:gravity属性的区别1

    一.介绍: gravity的中文意思就是"重心",就是表示view横向和纵向的停靠位置 (1).android:gravity:是对view控件本身来说的,是用来设置view本身的 ...

  8. 详解< meta http-equiv = "X-UA-Compatible" content = "IE=edge,chrome=1" />

    < meta http-equiv = "X-UA-Compatible" content = "IE=edge,chrome=1" /> 这是个是 ...

  9. ViewPager 滑动一半的判断方法以及左滑右滑判断

    做项目的时候,会碰到用viewpager + fragments去实现多页滑动.有些时候需要完成:界面在滑动到一半或是一半以上的时候,需要把title之类的切换到下一个页面.这个时候仅仅依赖Viewp ...

  10. final 、finally 和 finalize()的区别

    1. final 是一个关键字.可以修饰数据.方法.类. 1)final 数据:final 用来修饰一个永不改变的编译时常量,或者运行时初始化但是不希望被改变的常量.一个既是 static又是 fin ...