こんにちは、とまだです。
JavaScript アドベントカレンダー 2024 のうち、2 日目の記事をお届けします!
私が本格的に現場で JavaScript をはじめたとき、「this
を理解したら中級者」と言われました。
それぐらい、this
は JavaScript において重要な概念です。
突然ですが、以下のコードの出力結果は分かりますか?
const user = {
name: "Alice",
greet() {
console.log(`こんにちは、${this.name}さん!`);
},
};
const greet = user.greet;
greet(); // ???
どうでしょうか?
全体として、流れとしては user.greet
を呼び出しているので、以下のように出力されると思われるかもしれません。
こんにちは、Aliceさん!
しかし、実際の出力結果は以下の通りです。
こんにちは、undefinedさん!
この結果に驚いた方も多いのではないでしょうか?
実は this
は、どう呼び出すかによって値が変わる曲者なんです。
今回は「JavaScript の this を完全に理解する」ことを目指して、5 つのパターンを解説します!
なぜ this を理解する必要があるの?
JavaScript 経験者なら、このような経験があるかもしれません。
- コードは間違ってないはずなのに
undefined
が出る - React で
this.setState
が動かない - イベントハンドラの中で
this
がundefined
になる
これらの問題は、全て this
の理解が足りないことが多いです。
「this がよく分からない」と感じている方は、ぜひこの記事を読み進めてください!
this の基本:「誰が呼び出したか」が重要
まず、this
は「関数を呼び出したもの」を指すと覚えておきましょう。
例えば、以下のようなコードを見てみましょう。
const user = {
name: "Alice",
greet() {
console.log(`こんにちは、${this.name}さん!`);
},
};
// userがgreetを呼び出す
user.greet(); // こんにちは、Aliceさん!
// グローバルスコープでgreetを呼び出す
const greet = user.greet;
greet(); // こんにちは、undefinedさん!
同じ関数なのに、呼び出し方によって結果が変わってしまいます。
なぜこうなるのか、5 つのパターンを見ながら理解していきましょう。
this の 5 つの顔
1. メソッド呼び出し:オブジェクトのメソッドとして呼ぶ場合
オブジェクトのメソッドとして呼び出す場合、this
はそのオブジェクトを指します。
const user = {
name: "Alice",
greet() {
console.log(`こんにちは、${this.name}さん!`);
},
};
user.greet(); // こんにちは、Aliceさん!
この場合、関数を呼び出したのは user
オブジェクトなので、this
は user
を指します。
2. 普通の関数呼び出し:単独で呼び出す場合
普通の関数として呼び出す場合、this
は undefined
(strict モード)または window
(非 strict モード)を指します。
function standalone() {
console.log(this);
}
standalone(); // undefined または window
これは意図しない動作の原因になりやすいので、気をつけましょう。
3. アロー関数:外側の this をそのまま使う
アロー関数の this
は、関数が定義された場所の this
を引き継ぎます。
もう少し正確に言うと、アロー関数は this
を持たず、外側の this
をそのまま使います。
const user = {
name: "Alice",
// 通常の関数
greet() {
const arrow = () => {
console.log(`こんにちは、${this.name}さん!`);
};
arrow();
},
};
user.greet(); // こんにちは、Aliceさん!
アロー関数は this
を持たず、外側の this
をそのまま使うので、コールバック関数でよく使われます。
4. new による呼び出し:新しいオブジェクトを指す
new
で関数を呼び出すと、this
は新しく作られたオブジェクトを指します。
function User(name) {
this.name = name;
}
const alice = new User("Alice");
console.log(alice.name); // Alice
これは、JavaScript のクラス構文の内部でも使われています。
5. bind/call/apply による呼び出し:this を固定する
bind
、call
、apply
メソッドを使うと、this
の値を明示的に指定できます。
const user = {
name: "Alice",
greet() {
console.log(`こんにちは、${this.name}さん!`);
},
};
const greet = user.greet.bind(user);
greet(); // こんにちは、Aliceさん!
この方法は、イベントハンドラなどで this
の値を固定したい場合によく使われます。
実践的な使い方:よくあるバグと解決策
イベントハンドラでの問題
最もよく遭遇する this
のバグは、イベントハンドラでの問題です。
const button = document.querySelector("button");
const user = {
name: "Alice",
handleClick() {
console.log(`${this.name}がクリックしました!`);
},
};
// 🙅♂️ ダメな例:thisがundefinedになる
button.addEventListener("click", user.handleClick);
// 🙆♂️ 良い例1:bindを使う
button.addEventListener("click", user.handleClick.bind(user));
// 🙆♂️ 良い例2:アロー関数でラップする
button.addEventListener("click", () => user.handleClick());
イベントハンドラ内で this
を使いたい場合は、bind
やアロー関数を使って this
の値を固定しましょう。
React での this の問題
React でクラスコンポーネントを使う場合も、this
の問題に遭遇することがあります。
class Button extends React.Component {
// 🙅♂️ ダメな例:thisがundefinedになる
handleClick() {
this.setState({ clicked: true });
}
// 🙆♂️ 良い例1:メソッドをアロー関数で定義
handleClick = () => {
this.setState({ clicked: true });
};
render() {
// 🙆♂️ 良い例2:bindを使う
return <button onClick={this.handleClick.bind(this)}>Click me!</button>;
}
}
ここでも、bind
やアロー関数を使って this
の値を固定することで問題を解決できます。
最近の React では関数コンポーネントと Hooks を使うことが推奨されており、その場合は this の問題を気にする必要はありません。
まとめ
今回説明した内容を整理すると、以下のようになります。
-
this
は「関数を呼び出したもの」を指す - 呼び出し方によって
this
の値が変わる - 5 つのパターンを覚えておく
- メソッド呼び出し
- 普通の関数呼び出し
- アロー関数
- new による呼び出し
- bind/call/apply による呼び出し
覚えるのが大変そうに見えますが、基本的な考え方さえ理解できれば、あとは実践の中で自然と身についていきます。
最後まで読んでいただき、ありがとうございました!
他にもアドベントカレンダー記事を書いています!
他にも、2024 年のアドベントカレンダーに参加しています。
以下の記事でまとめているので、よければ他の記事も読んでいただけると嬉しいです!