🐸

JavaScriptのthisを解説

2023/07/09に公開

JavaScriptのthisについて解説します。

コンテキスト

JavaScriptにおけるthisは呼び出し元のオブジェクトへの参照を保持するキーワードです。thisはどの実行コンテキストで使用されるかで参照できる値が異なります。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/this

実行コンテキストとは、JavaScriptのコードが実行される前にJavaScriptエンジンによって作られるコードの実行環境です。コードが実行される状態によって参照できる変数や関数の情報を保持しています。

JavaScriptの実行コンテキストには、グローバルコンテキスト関数コンテキストの2種類があります。

グローバルコンテキスト

グローバルコンテキストはJSファイル内の直下に書かれているコードが実効される環境です。グローバルコンテキスト内でコードを実行する際は以下の3つが参照出来ます。

  • 同一コンテキスト内の変数と関数
  • this
  • グローバルオブジェクト

グローバルコンテキスト内でのthisはグローバルオブジェクトを参照します。グローバルオブジェクトはJavaScriptの実行環境によって変わります。ブラウザ環境でJavaScriptを実行している場合、グローバルオブジェクトはWindowオブジェクトになります。

console.log(this); // window{...}
console.log(this === window); // true

Windowオブジェクト

Windowオブジェクトはブラウザ環境でのグローバルオブジェクトです。WindowオブジェクトにはJavaScriptの組み込みのメソッドやプロパティが格納されています。

代表的なものとして、console.logやsetTimeoutなどはWindowオブジェクトのメソッドになります。通常オブジェクトのメソッドを実行する際は、オブジェクト名.メソッド() と書きますがWindowオブジェクトの場合は省略可能です。

// どちらでも良い
window.console.log();
console.log();

// どちらでも良い
window.setTimeout();
setTimeout();

他にも、Windowオブジェクトにプロパティやメソッドを追加することも出来ます。

window.prop = "プロパティが追加出来ます!";

window.method = function() {
  console.log("メソッドが追加出来ます!");
}

window.prop; // プロパティが追加出来ます!
window.method(); // メソッドが追加出来ます!

prop; // windowを省略しても値を表示できます。
method(); // windowを省略してもメソッドを実行できます。

Windowオブジェクトはコードのどこからでも参照出来るブラウザ環境においてのトップレベルのオブジェクトであることを覚えておいてください。

https://developer.mozilla.org/ja/docs/Web/API/Window

関数コンテキスト

関数コンテキストは、関数のブロックの中で書かれているコードが実行される環境です。関数コンテキストでは以下の5つが参照出来ます。

  • 同一コンテキスト内の変数と関数
  • 外部変数
  • this
  • super
  • arguments

関数コンテキスト内でのthisは関数がどのように呼ばれたかによって参照できる値が異なります。thisは基本的に関数の中で使用されるため、thisと関数の関係は非常に重要です。

ここからは関数の種類によってthisがどのような値を参照するか解説していきます。

関数コンテキストのthis

通常の関数の中でのthisはグローバルオブジェクトを参照します。ただJavaScriptのStrictモードが有効化されている場合はundefinedが結果として返ってきます。

function fn() {
  console.log(this);
}

fn(); // window{...}

function fn2() {
  "use strict"; // Strictモードを有効化
  console.log(this);
}

fn2(); // undefined

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Strict_mode

コールバック関数として実行された場合

コールバック関数の中のthisは通常の関数と同じくグローバルオブジェクトを参照します。Strictモードが有効になっている場合はグローバルオブジェクトではなくundefinedが返ってきます。

"use strict"; // Strictモードを有効にするとthisはundefinedとなります。

function fn(cb) {
  cb();
}

fn(function fn2() {
  console.log(this); // window{...}
});

オブジェクトのメソッドとして実行された場合

オブジェクトのメソッドの中でのthisは呼び出し元のオブジェクトを参照します。

const obj = {
  name: "太郎",
  greeting: function() {
    console.log("こんにちは" + this.name); // objオブジェクトのnameプロパティが参照されています。
  }
}

obj.method(); // こんにちは太郎

メソッドとして実行されている場合、thisは呼び出し元のオブジェクトが参照先となり、その中で定義されているプロパティやメソッドを取得します。

ただ例外として、オブジェクトのメソッドとして定義されていても関数として呼び出されている場合、thisはwindowオブジェクトを参照します。

window.name = "次郎";

const obj = {
  name: "太郎",
  greeting: function() {
    console.log("こんにちは" + this.name); // objオブジェクトのnameプロパティが参照されています。
  }
}

const fn = obj.greeting;
fn(); // こんにちは次郎

変数にメソッドが代入されると、関数の呼び出し元がオブジェクトではなくなるためthisの参照先がグローバルオブジェクトになります。

関数はオブジェクトですので、変数に代入されると代入元の関数への参照のコピーが変数へ渡ってきます。

これは参照渡しと言われる挙動です。JavaScriptのオブジェクトは参照渡しになります。参照渡しについては以下の記事で詳しく解説しているので気になる方は読んでみてください。

