非破壊的な関数は遅い
原則として「非破壊的な関数」は「破壊的な関数」よりも遅いです。
なぜなら「既存のデータ(配列)をコピーして新しく生成する処理」が余計にかかっているからです。
a.concat(b)
ならば「a
をコピーして新しく生成する処理」が push
の処理に加えてあるので、concat
はどうやっても push
よりも遅いのです。
同じ理由で下記関数は非破壊的なので「遅い」です(思いつく範囲でとりあげただけなので他にもあるかもしれません)。
- Array.prototype.slice
- Array.prototype.concat
- Array.prototype.map
- Array.prototype.filter
- Array.prototype.entries
- Array.from
同じ理由で jQuery の関数はそのほとんどが「遅い」です。
- jQuery(), $()
- jQuery#filter
- jQuery#slice
- jQuery#toArray
- jQuery#map
一般的に jQuery を使えば、新しいオブジェクトを生成して処理をするようコーディングしますが、もともとのオブジェクトをそのまま処理をする方が速い事は明白です。
破壊的/非破壊的
速度だけにとらわれると大事なことを見失います。
JavaScript
1var a = ['a', 'b', 'c'],
2 b = ['d', 'e', 'f'];
3
4console.log(a.concat(b)); // ["a", "b", "c", "d", "e", "f"]
5Array.prototype.push.apply(a, b);
6console.log(a); // ["a", "b", "c", "d", "e", "f"]
一見、同じ結果に見えますが、Array.prototype.push
は破壊的なのでコードの順番を変えると意図しない動きとなります。
JavaScript
1var a = ['a', 'b', 'c'],
2 b = ['d', 'e', 'f'];
3
4Array.prototype.push.apply(a, b);
5console.log(a); // ["a", "b", "c", "d", "e", "f"]
6console.log(a.concat(b)); // ["a", "b", "c", "d", "e", "f", "d", "e", "f"]
重要なのは「変数 a に破壊的な処理をしても問題ないのか」という観点です。
筆者は「push
を使うべき」というスタンスでしたので、この場合は破壊的な処理で問題なかったのでしょう。
しかし、「破壊的では困るケース」というものも存在するはずで、その場合に concat
は有用です。
Array.prototype.fill
Array.prototype.fill
を使うことでループ処理そのものをなくす事が出来ます。
ただし、数があまりに多いとコールスタックを使い切ってしまうようでループ回数を 90000 回にしたら、「RangeError: Maximum call stack size exceeded」の例外で実行できませんでした。
JavaScript
1'use strict';
2var a = ['a', 'b', 'c'],
3 b = ['d', 'e', 'f'];
4var start = new Date();
5Array.prototype.push.apply(a, Array.prototype.concat.apply(b, Array(79999).fill(b)))
6var end = new Date();
7console.log(end.getTime()-start.getTime());
8console.log(a.length);
9
10var a = ['a', 'b', 'c'],
11 b = ['d', 'e', 'f'];
12var start = new Date();
13var push = Array.prototype.push; // 何度も呼び出すプロパティは変数にキャッシュしておく
14for (var i = 0; i < 80000; i++){
15 push.apply(a, b);
16}
17var end = new Date();
18console.log(end.getTime() - start.getTime());
19console.log(a.length);
何度か試すと双方どちらとも一方を上回る場合もありますが、全体的には push
に軍配が上がりそうです。
しかし、この場合は「配列を生成する必要があるのか」を設計段階で考えておく必要があります。
なぜなら、この配列は「index が 3 の倍数なら "d" を返す配列」なので計算方法さえ確立すれば配列を生成する必要がなくなるからです。
無駄にコードを書かない為にも、コードを書き始めるために設計指針を決めておく事が極めて重要です。
Re: answer さん