thisにセットされるもの
@__gfx__さんがhttp://d.hatena.ne.jp/gfx/20120223/1329996834:JavaScriptのthisの扱いが難しすぎる件という記事を書いていたので見てみました。
いろいろなケースでthisに何がなぜ設定されるのかが分からない、というものです。以下転載。
var o = {}, tmp; o.f = function() { console.log(this.toString()) }; o.toString = function() { return "o" }; o.f.toString = function() { return "o.f" }; (function(){ return this })().toString = function() { return "global"; }; o.f(); // o (o.f)(); // o ([o.f][0])(); // o.f (new Array(o.f)[0])(); // o.f ([tmp = o.f][0])(); // o.f (tmp = o.f)(); // global (function(){ return o.f })()(); // global ({x: o.f, toString: function(){ return "t" }}.x)(); // t
JavaScriptのエキスパートではない僕にも当然分からなかったので、@bulkneetsさんや@kazuhoさんのツイートを足がかりに考えてみました。
いろいろ考えたり試したりした結果こうであろうと考えたルール:
- 「o.f()」のようにオブジェクトを明示した上でオブジェクトのプロパティにセットされている関数を呼び出す場合、thisにはそのオブジェクトがセットされる。
- オブジェクトを明示せずに関数を呼び出す場合、thisにはグローバルオブジェクトがセットされる。
- 配列aについて、a[n]はaの"n"というプロパティを指す。
- 「(o.f)()」のように、関数を表すものが括弧で囲われている場合、その括弧は(thisを決定する上では)無視される。
(JavaScriptのエキスパートから見ると回りくどい言い方になっているかもしれませんがご容赦下さい。)
これらのルールを使って説明していきます。
詳細な解説
o.f(); // o
ルール1よりthisにはoがセットされる。
(o.f)(); // o
ルール4より(o.f)の部分はo.fと同じと考えてよい。よってthisにはoがセットされる。
([o.f][0])(); // o.f
ルール4より([o.f][0])の部分は[o.f][0]と同じと考えてよい。またルール3よりこれは[o.f]という配列オブジェクトの"0"というプロパティを指す。さらにルール1よりthisには配列オブジェクト[o.f]がセットされる。
…となるはずなのに出力結果がo.fなのはどうして!?としばらく悩みましたが、実はo.fという出力はo.f.toString()によって出力されるのではなく配列[o.f]によって出力されるということに気づきました。通常配列のtoString関数は配列の中身をカンマ区切りで表示するため、要素がo.fだけの配列については出力がo.fとなるのでした。これについては@__gfx__さんも追記として書いていますが、@bulkneetsさんが
([o.f, "a"][0])();
はo.fではなくo.f,aとなりました。
(new Array(o.f)[0])(); // o.f
一つ前のケースと同様の考え方でthisには配列オブジェクトnew Array(o.f)がセットされる。
([tmp = o.f][0])(); // o.f
一つ前のケースと同様の考え方でthisには配列オブジェクト[tmp = o.f]がセットされる。また、式tmp = o.fの評価結果はo.f(の指すもの)なのでthisには[o.f]がセットされる。
(tmp = o.f)(); // global
(tmp = o.f)の評価値はo.fの内容になるが、この式ではオブジェクトが明示されていない。よってルール2よりthisにはグローバルオブジェクトがセットされる。
なおこれは一見(o.f)()と同じように見えるのですが、@kazuhoさんが
(function(){ return o.f })()(); // global
ルール2よりthisにはグローバルオブジェクトがセットされる。
({x: o.f, toString: function(){ return "t" }}.x)(); // t
ルール4より({x: o.f, toString: function(){ return "t" }}.x)の部分は{x: o.f, toString: function(){ return "t" }}.xと同じと考えてよい。またこの式ではオブジェクト{x: o.f, toString: function(){ return "t" }}が明示的に指定されているのでルール1よりthisには{x: o.f, toString: function(){ return "t" }}がセットされ、その結果このオブジェクトが持つtoString関数が呼び出されてtが表示される。
おまけ。@__gfx__さんが再追記で書いている(o.f=o.f)()がglobalになったことの解釈ですが、ルール4より(o.f=o.f)はo.f=o.fと同じと考えてよく、またo.f=o.fの評価値はo.fの内容になり、かつオブジェクトが明示されていないため、ルール2よりthisにはグローバルオブジェクトがセットされることになります。
まとめ
今回の件は
@kazuhoさんと@bulkneetsさんはすげー
が結論となります(笑)。