JavaScriptでメソッド参照したときの奇妙な動作(解決?)

先日メソッド参照とメソッド呼び出しをした場合に this の値が違う、ということを書きました。(http://d.hatena.ne.jp/serene/20090319/1237451298)

状況が分かりやすいように整理して書き直したコードはこうなります。

function Foo() {};
Foo.prototype.test = function() { return this; };
var foo = new Foo();
var func = foo.test;
alert([
    '1'.concat(func == foo.test    ), // true.
    '2'.concat(foo == foo.test()   ), // true.
    '3'.concat(func() == foo.test()), // false.
    '4'.concat(func() == this      ), // true.
    '5'.concat(this == foo.test()  ), // false.
]);

1 と 2 は期待通りとして、3が変だと思っていたのですが、4 と 5 の結果を考えると意味が分かってきます。testメソッドの中の this は、実際に呼び出されるまでは確定しておらず、呼び出される時点のコンテキストを参照して上が何であるかを決定しています。

func() については呼び出されて初めて中の this が宣言されていることに気づいて、上位の this 即ちグローバルコンテキストを返すことになっているわけです。ただ、だからと言って 単純に func は実は this.func なんだ!と思うとそれは以下の結果のごとく、違います。

function Foo() {};
Foo.prototype.test = function() { return this; };
var foo = new Foo();
var func = foo.test;
alert( func == this.func );  // false.

それでは上位の this をとるなら、以下のように書くとどうなるでしょうか?

function Foo() {};
Foo.prototype.test = function() { return this; };
var foo = new Foo();
var func = foo.test;

function Bar() {};
Bar.prototype.test = function(lambda) { return lambda(); };

var bar = new Bar();
alert([
    '1'.concat(bar.test(func) == bar     ), // false. 上位は bar ではない
    '2'.concat(bar.test(func) == this    ), // true.
    '3'.concat(bar.test(foo.test) == this), // true. 上位が foo ではない
]);

funcあるいはfoo.testが「参照された状況」でコンテキストを拾って this に割り当て、「実際に呼び出された状況」でそれを返すという動作をしていることがわかります。


それでは、あらかじめコンストラクタで設定した値を返すとどうなるか。

function Foo() { this.val = this; }
Foo.prototype.test = function() { return this.val; };
var foo = new Foo();
var func = foo.test;
alert([
    '1'.concat(func == foo.test    ), // true.
    '2'.concat(foo == foo.test()   ), // true.
    '3'.concat(func() == foo.test()), // false.
    '4'.concat(func() == null      ), // true.
    '5'.concat(foo == foo.test()   ), // true.
]);

結局のところ、this が何を指すかというと今のブロックが「参照されたとき」のブロックコンテキスト、ってことになりますね。それが関数なのかグローバルコンテキストなのかによって違いが出てくるということです。
参照と実行で分けて考えるとちょっとすっきりしたかも。