読み込み中...
読み込み中...
読み込み中...
読み込み中...
読み込み中...
JavaScriptのイテレーションプロトコルは、オブジェクトを反復処理するための統一的なインターフェースです。このプロトコルに従うことで、for...of、スプレッド構文、分割代入などの構文が利用可能になります。
2つのプロトコルが定義されています。
| プロトコル | 条件 | 説明 |
|---|---|---|
| Iterable プロトコル | Symbol.iterator メソッドを持つ | for...of で反復可能にする |
| Iterator プロトコル | next() メソッドを持つ | { value, done } を返す |
組み込みの Iterable オブジェクトには Array、String、Map、Set、arguments、NodeList などがあります。これらは Symbol.iterator メソッドを持っているため、for...of で反復できます。
自作のオブジェクトにも Symbol.iterator メソッドを実装すれば、反復可能にできます。
イテレータの手動操作とカスタムIterableの実装
// 配列のイテレータを手動で操作
const arr = [10, 20, 30];
const iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 10, done: false }
console.log(iterator.next()); // { value: 20, done: false }
console.log(iterator.next()); // { value: 30, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
// カスタム Iterable オブジェクト
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
if (current <= last) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
};
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}チャレンジ
countdown オブジェクトに Symbol.iterator メソッドを実装して、start から 1 までカウントダウンする Iterable にしてください。
ポイント
Iterable プロトコル(Symbol.iterator メソッド)を実装すると for...of やスプレッド構文で反復可能になります。Iterator プロトコルは next() メソッドで { value, done } を返す仕組みです。
ジェネレータ関数は function* 構文で定義される特殊な関数で、yield キーワードを使って値を一つずつ生成します。ジェネレータ関数を呼び出すと、関数本体は即座に実行されず、ジェネレータオブジェクトが返されます。
ジェネレータオブジェクトは Iterator プロトコルと Iterable プロトコルの両方を実装しており、next() メソッドを呼ぶたびに次の yield 式まで実行が進みます。
ジェネレータの最大の利点は**遅延評価(Lazy Evaluation)**です。全ての値を一度にメモリに保持する配列と異なり、ジェネレータは値が必要になったときに初めて計算します。無限シーケンスや巨大なデータセットの処理に適しています。
ジェネレータの基本と無限シーケンス
// 基本的なジェネレータ関数
function* colors() {
yield "赤";
yield "青";
yield "緑";
}
const gen = colors();
console.log(gen.next()); // { value: "赤", done: false }
console.log(gen.next()); // { value: "青", done: false }
console.log(gen.next()); // { value: "緑", done: false }
console.log(gen.next()); // { value: undefined, done: true }
// 無限シーケンス(遅延評価)
function* naturals() {
let n = 1;
while (true) {
yield n++;
}
}
const nums = naturals();
console.log(nums.next().value); // 1
console.log(nums.next().value); // 2
console.log(nums.next().value); // 3
// メモリを使い切ることなく無限に続けられるyield* を使うと、別の Iterable やジェネレータに処理を委譲できます。これにより、ジェネレータを組み合わせて複雑なシーケンスを構築できます。
next(value) の引数は、ジェネレータ内の直前の yield 式の戻り値として受け取れます。これにより双方向の通信が可能です。
yield* と双方向通信
// yield* による委譲
function* concat(...iterables) {
for (const iterable of iterables) {
yield* iterable;
}
}
const combined = [...concat([1, 2], [3, 4], [5])];
console.log(combined); // [1, 2, 3, 4, 5]
// next() で値を渡す
function* conversation() {
const name = yield "あなたの名前は?";
const hobby = yield `${name}さん、趣味は?`;
return `${name}さんの趣味は${hobby}です`;
}
const chat = conversation();
console.log(chat.next().value); // "あなたの名前は?"
console.log(chat.next("太郎").value); // "太郎さん、趣味は?"
console.log(chat.next("読書").value); // "太郎さんの趣味は読書です"チャレンジ
フィボナッチ数列を生成するジェネレータ関数 fibonacci を実装してください。最初の2つの値は 0 と 1 です。
ポイント
ジェネレータ関数(function*)は yield で値を一つずつ生成し、遅延評価を実現します。無限シーケンスの表現やメモリ効率の良いデータ処理に適しています。yield* で他の Iterable に処理を委譲できます。
for...of 文と**スプレッド構文(...)**は、Iterable プロトコルを活用する代表的な構文です。どちらも内部的に Symbol.iterator を呼び出してイテレータを取得し、next() を繰り返し呼ぶ仕組みです。
| 構文 | 反復対象 | 用途 |
|---|---|---|
for...of | 値(value) | 配列・Map・Set・文字列など Iterable の要素を反復 |
for...in | キー(key) | オブジェクトの列挙可能プロパティを反復 |
for...in はプロトタイプチェーン上のプロパティも列挙してしまうため、配列の反復には for...of を使いましょう。
スプレッド構文は Iterable を展開します。配列の結合、関数への引数展開、イテレータの配列変換などに使えます。
for...of と for...in の違い、Map の反復
// for...of と for...in の違い
const arr = ["a", "b", "c"];
arr.extra = "追加プロパティ";
console.log("--- for...of ---");
for (const value of arr) {
console.log(value); // "a", "b", "c"(値のみ)
}
console.log("--- for...in ---");
for (const key in arr) {
console.log(key); // "0", "1", "2", "extra"(キー、追加プロパティも含む)
}
// Map の for...of(分割代入と組み合わせ)
const userMap = new Map([
["id001", "田中"],
["id002", "佐藤"],
["id003", "鈴木"]
]);
for (const [id, name] of userMap) {
console.log(`${id}: ${name}`);
}スプレッド構文と分割代入
// スプレッド構文の活用
const set = new Set([1, 2, 3, 2, 1]);
const uniqueArray = [...set];
console.log(uniqueArray); // [1, 2, 3]
// 文字列の展開
const chars = [..."Hello"];
console.log(chars); // ["H", "e", "l", "l", "o"]
// 配列の結合
const merged = [...[1, 2], ...[3, 4], ...[5]];
console.log(merged); // [1, 2, 3, 4, 5]
// 分割代入との組み合わせ(残余要素)
const [first, second, ...rest] = [10, 20, 30, 40, 50];
console.log(first); // 10
console.log(rest); // [30, 40, 50]チャレンジ
配列 numbers から重複を除去し、昇順にソートした新しい配列 sorted を作成してください。Set とスプレッド構文を組み合わせて実現してください。
ポイント
for...of は Iterable の値を反復し、for...in はオブジェクトのキーを反復します。スプレッド構文 ... は Iterable を展開し、配列の結合・Set からの変換・分割代入の残余要素など幅広い場面で活用できます。
非同期イテレータは、非同期にデータを生成するイテレータです。通常のイテレータが next() で { value, done } を返すのに対し、非同期イテレータは Promise<{ value, done }> を返します。
for await...of 構文を使えば、非同期イテレータを同期的なループのように扱えます。API のページネーション、ストリームデータの処理、WebSocket のメッセージ受信などに活用されます。
| 種類 | プロトコル | ループ構文 | next() の返り値 |
|---|---|---|---|
| 同期イテレータ | Symbol.iterator | for...of | { value, done } |
| 非同期イテレータ | Symbol.asyncIterator | for await...of | Promise<{ value, done }> |
非同期ジェネレータと for await...of
// 非同期ジェネレータ
async function* fetchPages(baseUrl, totalPages) {
for (let page = 1; page <= totalPages; page++) {
// 擬似的な API 呼び出し
const data = await new Promise(resolve =>
setTimeout(() => resolve(`ページ${page}のデータ`), 100)
);
yield data;
}
}
// for await...of で非同期イテレータを反復
async function main() {
for await (const pageData of fetchPages("/api/items", 3)) {
console.log(pageData);
}
// "ページ1のデータ"
// "ページ2のデータ"
// "ページ3のデータ"
}
main();カスタム非同期 Iterable の実装
// カスタム非同期 Iterable
const asyncRange = {
from: 1,
to: 4,
[Symbol.asyncIterator]() {
let current = this.from;
const last = this.to;
return {
async next() {
// 非同期処理をシミュレート
await new Promise(r => setTimeout(r, 50));
if (current <= last) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
};
async function iterate() {
for await (const num of asyncRange) {
console.log(num); // 1, 2, 3, 4
}
}
iterate();チャレンジ
非同期ジェネレータ関数 delayedCount を実装してください。1 から n まで、各値を 100ms の遅延を挟んで yield してください。
ポイント
非同期イテレータは Symbol.asyncIterator と for await...of で非同期データの逐次処理を実現します。非同期ジェネレータ(async function*)を使えば、API ページネーションやストリーム処理を直感的に書けます。
実務での活用
大量のデータを一度にメモリに読み込むのではなく、必要な分だけ逐次処理する——この「遅延評価」の仕組みがイテレータとジェネレータの本質です。数万件のログを1行ずつ処理したり、APIのページネーション結果を順に取得したりする場面で、メモリ効率のよいコードが書けるようになります。
用語