※本記事にはPRを含む場合があります。

こんにちは。『脱会社員』脱サラを応援し続けるブログ、運営者の『なかびー』です。

JavaScriptの配列は、複数の値を順番付きで扱うためのデータ構造です。最初は、添字で取り出す、条件に合うものを探す、必要な行を選ぶ、形を変える、合計する、という操作を分けて覚えると理解しやすくなります。メソッド名だけでなく、戻り値と元データが変わるかを確認しましょう。

この記事では六行の架空の買い物明細を使い、filterで対象を選び、mapで金額を付け、reduceで集計し、toSortedで並べ替えます。最終的な件数4、数量11、金額1810円を手計算と実行結果で照合します。画面の部品を動かすDOM操作は中心にせず、データの処理だけを一つのファイルで練習します。

仕様は2026年10月4日に確認したMDNの資料に基づきます。主な完成例と補助例はNode.js 24.19.0で実行して確認しました。toSortedを含むため、別の実行環境では対応状況を確認してください。ブラウザー画面への描画、外部通信、実際の注文処理は行いません。データはすべて架空で、図版は説明用の生成イメージです。

配列の順番・添字・要素の形を理解する

六つの要素の順番を整理する説明用生成画像

最初の要素は0、最後はlengthから1を引いた位置

配列は角括弧で作れます。たとえば[‘鉛筆’, ‘ノート’, ‘消しゴム’]には三つの文字列が入っています。最初の要素の添字は0なので、items[0]が鉛筆、itemsがノートです。人が数える一番目と、コードの0番目を取り違えないことが最初のポイントになります。

lengthは配列の長さを表します。この三要素の例なら3で、最後の要素の位置はlength – 1、つまり2です。itemsは四つ目を参照することになり、この例ではundefinedになります。範囲外を読んだときに必ず例外が出るわけではないため、値がない場合をどう扱うかを考えます。

const items = ['鉛筆', 'ノート', '消しゴム'];
console.log(items[0]);                 // 鉛筆
console.log(items.length);             // 3
console.log(items[items.length - 1]);   // 消しゴム
console.log(items === undefined);   // true

配列の基本的な性質は、MDNのArray資料で確認できます。配列には空の要素位置がある特殊な状態も作れますが、初めは角括弧へ値を順に書いた、穴のない配列を使う方が理解しやすくなります。lengthと実際に存在する要素の数が常に同じだと一般化しないようにしましょう。

一つの明細をオブジェクト、一連の明細を配列にする

買い物明細には、名前だけでなく数量、単価、状態などがあります。この一行分を一つのオブジェクトにし、そのオブジェクトを配列へ並べます。配列の順番と、オブジェクトのプロパティ名の役割を分けると、複数の情報を扱いやすくなります。

今回の各行はid、name、quantity、unitPrice、statusという同じ項目を持ちます。quantityは数量、unitPriceは一個あたりの架空の単価です。statusはactiveかcancelledで、集計対象を判断します。名前が同じノートの行が二つあるため、商品名だけで一行を特定せず、IDで区別する練習にもなります。

※表は横にスクロールできます。

ID名前数量単価(円)状態
A01鉛筆3120active
A02ノート2350active
A03ファイル4200cancelled
A04消しゴム580active
A05ノート1350active
A06マーカー0150active

今回の集計条件は、状態がactiveで、数量が0より大きい行です。A03はキャンセルなので除外し、A06は数量0なので除外します。キャンセルをどう扱うか、0件の行を残すかは実際の業務によって違うため、ここでは条件を先に決めています。データの中身を見ずに「全部合計する」と決めないようにします。

配列かどうかと、各行が正しいかは別に確認する

Array.isArrayで、渡された値が配列かを確認できます。typeofで配列を調べるとobjectになるため、それだけで普通のオブジェクトと区別しようとしないでください。文字列の'[]’は空の配列を表す文字に見えても、実際の配列そのものではありません。

配列であることが分かっても、すべての行に正しい数量や単価があるとは限りません。数字の3と文字列の’3’は別の型です。外部データを扱う場合は、変換する方針か、型違いをエラーにする方針かを先に決めます。今回の完成例は、数値の整数だけを受け付ける方針にします。

追加・削除・検索で、戻り値と元配列の変化を確かめる

配列への追加と条件による検索を考える男性の説明用生成画像

pushとpopは、元の配列を変更する

pushは配列の末尾へ要素を加えます。戻り値は追加後の配列ではなく、新しいlengthです。popは末尾の要素を取り出して削除し、取り出した要素を返します。名前から何となく使うより、操作後の配列と戻り値を別々に出力して確かめると取り違えを防げます。

