読み込み中...
読み込み中...
読み込み中...
読み込み中...
読み込み中...
**デバウンス(Debounce)とスロットリング(Throttle)**は、頻繁に発生するイベントの実行頻度を制御するテクニックです。スクロール、リサイズ、入力イベントなど、短時間に大量に発火するイベントに対して、関数の呼び出し回数を減らしてパフォーマンスを改善します。
| テクニック | 動作 | ユースケース |
|---|---|---|
| デバウンス | 最後のイベントから一定時間後に実行 | 検索窓の入力、ウィンドウリサイズ完了後の処理 |
| スロットリング | 一定間隔で最大1回実行 | スクロール位置の追跡、マウスの移動追跡 |
デバウンスは「嵐が収まるのを待つ」イメージです。新しいイベントが来るたびにタイマーがリセットされ、最後のイベントから指定時間が経過して初めて実行されます。
スロットリングは「一定間隔でサンプリングする」イメージです。イベントが頻発しても、最低でも指定間隔を空けて実行されます。
デバウンスの実装と使用例
// デバウンス関数の実装
function debounce(fn, delay) {
let timerId;
return function(...args) {
clearTimeout(timerId);
timerId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 使用例: 検索窓の入力
const search = debounce((query) => {
console.log("検索:", query);
// API 呼び出し
}, 300);
// search("j"); → タイマーリセット
// search("ja"); → タイマーリセット
// search("jav"); → タイマーリセット
// 300ms後 → "検索: jav" が実行されるスロットリングの実装と使用例
// スロットリング関数の実装
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
// 使用例: スクロール追跡
const handleScroll = throttle(() => {
console.log("スクロール位置:", window.scrollY);
}, 200);
// window.addEventListener("scroll", handleScroll);
// → 最大200msに1回だけ実行されるチャレンジ
debounce 関数を実装してください。関数 fn と遅延時間 delay(ミリ秒)を受け取り、デバウンスされた新しい関数を返します。
ポイント
デバウンスは最後のイベントから一定時間後に実行、スロットリングは一定間隔で最大1回実行します。検索入力にはデバウンス、スクロール追跡にはスロットリングが適しています。どちらもクロージャとタイマーを活用します。
**メモ化(Memoization)**は、関数の計算結果をキャッシュして、同じ引数で再度呼び出されたときにキャッシュから結果を返すテクニックです。計算コストの高い純粋関数に対して特に効果的です。
メモ化が有効な条件は以下の通りです。
| 条件 | 理由 |
|---|---|
| 純粋関数である | 同じ入力に対して常に同じ出力を返す保証が必要 |
| 計算コストが高い | キャッシュのオーバーヘッドを上回る効果がある |
| 同じ引数で繰り返し呼ばれる | キャッシュヒット率が高い |
メモ化は空間(メモリ)と時間(計算速度)のトレードオフです。キャッシュが際限なく増えるとメモリを圧迫するため、キャッシュサイズの上限(LRU キャッシュなど)を設けることも検討しましょう。
メモ化の実装とフィボナッチの最適化
// 基本的なメモ化関数
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log("キャッシュヒット:", key);
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// 計算コストの高い関数をメモ化
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const memoFib = memoize(function fib(n) {
if (n <= 1) return n;
return memoFib(n - 1) + memoFib(n - 2);
});
console.time("通常");
console.log(fibonacci(35));
console.timeEnd("通常");
console.time("メモ化");
console.log(memoFib(35));
console.timeEnd("メモ化");メモリを際限なく使わないように、キャッシュサイズに上限を設けるのがベストプラクティスです。LRU(Least Recently Used)方式では、最も長く使われていないエントリを削除します。
チャレンジ
memoize 関数を実装してください。関数 fn を受け取り、結果をキャッシュする新しい関数を返します。キャッシュキーには引数を JSON.stringify で文字列化してください。
ポイント
メモ化は計算結果をキャッシュして再計算を避けるテクニックです。純粋関数で計算コストが高く、同じ引数で繰り返し呼ばれる場合に有効です。Map と JSON.stringify でシンプルに実装できます。
JavaScriptのガベージコレクション(GC)は到達可能性をベースに自動的にメモリを解放しますが、プログラマの意図しないメモリリークは依然として起こり得ます。ES2015以降に導入された WeakMap、WeakSet、そして ES2021 の WeakRef は、メモリリークを防ぐための重要なツールです。
| コレクション | キー/値の型 | GC への影響 | ユースケース |
|---|---|---|---|
Map | 任意 | キーへの強参照(GCされない) | 通常のデータ管理 |
WeakMap | オブジェクトのみ | キーへの弱参照(GC可能) | メタデータ、プライベートデータ |
Set | 任意 | 値への強参照(GCされない) | ユニーク値の管理 |
WeakSet | オブジェクトのみ | 値への弱参照(GC可能) | オブジェクトの追跡 |
**弱参照(Weak Reference)**とは、GCを妨げない参照です。WeakMap/WeakSet に保持されたオブジェクトは、他に参照がなくなればGCの対象になります。通常のMapやSetでは、参照が保持され続けるためGCされません。
WeakMap によるプライベートデータの管理
// WeakMap: オブジェクトに紐づくプライベートデータ
const privateData = new WeakMap();
class User {
constructor(name, email) {
this.name = name;
// パスワードのような秘密データは WeakMap に保持
privateData.set(this, { email, loginCount: 0 });
}
login() {
const data = privateData.get(this);
data.loginCount++;
return `${this.name}(${data.loginCount}回目のログイン)`;
}
getEmail() {
return privateData.get(this).email;
}
}
let user = new User("田中", "tanaka@example.com");
console.log(user.login()); // "田中(1回目のログイン)"
console.log(user.getEmail()); // "tanaka@example.com"
// user = null; にすると、privateData のエントリもGC対象になるWeakSet と WeakRef の活用
// WeakSet: オブジェクトの訪問追跡
const visited = new WeakSet();
function processNode(node) {
if (visited.has(node)) {
console.log("既に処理済み:", node.id);
return;
}
visited.add(node);
console.log("処理中:", node.id);
}
let nodeA = { id: "A" };
let nodeB = { id: "B" };
processNode(nodeA); // "処理中: A"
processNode(nodeB); // "処理中: B"
processNode(nodeA); // "既に処理済み: A"
// nodeA = null; にすると visited からも自動削除(GC対象)
// WeakRef: 弱参照で大きなオブジェクトをキャッシュ
function createCache() {
const cache = new Map();
return {
set(key, value) {
cache.set(key, new WeakRef(value));
},
get(key) {
const ref = cache.get(key);
if (ref) {
const value = ref.deref(); // GC済みなら undefined
if (value) return value;
cache.delete(key); // GC済みならエントリを削除
}
return undefined;
}
};
}チャレンジ
WeakMap を使って、オブジェクトにメタデータ(作成時刻)を紐づける trackCreation 関数を実装してください。getCreationTime でメタデータを取得できるようにしてください。
ポイント
WeakMap / WeakSet はオブジェクトへの弱参照を保持し、他に参照がなくなればGCの対象になります。プライベートデータの管理やオブジェクトの追跡に適しています。WeakRef は ES2021 で追加された弱参照のプリミティブです。
コードの品質は、読みやすさ・保守性・バグの少なさに直結します。ここではJavaScriptのコーディングにおける実践的なベストプラクティスをまとめます。
| 対象 | 規則 | 例 |
|---|---|---|
| 変数・関数 | camelCase | getUserName, isActive |
| クラス | PascalCase | UserProfile, HttpClient |
| 定数 | UPPER_SNAKE_CASE | MAX_RETRY_COUNT, API_URL |
| プライベート | _ プレフィックスまたは # | _internal, #count |
| boolean | is/has/can で始める | isLoggedIn, hasPermission |
const を優先、変更が必要な場合のみ let を使用var は使わない(スコープの問題があるため)ネストが深くなるのを避けるために、条件を満たさない場合は早めに return で抜けるパターンです。
ESLint はJavaScriptの静的解析ツールで、コーディング規約の遵守やバグの早期発見を支援します。プロジェクトに導入することで、チーム全体のコード品質を統一できます。
| ルール例 | 説明 |
|---|---|
no-unused-vars | 未使用変数の検出 |
eqeqeq | === の使用を強制 |
no-var | var の使用禁止 |
prefer-const | 再代入しない変数に const を強制 |
no-console | console.log の残留を検出 |
早期リターンによるネスト削減
// NG: ネストが深い
function processOrder(order) {
if (order) {
if (order.items.length > 0) {
if (order.status === "pending") {
// 処理...
return { success: true };
}
}
}
return { success: false };
}
// OK: 早期リターンでフラットに
function processOrderBetter(order) {
if (!order) return { success: false };
if (order.items.length === 0) return { success: false };
if (order.status !== "pending") return { success: false };
// メインロジック(ネストなし)
return { success: true };
}モダンJavaScriptの可読性パターン
// 可読性の高いコードのパターン
// 1. 分割代入で意図を明確にする
const { name, age, address: { city } } = user;
// 2. オプショナルチェーニングとNullish Coalescing
const userName = user?.profile?.name ?? "ゲスト";
// 3. テンプレートリテラルで可読性を高める
const message = `${userName}さん(${age}歳)が${city}からログインしました`;
// 4. オブジェクトリテラルで条件分岐を置き換える
const statusMessages = {
pending: "処理中です",
approved: "承認されました",
rejected: "却下されました"
};
const msg = statusMessages[order.status] ?? "不明なステータス";
// 5. Array.from + keys で連番配列を作成
const numbers = Array.from({ length: 5 }, (_, i) => i + 1);
console.log(numbers); // [1, 2, 3, 4, 5]チャレンジ
以下のネストの深い関数 validateUser を、早期リターンパターンを使ってリファクタリングしてください。条件を満たさない場合はエラーメッセージの文字列を返し、全て通過した場合は null(エラーなし)を返してください。
ポイント
命名規則(camelCase / PascalCase / UPPER_SNAKE_CASE)を統一し、const 優先で宣言します。早期リターンでネストを減らし、ESLint で静的解析を行いましょう。オプショナルチェーニングやテンプレートリテラルなどモダンな構文も積極的に活用してください。
実務での活用
検索バーの入力中に毎回APIを呼ぶとサーバーに過剰な負荷がかかります。デバウンスを適用すれば「入力が止まってから300ms後に1回だけ検索」という最適化が簡単に実装できます。パフォーマンス改善は「まず計測し、ボトルネックを特定してから、そこだけを改善する」のが鉄則です。
用語