Javascript初心者に初心者++程度の自分が言語仕様を説明したときのメモ(第二回)
今回はthisについて説明することに挑戦。
他の多くの言語では、オブジェクトのインスタンス自身を指す予約語として、thisとかselfとかがあるが、Javascriptにもthisがある。
が、Javascriptのthisは状況によって指す内容が変わってくる。
それをMDNでは、「this は関数に「渡される」のであって、関数に固定されている訳ではありません。」と言っている。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/this
よくわからんので、これについてググると「thisのパターンは4つある」、とか「いや、3つだ」みたいな内容が見つかるかもしれないけど、とりあえずそこはあまり気にしないで「3つとも4つとも取れるんだな」くらいに思っておけばいいんじゃないかと思う。で、理解したら持論を展開すればいいんじゃない。
わかりやすいやつから。
まずはクラスベースのthisと同じようなケースからさらっていく。
var myObj = { value: 10, getValue: function() { return this.value; } }; console.log(myObj.getValue()); // 10
thisはオブジェクト自身(myObj)を指していて、これはお馴染みな感じだ。
ここで前述のMDN内の「this は関数に「渡される」のであって、関数に固定されている訳ではありません。」を振り返ってみると、上記で使われているthisは
- getValue関数の中で使われている
- getValueはmyObjによって呼び出されている (myObj.getValue())
ことから、「myObjがgetValue関数を呼出すときに、(暗黙的に)thisに自分自身を参照させて渡した」ということになる。
つまり、MDNに記載されている3パターンのうち「メソッドの呼び出しで暗黙的に」というのに当てはまる。
myObjがgetValue関数を呼び出したから、thisがmyObjを指しているのであって、myObj.getValue()内のthisにmyObjが「固定されているわけではない」ことを意識しておく。(ややこしい)
このことを確認するために以下のようなコードを実行してみる。
var myObj = { value: 10, getValue: function() { return this.value; } }; var fnc = myObj.getValue // 関数自身を渡すときは()をつけない。()をつけると関数が実行されて結果が代入されてしまう。 console.log(fnc()); // undefined
myObjのgetValue関数を、別の変数に渡して実行しただけのコードで、エラーメッセージでundefinedと言われているはthis.valueのvalueだ。
さて、ここでのthisが何かというと、MDNでいうところの「上記のいずれの方法も取られなければ、グローバルオブジェクトがコンテキストオブジェクトとして渡されます」に該当し、ブラウザのグローバルオブジェクト=Windowということになる。
これも確認してみる。
var myObj = { value: 10, getValue: function() { return this; } }; console.log(myObj.getValue()); // Object (= myObj) var fnc = myObj.getValue; console.log(fnc()); // Window
ということで、valueじゃなくてthisを返して確認するとWindowオブジェクトが返ってきている。
であれば、グローバルオブジェクトであるWindowにvalueという変数があればそれが返ってくるんじゃないの、と推測されるのでそれも確認してみる。
var myObj = { value: 10, getValue: function() { return this.value; } }; var value = 20; // グローバルなvalue console.log(myObj.getValue()); // 10 var fnc = myObj.getValue; console.log(fnc()); // 20
ということで、MDNに書いてある4パターン(3パターン+3パターンのどれにも当てはまらない)のうち、2つのパターンの具体例が確認できた。
callとapply
そもそもcallとapplyがなんなのかというとFunctionオブジェクトに定義されているprototype関数だ。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
2つとも、今回のテーマであるthisを明示的に指定できる関数で、thisに指定するオブジェクト以外の引数の取り方が違うだけ。
- callは第一引数にthisに参照させするオブジェクトを指定し、第二引数以降に関数呼出しの際に渡す引数を可変的に設定できる。
- 例)myObj.setValue.call(yourObj, 1, 2, 3) // 第二引数以降の数は0..n
- applyも第一引数にthisに参照させるオブジェクトを指定するが、第二引数までしか持たず、第二引数は配列を取る
- 例) myObj.setValue.apply(yourObj, [1, 2, 3]) // 第二引数以降の数は0..1
というわけで、今んところはcallもapplyも「ほぼ同じ」ってことでいいと思う。
さて、実際にコードを書いて動きを確認してようと思う。
var myObj = { value: 10, addValue: function(value) { return this.value + value; }, }; console.log(myObj.addValue(20)); // 30
getValueの代わりにvalueに値を足して返すaddValueを定義してみた。
これにcallを当てはめる。具体的にはこれにvalue: 20をもつ、別オブジェクトyourObjを作って、myObjのaddValueを呼び出してみる。
var myObj = { value: 10, addValue: function(value) { return this.value + value; }, }; var yourObj = { value: 20 }; console.log(myObj.addValue.call(yourObj, 20)); // 40 console.log(myObj.addValue.apply(yourObj, [20])); // 40
このとおりcall(apply)関数によってthisはyourObjを指すため、30ではなく40が返る。
※applyはaddValueの中でvalue[0]みたいにする必要があると思ってたけど、呼び出してみたらふつうに40が返ってきた。
これで、MDNに書いてある全パターンが確認できた。
コンストラクタ内でのthis
が、実際thisにはもう1つパターンがある。
それはコンストラクタ内でのthisというパターンで、これはクラスベース言語ユーザー的にも直感的な部類だ。
function MyObject(value) { this.value = value; } myObj = new MyObject(1);
このthisは新しく作られたインスタンス(myObj)を指す、ということ。
ここでnewが登場するわけだけども、このあたりのことはまた次回以降に回すことにする。
ということで、パターンごとに章を分けなかったので数がわかりにくいけど、
- 関数呼び出し
- call, apply
- コンストラクタ
- それ以外(グローバル)
の4つかな。
全体像が見えてきたら最後にもう一度公式(もしくはそれに準ずる)ドキュメントを一読しておくのがいいと思う。いきなり読むと理解がしにくいことも多いけど、他の人が噛み砕いて説明してくれたものを読んでからだと、身につきやすい。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/this
以上、よろしくおねがいいたします。
- 作者: David Flanagan,村上列
- 出版社/メーカー: オライリージャパン
- 発売日: 2012/08/10
- メディア: 大型本
- 購入: 12人 クリック: 252回
- この商品を含むブログ (18件) を見る