背景
Array.fromとSpread構文は、配列やオブジェクトを新しい配列へ変換する際によく使用される機能である。
かつてはInternet ExplorerがArray.fromやSpread構文をサポートしておらず、Babelなどでトランスパイルが必要だった。しかし、モダンブラウザはこれらをネイティブでサポートしており、実装方法を簡略化できる。
これらの機能は使用場面が重複することも多く、どちらを選択すべきか判断が難しい場合もある。まずはパフォーマンスの観点から両者を比較することで、データ量や処理内容に応じた使い分けの基準を得ることができる。
Array.from とは
Array.from()は、以下のものからArrayを生成します。
- 反復可能オブジェクト(
MapやSetのような要素を取得するオブジェクト)- オブジェクトが反復可能でない場合は、配列風オブジェクト(
lengthプロパティおよび添字の付いた要素を持つオブジェクト)
コード例
// 1. 文字列から配列を作成
const str = 'hello';
Array.from(str); // ['h', 'e', 'l', 'l', 'o']
// 2. DOM NodeListを配列に変換
const divs = document.querySelectorAll('div');
const divsArray = Array.from(divs); // DOMノードの配列に変換
// 3. 連番の数値配列を生成
Array.from({ length: 5 }, (_, i) => i + 1); // [1, 2, 3, 4, 5]
// 4. Mapオブジェクトのキーまたは値を配列に変換
const map = new Map([
['a', 1],
['b', 2],
['c', 3],
]);
Array.from(map.keys()); // ['a', 'b', 'c']
Array.from(map.values()); // [1, 2, 3]
// 5. 関数の引数(arguments)を配列に変換
function example() {
return Array.from(arguments);
}
example(1, 2, 3); // [1, 2, 3]
// 6. 重複を除去しつつ配列化
Array.from(new Set([1, 2, 2, 3, 3, 4])); // [1, 2, 3, 4]Spread構文とは
スプレッド構文は、オブジェクトまたは配列の要素をすべて新しい配列またはオブジェクトに含める必要がある場合、または関数呼び出しの引数リストに1つずつ適用する必要がある場合に使用することができます。
コード例
// 1. 配列の結合
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
// 2. 配列の途中に要素を挿入
const numbers = [1, 2, 5, 6];
const withInserted = [1, 2, ...['3', '4'], 5, 6]; // [1, 2, '3', '4', 5, 6]
// 3. 関数の引数として展開
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
sum(...numbers); // 6
// 4. 文字列を文字の配列に分割
const chars = [...'hello']; // ['h', 'e', 'l', 'l', 'o']
// 5. オブジェクトのプロパティを展開
const defaults = { theme: 'dark', lang: 'en' };
const userConfig = { lang: 'ja' };
const merged = { ...defaults, ...userConfig }; // { theme: 'dark', lang: 'ja' }
// 6. 配列の複製と要素の追加
const original = [1, 2, 3];
const copy = [...original, 4]; // [1, 2, 3, 4]
// 7. Rest parametersとの組み合わせ
function example(first, ...rest) {
return [first, rest];
}
const numbers = [1, 2, 3, 4];
example(...numbers); // [1, [2, 3, 4]]ベンチマーク
以下のシーンでそれぞれのパフォーマンスを計測する。
- 大量の配列
- イミュータブルなオブジェクト
- マッピング(
.map())
Array.fromとスプレッド構文は、いずれも配列の要素数(n)に比例して処理時間が増加する O(n) の計算量を持つ。この性質を実証するため、データ量は100万件のデータを用いて実行速度を測定する。
計測はMeasureThat.netを利用した。
大量の配列
// 配列を作成
const largeArray = [];
for (let i = 0; i < 1_000_000; i++) {
largeArray.push(i);
}
// => [0, 1, 2, 3, 4, ..., 999999]Array.from
const arrayFrom = Array.from(largeArray);Spread
const arraySpread = [...largeArray];ベンチマーク結果
| Array.from | Spread | |
|---|---|---|
| Chrome 131 | 4550.9 Ops/sec | 4561.1 Ops/sec |
| Firefox 133 | 213.2 Ops/sec | 231.2 Ops/sec |
| Safari 18 | 248.6 Ops/sec | 1315.9 Ops/sec |
ChromeとFirefoxではそれぞれのパフォーマンスがほぼ同等である一方で、SafariではSpread構文の方が良い結果となった。
イミュータブルなオブジェクト
// Setオブジェクトを作成
const largeSet = new Set();
for (let i = 0; i < 1_000_000; i++) {
largeSet.add(i);
}
// => Set {0, 1, 2, 3, 4, ..., 999999}Array.from
const arrayFromSet = Array.from(largeSet);Spread
const arraySpreadSet = [...largeSet];ベンチマーク結果
| Array.from | Spread | |
|---|---|---|
| Chrome 131 | 826.5 Ops/sec | 819.7 Ops/sec |
| Firefox 133 | 179.2 Ops/sec | 92.7 Ops/sec |
| Safari 18 | 147.8 Ops/sec | 138.6 Ops/sec |
Chromeの結果では、Array.from と Spread構文のパフォーマンスにほとんど差が見られなかった。いくつかの測定で結果のばらつきが見られたものの、両者の性能はほぼ同等と考えられる。
マッピング
// 配列を作成
const largeArray = [];
for (let i = 0; i < 1_000_000; i++) {
largeArray.push(i);
}
// => [0, 1, 2, 3, 4, ..., 999999]Array.from
const arrFromMapped = Array.from(largeArray, (x) => x * 2);Spread
const arrSpreadMapped = [...largeArray].map((x) => x * 2);ベンチマーク結果
| Array.from | Spread | |
|---|---|---|
| Chrome 131 | 59.0 Ops/sec | 147.8 Ops/sec |
| Firefox 133 | 154.3 Ops/sec | 108.6 Ops/sec |
| Safari 18 | 80.7 Ops/sec | 400.6 Ops/sec |
Spread構文と.mapの組み合わせは配列を2回走査する必要があるため、1回の走査で完了するArray.fromの方が理論上は高速になる。しかし、Safariでの測定結果は予想に反しており、.mapメソッドが内部的に最適化されている可能性がある。ただし、ベンチマークの実施方法やサンプルデータの特性による影響も考慮すべきであり、これが一貫した性能向上を意味するかは別途検証が必要である。
