読者です 読者をやめる 読者になる 読者になる

今日の知っ得まる得 ECMAScript

ECMAScript JavaScript

ECMAScript の名前解決は Scope チェーンと Prototype チェーンの2次元探索です。

詳しくは、 [[HasProperty]] 内部関数の仕様を見るとよく分かるぜ。ちなみに、 [[Prototype]] 内部プロパティは実装系によっては __proto__ として実装されていますので読み替えると吉です。

Activation Object には [[Prototype]]ありません。 Global Object ( たいてい window ) には、たいていあります。したがって、 AO に対する prototype チェーン探索は必ず失敗して次の(親の) AO への Scope チェーン探索に移ります。これが、名前解決が関数の入れ子の順に進んでいっているように傍目には見える理由です。この違いは下のコード例参照。

僕の個人的な感覚では、 ECMAScript には OOP の感覚を持ち込まずに、これこれこういう名前解決法の上に OOP っぽいものを構築したんだ、という程度に理解しておいたほうが、他人のコードは読めるようになる気がしますです。


実際のコード例と名前付き Function Expression という特殊な例

  • Scope チェーン
  • Prototype チェーン
  • Variable Object (事実上は関数が生成する Activation Object ( AO ) と Global Object (たいてい window )しかない)

の三点だけ覚えておけば、 ECMAScript の名前解決はすべて判ったも同然です。

名前付き Function Expression (英語で Named Function Expression = NFE )だけは、再帰したり関数返しをするためにスコープにたいして特殊なことをやってます。そういう仕様です。

※スミマセン、 print() とか使ってるのは、このコード rhino で試してたからです。恐縮です。 rhino 以外でもこうなります。ただし NFE に関しては IE がかなり仕様と異なる実装をしているので要注意です。詳しくは Dmitry 先生のブログを見てちょ。

// global
// [[Scope]] == [globalobject];

var globalvar = 'global';
Object.prototype.test = 'test';

function A() { // Function Definition
    // Execution Context A
    // [[Scope]] == [AOofA, globalobject];

    function B() { // Function Definition
        // Execution Context B
        // [[Scope]] == [AOofB, AOofA, globalobject];

        var Cfe = function() { // Function Expression
            // Execution Context Anonymous Function Expression
            // [[Scope]] == [AOofCfe, AOofB, AOofA, globalobject];

            var cvar = 'c';

            // NFE の直前で、 [[Scope]] に特別なオブジェクト( prototype を持たない)が unshift される
            // [[Scope]].unshift({D:Dfunction Reference});
            var Dnfe = function D() { // Named Function Expression
                // Execution Context Anonymous Function Expression
                // [[Scope]] == [SpecialObject, AOofCfe, AOofB, AOofA, globalobject];

                print(D);
                // 1. Scope から一個 shift > SpecialObject
                // 2. SpecialObject.D > hit!
                //
                // function D() {...

                print(cvar);
                // 1. Scope から一個 shift > SpecialObject
                // 2. SpecialObject.cvar > fail
                // 3. 【prototype探索】SpecialObject.__proto__ > fail
                // 4. Scope から一個 shift > AOofCfe
                // 5. AoofCfe.cvar > hit!
                //
                // c

                print(globalvar);
                // 1. 以下略
                // 2. globalobject.globalvar > hit!
                //
                // global

                print(test);
                // 1. 以下略
                // 2. globalobject.test > fail
                // 3. 【prototype探索】globalobject.__proto__ > (Window.prototype).__proto__ > Object.prototype
                // 4. Object.prototype.test > hit!
                //
                // test
            };
            // NFE の直後で特別なオブジェクトは shift される。
            // [[Scope]].shift();

            // 依然として
            // [[Scope]] == [AOofCfe, AOofB, AOofA, globalobject];

            Dnfe();
        };

        Cfe();
    }

    B();
}

A();

参考

Dmitry 氏が全部説明してくれてます。

http://dmitrysoshnikov.com/