リア充爆発日記

You don't even know what ria-ju really is.

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が登場するわけだけども、このあたりのことはまた次回以降に回すことにする。

ということで、パターンごとに章を分けなかったので数がわかりにくいけど、

の4つかな。


全体像が見えてきたら最後にもう一度公式(もしくはそれに準ずる)ドキュメントを一読しておくのがいいと思う。いきなり読むと理解がしにくいことも多いけど、他の人が噛み砕いて説明してくれたものを読んでからだと、身につきやすい。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/this


以上、よろしくおねがいいたします。

JavaScript 第6版

JavaScript 第6版