As we all know, the identifier of a named function expression is only valid in the local scope of the function. But what does the local scope that contains this identifier look like? It's actually very simple. When a named function expression is evaluated, a special object is created whose sole purpose is to hold a property whose name corresponds to the function identifier and whose value corresponds to that function. This object will be injected at the front of the current scope chain. The "extended" scope chain is then used in the initialization function.
One thing that is very interesting here is the way ECMA-262 defines this "special" object (that holds function identifiers). The standard says to create this object "as if you were calling the new Object() expression". If you understand this sentence literally, then this object should be an instance of the global Object. However, there is only one implementation that does this literally as required by the standard, and that implementation is SpiderMonkey. Therefore, in SpiderMonkey, extending Object.prototype has the potential to interfere with the local scope of a function:
Object.prototype.x = 'outer'; (function(){ var x = 'inner'; /* 函数foo的作用域链中有一个特殊的对象——用于保存函数的标识符。这个特殊的对象实际上就是{ foo: <function object> }。 当通过作用域链解析x时,首先解析的是foo的局部环境。如果没有找到x,则继续搜索作用域链中的下一个对象。下一个对象 就是保存函数标识符的那个对象——{ foo: <function object> },由于该对象继承自Object.prototype,所以在此可以找到x。 而这个x的值也就是Object.prototype.x的值(outer)。结果,外部函数的作用域(包含x = 'inner'的作用域)就不会被解析了。 */ (function foo(){ alert(x); // 提示框中显示:outer })(); })();
However, later versions of SpiderMonkey changed the above behavior, probably because it was considered a security vulnerability. In other words, "special" objects no longer inherit Object.prototype. However, if you use Firefox 3 or lower, you can "revisit" this behavior.
Another browser that implements internal objects as global Object objects is the Blackberry browser. Currently, its activity object (Activation Object) still inherits Object.prototype. However, ECMA-262 does not say that active objects must be created "like calling the new Object() expression" (or like creating objects holding NFE identifiers). Other people's standards only say that the activity object is a mechanism in the standards.
Okay, let’s take a look at the behavior of the BlackBerry browser:
Object.prototype.x = 'outer'; (function(){ var x = 'inner'; (function(){ /* 在沿着作用域链解析x的过程中,首先会搜索局部函数的活动对象。当然,在该对象中找不到x。 可是,由于活动对象继承自Object.prototype,因此搜索x的下一个目标就是Object.prototype;而 Object.prototype中又确实有x的定义。结果,x的值就被解析为——outer。跟前面的例子差不多, 包含x = 'inner'的外部函数的作用域(活动对象)就不会被解析了。 */ alert(x); // 显示:outer })(); })();
But the magic is that the variables in the function will even conflict with existing members of Object.prototype. Let’s take a look at the following code :
(function(){ var constructor = function(){ return 1; }; (function(){ constructor(); // 求值结果是{}(即相当于调用了Object.prototype.constructor())而不是1 constructor === Object.prototype.constructor; // true toString === Object.prototype.toString; // true // …… })(); })();
To avoid this problem, avoid using property names in Object.prototype, such as toString, valueOf, hasOwnProperty, etc.
JScript solution
var fn = (function(){ // 声明要引用函数的变量 var f; // 有条件地创建命名函数 // 并将其引用赋值给f if (true) { f = function F(){ } } else if (false) { f = function F(){ } } else { f = function F(){ } } // 声明一个与函数名(标识符)对应的变量,并赋值为null // 这实际上是给相应标识符引用的函数对象作了一个标记, // 以便垃圾回收器知道可以回收它了 var F = null; // 返回根据条件定义的函数 return f; })();
Finally we give an application example of applying the above technology, which is a cross-browser addEvent function code:
// 1) 使用独立的作用域包含声明 var addEvent = (function(){ var docEl = document.documentElement; // 2) 声明要引用函数的变量 var fn; if (docEl.addEventListener) { // 3) 有意给函数一个描述性的标识符 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) 清除由JScript创建的addEvent函数 // 一定要保证在赋值前使用var关键字 // 除非函数顶部已经声明了addEvent var addEvent = null; // 5) 最后返回由fn引用的函数 return fn; })();