const list = ['A', 'B'];
const newLength = list.push('C');
console.log(newLength);        // 3
console.log(list.join(','));   // A,B,C
const removed = list.pop();
console.log(removed);          // C
console.log(list.join(','));   // A,B

constで宣言していても、配列の中身が変更できないという意味ではありません。constは変数へ別の値を代入し直すことを制限しますが、参照している配列のpushなどを一律に禁止するものではありません。元データを変えたくない場合は、使うメソッドとコピーの深さを別に考えます。

先頭へ追加するunshift、先頭を取り出すshift、途中を変更するspliceなどもありますが、最初から全部使う必要はありません。末尾へ追加するだけの処理と、途中の行を消す処理では目的が異なります。配列全体のどの位置が変わるかを、操作前後で確認しましょう。

findは最初の一件、filterは該当する行の配列

findは条件に合う最初の要素を返します。今回の明細で名前がノートの行をfindすると、元の順番ではA02が対象になります。もう一つのA05も欲しいなら、条件に合う要素を集めた配列を返すfilterを使う方が目的に合います。

見つからない場合、findはundefinedを返します。その結果へすぐ.nameなどを使うと、値がないためエラーになる場合があります。まず存在を確認するか、見つからなかったときの処理を用意してください。具体的な仕様は、MDNのfind資料で確認できます。

filterは該当がなければ空配列を返します。空配列自体は値として存在するため、if (filtered)だけで該当件数の有無を判断しないようにします。件数を知りたいときはfiltered.lengthなど、目的に合う確認を使います。findのundefinedと、filterの空配列を区別することが大切です。

someとeveryは、真偽値を求めるときに使う

一つでも条件に合う行があるかを知るならsome、すべての行が条件に合うかを知るならeveryが候補です。値そのものを取り出したいのか、存在だけ分かればよいのかで選びます。すべてをfilterしてから件数を調べる方法だけに統一する必要はありません。

空配列でeveryを使った場合はtrueになるという性質があります。「全件が条件に合う」と「一件以上のデータがある」を両方求める場合は、lengthの確認も必要です。空の入力を実際に試し、言葉の印象だけで期待値を決めないようにしましょう。

※表は横にスクロールできます。

操作主な戻り値元の配列への影響選ぶ目的
push追加後の長さ変更する末尾へ追加
pop取り出した要素変更する末尾を取り出す
find最初の要素またはundefined検索自体では変更しない一件を探す
filter条件に合う要素の新しい配列選択自体では変更しない複数行を選ぶ
map変換結果の新しい配列変換処理の書き方に注意各要素の形を変える
reduce積み上げた結果処理の書き方に注意合計などを作る

「変更しない」と説明されるメソッドでも、コールバックの中で元のオブジェクトを書き換えれば、その影響は残ります。メソッド名だけで安全だと判断せず、中に書いた処理も確認します。次の完成例では、新しい明細オブジェクトを返し、元の行へ金額を直接書き足さない構成にします。

filter・map・reduceで、対象の明細を変換して集計する

件数と数量と金額を別々に検算する女性の説明用生成画像

一つのファイルで、入力から検算までを実行する

次のコードを、専用の練習フォルダーへpractice.cjsとして保存します。Node.jsを利用できる環境で、node practice.cjsを実行すると結果が標準出力へ表示されます。実行するのはこの自作の明細だけで、ファイル削除、外部通信、注文確定などは含まれていません。

既存のNode.js環境がない場合は、利用端末のルールと公式の導入案内を確認してから準備してください。仕事用PCへ無断でソフトを入れる必要はありません。また、知らないサイトが示したコードを、ログイン中のサービスの開発者コンソールへ貼る方法は避けます。今回の例は内容を読める独立した練習ファイルとして扱います。

const orders = [
  { id: 'A01', name: '鉛筆', quantity: 3, unitPrice: 120, status: 'active' },
  { id: 'A02', name: 'ノート', quantity: 2, unitPrice: 350, status: 'active' },
  { id: 'A03', name: 'ファイル', quantity: 4, unitPrice: 200, status: 'cancelled' },
  { id: 'A04', name: '消しゴム', quantity: 5, unitPrice: 80, status: 'active' },
  { id: 'A05', name: 'ノート', quantity: 1, unitPrice: 350, status: 'active' },
  { id: 'A06', name: 'マーカー', quantity: 0, unitPrice: 150, status: 'active' }
];

