「thisの振る舞いがいまいち分からない…」
「アロー関数は使ってるけど、従来の関数と何が違うんだろう?」
「bindって結局いつ使えばいいの?」
今回は、コールバック関数でよく使用するアロー関数と、混乱しがちなthisの振る舞いについて、実践的に解説していきます。
従来の関数定義とthisの問題
まず、従来の関数定義での問題を見てみましょう:
JS
// jQuery時代によくあるコード
var Counter = {
count: 0,
button: $('#button'),
setup: function() {
// this を変数に保存
var self = this;
this.button.click(function() {
self.count++; // 直接thisは使えない
console.log(self.count);
});
}
};
この書き方の問題点:
self
やthat
などの変数が必要- thisの参照が分かりにくい
- コードが冗長になる
アロー関数による解決
JS
// モダンな書き方
const Counter = {
count: 0,
button: document.querySelector('#button'),
setup() {
this.button.addEventListener('click', () => {
this.count++; // thisが自動的に保持される
console.log(this.count);
});
}
};
アロー関数の特徴:
- レキシカルスコープでthisを継承
- 自身のthisを持たない
- より簡潔な構文
従来の関数とアロー関数の違い
1. thisの束縛
JS
// 従来の関数
function Traditional() {
this.value = 42;
this.getValue = function() {
return this.value;
};
this.delayed = function() {
setTimeout(function() {
console.log(this.value); // undefined
}, 1000);
};
}
// アロー関数
function Modern() {
this.value = 42;
this.getValue = () => {
return this.value; // 42
};
this.delayed = function() {
setTimeout(() => {
console.log(this.value); // 42
}, 1000);
};
}
ポイント:
- 従来の関数は呼び出し方によってthisが変わる
- アロー関数は定義時のスコープのthisを保持
2. メソッド定義での注意点
JS
const obj = {
name: 'Example',
// 従来の方法(推奨)
normalMethod() {
return this.name;
},
// アロー関数(非推奨)
arrowMethod: () => {
return this.name; // undefined
}
};
使い分けのポイント:
- オブジェクトのメソッドは通常の関数で定義
- コールバックはアロー関数を使用
実践的なリファクタリング例
イベントハンドラ
JS
// Before: jQuery
class UserList {
constructor() {
this.users = [];
var self = this;
$('#addUser').click(function() {
var name = $('#name').val();
self.addUser(name);
});
}
addUser(name) {
this.users.push(name);
this.render();
}
}
// After: Modern JavaScript
class UserList {
constructor() {
this.users = [];
document.querySelector('#addUser')
.addEventListener('click', () => {
const name = document.querySelector('#name').value;
this.addUser(name);
});
}
addUser(name) {
this.users.push(name);
this.render();
}
}
配列操作のコールバック
JS
// Before
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(function(num) {
return num * 2;
});
const filtered = numbers.filter(function(num) {
return num > 2;
});
// After
const doubled = numbers.map(num => num * 2);
const filtered = numbers.filter(num => num > 2);
非同期処理
JS
// Before
function loadData() {
var self = this;
fetch('/api/data')
.then(function(response) {
return response.json();
})
.then(function(data) {
self.processData(data);
})
.catch(function(error) {
self.handleError(error);
});
}
// After
function loadData() {
fetch('/api/data')
.then(response => response.json())
.then(data => this.processData(data))
.catch(error => this.handleError(error));
}
高度な使い分け
クラスメソッドでの利用
JS
class DataHandler {
constructor(data) {
this.data = data;
}
// インスタンスメソッド: 通常の関数
process() {
return this.data;
}
// イベントハンドラ: アロー関数
handleClick = () => {
this.process();
}
// 通常のメソッド + bind
handleSubmit() {
this.process();
}
}
const handler = new DataHandler([1, 2, 3]);
button.addEventListener('click', handler.handleClick); // OK
form.addEventListener('submit', handler.handleSubmit.bind(handler)); // OK
即時実行関数での使用
JS
// Before: IIFE
(function() {
var privateVar = 'secret';
// ...
})();
// After: アロー関数
(() => {
const privateVar = 'secret';
// ...
})();
まとめ
アロー関数を適切に使用することで:
- thisの管理が簡単に
- コードがより簡潔に
- コールバックの意図が明確に
- メンテナンス性が向上
次回は「テンプレートリテラルでHTML生成を最適化」について解説し、より洗練されたテンプレート処理を学んでいきましょう。