はじめに
JavaScriptで関数を書くとき、thisの挙動に悩まされた経験はありませんか?特にアロー関数と通常の関数ではthisの決まり方が根本的に異なります。この記事では、その違いを実例を交えながら解説していきます。
通常の関数におけるthisの決定方法
通常の関数(function宣言や関数式)では、thisは関数が呼び出されたときの状況によって動的に決まります。
決定のタイミング
主な決定パターン
パターン1: メソッドとして呼び出し
オブジェクトのメソッドとして呼ばれた場合、thisはそのオブジェクトを指します。
const obj = {
name: 'Taro',
greet: function() {
console.log(this.name);
}
};
obj.greet(); // 'Taro' - thisはobjを指す
パターン2: 単独で呼び出し
単独で呼ばれた場合、strictモードではundefined、非strictモードではwindow(ブラウザ環境)を指します。
function showThis() {
console.log(this);
}
showThis(); // undefined (strictモード) または window
パターン3: イベントハンドラ内
イベントハンドラでは、thisはイベントが発生した要素を指します。
button.addEventListener('click', function() {
console.log(this); // buttonエレメントを指す
});
アロー関数におけるthisの決定方法
アロー関数のthisは、通常の関数とは全く異なる仕組みで決まります。
決定のタイミング
アロー関数では、thisは関数が定義された場所の外側のスコープから継承され、呼び出し方法に関わらず変わりません。これを「レキシカルなthis」と呼びます。
具体例
const obj = {
name: 'Taro',
greet: () => {
console.log(this.name);
}
};
obj.greet(); // undefined - thisはobjではなく外側のスコープを指す
この例では、アロー関数が定義された時点で外側のスコープのthis(今回だとwindowまたはundefined)を捕捉するため、obj.nameにアクセスできません。
両者の違いを比較
以下の例で、同じ状況での挙動の違いを見てみましょう。
const counter = {
count: 0,
// 通常の関数
incrementNormal: function() {
setTimeout(function() {
this.count++;
console.log(this.count); // NaN - thisがcounterを指していない
}, 1000);
},
// アロー関数
incrementArrow: function() {
setTimeout(() => {
this.count++;
console.log(this.count); // 1 - thisがcounterを指している
}, 1000);
}
};
従来の回避方法
アロー関数が登場する前は、以下のような方法でthisを保持していました。
incrementOld: function() {
const self = this; // thisを変数に保存
setTimeout(function() {
self.count++;
console.log(self.count);
}, 1000);
}
アロー関数を使えば、この手間が不要になります。
使い分けのポイント
通常の関数を使うべき場合
- オブジェクトのメソッドとして、そのオブジェクト自身にアクセスしたい
- イベントハンドラで、イベントが発生した要素にアクセスしたい
- コンストラクタ関数として使いたい
const person = {
name: 'Hanako',
greet: function() {
console.log(`Hello, I'm ${this.name}`);
}
};
アロー関数を使うべき場合
- コールバック関数内で外側のスコープの
thisを使いたい - 短い関数を簡潔に書きたい
-
thisの値を固定したい
class Timer {
constructor() {
this.seconds = 0;
}
start() {
setInterval(() => {
this.seconds++;
console.log(this.seconds);
}, 1000);
}
}
まとめ
JavaScriptのthisは、関数の種類によって決定方法が異なります。
通常の関数: 呼び出し時の状況で動的に決まる
アロー関数: 定義時の外側のスコープから継承され、固定される
この違いを理解することで、適切な関数を選択でき、this関連のバグを減らせます。特にコールバック関数やクラスのメソッド内では、アロー関数が有効ですね。状況に応じて使い分けていきましょう。