JavaScriptの配列とオブジェクト:map・filter・reduceでデータを処理する
JavaScriptのmap・filter・reduce・find、オブジェクトのコピー、MapとSetを買い物データで解説。元のデータを変更しない更新方法を学びます。
第3回では、商品をオブジェクト、その一覧を配列として扱います。元のデータを変更する操作と、新しいデータを作る操作の違いを確認しながら、絞り込み・変換・集計を実装します。関数とモジュールの続きとして読めます。
データの形を決める
オブジェクトに関連する値をまとめる
商品にはID、名前、単価、個数を持たせます。IDは表示名とは別にし、名前が同じ商品があっても区別できるようにします。この例の金額は円単位の整数を前提とした練習用データです。
cart.mjsを作成する
cart.mjsに次のコードを保存します。各行が何を返すか、実行前に予想してみてください。
export const items = [
{ id: 'notebook', name: 'ノート', price: 200, quantity: 2 },
{ id: 'pen', name: 'ペン', price: 120, quantity: 3 },
{ id: 'eraser', name: '消しゴム', price: 80, quantity: 0 },
];
export const purchased = items.filter((item) => item.quantity > 0);
export const lines = purchased.map(({ name, price, quantity }) => ({
name,
subtotal: price * quantity,
}));
export const total = lines.reduce((sum, line) => sum + line.subtotal, 0);
export const selected = items.find((item) => item.id === 'pen');
export const sorted = [...items].sort((a, b) => a.price - b.price);
export const updated = items.map((item) =>
item.id === 'pen' ? { ...item, quantity: item.quantity + 1 } : item
);
console.log(lines); // ノート400円、ペン360円の2行
console.log(total); // 760
console.log(selected?.name ?? '見つかりません'); // ペン
console.log(sorted.map((item) => item.name)); // 消しゴム、ペン、ノート
console.log(items[1].quantity, updated[1].quantity); // 3 4
配列メソッドを使い分ける
filterとmapの違い
filterは条件に合う要素を残します。mapは各要素から新しい値を作ります。戻り値を使わず表示だけしたい場合に、無理にmapを使う必要はありません。
reduceの初期値を指定する
reduceの第2引数に0を指定しているので、対象が空配列でも合計は0になります。初期値を省略した場合の空配列はエラーになるため、集計の開始値を明示します。
findが見つからない場合を扱う
findは最初に一致した要素、見つからなければundefinedを返します。selected?.nameは値がない場合のプロパティ参照を避け、??はnullかundefinedのときだけ代わりの値を使います。0や空文字列まで置き換える||とは異なります。
コピーと変更の範囲に注意する
sortは元の配列を変更する
この例では[...items]で配列をコピーしてからsortしています。items.sort(...)と書くと、元の配列の順序が変わります。また、数値の並び替えでは比較関数を指定します。
スプレッド構文は浅いコピー
配列をコピーしても、配列の中のオブジェクトまで自動的に複製されるわけではありません。updatedでは変更する商品だけを{ ...item }でコピーしています。さらに深い階層を変更するなら、その階層までコピーする必要があります。
MapとSetで検索と重複除去を行う
IDから商品を探すMap
同じフォルダーにlookup.mjsを作成します。import時にはcart.mjsの表示処理も動きます。再利用ライブラリにする場合は、表示を別の実行ファイルへ移してください。
import { items } from './cart.mjs';
const byId = new Map(items.map((item) => [item.id, item]));
const requestedIds = ['pen', 'pen', 'notebook'];
const uniqueIds = [...new Set(requestedIds)];
console.log(byId.get('pen')?.name); // ペン
console.log(uniqueIds); // ['pen', 'notebook']
console.log(byId.has('unknown')); // false
重複IDの扱いを決める
Mapへ同じキーを登録すると、後から登録した値に置き換わります。外部データを取り込む処理では、ID重複を許すかエラーにするかを決めます。Setにオブジェクトを入れた場合は、見た目の中身ではなく参照によって区別されます。
練習と次のステップ
結果と元データを比較する
- 購入個数をすべて0にし、合計が0になることを確認します。
- 存在しないIDを
findで探し、代替表示を確認します。 - 並び替え後も
itemsの順序が変わっていないことを確認します。
画面に結果を表示する
次はDOMとイベントで、入力した内容をブラウザーの一覧へ追加します。