JavaScript で Array に対して処理する方法はいくつかあります。
Array が提供しているメソッドとループで処理した場合にどちらが速いのか調べてみました。
※ 特に記載がない限り 2021 年 12 月 18 日時点の情報になります。
短い結論
普通の for ループが最速です。
とはいえ Array が提供するメソッドも十分に速いです。
可読性やコード量を考えると速度がとても重要かつ高頻度で呼ばれる処理以外は Array が提供するメソッドで問題ないと思います。
先行研究
2 年以上前の記事ですが次の記事でループ処理について調べられています。
それによると普通の for ループが最速だけど Babel や TypeScript は for...of を普通の for ループに展開するとのことでした。
ほかにも Array の処理について調べた記事がいくつかありました。 1 2
測定方法
10000 個の数値が格納された Array を処理するテストコードを JSBench.me で作成しました。
テストコードは早期終了しないように条件を整えています。
-
Arrayのメソッドを使用する。 -
forループでインデックスアクセスする。 -
for...ofループでアクセスする。 -
Array.prototype.forEach()を使用する。
for...in は配列での使用が推奨されていないため試していません。
測定環境は Windows 10 21H2 上の Chrome 96 および Firefox 95 と、 iOS 15.2 上の Safari です。
Node.js や Deno は試していませんが両方とも V8 を利用していますので Chrome とそれほど変わらないのではないかと思っています。
結果
結果はあくまでテストコードのものであり実際の処理を反映したものではないことにご注意ください。
最も速かった方法を 太字 で、最も遅かった方法を 斜体 にしています。
差異が 5 % 未満の場合は同率とみなしました。
iOS に比べて Windows 10 の数字が振るわないのは使用した PC が古いからです。
Array.prototype.every()
Array.prototype.every() はすべての要素が条件を満たしているかどうかを boolean 値で返します。
※ forEach() は早期終了できないため本来この用途で使うべきではありません。
| Chrome 96 | Firefox 95 | Safari 15.2 | |
|---|---|---|---|
every() |
7380.3 ops/s | 10075.81 ops/s | 20740.33 ops/s |
for |
124654.9 ops/s | 75078.59 ops/s | 160115.59 ops/s |
for...of |
73325.97 ops/s | 12734.04 ops/s | 50587.57 ops/s |
forEach() |
9343.29 ops/s | 10306.86 ops/s | 4067.92 ops/s |
普通の for ループが最速で every() との差は 7.5 ~ 16.9 倍程度あります。
for...of ループは every() より速いものの for との間には 1.7 ~ 5.8 倍程度の差があります。
Array.prototype.filter()
Array.prototype.filter() は条件を満たした要素による新しい配列を返します。
| Chrome 96 | Firefox 95 | Safari 15.2 | |
|---|---|---|---|
filter() |
5118.88 ops/s | 3812.93 ops/s | 12337.09 ops/s |
for |
24883.59 ops/s | 5260.2 ops/s | 27659.75 ops/s |
for...of |
23145.85 ops/s | 4179.7 ops/s | 27088.03 ops/s |
forEach() |
6770.83 ops/s | 3569.15 ops/s | 3188.5 ops/s |
普通の for ループが最速で filter() との差は 1.4 ~ 4.9 倍程度あります。
for...of ループはブラウザにもよりますが for() に近い性能が出ています。
Array.prototype.find()
Array.prototype.find() は条件を満たした最初の要素を返します。
※ forEach() は早期終了できないため本来この用途で使うべきではありません。
| Chrome 96 | Firefox 95 | Safari 15.2 | |
|---|---|---|---|
find() |
8869.38 ops/s | 10437.32 ops/s | 17689.58 ops/s |
for |
75320.49 ops/s | 75468.88 ops/s | 160571.43 ops/s |
for...of |
73324.49 ops/s | 12875.45 ops/s | 50507.62 ops/s |
forEach() |
9098.93 ops/s | 10527.09 ops/s | 4063.55 ops/s |
普通の for ループが最速で find() との差は 7.2 ~ 9.1 倍程度あります。
for...of ループは Chrome では for ループと同等の性能が出ていますが Firefox / Safari では for との間に明らかな差がついています。
Array.prototype.findIndex()
Array.prototype.findIndex() は条件を満たした最初の要素のインデックスを返します。
※ forEach() は早期終了できないため本来この用途で使うべきではありません。
| Chrome 96 | Firefox 95 | Safari 15.2 | |
|---|---|---|---|
findIndex() |
9270.52 ops/s | 10886.56 ops/s | 24088.21 ops/s |
for |
124751.52 ops/s | 74632.83 ops/s | 157593.69 ops/s |
for...of |
60863.4 ops/s | 11657.37 ops/s | 49029.13 ops/s |
forEach() |
8300.89 ops/s | 8399.1 ops/s | 3812.51 ops/s |
普通の for ループが最速で findIndex() との差は 6.5 ~ 13.5 倍程度あります。
for...of ループは findIndex() より速いものの for との間には 2.0 ~ 6.4 倍程度の差があります。
また、 Firefox では for...of ループと findIndex() が近い結果になっています。
Array.prototype.includes()
Array.prototype.includes() は指定した値が含まれているかどうかを boolean 値で返します。
※ forEach() は早期終了できないため本来この用途で使うべきではありません。
| Chrome 96 | Firefox 95 | Safari 15.2 | |
|---|---|---|---|
includes() |
63892.08 ops/s | 53757.02 ops/s | 123028.06 ops/s |
for |
124989.14 ops/s | 74253.94 ops/s | 160479.43 ops/s |
for...of |
73350.58 ops/s | 12760.88 ops/s | 50813.92 ops/s |
forEach() |
9090.29 ops/s | 10187.45 ops/s | 3769.47 ops/s |
普通の for ループが最速で includes() との差は 1.3 ~ 2.0 倍程度あります。
for...of ループは Chrome では includes() より速いものの、 Firefox / Safari では includes() の方が倍以上速いという結果になりました。
Array.prototype.indexOf()
Array.prototype.indexOf() は指定した値と同じ最初の要素のインデックスを返します。
※ forEach() は早期終了できないため本来この用途で使うべきではありません。
| Chrome 96 | Firefox 95 | Safari 15.2 | |
|---|---|---|---|
indexOf() |
63841.43 ops/s | 61759.94 ops/s | 243970.6 ops/s |
for |
124977.31 ops/s | 75421.91 ops/s | 159873.61 ops/s |
for...of |
60847.14 ops/s | 11882.97 ops/s | 48975.95 ops/s |
forEach() |
8288.36 ops/s | 8733.95 ops/s | 3986.41 ops/s |
Chrome / Firefox では普通の for ループが最速でしたが Safari は indexOf() の方が速い結果になりました。
for...of ループの結果も Chrome では indexOf() とほぼ同じ、 Firefox / Safari では indexOf() の方が 5 倍前後速いという結果になりました。
Array.prototype.map()
Array.prototype.map() は与えられた関数をすべての要素に対して呼び出した結果を新しい配列として返します。
| Chrome 96 | Firefox 95 | Safari 15.2 | |
|---|---|---|---|
map() |
7328.94 ops/s | 5719.84 ops/s | 3198.75 ops/s |
for |
25419.7 ops/s | 5061.33 ops/s | 27739.21 ops/s |
for...of |
24455.61 ops/s | 4001.65 ops/s | 27471.53 ops/s |
forEach() |
6891.12 ops/ | 3081.78 ops/s | 3219.1 ops/s |
Chrome / Safari では普通の for ループが 3.5 ~ 8.7 倍程度速いものの Firefox は map() の方が速い結果になりました。
for...of ループの結果も Chrome / Safari では for とほぼ同じですが Firefox では for の方が 1.3 倍前後速いという結果になっています。
Array.prototype.reduce()
Array.prototype.reduce() は与えられた関数をすべての要素に対して呼び出した結果を返します。
| Chrome 96 | Firefox 95 | Safari 15.2 | |
|---|---|---|---|
reduce() |
6279.79 ops/s | 7563.36 ops/s | 3620.21 ops/s |
for |
124838.37 ops/s | 62598.63 ops/s | 16696.64 ops/s |
for...of |
72855.98 ops/s | 11517.46 ops/s | 12516.1 ops/s |
forEach() |
4856.55 ops/s | 8562.24 ops/s | 3592 ops/s |
普通の for ループが最速で reduce() との差は 4.6 ~ 19.9 倍程度あります。
for...of ループは reduce() より速いものの for との間には 1.3 ~ 5.4 倍程度の差があります。
Array.prototype.some()
Array.prototype.some() は条件を満たす要素が含まれているかどうか boolean 値で返します。
※ forEach() は早期終了できないため本来この用途で使うべきではありません。
| Chrome 96 | Firefox 95 | Safari 15.2 | |
|---|---|---|---|
some() |
9083.38 ops/s | 9889.48 ops/s | 22580.03 ops/s |
for |
124672.56 ops/s | 74082.2 ops/s | 159535.44 ops/s |
for...of |
73228.52 ops/s | 12655.53 ops/s | 50509 ops/s |
forEach() |
9092.02 ops/s | 10095.89 ops/s | 3891.24 ops/s |
普通の for ループが最速で some() との差は 7.1 ~ 13.7 倍程度あります。
for...of ループは some() より速いものの for との間には 1.7 ~ 5.9 倍程度の差があります。
トランスパイル
前述の記事にも記載されていますが現代では書いた JavaScript をそのまま実行する場面は少なく、多くの場合はトランスパイルで変換されたコードを実行しています。
今回試したコードについてもどのようにトランスパイルされるのかを Babel および TypeScript のサイトで確認しましたがデフォルト設定では変換されませんでした。
前述の記事に記載されている for...of は ES2015 (ES6) で、最も新しい Array.prototype.includes() でも ES2016 (ES7) で追加された仕様ですので古いブラウザをサポートしない環境では不思議なことではないと思います。
まとめ
次のことが確認できました。
- 多くの場合は普通の
forループによるインデックスアクセスが最速 - 関数呼び出しは高コスト
- 引数に関数ではなく値を渡す場合は
forループとの差が小さくなる
- 引数に関数ではなく値を渡す場合は
-
forEach()は安定して遅い (特に Safari)
冒頭の繰り返しになりますが可読性やコード量を考えると速度がとても重要かつ高頻度で呼ばれる処理以外は Array が提供するメソッドで問題ないと思います。