[JavaScript] Activationオブジェクトらへんで誤解があったらしい

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

[JavaScript] Activationオブジェクトらへんで誤解があったらしい

Activationオブジェクト周りでちょっと気になって、いろいろとコード書いてみて、ECMAScript 3の仕様書まで読み漁ったので、忘れないうちにメモ。

Activation Object

JavaScript使いを魅了してやまないActivationオブジェクト。ActivationオブジェクトはJavaScriptの関数が実行されるときに生成されるオブジェクトで、その関数内で使われている変数・関数や引数についての情報が含まれています。例えば、↓のような関数が定義されていたとして、

function fn(x, y) {
    var localVar = 'I am a local variable.';

    return localVar + ' ' + localFn();

    function localFn() {
        return 'I am a local function.';
    }
}

↓のように普通に呼び出してみたとします。

fn(1, 2);    //"I am a local variable. I am a local function."

実行結果はコメントに書いた通りで、何の変哲もない普通の関数なのですが、実はこの関数が実行される瞬間にもActivationオブジェクトが生成されています。Activationオブジェクトについての詳細はECMAScript 3の仕様書第10章 実行コンテキスト (Execution Contexts)の辺りを読めば詳しく書いてありますが、面倒な部分をはしょってその内容を大雑把にまとめると、↓のようなActivationオブジェクトが関数実行直前に生成されていることになります。

activationObject = {
    arguments: { /* 省略 */ },
    x: 1,
    y: 2,
    localFn: function localFn() {
        return 'I am a local function.';
    },
    localVar: undefined
};

一目見ればわかるとおり、関数内で変数としてアクセスできる識別子がActivationオブジェクトのプロパティとして初期化されています。JavaScriptの関数は、このActivationオブジェクトをスコープチェーンの先頭にセットした後に実行されます。関数全体をwith (activationObject) { ... }で括ったような感覚ですね。

このActivationオブジェクトに関して、JavaScriptに不慣れな人の直感に反することとしては、まず、関数宣言されたlocalFn()が、Activationオブジェクト生成段階で生成されていることでしょうか。この仕様のおかげで、localFn()は関数宣言が書かれている前のreturn文で呼び出してもエラーになりません。それから、var文で宣言されたlocalVarが、'I am a local variable.'で初期化される前にundefined値で初期化されていることでしょうか。この仕様は↓のような関数を書いたときに直感に反する挙動を起こすので注意が必要です。

function fn() {
    var window = window;
    return window;    //undefined
}

変数もオブジェクトのプロパティなんだ

話を戻すと、JavaScriptの変数は仕様上はActivationオブジェクトのプロパティにあたるわけです。JavaScript使いが「変数名・変数値 == オブジェクト == 連想配列」というような考え方をするのは、この仕様が理由だと思われます。

変数に代入された関数の中でthisがさすのはActivationオブジェクトじゃね?

で、昨日ふと考えたのが、そのActivationオブジェクトを取得する方法。変数がActivationオブジェクトのプロパティならば、変数に代入された関数を実行したときのthisの値はActivationオブジェクト自身になるのではないかと思ったわけです。つまり、↓のようなコードを書けばActivationオブジェクトを取得してscopeという変数に束縛できるのではないかと考えたわけです。

(function() {
    var scope = getScope();

    console.log(scope);

    function getScope() {
        return this;
    }
})()

で、このスクリプトをFirefoxで走らせて見たわけですが、結果は↓。

Firebugコンソールで実行した結果

なぜWindowオブジェクトが返ってくるんだー><

というわけで、

仕様書を当たってみた。前述の通り、関数の実行に関することは、Execution Contextsの項に書いてあるので、そこをよく読んでみる。すると、Activationオブジェクトのセクションで↓のような文を発見。

ECMAScript Language Specification 10.1.6 Activation Object

The activation object is purely a specification mechanism. It is impossible for an ECMAScript program to access the activation object.

(超訳) Activationオブジェクトはただの仕様上の代物で、ECMAScriptのプログラムからActivationオブジェクトにアクセスすることはできない。

ということらしい。続く文には、Activationオブジェクトのプロパティにアクセスすることはできるが、Activationオブジェクトそのものには触れない的なことが書いてある。

さらにその先を読んでみると、

ECMAScript Language Specification 10.1.6 Activation Object

When the call operation is applied to a Reference value whose base object is an activation object, null is used as the this value of the call. (太字は原文)

(超訳) Activationオブジェクトのプロパティになっている関数を呼び出すと、thisの値はnullになるよ。

ということらしい。わざわざ例外的な処理をしてまでも、Activationオブジェクトには直接触らせてくれないらしい。

とはいえ、ここではthisの値はnullになると書いてあるのに、↑で実行したスクリプトではWindowオブジェクトになっていた。これはなぜだろうかということで、仕様書をもう少し読んでみた。すると↓のような文を発見。

ECMAScript Language Specification 10.2.3 Function Code

  • The caller provides the this value. If the this value provided by the caller is not an object (including the case where it is null), then the this value is the global object. (太字は原文)
  • (超訳) もし、thisの値がnullや非オブジェクトであった場合、thisの値はグローバルオブジェクトになる。

ということらしい。つまり、Activationオブジェクトがthisの値として渡されそうになると、nullがかわりに渡されて、thisの値としてnullが渡されたときは、かわりにグローバルオブジェクトが使われるらしい。だから、↑で実行したスクリプトではWindowオブジェクトが渡されていたんだね。

まとめ

  • Activationオブジェクトには直接触ることができない。
  • Activationオブジェクトがthis値になりそうなときは、グローバルオブジェクトがかわりにthis値になる。

なんでこんな面倒な仕様になっているのだろう。そのままActivationオブジェクトをthisで扱えるようにしたほうが美しい仕様になりそうなのに。

もしActivationオブジェクトを自在に操れたら、例えば「処理を実行したあとにActivationオブジェクトを返す関数を作って、その関数が返すオブジェクトを別の関数に渡して、それをwith文でレストアして...」みたいなことができて、まるで継続を扱っているかのような感じになるよね。

ついでに、スコープチェインの仕組みをちょこっと変えて、Activationオブジェクトは親スコープのActivationオブジェクトをprototypeとして持つような仕組みにして、「スコープチェイン == プロトタイプチェイン」みたいな仕組みにしてしまえば、ますます仕様的に美しくなりそうだし、関数の実行環境をそのまま一つのオブジェクトに閉じ込めるようになるから、ますます継続渡しっぽくなりそうだよね。

スポンサーサイト

関連記事

トラックバック URL

http://liosk.blog103.fc2.com/tb.php/136-70e7ac12

トラックバック

コメント

Activation オブジェクトは、C, C++ でいうところのコールスタックであって、C++, Java でいうところの this ポインタとは根本的に違うと思います。this は、インスタンス化された場合の実際のオブジェクトを指しているので、オブジェクトのメソッドでもない単なるローカル関数は、this 値が null になるのは仕様上美しいと思います。あくまで、自分の知識の範囲ないですので間違っている可能性はありますが。
  • 2009-12-15
  • by eternalharvest
  • id:TfD64Nso
  • 編集
> Activation オブジェクトは、C, C++ でいうところのコールスタック

なるほど、確かにactivationオブジェクトはコールスタックを抽象化した概念と言えそうですね。言われてみるまで気が付きませんでした。


ただ、

> this は、インスタンス化された場合の実際のオブジェクトを指している

というのは誤解があるように感じます。JavaScriptのthisはC++, Java でいうところの this ポインタとは根本的に違いますので…

  • 2009-12-19
  • by LiosK
  • id:-

コメントの投稿

お名前
コメント
編集キー
 
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。