function summarizeOrders(rows) {
  if (!Array.isArray(rows)) {
    throw new TypeError('配列を渡してください');
  }
  const ids = new Set();
  for (const row of rows) {
    if (!row || typeof row !== 'object' ||
        typeof row.id !== 'string' || row.id.trim() === '' ||
        typeof row.name !== 'string' || row.name.trim() === '' ||
        !Number.isSafeInteger(row.quantity) || row.quantity < 0 ||
        !Number.isSafeInteger(row.unitPrice) || row.unitPrice < 0 ||
        !['active', 'cancelled'].includes(row.status)) {
      throw new TypeError('明細の型または値が不正です');
    }
    if (ids.has(row.id)) {
      throw new Error('IDが重複しています');
    }
    ids.add(row.id);
  }
  const selected = rows.filter(row => row.status === 'active' && row.quantity > 0);
  const lines = selected.map(row => {
    const amount = row.quantity * row.unitPrice;
    if (!Number.isSafeInteger(amount)) {
      throw new RangeError('明細金額が安全な整数の範囲を超えています');
    }
    return { id: row.id, name: row.name, quantity: row.quantity, amount };
  });
  const totalQuantity = lines.reduce((sum, row) => sum + row.quantity, 0);
  const totalAmount = lines.reduce((sum, row) => sum + row.amount, 0);
  if (!Number.isSafeInteger(totalQuantity) || !Number.isSafeInteger(totalAmount)) {
    throw new RangeError('合計が安全な整数の範囲を超えています');
  }
  return {
    count: lines.length,
    totalQuantity,
    totalAmount,
    lines: lines.toSorted((a, b) => a.amount - b.amount)
  };
}

const before = JSON.stringify(orders);
const result = summarizeOrders(orders);
console.log(JSON.stringify(result, null, 2));
console.log('元データは同じ:', before === JSON.stringify(orders));
console.log('該当なし:', orders.find(row => row.id === 'A99') === undefined);
console.log('空配列の合計:', summarizeOrders([]).totalAmount);

執筆時の実行環境はNode.js 24.19.0です。主な結果はcountが4、totalQuantityが11、totalAmountが1810で、金額順のIDはA05、A01、A04、A02になりました。元データの比較はtrue、該当しないIDの確認はtrue、空配列の合計は0です。これらは今回の入力と条件に対する実行結果です。

filterで、集計する行を先に選ぶ

filterの条件では、status === ‘active’とquantity > 0を&&で結んでいます。両方を満たす行だけを残すため、A03とA06は対象外になります。状態だけで選ぶと数量0のA06が残り、数量だけで選ぶとキャンセルのA03が残るので、条件の意味を先に文章へすると確認しやすくなります。

filterは行を選ぶ処理です。選ばれた行の内容を変えることまでは、この段階の目的にしません。MDNのfilterの資料にも、条件に合う要素を持つ新しい配列を作る動作が説明されています。ただし、内側のオブジェクトは共有され得る点を、後のコピーの章で確認します。

絞り込み後の件数は4ですが、数量の合計は11です。件数と数量を同じものとして扱うと、正しい結果でも違って見えます。まずどの行が残ったかをIDで確認し、その後に数量や金額を検算します。合計値だけ一致していても、別の行が混ざっている可能性はあります。

mapで、一行ごとの金額を持つ新しい形へ変える

mapでは、各行についてquantity × unitPriceを計算し、id、name、quantity、amountを持つ新しいオブジェクトを返しています。元の行へamountを直接代入していないため、元データの項目を増やさずに、集計しやすい形を作れます。

mapのコールバックで波括弧を使う場合は、returnを書き忘れないようにします。計算だけ行って値を返さないと、結果の配列にundefinedが並ぶ原因になります。矢印関数の省略形と、処理を複数行書く形で戻し方が変わることを確認してください。仕様の確認先はMDNのmap資料です。

今回の一行ごとの金額は、A01が360円、A02が700円、A04が400円、A05が350円です。まず一行ずつ計算できる値にしておくと、合計が違ったときにどの段階からずれたかを探せます。複雑な計算を一つの長い式へ詰めるより、途中の形が分かる方が練習しやすくなります。

reduceは初期値を付けて、空の場合も決める

reduceで数量と金額をそれぞれ加算しています。最後の0は初期値です。空配列でも0から始められるため、該当行がないときの合計を0として扱えます。初期値を省略したreduceは、空配列などで期待どおりに扱えない場合があるため、合計の例では明示しておくと分かりやすくなります。

※表は横にスクロールできます。

段階行または値確認すること
元データ6行IDがA01〜A06で重複しない
絞り込み後A01・A02・A04・A05キャンセルと数量0を除外
数量3+2+5+1=11件数4と混同しない
金額360+700+400+350=1810単価だけを足していない
金額の昇順350・360・400・700元の順序とは別の並び

