JavaScript の配列の個々の要素を処理する

ここでは配列の要素ひとつひとつを処理する必要があるときの、現代的な方法を紹介します。

for ループで要素をひとつひとつチェックする方法に比べて、よりずっと簡潔でわかりやすいコードがかけるはずです。

forEach() 全ての要素に対して関数を呼ぶ

forEach( callbackfn ) を使うと、配列内の要素それぞれに対して、 引数で渡した callbackfn 関数を呼ぶことができます。

引数の callbackfn 関数は、三つの引数を受け取ります。ひとつは要素の値、インデックス、元の配列そのものの三つです。

let a = [5, 3, 15, 2, 11];
a.forEach((val) => {
    console.log(val);
});
console.log('-- a --');
console.log(a);

尚、forEach() のコールバック関数は empty 要素 (値の設定されていない要素) に対しては呼び出されないことに注意が必要です。

例えば次のように、.length を設定して配列のサイズを拡張すると、拡張された部分は empty となります。 この部分については、forEach() のコールバック関数が呼び出されません。

const a = [];
a.length = 5;
console.log(a); // [empty x 5]
a.forEach((val, idx) => {
    console.log(`${idx}`); // 出力されない
    a[idx] = idx;
});
console.log(a); // [empty x 5] のまま

しかし、次のように要素に値を割り当てると、確かにコールバック関数が呼ばれます。

const a = [];
a.length = 5;
a.fill(0); // 値を設定
console.log(a);
a.forEach((val, idx) => {
    console.log(`${idx}`);
    a[idx] = idx;
});
console.log(a);

forEach()は empty の箇所の初期化には使えない、ということになります。

map() 元の配列の要素を引数にして関数を呼び出し、その結果からなる配列を取得

map(callbackfn) を使うと、配列の要素に対してなんらかの処理を実行して結果を取得し、その結果からなる配列を取得できます。

引数の callbackfn 関数は、三つの引数を受け取ります。ひとつは要素の値、インデックス、元の配列そのものの三つです。

次の例では、map()関数を利用し、数字の配列 a を元にして、その元の値とそれを二乗した値を、それぞれ mn というプロパティに持つオブジェクトの配列を作っています。

let a = [5, 3, 15, 2, 11];
let b = a.map((val) => {
    return {
        m: val,
        n: val * val,
    };
});
console.log('-- a --');
console.log(a);
console.log('-- b --');
console.log(b);

flatMap() でフラット配列に同時変換

map() の結果作成された配列が、[1,2,[3,4]] のような入れ子の形になった時に、 それを [1,2,3,4] というフラットな形に変換して取得したい場合、flatMap() が使えます。 flatMap() ではフラットにする深さが 1 に限られます。

フラットな配列については「flat() 入れ子の配列をフラットにして新しい配列を作る」をみてください。

reduce() 全ての要素の値を処理して結果を取得

reduce(callbackfn ,[initValue]) を使うと、配列の全ての要素の値を処理してひとつの結果となる値を取得できます。どのような値を取得するかは、 ユーザーが指定するコールバック関数の内容に依存します。例えば、要素の値を足し合わせるコールバック関数を指定すれば、合計値が得られます。

引数の callbackfn 関数は、4つの引数を受け取ります。ひとつは「累積値」、「現在の値」、「インデックス」、「元の配列そのもの」の4つです。

reduceRight() 関数は同様の動作を行いますが、全要素を辿るときに、インデックスで降順の順番で辿ります。

次の例では、reduce(callbackfn)関数を利用し、配列内の要素の値の合計値を取得します。

const a = [3, 5, 7, 9, 11];
const b = a.reduce((accumulator, currentValue, idx, obj) => {
  console.log(`${accumulator}, ${currentValue}, ${idx}`);
  return accumulator + currentValue;
}, 0);
console.log(b); // 35
0, 3, 0
3, 5, 1
8, 7, 2
15, 9, 3
24, 11, 4
35

この例ではreduce() の第二引数に初期値として 0 を渡しています。

もしreduce() の第二引数に初期値を渡していない場合はコールバック関数は、最初に 2 番目の要素に対して呼ばれます。このとき 1 番目の要素の値は、累積値 accumulator として入ってきます。

総和を計算するなら、accumulator に、現在の値 currentValue を足して、それを返します。すると、次の要素に対するコールバックとしては、 前回のコールバックの戻り値が accumulator に設定され呼び出されます。これにより、累積値に現在の値を加えて返すことで、全要素の値の総和が得られます。

最終的に accumulatorreduce() 関数の戻り値になります。

reduce() の初期値に注意

上で少し書きましたが、reduce()のひとつ目の引数は上で書いたコールバック関数ですが、二つ目には積算の初期値を設定することになっています。

初期値の設定がない場合、コールバック関数は配列の二つ目の要素 (インデックス 1) から呼び出されます。

では、初期値の設定がなく、さらに配列の要素がひとつだけの場合、どのような動作になるでしょうか?

このときは配列の一つ目の要素の値が reduce() の値として返ります。

const a = [3];
const b = a.reduce((acc, curr) => {
  console.log(`acc=${acc}, curr=${curr}`);
  return acc + curr;
});
console.log(b); // 3

このように、単純に配列の要素の値の総和を求める場合には、要素の数にかかわらず結果的に正常動作しているといえます。

しかし、次のようにオブジェクトの配列を扱うときには、初期値がないと正常動作しません。

const a = [
  { qty: 3, name: 'Apple' },
  { qty: 5, name: 'Banana' },
  { qty: 4, name: 'Orange' },
];
const sum = a.reduce((acc, curr) => acc + curr.qty, 0);
console.log(`sum=${sum}`); // sum=12

もし初期値がなければ、配列の要素がひとつの場合には { qty: 3, name: 'Apple' }が返り、 要素が二つ以上ある場合には最初のコールバック時に acc{ qty: 3, name: 'Apple' }というオブジェクトが渡されるため、足し算ができません。

reduce() を使うときには必ず初期値を渡しましょう。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 JavaScript 入門