JavaScriptの"this"を完全解説!!【開眼JavaScriptまとめ】
目次
今回のブログではJavaScriptの中でも複雑でつまずきやすいthis
について解説します。
JavaScriptでは、this
の値は独特な働きをするため、this
の挙動に悩まさせている方も多くいるのではないでしょうか?
今回のブログは、そんなJavaScriptの悩みを解決するためにthis
について解説いたします。
また、今回も開眼JavaScriptの内容を自分なりにまとめたものになります。
開眼JavaScriptとは?
最初に本記事の参考文献である開眼JavaScriptという技術書について軽く説明したいと思います。
開眼JavaScriptの正式なタイトルは”開眼JavaScript 言語仕様から学ぶJavaScriptの本質”といいます。
そのタイトル通りではありますが、JavaScriptの言語特性で知っておくべきポイントを纏めた書籍です。挫折しがちな点である、this、プロトタイプチェーン(継承)、スコープチェーンなどが、サンプルを使って説明されています。
JavaScriptのバージョンはES3を参考に書かれているため、古い仕様ではありますが、JavaScriptの本質的な理解を助けてくれる1冊です。
thisキーワードの参照先
冒頭でも説明したとおり、thisの正体は、スクリプトのどこからでも参照できる変数です。そして、すべての関数に渡されるthis
の値は、関数が実行時に呼び出されるコンテクスト(状況)に依存します。さらに、Strict モードの有無によっても参照先が変わります。
とても不思議な変数ですよね。その性質上、this
はJavaScript初心者にとってはわかりにくく、挫折する原因にもなることがあります。そのため、このブログでわかりやすく整理して解説します。
this
の参照先は以下の条件で変化します。
コンテクスト(状況) | thisキーワードの参照先 | |
1 | トップレベル(関数の外) | グローバルオブジェクト |
2 | 関数内 | グローバルオブジェクト(Strict モードではundefined) |
3 | アロー関数内 | 呼び出し元のオブジェクト(レシーバーオブジェクト) |
4 | call / apply メソッド | 引数で指定されたオブジェクト |
5 | bind メソッド | 引数で指定されたオブジェクト |
6 | イベントリスナー / イベントハンドラー | イベントの発生元 |
7 | コンストラクター | 生成されたインスタンス |
8 | メソッド | 呼び出し元のオブジェクト(レシーバーオブジェクト) |
上記のように、this
には8種類の使用方法が存在します。それでは具体的に見ていきましょう。
トップレベル(関数の外)ではthisはグローバルオブジェクトを参照する
トップレベル(関数の外)でthis
を使用するときは、Strict モードであるかどうかに関わらず、グローバルオブジェクトを参照します。
ここで1つ新たに疑問が生まれます。グローバルオブジェクトとは何なのでしょうか。
解説します。
グローバルオブジェクトについて理解した所で、実際にコード上で例を見ていきましょう。
console.log(this === window); // 結果:true
const foo = "bar";
a = window; b = this;
console.log(a, b); // 結果:window, window どちらもwindowオブジェクトへの参照を出力 console.log(a.foo, b.foo); // 結果:"bar", "bar"
このように、this
がwindowオブジェクトを参照していることが分かります。
関数内ではthisはグローバルオブジェクトを参照する
関数の中でのthis
の値は、関数の呼び出され方によって異なります。
基本的に、関数の中でthis
を使用する場合には、グローバルオブジェクトを参照します。しかし、例外があって、Strict モードではundefined
になります。
実際にコード上で例を見ていきましょう。最初は非Strict モードでthis
を使用した例です。
function a() { return this; }
// ブラウザー上で a() === window; // true
// Node 上で a() === global; // true
次は、Strict モードでthis
を使用して例です。Strict モードではthis
はundefined
を出力します。
function b() { 'use strict'; // 厳格モードにする return this; }
b() === undefined; // true
このように、this
の値はStrict モードかそうでないかによって異なります。
アロー関数内ではthisは呼び出し元のオブジェクト(レシーバーオブジェクト)を参照する
関数であってもアロー関数内では、this
の値は通常の関数定義や関数式とは異なる値を参照します。
アロー関数内のthis
は定義されておらず、呼び出し元のオブジェクト(レシーバーオブジェクト)を参照します。
実際にコード上で見ていきましょう。
let a = { firstName: "Ui", sayHi() { let arrow = () => { console.log(this); }; arrow(); } };
a.sayHi(); // 結果:オブジェクトa
let b = { firstName: "Ui", sayHi() { let arrow = function () { console.log(this); }; arrow(); } };
b.sayHi(); // 結果:windowオブジェクト
アロー関数を使用した、オブジェクトaではthis
はオブジェクトaを参照しています。それに対して、関数式を使用した、オブジェクトbでは、this
はwindowオブジェクトを参照しています。
オブジェクトaではアロー関数を使用しているため、sayHi()
メソッドが含まれているaオブジェクトを参照します。それに対し、オブジェクトbでは関数式を使用しています。関数内ではthisはグローバルオブジェクトを参照するため、this
はwindowオブジェクトを参照しています。
call / apply メソッドでthisの参照先を指定する
this
はcall / apply メソッドを使用することで、参照先をthis
を呼び出す側からより自由にコントロールできます。
callもapplyもFunctionオブジェクトのprototypeであり、用法が良く似ています。関数実行時に、func.call()
や、func.apply()
という方法で実行します。call / apply メソッドの違いは実行すべき関数に渡す引数の指定方法だけです。
実際のコード上で見ていきましょう。
const objectA = {data: "A"}; const objectB = {data: "B"};
function hoge() { console.log(this) }
hoge(); // 結果:windowオブジェクト hoge.call(null); // 結果:windowオブジェクト hoge.call(objectA); // 結果:{data: "A"} hoge.call(objectB); // 結果:{data: "B"}
引数にオブジェクトを渡すことで、関数オブジェクト配下のthis
が参照する値を無理矢理変更できます。上記のコードでもhoge関数以下のthis
の内容が変化していることがわかると思います。また、引数にnul
lを渡した場合には暗黙的にグローバルオブジェクトが渡されたものとみなされます。
ちなみにcall / apply メソッドでは第2引数に実行する関数に渡す引数を渡すことができます。このあたりの話はthis
とは少し路線がずれるため、また別のブログで解説します。
bind メソッドでthisの参照先を指定する
JavaScriptではbind メソッドを使用することでもthis
の値を自由にコントロールすることができます。
call / apply メソッドとの違いはcall / apply メソッドはthisの値を固定した関数をcallの呼び出しと同時に実行するのに対し、bindはthisの値を固定した新しい関数を作成します。callと違ってbindしただけでは実行されません。
function say(){ console.log("こんにちは。" + this + "です"); }
const riku = say.bind("riku"); riku(); // 結果:こんにちは。rikuです
このように、bind メソッドは関数の定義時に使用し、関数オブジェクト配下のthis
が参照する値を無理矢理固定できます。
イベントリスナー / イベントハンドラー内ではthisはイベントの発生元を参照する
イベントリスナー / イベントハンドラー配下のthis
はイベントの発生元(要素)を参照します。
ただし、最近はイベントリスナー / イベントハンドラーの引数にアロー関数を使用します。アロー関数を引数に渡した場合には、アロー関数の判定が優先され、this
は呼び出し元のオブジェクト(レシーバーオブジェクト)を参照します。
実際にコードを見ていきましょう。
<div id="app">クリックしてね</div>
document.getElementById("app").addEventListener("click", function () { console.log(this); // 結果:<div id="app">クリックしてね</div> });
document.getElementById("app").addEventListener("click", () => { console.log(this); 結果:windowオブジェクト });
↑の例のように、イベントリスナー / イベントハンドラーの引数に無名関数を渡した場合はイベントの発生元を参照する。それに対して、イベントリスナー / イベントハンドラーの引数にアロー関数を渡した場合は呼び出しオブジェクトを参照するため、windowオブジェクトを参照します。
コンストラクターで呼び出す場合、thisは生成されたインスタンスを参照する。
関数がコンストラクターとして、(newキーワードと共に)使用されたとき、そのthis
は新たに生成されたインスタンスを参照します。
実際にコードを見ていきましょう。
//コンストラクター const Person = function(name, age){ this.name = name; this.age = age; };
//インスタンス生成 const Riku = new Person("Riku", 22); console.log(Riku.name); // 結果:Riku
//クラスによるコンストラクター class Person { constructor(name, age) { this.name = name; this.age = age; } }
//インスタンス生成 const Riku = new Person("Riku", 22);
console.log(Riku.name); // 結果:Riku console.log(Riku.age); // 結果:22
この例では、this
は生成されたインスタンスであるRikuを参照しています。
このように、通常の関数コンストラクターと、クラスによるコンストラクターどちらでthis
を使用した場合でも、そのthis
は新たに生成されたインスタンスを参照します。
メソッド内ではthisは呼び出し元のオブジェクト(レシーバーオブジェクト)を参照する
メソッド内でthis
が使用される場合には、呼び出し元のオブジェクト(レシーバーオブジェクト)を参照します。
実際にコードを見ていきましょう。
//コンストラクター const Person = function(name, age){ this.name = name; this.age = age; this.getName = () => { return this.name; } };
//インスタンス生成 const Riku = new Person("Riku", 22); console.log(Riku.getName()); // 結果:Riku
class Person { constructor(name, age) { this.name = name; this.age = age; }
getName() { return this.name; } }
//インスタンス生成 const Riku = new Person("Riku", 22);
console.log(Riku.getName()); // 結果:Riku
このように、メソッド内のthis
は呼び出し元のオブジェクト(レシーバーオブジェクト)を参照します。また、呼び出し元のオブジェクト(レシーバーオブジェクト)を参照することは実質、生成されるインスタンスを参照していることと同じ意味になります。
少し古い使用ではありますが、プロトタイプメソッド内のthis
も生成されるインスタンスを参照します。
まとめ
今回のブログではJavaScriptのthis
キーワードについて徹底解説しました。this
は初心者にとっては複雑で難しいですが、怖がらずに使用していれば、少しずつ慣れてくると思うのでJavaScriptをマスターするためにも理解できるように頑張りましょう!!
最後にまとめの表を載せておきます。
コンテクスト(状況) | thisキーワードの参照先 | |
1 | トップレベル(関数の外) | グローバルオブジェクト |
2 | 関数内 | グローバルオブジェクト(Strict モードではundefined) |
3 | アロー関数内 | 呼び出し元のオブジェクト(レシーバーオブジェクト) |
4 | call / apply メソッド | 引数で指定されたオブジェクト |
5 | bind メソッド | 引数で指定されたオブジェクト |
6 | イベントリスナー / イベントハンドラー | イベントの発生元 |
7 | コンストラクター | 生成されたインスタンス |
8 | メソッド | 呼び出し元のオブジェクト(レシーバーオブジェクト) |