reduceは合計以外にも使えますが、初めから何でも一つのreduceで処理する必要はありません。今回のように選ぶ、変える、足すを分けると、各段階の結果を確認できます。詳しい引数と空配列の扱いは、MDNのreduce資料で確認してください。

並べ替えとコピーで、元データが変わる範囲を理解する

元データと処理後のデータを照合する二人の説明用生成画像

sortの既定の並びと、数値の大小は同じとは限らない

sortは元の配列を並べ替えます。比較関数を省略すると、通常は値を文字列として扱う順序が関係するため、数字を小さい順にしたいときは比較関数が必要です。2、10、1を並べる例なら、数値の昇順は1、2、10であって、1、10、2ではありません。

const numbers = [2, 10, 1];
console.log(numbers.toSorted().join(','));             // 1,10,2
console.log(numbers.toSorted((a, b) => a - b).join(',')); // 1,2,10
console.log(numbers.join(','));                       // 2,10,1

sortの詳しい動作はMDNのsort資料で確認できます。オブジェクトの配列を金額順にしたいなら、オブジェクトそのものを引くのではなく、a.amount – b.amountのように比べる項目を指定します。

文字列を日本語として並べたい場合は、数値の引き算とは別の比較方法が必要です。名前の順序、IDの順序、金額の順序を混同しないようにします。並べ替えた後も、どの値を基準にした結果なのかを説明できることが大切です。

toSortedは、新しい配列を返す方法

完成例ではtoSortedを使っています。元のlinesの並びを変える代わりに、並べ替えた新しい配列を返す方法です。MDNのtoSortedの資料で動作と対応状況を確認できます。今回のNode.jsでは実行できていますが、古い環境ではそのまま使えない可能性があります。

対応していない環境では、用途を確認したうえで[…lines].sort((a, b) => a.amount – b.amount)のように外側の配列をコピーしてからsortする方法があります。ただし、このコピーも浅いコピーです。元の配列の順序を守ることと、内側のオブジェクトを独立させることは別に考えます。

「toSortedが使えない」というエラーが出たときは、まず実行環境の版を確認します。別の問題を直すために、出所不明の拡張やコードを追加する必要はありません。対象環境を決め、その環境で使える方法へ合わせる方が、再現しやすいコードになります。

スプレッド構文で複製しても、内側は共有される

配列を[…source]の形でコピーしても、配列の中にあるオブジェクトまで自動で別物になるわけではありません。外側の入れ物は新しくても、中のオブジェクトは同じものを参照する場合があります。次の短い例は、その影響を意図的に確かめるものです。実際の明細とは別のデータで試します。

const source = [{ name: '練習A', quantity: 1 }];
const shallow = [...source];
shallow[0].quantity = 2;
console.log(source[0].quantity);  // 2
console.log(source === shallow); // false
console.log(source[0] === shallow[0]); // true

外側の配列が違うのでsource === shallowはfalseですが、中の一番目のオブジェクトは共有されるためtrueになります。filterで新しい配列を作った場合も、要素として元のオブジェクトを参照する点に注意します。新しい配列という説明を、深いコピーという意味へ広げないようにしましょう。

今回の完成例は、mapで必要な文字列と数値を使った新しいオブジェクトを作っています。そのため、返した明細の数量を変えても、元データの同じ数量へ直接影響しない構成です。ただし、もしmetaなどの入れ子のオブジェクトをそのまま返すよう変更したら、さらに内側の参照を考える必要があります。

変更してよいデータを、関数の入口で決める

関数を作るときは、「入力の配列は変更しない」「結果は別に返す」といった約束を決めると、呼び出す側が使いやすくなります。逆に、配列をその場で更新する必要がある処理なら、変更することが分かる名前や説明を付けます。どちらかが常に正しいのではなく、役割が明確なことが重要です。

完成例の最後では、処理前後のordersをJSON文字列にして一致を確認しています。今回のように文字列・数値中心の単純な架空データに対する点検です。JSON化ですべてのJavaScriptの値や参照関係を完全に比較できるという意味ではありません。関数、循環参照、特殊な型などを扱う場合は別の方法が必要になります。

空配列・型違い・重複を試し、使える範囲を確かめる

空配列や型違いの結果を確認して記録する男性の説明用生成画像

成功した入力だけで、関数の完成にしない

買い物明細が想定どおりの形なら、集計が合うことは確認しやすいでしょう。しかし、実務では空の入力、キャンセルだけ、数量0だけ、文字列の数字、重複したIDなどが混ざる場合があります。どれを許可し、どれをエラーにするかを決めて試すことが大切です。

