ご存知のとおり、名前付き関数式の識別子は、関数のローカル スコープ内でのみ有効です。しかし、この識別子を含むローカル スコープはどのようなものになるでしょうか?実はとてもシンプルなのです。名前付き関数式が評価されると、名前が関数識別子に対応し、値がその関数に対応するプロパティを保持することを唯一の目的とする特別なオブジェクトが作成されます。このオブジェクトは、現在のスコープ チェーンの先頭に挿入されます。次に、「拡張された」スコープ チェーンが初期化関数で使用されます。
ここで非常に興味深い点の 1 つは、ECMA-262 がこの「特別な」オブジェクト (関数識別子を保持する) を定義する方法です。標準では、このオブジェクトを「new Object() 式を呼び出しているかのように」作成するように定められています。この文を文字通りに理解すると、このオブジェクトはグローバル オブジェクトのインスタンスであるはずです。ただし、これを標準の要求どおりに文字通り実行する実装は 1 つだけあり、その実装は SpiderMonkey です。したがって、SpiderMonkey では、Object.prototype を拡張すると、関数のローカル スコープに干渉する可能性があります:
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 })(); })();
ただし、SpiderMonkey の後のバージョンでは、おそらくセキュリティ上の脆弱性とみなされたため、上記の動作が変更されました。言い換えれば、「特別な」オブジェクトは Object.prototype を継承しなくなりました。ただし、Firefox 3 以前を使用している場合は、この動作を「見直す」ことができます。
内部オブジェクトをグローバル Object オブジェクトとして実装するもう 1 つのブラウザーは、Blackberry ブラウザーです。現在、そのアクティビティ オブジェクト (Activation Object) は依然として Object.prototype を継承しています。ただし、ECMA-262 では、アクティブ オブジェクトを「new Object() 式の呼び出しと同じように」 (または NFE 識別子を保持するオブジェクトを作成するように) 作成する必要があるとは述べていません。 他の人の標準では、アクティビティ オブジェクトが標準のメカニズムであるとしか述べられていません。
それでは、BlackBerry ブラウザの動作を見てみましょう:
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 })(); })();
しかし、不思議なのは、関数内の変数が Object.prototype の既存のメンバーとさえ競合するということです。次のコードを見てみましょう:
(function(){ var constructor = function(){ return 1; }; (function(){ constructor(); // 求值结果是{}(即相当于调用了Object.prototype.constructor())而不是1 constructor === Object.prototype.constructor; // true toString === Object.prototype.toString; // true // …… })(); })();
この問題を回避するには、Object.prototype で toString、valueOf、hasOwnProperty などのプロパティ名を使用しないでください。
JScript ソリューション
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; })();
最後に、上記のテクノロジーを適用するアプリケーション例を示します。これは、クロスブラウザーの addEvent 関数コードです:
// 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; })();