はじめに#
JavaScriptを学ぶ上で、多くの初心者がつまずくポイントの一つがthisキーワードです。thisは「呼び出し方」によって参照先が変わるという特性を持っており、他のプログラミング言語と異なる挙動を示すことがあります。
本記事では、JavaScriptのthisについて以下の内容を初心者向けにわかりやすく解説します。
thisとは何か、なぜ重要なのか
- グローバルコンテキストでの
this
- 通常の関数での
this
- オブジェクトのメソッドでの
this
- イベントハンドラーでの
this
- クラスでの
this
- アロー関数での
this
call・apply・bindによるthisの明示的な指定
thisの挙動を正しく理解することで、意図しないバグを防ぎ、より堅牢なJavaScriptコードを書けるようになります。
thisとは#
thisの基本概念#
thisは、JavaScriptの特別なキーワードで、現在のコードが実行されているコンテキスト(文脈)を参照します。簡単に言えば、「今、誰がこのコードを実行しているか」を指し示すものです。
1
2
3
4
5
6
7
8
|
const person = {
name: "田中",
greet() {
console.log(`こんにちは、${this.name}です`);
}
};
person.greet(); // こんにちは、田中です
|
上記の例では、greetメソッド内のthisはpersonオブジェクトを参照しています。this.nameはperson.nameと同じ意味になり、「田中」が出力されます。
thisが重要な理由#
thisを正しく理解することで、以下のメリットがあります。
- 同じメソッドを異なるオブジェクトで再利用できる
- オブジェクト指向プログラミングの基盤を理解できる
- コールバック関数やイベントハンドラーでのバグを防げる
- 他のJavaScriptライブラリやフレームワークのコードを読み解ける
thisの値が決まるタイミング#
JavaScriptのthisの値は、関数が定義されたときではなく、呼び出されたときに決まります。これがJavaScriptのthisを理解する上で最も重要なポイントです。
1
2
3
4
5
6
7
8
9
|
function showThis() {
console.log(this);
}
const obj1 = { name: "obj1", showThis };
const obj2 = { name: "obj2", showThis };
obj1.showThis(); // { name: 'obj1', showThis: [Function: showThis] }
obj2.showThis(); // { name: 'obj2', showThis: [Function: showThis] }
|
同じ関数showThisでも、呼び出し方によってthisの値が異なることがわかります。
コンテキスト別のthisの挙動#
thisの値は、コードが実行されるコンテキストによって異なります。ここでは、各コンテキストでのthisの挙動を詳しく見ていきます。
グローバルコンテキストでのthis#
グローバルスコープ(関数やクラスの外)でのthisは、グローバルオブジェクトを参照します。
1
2
3
4
5
|
// ブラウザ環境の場合
console.log(this === window); // true
// Node.js環境の場合(モジュール外)
console.log(this === globalThis); // true
|
ブラウザではwindowオブジェクト、Node.jsではglobalオブジェクトがグローバルオブジェクトとなります。ES2020以降では、環境に依存しないglobalThisを使用することが推奨されています。
注意点: ESモジュールとして読み込まれたスクリプト(<script type="module">)では、トップレベルのthisはundefinedになります。
1
2
3
|
<script type="module">
console.log(this); // undefined
</script>
|
通常の関数でのthis#
通常の関数内でのthisは、関数の呼び出し方によって決まります。
単独で呼び出した場合#
関数をオブジェクトに関連付けずに直接呼び出した場合、thisの値は実行モードによって異なります。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function showThis() {
console.log(this);
}
// 非strictモード
showThis(); // Window オブジェクト(ブラウザの場合)
// strictモード
function showThisStrict() {
"use strict";
console.log(this);
}
showThisStrict(); // undefined
|
非strictモードではthisがグローバルオブジェクトに置き換えられますが、strictモードではundefinedのままです。これは意図しないグローバル変数の作成を防ぐためのJavaScriptの安全機能です。
メソッドとして呼び出した場合#
関数がオブジェクトのメソッドとして呼び出された場合、thisはそのオブジェクトを参照します。
1
2
3
4
5
6
7
8
9
10
11
12
|
const calculator = {
value: 10,
add(num) {
return this.value + num;
},
multiply(num) {
return this.value * num;
}
};
console.log(calculator.add(5)); // 15
console.log(calculator.multiply(3)); // 30
|
オブジェクトのメソッドでのthis#
オブジェクトのメソッド内でのthisは、そのメソッドを呼び出したオブジェクトを参照します。これは最も一般的なthisの使い方です。
1
2
3
4
5
6
7
8
9
|
const user = {
firstName: "太郎",
lastName: "山田",
getFullName() {
return `${this.lastName} ${this.firstName}`;
}
};
console.log(user.getFullName()); // 山田 太郎
|
メソッドを別の変数に代入した場合の注意点#
メソッドを変数に代入して呼び出すと、thisの参照先が変わってしまいます。
1
2
3
4
5
6
7
8
9
10
11
|
const user = {
name: "佐藤",
greet() {
console.log(`こんにちは、${this.name}です`);
}
};
user.greet(); // こんにちは、佐藤です
const greetFunc = user.greet;
greetFunc(); // こんにちは、undefinedです(strictモードではエラー)
|
greetFunc()はオブジェクトに関連付けられずに呼び出されるため、thisがグローバルオブジェクト(またはundefined)を参照してしまいます。
ネストしたオブジェクトでのthis#
thisは、メソッドを直接呼び出したオブジェクトを参照します。プロトタイプチェーン上のオブジェクトではなく、実際に呼び出しに使用されたオブジェクトです。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
const parent = {
name: "親オブジェクト",
showName() {
console.log(this.name);
}
};
const child = {
name: "子オブジェクト",
__proto__: parent
};
child.showName(); // 子オブジェクト
|
イベントハンドラーでのthis#
DOMイベントハンドラーとして使用される関数内でのthisは、イベントが発生した要素を参照します。
1
2
3
4
5
6
|
const button = document.getElementById("myButton");
button.addEventListener("click", function(event) {
console.log(this === event.currentTarget); // true
this.style.backgroundColor = "blue";
});
|
上記の例では、thisはクリックされたbutton要素を参照します。これにより、イベントが発生した要素に対して直接操作を行うことができます。
インラインイベントハンドラーでのthis#
HTMLのイベント属性で定義されたハンドラーでも、thisはその要素を参照します。
1
2
|
<button onclick="console.log(this.tagName)">クリック</button>
<!-- 出力: BUTTON -->
|
ただし、インラインハンドラー内で定義された関数は、通常の関数呼び出しと同じルールに従います。
1
2
|
<button onclick="(function() { console.log(this); })()">クリック</button>
<!-- 出力: Window オブジェクト(非strictモード) -->
|
クラスでのthis#
ES6で導入されたクラス構文では、thisはクラスのインスタンスを参照します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
introduce() {
console.log(`私は${this.name}、${this.age}歳です`);
}
celebrateBirthday() {
this.age++;
console.log(`お誕生日おめでとう!${this.age}歳になりました`);
}
}
const person = new Person("花子", 25);
person.introduce(); // 私は花子、25歳です
person.celebrateBirthday(); // お誕生日おめでとう!26歳になりました
|
クラスメソッドをコールバックとして渡す場合の問題#
クラスメソッドを他の関数にコールバックとして渡すと、thisの参照が失われることがあります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class Counter {
constructor() {
this.count = 0;
}
increment() {
this.count++;
console.log(this.count);
}
}
const counter = new Counter();
counter.increment(); // 1
// コールバックとして渡すと問題が発生
setTimeout(counter.increment, 1000); // NaN(thisがundefined)
|
この問題を解決する方法については、後述の「bindによるthisの固定」で説明します。
静的メソッドでのthis#
静的メソッド内でのthisは、クラス自体を参照します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class MathUtils {
static PI = 3.14159;
static circleArea(radius) {
return this.PI * radius * radius;
}
static info() {
console.log(`これは${this.name}クラスです`);
}
}
console.log(MathUtils.circleArea(5)); // 78.53975
MathUtils.info(); // これはMathUtilsクラスです
|
アロー関数でのthis#
アロー関数は、通常の関数とは異なり、独自のthisを持ちません。代わりに、定義された時点での外側のスコープのthisを継承(レキシカルスコープ)します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
const obj = {
name: "オブジェクト",
// 通常の関数: thisは呼び出し元によって決まる
regularFunc: function() {
console.log("通常の関数:", this.name);
},
// アロー関数: thisは定義時の外側スコープを継承
arrowFunc: () => {
console.log("アロー関数:", this.name);
}
};
obj.regularFunc(); // 通常の関数: オブジェクト
obj.arrowFunc(); // アロー関数: undefined(グローバルスコープのthis)
|
アロー関数のthisは、objではなく、アロー関数が定義された時点の外側スコープ(この場合はグローバルスコープ)のthisを参照します。
アロー関数が有効なケース#
アロー関数のthisの特性は、コールバック関数で特に有効です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class Timer {
constructor() {
this.seconds = 0;
}
start() {
// アロー関数を使うと、thisがTimerインスタンスを参照し続ける
setInterval(() => {
this.seconds++;
console.log(`${this.seconds}秒経過`);
}, 1000);
}
}
const timer = new Timer();
timer.start();
// 1秒経過
// 2秒経過
// 3秒経過...
|
通常の関数を使った場合、setIntervalのコールバック内でのthisはグローバルオブジェクトを参照してしまいます。アロー関数を使うことで、外側のthis(Timerインスタンス)を保持できます。
アロー関数を使うべきでないケース#
以下のケースでは、アロー関数の使用は避けるべきです。
1
2
3
4
5
6
7
8
9
10
11
|
// オブジェクトのメソッドとして使う場合
const counter = {
count: 0,
// 良くない: thisがcounterを参照しない
increment: () => {
this.count++;
}
};
counter.increment();
console.log(counter.count); // 0(期待通りに動作しない)
|
1
2
3
4
5
6
|
// コンストラクター関数として使う場合
const Person = (name) => {
this.name = name;
};
// const p = new Person("太郎"); // TypeError: Person is not a constructor
|
thisの明示的な指定#
JavaScriptでは、call、apply、bindメソッドを使ってthisの値を明示的に指定できます。
callメソッド#
callメソッドは、関数を呼び出す際にthisの値を指定できます。引数は個別に渡します。
1
2
3
4
5
6
7
8
9
|
function greet(greeting, punctuation) {
console.log(`${greeting}、${this.name}${punctuation}`);
}
const person1 = { name: "山田" };
const person2 = { name: "鈴木" };
greet.call(person1, "こんにちは", "!"); // こんにちは、山田!
greet.call(person2, "おはよう", "。"); // おはよう、鈴木。
|
applyメソッド#
applyメソッドはcallと同様ですが、引数を配列として渡します。
1
2
3
4
5
6
7
8
|
function introduce(hobby1, hobby2) {
console.log(`私は${this.name}です。趣味は${hobby1}と${hobby2}です。`);
}
const user = { name: "佐藤" };
const hobbies = ["読書", "映画鑑賞"];
introduce.apply(user, hobbies); // 私は佐藤です。趣味は読書と映画鑑賞です。
|
callとapplyの使い分けは、引数の渡し方の違いだけです。配列で引数を持っている場合はapplyが便利です。
bindメソッド#
bindメソッドは、thisの値を永続的に固定した新しい関数を作成します。
1
2
3
4
5
6
7
8
9
10
11
12
|
const module = {
x: 42,
getX() {
return this.x;
}
};
const unboundGetX = module.getX;
console.log(unboundGetX()); // undefined(thisがmoduleではない)
const boundGetX = module.getX.bind(module);
console.log(boundGetX()); // 42
|
クラスメソッドのバインド#
先ほど紹介したクラスメソッドをコールバックとして渡す問題は、bindで解決できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class Counter {
constructor() {
this.count = 0;
// コンストラクターでメソッドをバインド
this.increment = this.increment.bind(this);
}
increment() {
this.count++;
console.log(this.count);
}
}
const counter = new Counter();
setTimeout(counter.increment, 1000); // 1(正しく動作する)
|
または、クラスフィールドとアロー関数を組み合わせる方法もあります。
1
2
3
4
5
6
7
8
9
10
11
12
|
class Counter {
count = 0;
// アロー関数で定義するとthisが自動的にバインドされる
increment = () => {
this.count++;
console.log(this.count);
};
}
const counter = new Counter();
setTimeout(counter.increment, 1000); // 1
|
thisの動作パターンまとめ#
ここまで学んだthisの動作パターンを表にまとめます。
| コンテキスト |
thisの値 |
備考 |
| グローバル(非strictモード) |
グローバルオブジェクト |
ブラウザではwindow |
| グローバル(strictモード) |
グローバルオブジェクト |
モジュールではundefined |
| 通常の関数(単独呼び出し・非strict) |
グローバルオブジェクト |
this置換が発生 |
| 通常の関数(単独呼び出し・strict) |
undefined |
安全な動作 |
| オブジェクトのメソッド |
呼び出し元のオブジェクト |
ドットの左側のオブジェクト |
| イベントハンドラー |
イベントが発生した要素 |
event.currentTargetと同じ |
| コンストラクター |
新しく作成されるインスタンス |
newキーワード使用時 |
| クラスメソッド |
クラスのインスタンス |
静的メソッドはクラス自体 |
| アロー関数 |
定義時の外側スコープのthis |
呼び出し方に関わらず固定 |
call/apply/bind |
第1引数で指定した値 |
明示的な指定 |
thisの判定フローチャート#
thisの値を判定する際は、以下のフローチャートが参考になります。
flowchart TD
A[関数が呼び出された] --> B{アロー関数か?}
B -->|はい| C[定義時の外側スコープのthisを使用]
B -->|いいえ| D{new演算子で呼び出されたか?}
D -->|はい| E[新しいオブジェクトがthis]
D -->|いいえ| F{call/apply/bindが使われたか?}
F -->|はい| G[指定されたオブジェクトがthis]
F -->|いいえ| H{メソッドとして呼び出されたか?}
H -->|はい| I[ドットの左側のオブジェクトがthis]
H -->|いいえ| J{strictモードか?}
J -->|はい| K[thisはundefined]
J -->|いいえ| L[thisはグローバルオブジェクト]よくある間違いと対処法#
コールバック関数でのthisの喪失#
1
2
3
4
5
6
7
8
9
10
|
const obj = {
name: "テスト",
delayedLog() {
setTimeout(function() {
console.log(this.name); // undefined
}, 1000);
}
};
obj.delayedLog();
|
対処法1: アロー関数を使用
1
2
3
4
5
6
7
8
|
const obj = {
name: "テスト",
delayedLog() {
setTimeout(() => {
console.log(this.name); // テスト
}, 1000);
}
};
|
対処法2: bindを使用
1
2
3
4
5
6
7
8
|
const obj = {
name: "テスト",
delayedLog() {
setTimeout(function() {
console.log(this.name); // テスト
}.bind(this), 1000);
}
};
|
対処法3: thisを変数に保存
1
2
3
4
5
6
7
8
9
|
const obj = {
name: "テスト",
delayedLog() {
const self = this; // thisを変数に保存
setTimeout(function() {
console.log(self.name); // テスト
}, 1000);
}
};
|
配列メソッドのコールバックでのthis#
配列のforEach、map、filterなどのメソッドでは、第2引数でthisを指定できます。
1
2
3
4
5
6
7
8
9
10
11
|
const processor = {
prefix: "[処理済] ",
process(items) {
return items.map(function(item) {
return this.prefix + item;
}, this); // 第2引数でthisを指定
}
};
console.log(processor.process(["A", "B", "C"]));
// ["[処理済] A", "[処理済] B", "[処理済] C"]
|
アロー関数を使えば、第2引数は不要です。
1
2
3
4
5
6
|
const processor = {
prefix: "[処理済] ",
process(items) {
return items.map(item => this.prefix + item);
}
};
|
まとめ#
本記事では、JavaScriptのthisキーワードについて詳しく解説しました。
thisの値は関数が呼び出されたときに決まる
- グローバルコンテキストでは
thisはグローバルオブジェクトを参照
- メソッドとして呼び出された場合、
thisは呼び出し元のオブジェクトを参照
- アロー関数は独自の
thisを持たず、定義時の外側スコープを継承
call、apply、bindでthisを明示的に指定可能
thisの挙動は最初は複雑に感じるかもしれませんが、基本的なルールを理解すれば予測可能です。特にアロー関数と通常の関数の違いを意識することで、多くのバグを防ぐことができます。
次のステップとして、Promiseやasync/awaitによる非同期処理について学ぶと、コールバック関数でのthisの扱いがより明確になります。
参考リンク#