今回の関数は、配列でない入力をエラーにし、数量と単価を0以上の安全な整数として確認します。idとnameは空の文字列を認めず、statusはactiveまたはcancelledに限定しています。IDが同じ二行も、同じ明細を二度数える可能性があるためエラーにします。この方針がすべての仕事に適するわけではなく、例として明示したルールです。

※表は横にスクロールできます。

入力の条件今回の期待結果確認の目的
通常の六行4行・数量11・金額1810基本の抽出と計算
空配列件数0・数量0・金額0reduceの初期値
キャンセルだけ集計対象0行状態条件の確認
数量0だけ集計対象0行数量条件の確認
配列以外TypeError入口の型を確認
数量が文字列TypeError暗黙の型変換に頼らない
ID重複Error同じ行の二重処理を防ぐ
安全な整数の範囲を超える金額RangeError大きな値の精度を確認

これらの条件は、今回の実装に対するテストで確認しました。表示の見た目や実際の決済処理を検証したものではありません。合計が出たから注文や請求にそのまま使えると考えず、税、丸め、割引、通貨など、実務で必要な規則を別に設計してください。

数字に見える文字列を、勝手に足さない

JavaScriptでは、+が数値の加算にも文字列の連結にも関係します。数量や単価が文字列のままだと、期待した計算と違う結果になることがあります。エラーを避けるためだけに何でもNumberへ渡すと、空文字などが意図しない値になる場合もあります。入力をどの形式で受けるかを先に決めましょう。

今回の例は数量と単価を整数に限定し、文字列の’3’は受け付けません。文字列の数字を許可する要件がある場合は、空文字、空白、桁区切り、負数、小数、単位付きの文字などをどう扱うかを決めてから変換します。変換できた値と、仕事の規則として許可できる値も別です。

Number.isSafeIntegerは、JavaScriptで正確に扱える整数の範囲にあるかを確認するために使っています。詳細はMDNのNumber.isSafeIntegerを参照してください。小数の通貨や巨大な数値を扱う場合は、この練習の整数計算をそのまま拡張するだけで済むとは限りません。

undefinedが出たら、どの段階で消えたかを見る

findで見つからなかった、添字が範囲外だった、mapでreturnを忘れた、オブジェクトのプロパティ名が違ったなど、undefinedが出る理由は複数あります。「配列が壊れた」とまとめず、直前の値と処理を順に確認します。

filter直後の件数とID、map直後の一行の形、reduceへ渡す値をそれぞれ出すと、原因を見つけやすくなります。すべての処理を一行へつなげたコードは短くても、初めは途中結果が見えにくい場合があります。まず変数へ分けて確認し、理解できてから必要に応じてまとめるとよいでしょう。

エラーをcatchして空配列へ変えるだけでは、不正な入力なのか、本当に該当がないのかを区別できなくなります。利用者へどう伝えるか、ログへ何を残すかも役割に合わせて決めます。実際の顧客データをそのままログへ大量に出すのではなく、必要な範囲の識別情報と原因を扱うことが大切です。

同じファイルを繰り返し実行し、結果が変わらないか見る

完成例は毎回同じ架空データを定義するため、同じ環境で実行すれば同じ件数と合計を期待します。元データを関数内で変更してしまう実装では、一回目と二回目で結果が変わることがあります。テストでは連続して呼び出す場合も考え、入力が保持される約束を確認します。

一つの対話コンソールへconstの例を何度も貼ると、同じ名前を再宣言できないというエラーが出る場合があります。そのエラーは配列の計算自体とは別です。今回のようにファイルとして実行するか、新しい実行環境で試し、コードの状態と入力データをそろえて確認しましょう。

小さなデータ処理を、説明できる成果にまとめる

学習成果としては、コード、入力データ、期待する出力、実行した環境、試した失敗条件を一組で残します。今回なら「六行からキャンセルと数量0を除き、4行・数量11・1810円を得た。元データは変更しない」と説明できます。メソッドを何個知っているかより、条件と結果を説明できる方が応用につながります。

作品のまとめ方は、プログラミングのポートフォリオとREADMEの作り方も参考にしてください。学習支援を比較したい場合は、副業向けプログラミングスクールの比較で、費用や支援の範囲を確認する方法もあります。受講やこのコードの完成だけで、仕事や収入が保証されるわけではありません。

JavaScriptの配列は、順番、条件、戻り値、元データへの影響を分けて確認すると使いやすくなります。取り出す、選ぶ、形を変える、合計する、並べ替えるという流れを、小さな明細で検算しながら練習していきましょう。