https://zenn.dev/ryu0947/articles/ff9117c214911e

参照している関数自体は同じでもどこから呼び出されたかによってthisの参照先は変化します。

他にもオブジェクトのメソッドをコールバック関数として引数に渡したり、オブジェクトのメソッドの引数にコールバック関数を渡してもthisの参照先はグローバルオブジェクトとなります。

window.name = "次郎";

const obj = {
  name: "太郎",
  greeting: function () {
    console.log("こんにちは" + this.name);
  },
};

function fn(cb) {
  cb(); // ここで関数として実行されるため参照先はwindowになる
}

fn(obj.greeting); // こんにちは次郎

この場合のthisがグローバルオブジェクトを参照する理由は、コールバック関数として渡されているメソッドが関数として実行されているからです。下記のようにしてみると結果の違いが分かります。

window.name = "次郎";

const obj = {
  name: "太郎",
  greeting: function () {
    console.log("こんにちは" + this.name);
  },
};

function fn(cb) {
  cb(); // ここで関数として実行されるため参照先はwindowになる
  obj.greeting(); // メソッドとして実行されるためthisの参照先はobjオブジェクト
}

fn(obj.greeting);

cb() で実行すると出力結果は「こんにちは次郎」ですが、obj.greeting() と実行すると出力結果は「こんにちは太郎」となります。

関数として実行する場合とオブジェクトのメソッドとして実行する場合でthisの参照先が変わります。

ちなみに下記コードのようにしても結果は同じです。

window.name = "次郎";

const obj = {
  name: "太郎",
  greeting: function (cb) {
    cb(); // メソッドの中であっても関数として実行されているため参照先はwindowとなる
  },
};

function fn() {
  console.log("こんにちは" + this.name);
  obj.greeting();
}

obj.greeting(fn); // こんにちは次郎

アロー関数として実行された場合

アロー関数が実行されたときの関数コンテキストにthisは存在しません。そのため、アロー関数内でthisが使用された場合はレキシカルスコープに対してthisを探しに行きます。

window.name = "次郎";

const obj = {
  name: "太郎",
  greeting: () => {
    console.log("こんにちは" + this.name);
  },
};

obj.greeting(); // こんにちは次郎

オブジェクトのメソッドとして定義されていたとしても、アロー関数として実行されるとthisの参照先はレキシカルスコープを辿って最初に見つかったthisの参照先となります。

例のコードではレキシカルスコープがグローバルコンテキストのthisとなるため、window.nameが参照されて「こんにちは次郎」が出力されます。

アロー関数は関数の省略記法ですが、thisの参照先が通常の関数やメソッドと異なるため使用する際は注意が必要です。

例えば下記のコード内の関数をアロー関数に書き換えた場合、thisの参照先が変わってしまい出力結果に影響が出てしまいます。

window.name = "次郎";

const obj = {
  name: "太郎",
  greeting: function() {
    console.log("こんにちは" + this.name);
  },
};

const arrowFn = function () {
  obj.greeting();
};

arrowFn(); // こんにちは太郎

↓アロー関数に変更後

window.name = "次郎";

const obj = {
  name: "太郎",
  greeting: () => {
    console.log("こんにちは" + this.name);
  },
};

const arrowFn = () => {
  obj.greeting();
};

arrowFn(); // こんにちは次郎

通常の関数では「こんにちは太郎」と出力されていましたが、アロー関数に書き換えたことによって出力結果が「こんにちは次郎」に変わってしまいました。

これはアロー関数によってthisの参照先が変わったことによる影響です。

他にもオブジェクトが多階層となる場合などはアロー関数がどのレキシカルスコープにあるthisを参照するのかを把握しておく必要があります。

const obj = {
  name: "太郎",
  greeting: function () { // obj2のgreetingメソッドのthisから見たレキシカルスコープ

    const obj2 = {
      name: "次郎",
      greeting: () => {
	// このthisはgreetingメソッドのコンテキストのthisを参照する
        console.log("こんにちは" + this.name);
      },
    };

    obj2.greeting();
  },
};

obj.greeting(); // こんにちは太郎

上記のコードでは、obj2オブジェクトのgreetingメソッドのthisがレキシカルスコープのobjオブジェクトのgreetingメソッドのthisを参照しています。

objオブジェクトのgreetingメソッドは無名関数のメソッドとなるためthisの参照先は呼び出し元となるobjオブジェクトです。

このようにアロー関数は自分自身のthisを持たないため、予期しない結果を出力する可能性があるあります。thisの挙動を事を理解したうえで使用しましょう。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Arrow_functions

まとめ

JavaScriptのthisは実行コンテキストによって参照できる値が異なります。基本的にthisは関数の中で使用することが多いため、関数がどのように実行されているかを把握しておくことが重要です。

当記事がthisの理解の参考になれば嬉しいです。最後までお読みいただきありがとうございました。

Discussion