JavaScript アドバンス

JavaScript の this キーワード

1. this とは何か?

関数内で this キーワードが使用されるとき、それはある オブジェクト(Object) を参照します。

どのオブジェクトを参照するかは、関数が「書かれたとき」には決まりません。
this の値は、関数が 「呼び出された(実行された)とき」 に決定されます。

       注意: この章ではアドバンスド(高度)なトピックを扱います。続行する前に、JavaScript 関数の基本を理解していることを確認してください。

2. 関数はオブジェクトのメソッドである

JavaScript において、すべての関数は実質的に メソッド(Method) です。
関数は以下のいずれかとして存在します:

  • JavaScript オブジェクトのメソッド
  • グローバルオブジェクトのメソッド

3. オブジェクトメソッド内の this

関数が オブジェクトメソッド として定義されている場合:
this はそのメソッドを保持している オブジェクト を参照します。

3.1 オブジェクトメソッドの例

以下は firstNamelastNamefullName の3つのプロパティを持つオブジェクトの例です。

const person = {
  firstName: "太郎",
  lastName: "山田",
  fullName: function() {
    // this は person オブジェクトを参照する
    return this.firstName + " " + this.lastName;
  }
};

person.fullName();

上記の例において、thisperson オブジェクトを指します。
this.firstNameperson.firstName と同じ意味になります。

4. 関数内での this(デフォルト)

通常の関数内で this が使用される場合、デフォルトでは グローバルオブジェクト を参照します。
ブラウザのウィンドウ環境では、グローバルオブジェクトは [object Window] です。

function myFunction() {
  return this;
}

この場合、this はグローバルオブジェクト(window)となります。

5. 関数内での this(Strict Mode)

JavaScript の strict mode(厳格モード) では、デフォルトのバインディング(結合)が許可されません。
strict mode では、関数内の thisundefined になります。

"use strict";
function myFunction() {
  return this;
}

この例では、this の値は undefined を返します。

6. 単独で使用される this

関数の外側(グローバルスコープ)で this が使用される場合、常に グローバルオブジェクト を参照します。
ブラウザ環境では [object Window] です。

let x = this;

strict mode であっても、関数の外で単独で使用される this はグローバルオブジェクトを参照します。

"use strict";
let x = this;

これらは、thisグローバルスコープ(Global Scope) に存在するためです。

7. JavaScript globalThis

globalThis は、コードが実行されている環境(ブラウザ、Node.js、Web Worker など)に関わらず、標準的な方法でグローバルオブジェクトにアクセスするための特別な組み込みオブジェクトです。

各実行環境におけるグローバルオブジェクトの名称:

  • ブラウザ: window
  • Node.js: global
  • Web Worker: self

環境ごとに名称が異なることは、マルチプラットフォームで動作するコードを書く際の互換性の問題となっていました。これを解決するために、ECMAScript 2020 (ES11)globalThis が導入されました。

globalThis === window; // ブラウザ環境では true
globalThis === global; // Node.js 環境では true

8. イベントハンドラー内の this

HTML の イベントハンドラー(Event Handler) において、this はイベントを受け取った HTML 要素を参照します。

<button onclick="this.innerHTML='クリックされました!'">ここを押して</button>

<button onclick="this.style.display='none'">
  クリックでこのボタンを削除
</button>

9. アロー関数内の this

アロー関数(Arrow Functions) は、独自の this を持ちません。代わりに、自身が定義された外側のスコープ(レキシカルスコープ)から this を継承します。

  • 通常の関数: this はその関数を呼び出したオブジェクト(window、document、ボタン要素など)を表す。
  • アロー関数: this はそのアロー関数を「定義した」オブジェクトを常に表す。

10. まとめ:this が参照するもの

JavaScript における this の参照先は、使用されるコンテキストによって以下のように変化します。

コンテキストthis が参照するオブジェクト
単独で使用グローバルオブジェクト
関数内(通常)グローバルオブジェクト
関数内(Strict mode)undefined
オブジェクトメソッド内そのメソッドを持つオブジェクト
イベント内イベントを受け取った HTML 要素
call(), apply(), bind()引数で指定された任意のオブジェクト

this は変数ではなく キーワード(予約語) です。そのため、その値を手動で書き換えることはできません。

11. this の優先順位(Precedence)

どのオブジェクトが this として参照されるかを判断する際は、以下の優先順位(プレシデンス)に従います。

優先順位手法・コンテキスト理由
1bind()bind() によって明示的に固定された関数内にあるため
2apply() / call()apply() または call() で指定されたオブジェクトが優先されるため
3オブジェクトメソッドオブジェクトの関数(メソッド)として呼び出されているため
4グローバルスコープどのオブジェクトにも属さず、グローバルに存在するため

12. 比較例:通常関数 vs アロー関数

同じメソッドを「ページロード時」と「ボタンクリック時」の2回呼び出した場合の挙動の違いを見てみましょう。

12.1 通常関数の場合

通常の関数では、this は関数を 呼び出した オブジェクトを表します。

// 通常の関数
hello = function() {
  document.getElementById("demo").innerHTML += this;
}

// window オブジェクトが呼び出し元になる: [object Window]
window.addEventListener("load", hello);

// ボタンオブジェクトが呼び出し元になる: [object HTMLButtonElement]
document.getElementById("btn").addEventListener("click", hello);

12.2 アロー関数の場合

アロー関数では、this はその関数の オーナー(定義時のスコープ) を表します。

// アロー関数
hello = () => {
  document.getElementById("demo").innerHTML += this;
}

// window オブジェクトがオーナー: [object Window]
window.addEventListener("load", hello);

// 呼び出し元がボタンでも、オーナーである window を参照し続ける: [object Window]
document.getElementById("btn").addEventListener("click", hello);

13. メソッド内でのアロー関数の注意点

アロー関数は this のバインディングを持たないため、オブジェクトメソッドを定義するのには適していません。

const person = {
  firstName: "太郎",
  sayHello: () => {
    // 期待通りに動作しない(this は person を指さない)
    return this.firstName;
  }
};

person.sayHello(); // undefined またはエラー

ただし、メソッドの中で this の値を維持したまま別の処理を行いたい場合には、アロー関数が非常に有効です。

const person = {
  firstName: "太郎",
  sayHello: function() {
    // メソッド内のクロージャとしてアロー関数を使うことで、
    // 外側の sayHello の this(person)を継承できる
    return () => this.firstName;
  }
};

let hello = person.sayHello();
hello(); // "太郎" を返す