モダンJavaScriptマスターへの道
最新のデータ操作手法
高度なデータグルーピング
日々の業務では、特定の条件に基づいて配列内のデータを分類する必要がよくあります。例えば、ユーザーのリストを役割ごとにまとめたり、商品のリストをカテゴリ別に整理したりするケースです。従来、この処理は reduce メソッドを使って手動で実装するのが一般的でした。
const products = [
{ name: 'Laptop', category: 'Electronics' },
{ name: 'T-shirt', category: 'Apparel' },
{ name: 'Keyboard', category: 'Electronics' },
{ name: 'Jeans', category: 'Apparel' },
];
// 従来のグルーピング方法
const groupedByCategory = products.reduce((acc, product) => {
const key = product.category;
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(product);
return acc;
}, {});
console.log(groupedByCategory);
/*
{
Electronics: [ { name: 'Laptop', ... }, { name: 'Keyboard', ... } ],
Apparel: [ { name: 'T-shirt', ... }, { name: 'Jeans', ... } ]
}
*/
このコードは機能しますが、少し冗長です。ES2024で導入された Object.groupBy() を使うと、この一般的なパターンを一行で、より宣言的に記述できます。
// Object.groupBy() を使った新しい方法
const grouped = Object.groupBy(products, product => product.category);
console.log(grouped);
結果は reduce の例と同じですが、コードの意図がはるかに明確になります。Object.groupBy() は、第二引数で渡されたコールバック関数が返す値をキーとして、新しいオブジェクトを生成します。このとき生成されるオブジェクトは、Object.create(null) で作られるため、プロトタイプチェーンを持ちません。これにより、意図しないプロパティ(例: toString)との衝突を避けられます。
同様の機能を持つ Map.groupBy() もあります。こちらは、キーに文字列以外の値(オブジェクトや数値など)を使いたい場合に便利で、結果として標準の Map オブジェクトを返します。
副作用のない配列操作
JavaScriptの sort() や reverse() のような従来の配列メソッドには、一つ注意点があります。それは、元の配列を直接変更してしまうことです。これは「」と呼ばれ、特にReactのような宣言的なUIライブラリを使用している場合に、予期せぬバグの原因となることがあります。状態の変更を追跡するのが難しくなるためです。
データを変更するのではなく、変更された新しいデータのコピーを作成するアプローチは「イミュータビリティ(不変性)」と呼ばれます。
この問題を解決するため、元の配列を変更しない「非破壊的」な新しいメソッドが導入されました。
| 破壊的メソッド (従来) | 非破壊的メソッド (新規) | 説明 |
|---|---|---|
sort() | toSorted() | 配列をソートした新しい配列を返す |
reverse() | toReversed() | 配列を逆順にした新しい配列を返す |
splice() | toSpliced() | 配列の一部を削除・置換した新しい配列を返す |
arr[i] = value | with(index, value) | 指定したインデックスの要素を置き換えた新しい配列を返す |
これらの新しいメソッドを使うことで、元のデータを安全に保ちながら、必要な操作を行うことができます。
const originalNumbers = [3, 1, 4, 2];
// toSorted() は新しい配列を返す
const sortedNumbers = originalNumbers.toSorted();
console.log(sortedNumbers); // [1, 2, 3, 4]
console.log(originalNumbers); // [3, 1, 4, 2] (元の配列は変更されない)
// with() で特定要素を置き換える
const updatedNumbers = originalNumbers.with(1, 99);
console.log(updatedNumbers); // [3, 99, 4, 2]
console.log(originalNumbers); // [3, 1, 4, 2] (元の配列は変更されない)
巨大なデータを効率的に扱う
数十万件、数百万件といった巨大なデータセットを扱うとき、map や filter を使って一連の処理を行うと、各ステップで巨大な中間配列が生成され、メモリを圧迫することがあります。例えば、巨大なログファイルから特定のエラーを探し、最初の10件だけを取得するようなケースを考えてみましょう。
ES2025で導入された は、この問題を解決します。これは、「」というアプローチを取ることで、必要なデータだけを、必要なタイミングで処理します。これにより、メモリ使用量を劇的に削減できます。
// usersは巨大なユーザーリストのイテレータと仮定
function* generateLargeUserList() {
let id = 0;
while(id < 1000000) {
yield { id: id++, isActive: Math.random() > 0.5, name: `User ${id}` };
}
}
const usersIterator = generateLargeUserList();
// Iterator Helpers を使った処理
const firstFiveActiveUsers = usersIterator
.filter(user => user.isActive)
.map(user => user.name)
.take(5); // 最初の5件が見つかった時点で処理を停止
// toArray()が呼ばれるまで、実際の処理はほとんど実行されない
const result = firstFiveActiveUsers.toArray();
console.log(result);
// 例: [ 'User 2', 'User 5', 'User 6', 'User 8', 'User 11' ]
この例では、filter や map は中間配列を生成しません。代わりに、次の処理ステップを定義した新しいイテレータを返します。最終的に take(5) が5つの要素を見つけた時点で、全体の処理が停止します。100万件すべてのユーザーをメモリに読み込むことなく、効率的に目的の結果を得ることができるのです。
Setの集合演算
Setオブジェクトはユニークな値のコレクションを扱うのに便利ですが、これまで複数のSet間で和集合や積集合といった操作を行うには、手動でループ処理などを書く必要がありました。
最近のJavaScriptでは、これらの一般的な集合演算を行うためのメソッドがSetのプロトタイプに直接追加され、コードが大幅に簡潔になりました。
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// 和集合 (A ∪ B)
const unionSet = setA.union(setB);
console.log(unionSet); // Set(6) { 1, 2, 3, 4, 5, 6 }
// 積集合 (A ∩ B)
const intersectionSet = setA.intersection(setB);
console.log(intersectionSet); // Set(2) { 3, 4 }
// 差集合 (A - B)
const differenceSet = setA.difference(setB);
console.log(differenceSet); // Set(2) { 1, 2 }
これらのメソッドは、数学的な集合の概念を直感的にコードに落とし込むことを可能にし、データの比較やフィルタリングをよりエレガントに記述できます。
ES2024で導入された Object.groupBy() メソッドの主な利点は何ですか?
JavaScriptの sort() メソッドは元の配列を直接変更しますが、この振る舞いは「副作用」と呼ばれます。
これらの新しいデータ操作テクニックは、コードをよりクリーンで、効率的で、安全なものにします。特にイミュータブルな操作と遅延評価は、現代的なアプリケーション開発における重要なパラダイムです。状況に応じてこれらの手法を使い分けることで、より質の高いコードを書くことができるようになります。