web-dev-qa-db-ja.com

JavaScriptで配列を複製する最速の方法 - slice vs. 'for'ループ

JavaScriptで配列を複製するには、次のうちどれを使用するのが速いですか?

スライス法

var dup_array = original_array.slice();

Forループ

for(var i = 0, len = original_array.length; i < len; ++i)
   dup_array[i] = original_array[i];

両方の方法で 浅いコピーしかできないことを知っています :original_arrayにオブジェクトへの参照が含まれている場合、オブジェクトは複製されず、参照だけがコピーされます。しかし、これはこの質問のポイントではありません。

私はスピードについてだけ聞いています。

546
Marco Demaio

配列を複製するには、少なくとも 5 (!)の方法があります。

  • loop
  • slice
  • Array.from()
  • コンカット
  • スプレッド演算子(最速)

BENCHMARKSスレッド があり、以下の情報を提供しています。

  • blink browsersの場合slice()が最速の方法、concat()が少し遅く、そしてwhile loopが2.4倍遅くなります。

  • 他のブラウザではwhile loopが最速の方法です、なぜならそれらのブラウザはsliceconcatのための内部最適化を持っていないからです。

これは2016年7月にも当てはまります。

以下はブラウザのコンソールにコピー&ペーストして画像を見るために数回実行することができる簡単なスクリプトです。それらはミリ秒を出力します、低い方が良いです。

whileループ

n = 1000*1000;
start = + new Date();
a = Array(n); 
b = Array(n); 
i = a.length;
while(i--) b[i] = a[i];
console.log(new Date() - start);

スライス

n = 1000*1000;
start = + new Date();
a = Array(n); 
b = a.slice();
console.log(new Date() - start);

これらのメソッドはArrayオブジェクト自体を複製しますが、配列の内容は参照によってコピーされるため、詳細複製はされません。

origAr == clonedArr //returns false
origAr[0] == clonedArr[0] //returns true
706
Dan

技術的にはslice最速です。 しかし 0 beginインデックスを追加するとさらに速くなります。

myArray.slice(0);

より速いです

myArray.slice();

http://jsperf.com/cloning-arrays/3

202
KingKongFrog

eS6の方法はどうですか?

arr2 = [...arr1];
112
Yukulélé

ArrayまたはObjectをディープクローンする最も簡単な方法:

var dup_array = JSON.parse(JSON.stringify(original_array))
42
var cloned_array = [].concat(target_array);
26
Sajjad Shirazy

私は簡単なデモをまとめました: http://jsbin.com/agugo3/edit

Internet Explorer 8での結果は156、782、および750です。これは、この場合、sliceがはるかに速いことを示しています。

25
lincolnk

a.map(e => e)はこの仕事のもう一つの選択肢です。今日現在、.map()はFirefoxでは非常に高速です(ほぼ.slice(0)と同じくらい高速です)が、Chromeではそうではありません。

一方、配列が多次元の場合、配列はオブジェクトでオブジェクトは参照型なので、スライスメソッドや連結メソッドはどれも解決策にはなりません。したがって、配列を複製する適切な方法の1つはArray.prototype.clone()の発明です。次のように。

Array.prototype.clone = function(){
  return this.map(e => Array.isArray(e) ? e.clone() : e);
};

var arr = [ 1, 2, 3, 4, [ 1, 2, [ 1, 2, 3 ], 4 , 5], 6 ],
    brr = arr.clone();
brr[4][2][1] = "two";
console.log(JSON.stringify(arr));
console.log(JSON.stringify(brr));
19
Redu

クローンアレイへの最速の方法

私はそれが配列をクローンするのにかかる時間をテストするためにこの非常に明白な効用関数を作りました。これは100%信頼できるわけではありませんが、既存のアレイのクローンを作成するのにかかる時間については、大まかな考えを与えることができます。

function clone(fn) {
    const arr = [...Array(1000000)];
    console.time('timer');
    fn(arr);
    console.timeEnd('timer');
}

そして、さまざまなアプローチをテストしました。

1)   5.79ms -> clone(arr => Object.values(arr));
2)   7.23ms -> clone(arr => [].concat(arr));
3)   9.13ms -> clone(arr => arr.slice());
4)  24.04ms -> clone(arr => { const a = []; for (let val of arr) { a.Push(val); } return a; });
5)  30.02ms -> clone(arr => [...arr]);
6)  39.72ms -> clone(arr => JSON.parse(JSON.stringify(arr)));
7)  99.80ms -> clone(arr => arr.map(i => i));
8) 259.29ms -> clone(arr => Object.assign([], arr));
9) Maximum call stack size exceeded -> clone(arr => Array.of(...arr));

