JavaScriptの"this"を完全解説!!【開眼JavaScriptまとめ】

公開日:

目次

今回のブログではJavaScriptの中でも複雑でつまずきやすいthisについて解説します。

JavaScriptでは、thisの値は独特な働きをするため、thisの挙動に悩まさせている方も多くいるのではないでしょうか?

今回のブログは、そんなJavaScriptの悩みを解決するためにthisについて解説いたします。

また、今回も開眼JavaScriptの内容を自分なりにまとめたものになります。

今回のブログはこんな方におすすめ!
  • JavaScriptのthisの挙動に悩まされている方
  • JavaScriptを深く理解したい方
  • フロントエンドエンジニアを目指されている方

開眼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アロー関数内呼び出し元のオブジェクト(レシーバーオブジェクト)
4call / apply メソッド引数で指定されたオブジェクト
5bind メソッド引数で指定されたオブジェクト
6イベントリスナー / イベントハンドラーイベントの発生元
7コンストラクター生成されたインスタンス
8メソッド呼び出し元のオブジェクト(レシーバーオブジェクト)

上記のように、thisには8種類の使用方法が存在します。それでは具体的に見ていきましょう。

トップレベル(関数の外)ではthisはグローバルオブジェクトを参照する

トップレベル(関数の外)でthisを使用するときは、Strict モードであるかどうかに関わらず、グローバルオブジェクトを参照します。

ここで1つ新たに疑問が生まれます。グローバルオブジェクトとは何なのでしょうか。

解説します。

グローバルオブジェクトはwindowオブジェクト

Webブラウザ環境でJavaScriptのコードを書く場合には、グローバルオブジェクトはwindowオブジェクトと定義されています。

グローバルオブジェクトについてより詳しく説明すると、実は全てのJavaScriptのコードはオブジェクトに格納されている必要があります。その例として、Webブラウザ環境でJavaScriptのコードを書く場合にはwindowオブジェクト内にJavaScriptを格納し、windowオブジェクト内でJavaScriptを実行します。

このような、windowオブジェクトや他の環境でwindowオブジェクトと同じように、JavaScriptのコードを格納する働きをするオブジェクトのことをグローバルオブジェクトと呼びます。

グローバルオブジェクトについて理解した所で、実際にコード上で例を見ていきましょう。

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 モードではthisundefinedを出力します。

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の内容が変化していることがわかると思います。また、引数にnullを渡した場合には暗黙的にグローバルオブジェクトが渡されたものとみなされます。

ちなみに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アロー関数内呼び出し元のオブジェクト(レシーバーオブジェクト)
4call / apply メソッド引数で指定されたオブジェクト
5bind メソッド引数で指定されたオブジェクト
6イベントリスナー / イベントハンドラーイベントの発生元
7コンストラクター生成されたインスタンス
8メソッド呼び出し元のオブジェクト(レシーバーオブジェクト)