JavaScript の this キーワードを完全理解!初心者がつまずくポイント解説

JavaScript を学習していると、必ず出会うのが this
キーワードです。しかし、他のプログラミング言語とは異なる動作をするため、多くの初心者の方が混乱してしまいます。
本記事では、JavaScript の this
について、つまずきやすいポイントを含めて丁寧に解説いたします。最後まで読んでいただければ、this
の動作が完全に理解でき、自信を持ってコードを書けるようになるでしょう。
背景
JavaScript の this は特殊な存在
JavaScript の this
は、他のプログラミング言語(Java や C# など)とは大きく異なる動作をします。多くの言語では this
は「現在のオブジェクトインスタンス」を指すのが一般的ですが、JavaScript では 実行時のコンテキスト によって値が決まるのです。
この特殊な仕様は JavaScript の柔軟性を生み出している一方で、初心者の方には理解の壁となってしまいます。実際、多くのエンジニアが JavaScript 学習の際に最も苦労する概念の一つとして this
を挙げているんですね。
以下の図で、JavaScript の this
の特殊性を他言語と比較してみましょう。
mermaidflowchart TD
other[他の言語] -->|固定的| obj[オブジェクトインスタンス]
js[JavaScript] -->|動的| context[実行コンテキスト]
context --> globalObj[グローバルオブジェクト]
context --> methodOwner[メソッドの所有者]
context --> constructorFn[新しいインスタンス]
context --> explicitBind[明示的バインド]
図からわかるように、JavaScript では実行時の状況によって this
が指す対象が変わります。この動的な性質こそが、理解を困難にしている要因なのです。
実行コンテキストが決める this の値
JavaScript エンジンは、関数が呼び出される際に 実行コンテキスト を作成します。この実行コンテキストの中で this
の値が決定されるため、同じ関数でも呼び出し方によって this
の値が変わってしまうのです。
javascriptconst person = {
name: 'Taro',
greet: function () {
console.log(`Hello, ${this.name}`);
},
};
// 同じ関数でも呼び出し方で this が変わる
person.greet(); // "Hello, Taro"
const greetFunc = person.greet;
greetFunc(); // "Hello, undefined" または エラー
この例では、同じ greet
関数が異なる結果を返しています。これが JavaScript の this
の特殊性を表す典型的な例なんですね。
課題
this の値が呼び出し方で変わる複雑さ
初心者の方が最も混乱するのは、同じ関数でも呼び出し方によって this
の値が変わる ことです。オブジェクト指向プログラミングに慣れている方ほど、この動作に戸惑ってしまいます。
javascriptconst calculator = {
value: 0,
add: function (num) {
this.value += num;
return this.value;
},
};
// メソッドとして呼び出し(期待通り動作)
console.log(calculator.add(5)); // 5
// 関数として取り出して呼び出し(期待と異なる動作)
const addFunc = calculator.add;
addFunc(3); // エラーまたは予期しない結果
このような予期しない動作により、「なぜ動かないの?」という疑問が生まれ、JavaScript 学習の大きな壁となってしまいます。
アロー関数と通常関数での this の違い
ES6 で導入されたアロー関数は、this
の扱いが通常の関数と全く異なります。この違いを理解せずに使い分けることで、さらなる混乱が生じてしまうのです。
以下の図で、通常関数とアロー関数での this
の決定方法を比較してみましょう。
mermaidflowchart LR
call[関数呼び出し] --> normal{通常関数?}
normal -->|Yes| dynamic[動的に this を決定]
normal -->|No| arrow[アロー関数]
arrow --> lexical[レキシカルスコープの this を使用]
dynamic --> caller[呼び出し元によって決定]
lexical --> parent[定義時の親スコープの this]
アロー関数は定義時点で this
が固定されるため、呼び出し方に関係なく常に同じ this
を参照します。この特性を理解しないまま使うと、期待した動作にならないことがあります。
メソッド、コールバック、イベントハンドラでの予期しない動作
実際の開発では、メソッドをコールバック関数として渡したり、イベントハンドラとして設定したりする場面が多くあります。このような場合に this
の値が予期しない値になることが頻発します。
javascriptclass Timer {
constructor() {
this.seconds = 0;
}
start() {
// setInterval のコールバック内での this は Timer インスタンスを指さない
setInterval(function () {
this.seconds++; // this は window または undefined
console.log(this.seconds); // NaN または エラー
}, 1000);
}
}
このようなコードは一見正しく見えますが、実際には期待通りに動作しません。コールバック関数内の this
が Timer インスタンスを指していないからです。
解決策
this の 4 つのバインディングルールの理解
JavaScript の this
は、以下の 4 つのルールに従って値が決まります。この優先順位を理解することで、this
の値を正確に予測できるようになります。
# | バインディングルール | 説明 | 優先度 |
---|---|---|---|
1 | new バインディング | new 演算子で関数を呼び出した場合 | 最高 |
2 | 明示的バインディング | call , apply , bind を使った場合 | 高 |
3 | 暗黙的バインディング | オブジェクトのメソッドとして呼び出した場合 | 中 |
4 | デフォルトバインディング | 上記以外の場合 | 最低 |
この優先順位を覚えることで、どのような状況でも this
の値を正確に判断できるようになります。
以下の図で、バインディングルールの適用プロセスを可視化してみましょう。
mermaidflowchart TD
start[関数呼び出し] --> new_check{new で呼び出し?}
new_check -->|Yes| new_binding[new バインディング<br/>新しいインスタンス]
new_check -->|No| explicit_check{call/apply/bind?}
explicit_check -->|Yes| explicit_binding[明示的バインディング<br/>指定されたオブジェクト]
explicit_check -->|No| implicit_check{オブジェクトメソッド?}
implicit_check -->|Yes| implicit_binding[暗黙的バインディング<br/>呼び出し元オブジェクト]
implicit_check -->|No| default_binding[デフォルトバインディング<br/>グローバル or undefined]
このフローチャートに従って判断することで、this
の値を確実に特定できます。
バインドメソッドの活用法
this
の値を明示的にコントロールするために、JavaScript では call()
, apply()
, bind()
の 3 つのメソッドが用意されています。
call() メソッド
call()
メソッドは、this
の値を指定して関数を即座に実行します。
javascriptfunction introduce() {
console.log(`私は${this.name}です。${this.age}歳です。`);
}
const person1 = { name: '田中', age: 25 };
const person2 = { name: '佐藤', age: 30 };
// this を person1 に設定して実行
introduce.call(person1); // "私は田中です。25歳です。"
introduce.call(person2); // "私は佐藤です。30歳です。"
apply() メソッド
apply()
は call()
と似ていますが、引数を配列として渡す点が異なります。
javascriptfunction calculateSum(a, b, c) {
console.log(`${this.name}の計算結果: ${a + b + c}`);
return a + b + c;
}
const calculator = { name: '計算機' };
// 引数を個別に渡す場合
calculateSum.call(calculator, 1, 2, 3); // "計算機の計算結果: 6"
// 引数を配列で渡す場合
calculateSum.apply(calculator, [1, 2, 3]); // "計算機の計算結果: 6"
bind() メソッド
bind()
は新しい関数を作成し、その関数の this
を永続的に固定します。
javascriptclass EventHandler {
constructor() {
this.message = 'ボタンがクリックされました!';
}
handleClick() {
console.log(this.message);
}
}
const handler = new EventHandler();
// bind() で this を固定した新しい関数を作成
const boundHandler = handler.handleClick.bind(handler);
// コールバック関数として安全に使用可能
document
.getElementById('btn')
.addEventListener('click', boundHandler);
適切な関数定義の選択方法
状況に応じて通常関数とアロー関数を使い分けることで、this
に関する問題を予防できます。
通常関数を選ぶべき場面
- オブジェクトのメソッドを定義する場合
- コンストラクタ関数を作成する場合
this
の値を動的に変更したい場合
javascriptconst userService = {
users: ['Alice', 'Bob', 'Charlie'],
// オブジェクトメソッドは通常関数で定義
getUsers: function () {
return this.users; // userService を指す
},
addUser: function (name) {
this.users.push(name);
return this.users.length;
},
};
アロー関数を選ぶべき場面
- コールバック関数として使用する場合
- 親スコープの
this
を保持したい場合 - イベントハンドラで外側のコンテキストを参照したい場合
javascriptclass Counter {
constructor() {
this.count = 0;
}
startTimer() {
// アロー関数で親スコープ(Counter インスタンス)の this を保持
setInterval(() => {
this.count++; // Counter インスタンスを指す
console.log(this.count);
}, 1000);
}
}
具体例
グローバルコンテキストでの this
グローバルスコープ(どの関数にも属さない場所)で this
を参照すると、ブラウザ環境では window
オブジェクト、Node.js 環境では global
オブジェクトを指します。
javascript// ブラウザ環境
console.log(this); // window オブジェクト
console.log(this === window); // true
// strict mode では undefined
('use strict');
console.log(this); // undefined
グローバルコンテキストでの this
は実際の開発ではほとんど使用しませんが、理解しておくことで他の場面での動作予測に役立ちます。
オブジェクトメソッドでの this
オブジェクトのプロパティとして定義された関数(メソッド)では、this
はそのオブジェクト自身を指します。これが最も直感的で理解しやすいパターンです。
javascriptconst book = {
title: 'JavaScript 入門',
author: '山田太郎',
pages: 320,
// メソッド内の this は book オブジェクトを指す
getInfo: function () {
return `『${this.title}』著者:${this.author}、${this.pages}ページ`;
},
};
console.log(book.getInfo());
// "『JavaScript 入門』著者:山田太郎、320ページ"
ただし、メソッドを変数に代入してから呼び出すと、this
の値が変わってしまいます。
javascript// メソッドを変数に代入
const getBookInfo = book.getInfo;
// この時点で this は book オブジェクトではなくなる
console.log(getBookInfo()); // エラーまたは予期しない結果
このような問題を回避するために、次に説明するバインドメソッドを使用します。
コンストラクタ関数での this
new
演算子を使って関数を呼び出すと、その関数内の this
は新しく作成されるオブジェクトを指します。これがコンストラクタ関数の仕組みです。
javascript// コンストラクタ関数の定義
function Car(make, model, year) {
// this は新しく作成される Car インスタンス
this.make = make;
this.model = model;
this.year = year;
this.getInfo = function () {
return `${this.year}年式 ${this.make} ${this.model}`;
};
}
// new 演算子で新しいインスタンスを作成
const myCar = new Car('Toyota', 'Prius', 2022);
console.log(myCar.getInfo()); // "2022年式 Toyota Prius"
コンストラクタ関数内では、this
を通じて新しいオブジェクトにプロパティやメソッドを追加できます。
以下の図で、コンストラクタ関数での this
のバインディング過程を見てみましょう。
mermaidsequenceDiagram
participant caller as 呼び出し元
participant engine as JS エンジン
participant instance as 新インスタンス
caller->>engine: new Car('Toyota', 'Prius')
engine->>instance: 空のオブジェクト作成
engine->>engine: this = 新インスタンス
engine->>instance: this.make = 'Toyota'
engine->>instance: this.model = 'Prius'
engine->>caller: インスタンスを返却
この過程により、new
演算子使用時は必ず新しいインスタンスが this
となります。
アロー関数での this
アロー関数は、定義された時点で親スコープの this
を「キャプチャ」します。その後、どのように呼び出されても this
の値は変わりません。
javascriptconst team = {
name: '開発チーム',
members: ['Alice', 'Bob', 'Charlie'],
// 通常関数でのメソッド
showMembers: function () {
console.log(`${this.name}のメンバー:`);
// アロー関数は親スコープ(showMembers)の this を継承
this.members.forEach((member) => {
console.log(`- ${member} (チーム: ${this.name})`);
});
},
};
team.showMembers();
// 開発チームのメンバー:
// - Alice (チーム: 開発チーム)
// - Bob (チーム: 開発チーム)
// - Charlie (チーム: 開発チーム)
もし forEach
の中で通常関数を使用した場合は、以下のような問題が発生します。
javascriptconst team = {
name: '開発チーム',
members: ['Alice', 'Bob'],
showMembers: function () {
this.members.forEach(function (member) {
// この this は team オブジェクトを指さない
console.log(`- ${member} (チーム: ${this.name})`); // エラー
});
},
};
イベントハンドラでの this
DOM イベントハンドラでは、this
は通常イベントが発生した要素を指します。しかし、クラスのメソッドをイベントハンドラとして使用する場合は注意が必要です。
javascriptclass ButtonController {
constructor(element) {
this.element = element;
this.clickCount = 0;
}
// 通常関数でのイベントハンドラ
handleClick() {
this.clickCount++; // この this は button 要素を指す
console.log(`クリック数: ${this.clickCount}`); // エラー
}
// 正しい実装方法 1: bind() を使用
setupWithBind() {
this.element.addEventListener(
'click',
this.handleClick.bind(this)
);
}
// 正しい実装方法 2: アロー関数を使用
handleClickArrow = () => {
this.clickCount++; // この this は ButtonController インスタンス
console.log(`クリック数: ${this.clickCount}`);
};
}
以下の図で、イベントハンドラでの this
のバインディングパターンを整理してみましょう。
mermaidflowchart TD
event[イベント発生] --> handler_type{ハンドラの種類}
handler_type -->|通常関数| element[this = イベント要素]
handler_type -->|アロー関数| lexical[this = 定義時の親スコープ]
handler_type -->|bind済み関数| bound[this = bind で指定したオブジェクト]
element --> problem[期待と異なる動作]
lexical --> expected[期待通りの動作]
bound --> expected
この図からわかるように、通常関数をそのまま使うと期待と異なる結果になりがちです。
まとめ
this の動作パターン総括
JavaScript の this
について、重要なポイントを振り返りましょう。
図で理解できる要点
this
は実行時のコンテキストで動的に決まる- 4 つのバインディングルールには優先順位がある
- アロー関数は定義時の
this
を固定する
呼び出しパターン | this の値 | 使用場面 |
---|---|---|
obj.method() | obj オブジェクト | オブジェクトメソッド |
new Constructor() | 新しいインスタンス | オブジェクト生成 |
func.call(obj) | 指定したオブジェクト | 明示的制御 |
func() | window/global または undefined | 通常の関数呼び出し |
アロー関数 | 定義時の親スコープ | コールバック関数 |
適切な使い分けのガイドライン
this
を正しく使うための実践的なガイドラインです。
通常関数を使う場面
- オブジェクトのメソッドを定義するとき
- コンストラクタ関数を作成するとき
this
の値を動的に変更したいとき
アロー関数を使う場面
- コールバック関数として使用するとき
- 親スコープの
this
を保持したいとき - イベントハンドラで外側のコンテキストを参照したいとき
バインドメソッドを使う場面
- メソッドを他の場所で使い回したいとき
this
の値を明示的に固定したいとき- イベントハンドラで特定のオブジェクトを参照したいとき
これらのガイドラインを参考に、状況に応じて最適な方法を選択してください。最初は混乱するかもしれませんが、実際にコードを書きながら経験を積むことで、自然と適切な判断ができるようになります。
JavaScript の this
は確かに複雑ですが、その仕組みを理解すれば、より柔軟で表現力豊かなコードが書けるようになるでしょう。ぜひ今日学んだ内容を実際のプロジェクトで活用してみてくださいね。
関連リンク
- article
JavaScript の this キーワードを完全理解!初心者がつまずくポイント解説
- article
【徹底比較】JavaScript での null と undefined の違いと正しい使い分け
- article
JavaScript のクロージャ完全ガイド:スコープとメモリの仕組みを深掘り
- article
【保存版】JavaScript のイベントループとタスクキューを図解で理解する
- article
JavaScript モジュール徹底解説:CommonJS・AMD・ESM の違いと使い分け
- article
【入門】JavaScript の非同期処理を完全理解!Promise・async/await の基礎と実践
- article
Gemini CLI のプロンプト設計術:高精度応答を引き出すテクニック 20 選
- article
gpt-oss と OpenAI GPT の違いを徹底比較【コスト・性能・自由度】
- article
【保存版】Git のタグ(tag)の使い方とリリース管理のベストプラクティス
- article
JavaScript の this キーワードを完全理解!初心者がつまずくポイント解説
- article
GPT-5 で作る AI アプリ:チャットボットから自動化ツールまでの開発手順
- article
Motion(旧 Framer Motion)基本 API 徹底解説:motion 要素・initial/animate/exit の正しい使い方
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来