_更新_
注:これらすべてのうち、配列をディープクローンする唯一の方法はJSON.parse(JSON.stringify(arr))を使用することです。

そうは言っても、あなたの配列がnullを返すので関数に配列が含まれる可能性がある場合は上記を使わないでください。
このアップデートをありがとうございました

11
Lior Elrom

ブラウザによって異なります。ブログ記事Array.prototype.sliceと手動の配列作成を比較すると、それぞれのパフォーマンスに関する大まかなガイドがあります。

Enter image description here

結果:

Enter image description here

7
kyndigs

.slice()は二次元配列では動作しないことを忘れないでください。あなたはこのような機能が必要です。

function copy(array) {
  return array.map(function(arr) {
    return arr.slice();
  });
}
6
martinedwards

link を見てください。スピードではなく快適さです。ご覧のとおり、 slice(0) on プリミティブ型 しか使用できません。

配列の複製ではなく、配列の独立したコピーを作成するには、配列スライス方式を使用できます。

例:

配列の複製ではなく、配列の独立したコピーを作成するには、配列スライス方式を使用できます。

var oldArray = ["mip", "map", "mop"];
var newArray = oldArray.slice();

オブジェクトをコピーまたは複製するには:

function cloneObject(source) {
    for (i in source) {
        if (typeof source[i] == 'source') {
            this[i] = new cloneObject(source[i]);
        }
        else{
            this[i] = source[i];
  }
    }
}

var obj1= {bla:'blabla',foo:'foofoo',etc:'etc'};
var obj2= new cloneObject(obj1);

出典: リンク

6
Margus

もっときれいな解決策があります。

var srcArray = [1, 2, 3];
var clonedArray = srcArray.length === 1 ? [srcArray[0]] : Array.apply(this, srcArray);

Arrayコンストラクタは、引数を1つだけ指定して呼び出された場合とは動作が異なるため、長さチェックが必要です。

6
ciembor

@Danが "この答えは早く時代遅れになる。 benchmarks を使って実際の状況をチェックする"と言ったように、jsperfからの答えがない具体的な答えが1つあります: while

var i = a.length;
while(i--) { b[i] = a[i]; }

ランナーナップa.concat()が578,129 ops /秒で960,589 ops /秒、これは60%です。

これは最新のFirefox(40)64ビットです。


@aleclarsonは、より信頼性の高い新しいベンチマークを作成しました。

6
serv-inc

配列の長さによって異なります。配列の長さが1,000,000以下の場合、sliceメソッドとconcatメソッドはほぼ同じ時間がかかります。しかし、より広い範囲を指定すると、concatメソッドが勝ちます。

たとえば、次のコードを試してください。

var original_array = [];
for(var i = 0; i < 10000000; i ++) {
    original_array.Push( Math.floor(Math.random() * 1000000 + 1));
}

function a1() {
    var dup = [];
    var start = Date.now();
    dup = original_array.slice();
    var end = Date.now();
    console.log('slice method takes ' + (end - start) + ' ms');
}

function a2() {
    var dup = [];
    var start = Date.now();
    dup = original_array.concat([]);
    var end = Date.now();
    console.log('concat method takes ' + (end - start) + ' ms');
}

function a3() {
    var dup = [];
    var start = Date.now();
    for(var i = 0; i < original_array.length; i ++) {
        dup.Push(original_array[i]);
    }
    var end = Date.now();
    console.log('for loop with Push method takes ' + (end - start) + ' ms');
}

function a4() {
    var dup = [];
    var start = Date.now();
    for(var i = 0; i < original_array.length; i ++) {
        dup[i] = original_array[i];
    }
    var end = Date.now();
    console.log('for loop with = method takes ' + (end - start) + ' ms');
}

function a5() {
    var dup = new Array(original_array.length)
    var start = Date.now();
    for(var i = 0; i < original_array.length; i ++) {
        dup.Push(original_array[i]);
    }
    var end = Date.now();
    console.log('for loop with = method and array constructor takes ' + (end - start) + ' ms');
}

a1();
a2();
a3();
a4();
a5();

Original_arrayの長さを1,000,000に設定した場合、sliceメソッドとconcatメソッドはほぼ同じ時間(乱数に応じて3〜4ミリ秒)かかります。

