はじめに
JavaScriptの「this」は他の言語と比較し挙動に少々癖があります。呼び出し方によってどの値を指すかが変わるためです。
思ったような値が得られない時は、thisの呼び出し方に問題があるかもしれません。
この記事では呼び出し方の違いによるthisの挙動を説明します。
thisの呼び出し方:基礎編
まずはよく使われるオーソドックスな呼び出し方を見ていきましょう。
基礎編クイズ
value = 1; var obj = { value: 2, func: function() { console.log(this.value) } } function func() { console.log(this.value) } var test = obj.func; console.log(this.value); //① obj.func(); //② test(); //③ func(); //④
上記①②③④では何が表示されるでしょうか。少し考えてみてください。
答えは下記のようになります。
> 1 //① > 2 //② > 1 //③ > 1 //④
正解できましたか?
①②③④はいずれもthis.valueの呼び出しをしていますが、この通り表示される値が違います。なぜこのようになるのか、順を追って説明していきます。
グローバルコンテキストでの呼び出し
①ではthisをグローバルコンテキスト(関数やオブジェクトの外側)で呼び出しています。この場合、thisにはグローバルオブジェクトが入ります。
グローバルオブジェクトはJavaScriptが実行される環境により変わるのですが、ブラウザの場合はWindowオブジェクトとなります。
下記のようにconsole.logでthisを表示させればグローバルオブジェクトが何なのか確認できます。
console.log(this); //> [object Window]が表示される
今回は1行目でvalue = 1;としたことで、このグローバルオブジェクトにvalueという値が定義され1が代入されていたため1が表示されました。
このように、グローバルコンテキストでthisを呼び出すと、グローバルオブジェクトが入ります。
オブジェクトのメソッド呼び出し
②は「objオブジェクトのfunc」をメソッド呼び出しています。
このように「〇〇.func();」といった具合で関数呼び出しの前に「〇〇.」がつく場合、関数「func」のスコープ内でのthisが指すのは前置きの〇〇が示すオブジェクトです。
この例ではobjのfunc関数スコープ内でthisが呼び出されているので、このthisが指すのは自分自身であるobjオブジェクトですね。
そしてobjは自身のvalueを持つため、結果的にthis.valueはこれを呼び出して2が入ります。
③は同じくobjの中で定義されたfuncを実行しているのですが、testにobj.funcを代入したあとでtest();と前置きなしで呼び出されていますね。
このように、グローバルコンテキストで前置きなしに関数だけが呼び出されている場合の関数内thisは、次項の「関数呼び出し」のケースにあたりグローバルオブジェクトを指すため1が表示されます。
関数呼び出し
④はグローバルコンテキストで定義された関数funcを呼び出しています。メソッド呼び出しではないので、「〇〇.」のような前置きはありません。
この場合の関数内thisもグローバルオブジェクトを指します。
グローバルオブジェクト(ブラウザで実行の場合はWindowオブジェクト)のvalueは一行目で定義されている通り1なので、1が表示されるわけです。
thisの呼び出し方:応用編
基礎編で見たように、thisの挙動は呼び出し方によって変わります。thisに何を入れたいかを明示的に指定するためのいくつかの方法を紹介します。
アロー関数呼び出し
ES6からはアロー関数が使用できるようになりました。関数としての基本的な挙動は通常の関数(function)と変わらないのですが、関数スコープ内のthisについては挙動が異なります。
アロー関数の中で呼び出されたthisは、関数の呼び出され方に関わらず、その関数が最初に定義された時点で指していた対象を指し続けるのが特徴です。
例を見てみましょう。
value = 5; function func1() { console.log(this.value) } var func2 =()=> { console.log(this.value) } var obj1 = { value: 1, func1 } var obj2 = { value: 2, func2 } obj1.func1(); //1が表示される obj2.func2(); //5が表示される
func1は従来の関数、func2はアロー関数です。この時点では両方ともグローバルコンテキストで定義されているため、thisはグローバルオブジェクトを指します。
その後に2種類のオブジェクト:obj1とobj2が定義され、それぞれfunc1とfunc2を保有していますね。
obj1のメソッド呼び出しでは、基礎編で触れたようにthisはobj1を指すので、obj1が持つvalueの値である1が表示されます。
これに対し、obj2のメソッド呼び出しではfunc2がアロー関数なので、thisが指すのはobj2でなく定義された時点で指していたグローバルオブジェクトのままです。
結果として、ojbのvalue(値は2)でなく、グローバルのvalue(値は5)が表示されます。
このように、アロー関数を使うと呼び出され方によらずthisが指す対象を定義当初のまま固定できるのです。
関数のコンストラクタ呼び出し
関数をグローバルコンテキストで前置き無しに呼び出すとその関数内でのthisはグローバルオブジェクトを指すのでした。
しかし、関数をコンストラクタ呼び出しすると、関数自体がインスタンスとなりthisは自身を指すようになります。
function Obj() { console.log(this) } Obj(); //> [object Window]が表示される var obj = new Obj(); //> [object Object]が表示される
1行目の関数を2行目で呼び出した場合はthisはグローバルオブジェクトであるWindowオブジェクトになりますが、3行目のようにコンストラクタ呼び出しをするとObjectオブジェクトとなっていることが分かりますね。
実際に通常の関数呼び出しとコンストラクタ呼び出しの挙動を比較してみましょう。
function MyObject(num){ this.value = num; console.log(this.value); this.show = function() { console.log(this.value) }; } MyObject(5); //5が表示される var myObj = new MyObject(3); //3が表示される console.log(this.value); //5が表示される myObj.show(); //3が表示される
6行目が通常の関数呼び出し、7行目がコンストラクタ呼び出しです。
関数MyObjectではthis.valueに引数の値を代入しますが、通常呼び出しではグローバルオブジェクトのvalueに、コンストラクタ呼び出しでは自身(MyObjectのインスタンス)のvalueに値を入れています。
そのため6行目・8行目ではグローバルvalueが呼ばれるので5が、7行目・9行目ではMyObjectインスタンスのvalueが呼ばれるので3が表示されるわけです。
apply・call関数を使った呼び出し
関数呼び出しにおいてthisに何を入れるか明示的に指定できるのがapply関数とcall関数です。
使い方を見てみましょう。
value = 0; var obj1 = { value: 1 }; var obj2 = { value: 2 }; function func() { console.log(this.value) } func(); //0が表示される func.call(obj1); //1が表示される func.apply(obj2); //2が表示される
4行目の関数funcのthisはグローバルオブジェクトを指すので、6行目のように呼び出すとグローバルのvalueである0が表示されますね。
一方、7行目・8行目のようにcallやapply関数を使って呼び出すと引数に指定したオブジェクトをthisとして指定することができるので、それぞれobj1・obj2のvalueである1と2を表示します。
call・applyでは関数に渡す引数を設定することもでき、その方法によりこれらを使い分けます。詳しくはドキュメント(MDN)を参照してください。
まとめ
JavaScriptでのthisについて、呼び出し方による挙動の違いを説明しました。
最初は理解しにくいので、実際にコードを書いて試していくのがおすすめです。
動画で学ぶプログラミング入門
オンラインスクールProglus(プログラス)でプログラミングを学んで、創れる人になろう!
在宅学習応援!今なら複数の基礎コースが無料!!
今すぐ詳細を確認する