JavaScriptは昔から存在するプログラミング言語なので、昔からの書き方とモダンな書き方が混在しています。

後方互換性が高い言語なので、昔からの書き方でも書けますが、新しい書き方の方がより効率的です。

今回の記事では特に配列操作に注目して、よりモダンな書き方を実現するメソッドを紹介します。

forを使った配列の処理

従来は for を使って配列処理を行ってきました。

const items = ['a', 'b', 'c'];

for (let i = 0; i < items.length; i++) {
    const item = items[i];
    console.log(item); // => a 、次は b、最後に c
}

この書き方の場合、 i という変数が必要だったり、 for ループの中で item を定義しないといけません。

for of を使った方法

少しモダンな書き方として、 for 〜 of を使った書き方もあります。

const items = ['a', 'b', 'c'];

for (const item of items) {
    console.log(item); // => a 、次は b、 最後に c
}

for 〜 in は値ではなくインデックス値が取得できます。

const items = ['a', 'b', 'c'];

for (const item in items) {
    console.log(item); // => 0 、次は 1、 最後に 2
}

for offor in の覚え方は、 in はインデックス向きと覚えておくと良いでしょう。

配列用のメソッド

では、ここから配列用のメソッドになります。

forEach

forEach は配列の要素を順番に関数に渡します。

返却値は受け取らないのが特徴です。

古い書き方だと、次のようになります。

const items = ['a', 'b', 'c'];

items.forEach(function(item) {
    console.log(item); // => a 、次は b 最後に c
});

モダンな書き方でアロー関数にすると、次のようになります。

引数が1つの場合は括弧を省略できます。

items.forEach(item => {
    console.log(item); // => a 、次は b 最後に c
});

さらに1行だけの実行であれば、波括弧も外せます。

items.forEach(item => console.log(item.repeat(2))); // => aa 、次は bb 最後に cc

配列のインデックスは2つ目の引数として渡されます。

これを使うと、以下のような書き方もできます。

repeat メソッドは文字列を指定回数繰り返すメソッドです。

items.forEach((item, i) => console.log(item.repeat(i)));
// => 空文字, 次は b 最後に cc

map

mapは配列を順番に処理し、返却値を新しい配列として受け取ります。

const items = ['a', 'b', 'c'];

const res = items.map((item, i) => item.repeat(i));

この時の res の内容は ['', 'b', 'cc'] になります。

もし1行ではない場合には、最後に return を書きます。

const res = items.map((item, i) => {
    i = i * 2; // 最初が0、次が2、最後は4
    return item.repeat(i);
});

この時の res['', 'bb', 'cccc'] になります。

map は、配列の一部を取り出すのに便利です。

const ary = [
    {name: 'Monaca'},
    {name: 'Taro'},
    {name: 'Hanako'},
];

const name = ary
    .map(item => item.name) // nameキーの値だけを返す
    .join(', '); // カンマで連結

console.log(name); // 'Monaca, Taro, Hanako'

filter

filter は配列を検索し、一致する配列を返します。

以下は偶数だけを返す処理です。

% は余りの数を返す処理になります。

const numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const even = numbers
    .filter(number => number % 2 == 0); // 2で割り切れるかどうか判定

filterもインデックスが取得できます。

const numbers = [0, 1, 2, 4, 6, 8, 10, 12, 14, 16, 18];

const even = numbers
    .filter((number, index) => index % 2 == 0); // インデックスで割り切れるかどうか

console.log(even);
// [0, 2, 6, 10, 14, 18]

find

find は配列を検索し、最初に一致した結果を返します。その後は検索しません。

const numbers = [0, 1, 2, 3, 4, 2, 2, 3, 5];

const number = numbers.find(number => {
    console.log(数値 ${number});
    return number == 2;
});

このように書くと、以下のように途中まで出力されます。

3以降の数字は処理されません。

数値 0
数値 1
数値 2

そして返ってきた number2 になります。

filterとfindの違い

filter は条件に一致しようがしまいが、配列すべての要素に対して処理を実行します。

find はマッチする最初の1件目があれば、そこで処理が終了します。

reduce

reduce は前の値と現在の値を引数に渡す変数です。

以下は配列すべての要素を足し算する(集計)処理です。

const numbers = [1, 2, 4, 6, 8];

const sum = numbers.reduce((previous, current) => {
    console.log(previous -> ${previous});
    console.log(current -> ${current});
    return previous + current;
});

console.log(sum -> ${sum});

この処理は以下のようなログが出力されます。

previous -> 1
current -> 2
previous -> 3 // <- 前の足した結果>
current -> 4
previous -> 7
current -> 6
previous -> 13
current -> 8
sum -> 21

非同期処理への対応

今回紹介したforEach/map/filter/find/reduceなどは、非同期処理には対応していません。

例えばネットワーク処理を伴うような forEarch を下記のように書いたとします。

items.forEach(async (item) => {
    console.log(Item ID -> ${item.id});
    // 何か処理
    // ネットワーク上に保存
    await item.save();
    console.log(Item ID ${item.id} を保存しました。);
});

こういった処理を書く時の期待は次のような結果になることでしょう。

Item ID -> 1
Item ID 1 を保存しました
Item ID -> 2
Item ID 2 を保存しました
Item ID -> 3
Item ID 3 を保存しました

保存処理にかかる時間にもよりますが、実際には次のようになります。

Item ID -> 1
Item ID -> 2
Item ID -> 3
Item ID 1 を保存しました
Item ID 2 を保存しました
Item ID 3 を保存しました

保存処理にかかる時間によっては、次のようになるかも知れません。

Item ID -> 1
Item ID -> 2
Item ID -> 3
Item ID 3 を保存しました
Item ID 1 を保存しました
Item ID 2 を保存しました

つまり、非同期処理における処理順番は保証されないので注意が必要です。

また、ループ処理におけるasync/awaitの利用は推奨されていないので、この点も注意してください。

解決策1:forを使う

非同期処理のループでasync/awaitを使いたい場合には for of を使う方法があります。

for (const item of items) {
    console.log(Item ID -> ${item.id});
    // 何か処理
    // ネットワーク上に保存
    await item.save();
    console.log(Item ID ${item.id} を保存しました。);
}

これは期待通りの結果になるでしょう。

Item ID -> 1
Item ID 1 を保存しました
Item ID -> 2
Item ID 2 を保存しました
Item ID -> 3
Item ID 3 を保存しました

ただし、処理の順番が直列になっている分、時間がかかります。

解決策2:mapを使う

解決策2つ目としては、mapを利用します。

const promises = items.map(item => {
    console.log(Item ID -> ${item.id});
    // 何か処理
    // ネットワーク上に保存
    return item.save();
});

// ネットワーク処理を実行待ちする
await Promise.all(promises);
console.log('保存しました');

この場合は直列処理ではないので、保存処理は並列実行されます。

その分、処理は高速に完了します。

ただし、保存順番は保証されないので注意してください。

まとめ

配列用に追加された各メソッドを使いこなせば、コード全体の可読性が上がったり、処理がよりシンプルに書けるようになります。

ぜひ使いこなしてあなたのアプリ開発を効率化してください。