Original_arrayの長さを10,000,000に設定した場合、sliceメソッドは60ミリ秒以上かかり、concatメソッドは20ミリ秒以上かかります。

5
Gor

Spread演算子を使用したECMAScript 2015の方法:

基本的な例:

var copyOfOldArray = [...oldArray]
var twoArraysBecomeOne = [...firstArray, ..seccondArray]

ブラウザコンソールで試してください。

var oldArray = [1, 2, 3]
var copyOfOldArray = [...oldArray]
console.log(oldArray)
console.log(copyOfOldArray)

var firstArray = [5, 6, 7]
var seccondArray = ["a", "b", "c"]
var twoArraysBecomOne = [...firstArray, ...seccondArray]
console.log(twoArraysBecomOne);

参考文献

4
Marian07

簡単な解決策:

original = [1,2,3]
cloned = original.map(x=>x)
3
Caio Santos
        const arr = ['1', '2', '3'];

         // Old way
        const cloneArr = arr.slice();

        // ES6 way
        const cloneArrES6 = [...arr];

// But problem with 3rd approach is that if you are using muti-dimensional 
 // array, then only first level is copied

        const nums = [
              [1, 2], 
              [10],
         ];

        const cloneNums = [...nums];

// Let's change the first item in the first nested item in our cloned array.

        cloneNums[0][0] = '8';

        console.log(cloneNums);
           // [ [ '8', 2 ], [ 10 ], [ 300 ] ]

        // NOOooo, the original is also affected
        console.log(nums);
          // [ [ '8', 2 ], [ 10 ], [ 300 ] ]

それで、これらのシナリオが起こるのを避けるためには、

        const arr = ['1', '2', '3'];

        const cloneArr = Array.from(arr);
1
Anki

ベンチマーク時間!

function log(data) {
  document.getElementById("log").textContent += data + "\n";
}

benchmark = (() => {
  time_function = function(ms, f, num) {
    var z = 0;
    var t = new Date().getTime();
    for (z = 0;
      ((new Date().getTime() - t) < ms); z++)
      f(num);
    return (z)
  }

  function clone1(arr) {
    return arr.slice(0);
  }

  function clone2(arr) {
    return [...arr]
  }

  function clone3(arr) {
    return [].concat(arr);
  }

  Array.prototype.clone = function() {
    return this.map(e => Array.isArray(e) ? e.clone() : e);
  };

  function clone4(arr) {
    return arr.clone();
  }


  function benchmark() {
    function compare(a, b) {
      if (a[1] > b[1]) {
        return -1;
      }
      if (a[1] < b[1]) {
        return 1;
      }
      return 0;
    }

    funcs = [clone1, clone2, clone3, clone4];
    results = [];
    funcs.forEach((ff) => {
      console.log("Benchmarking: " + ff.name);
      var s = time_function(2500, ff, Array(1024));
      results.Push([ff, s]);
      console.log("Score: " + s);

    })
    return results.sort(compare);
  }
  return benchmark;
})()
log("Starting benchmark...\n");
res = benchmark();

console.log("Winner: " + res[0][0].name + " !!!");
count = 1;
res.forEach((r) => {
  log((count++) + ". " + r[0].name + " score: " + Math.floor(10000 * r[1] / res[0][1]) / 100 + ((count == 2) ? "% *winner*" : "% speed of winner.") + " (" + Math.round(r[1] * 100) / 100 + ")");
});
log("\nWinner code:\n");
log(res[0][0].toString());
<textarea rows="50" cols="80" style="font-size: 16; resize:none; border: none;" id="log"></textarea>

ボタンをクリックすると、ベンチマークは10秒間実行されます。

私の結果:

Chrome(V8エンジン):

1. clone1 score: 100% *winner* (4110764)
2. clone3 score: 74.32% speed of winner. (3055225)
3. clone2 score: 30.75% speed of winner. (1264182)
4. clone4 score: 21.96% speed of winner. (902929)

Firefox(SpiderMonkeyエンジン):

1. clone1 score: 100% *winner* (8448353)
2. clone3 score: 16.44% speed of winner. (1389241)
3. clone4 score: 5.69% speed of winner. (481162)
4. clone2 score: 2.27% speed of winner. (192433)

勝者コード:

function clone1(arr) {
    return arr.slice(0);
}

勝者エンジン:

SpiderMonkey(Mozilla/Firefox)

0
Zibri