JavaScriptで配列を複製する最速の方法-スライス対「for」ループ


634

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にオブジェクトへの参照が含まれている場合、オブジェクトは複製されませんが、参照のみがコピーされるため、両方の配列は同じオブジェクトへの参照を持ちます。しかし、これはこの質問の要点ではありません。

私は速度についてのみ尋ねています。


3
jsben.ch/#/wQ9RU <=アレイのクローンを作成する最も一般的な方法のベンチマーク
EscapeNetscape 2016年

回答:


776

アレイのクローンを作成するには、少なくとも5つの方法(!)があります。

  • ループ
  • スライス
  • Array.from()
  • 連結
  • スプレッドオペレーター(最速)

以下の情報を提供する、巨大なBENCHMARKSスレッドがありました。

  • 以下のために点滅ブラウザslice()最速の方法ですが、concat()少し遅く、while loop遅く2.4倍です。

  • 他のブラウザのためにwhile loopこれらのブラウザがための内部最適化を持っていないので、最速の方法であるsliceconcat

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

以下は、ブラウザのコンソールにコピーして貼り付け、数回実行して画像を表示できる簡単なスクリプトです。それらはミリ秒を出力し、低いほど良いです。

ループ

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

48
@ cept0感情なし、ベンチマークjsperf.com/new-array-vs-splice-vs-slice/31
Dan

2
@ダンだから何?テストケースの結果:毎晩のFirefox 30は、Chromeよりも約230%高速です。V8のソースコードを確認すると、splice驚かれるでしょう(その間...)
mate64

4
残念ながら、短い配列の場合、答えは大きく異なります。たとえば、各リスナーを呼び出す前にリスナーの配列を複製します。これらの配列は小さいことが多く、通常は1要素です。
gman 2015

6
このメソッドを見逃しました:A.map(function(e){return e;});
wcochran

13
あなたは瞬くブラウザについて書いています点滅はレイアウトエンジンだけでなく、主にHTMLレンダリングに影響を与えるため、重要ではありませんか?ここではV8、スパイダーモンキー、そして友達について話したいと思いました。私を混乱させただけのこと。私が間違っているなら、私を啓発してください。
Neonit

242

技術的にslice 最速の方法です。ただし0開始インデックスを追加すると、さらに高速になります。

myArray.slice(0);

より速い

myArray.slice();

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


そしてmyArray.slice(0,myArray.length-1);より速いですmyArray.slice(0);か?
jave.web

1
@ jave.web you; ve配列の最後の要素を削除しました。完全なコピーはarray.slice(0)またはarray.slice(0、array.length)
Marek Marczak

137

es6方法はどうですか?

arr2 = [...arr1];

23
バベルで変換した場合:[].concat(_slice.call(arguments))
CHAN

1
どこargumentsから来ているのかわからない...私はあなたのバベル出力がいくつかの異なる機能を融合していると思います。可能性が高いarr2 = [].concat(arr1)です。
スターリングアーチャー

3
@SterlingArcherはとarr2 = [].conact(arr1)は異なりarr2 = [...arr1]ます。[...arr1]構文は穴をに変換しundefinedます。たとえば、arr1 = Array(1); arr2 = [...arr1]; arr3 = [].concat(arr1); 0 in arr2 !== 0 in arr3
tsh 2016

1
私はこれを私のブラウザ(Chrome 59.0.3071.115)で上記のDanの答えに対してテストしました。.slice()よりも10倍以上遅い。n = 1000*1000; start = + new Date(); a = Array(n); b = [...a]; console.log(new Date() - start); // 168
ハリースティーブンス

1
ただし、次のようなものは複製されません[{a: 'a', b: {c: 'c'}}]c「複製された」配列での値が変更された場合、それは元の配列で変更されます。これは単なる複製ではなく、単なる参照コピーだからです。
神経伝達物質

44

配列またはオブジェクトをディープクローンする最も簡単な方法:

var dup_array = JSON.parse(JSON.stringify(original_array))

56
初心者のための重要な注意:これはJSONに依存するため、この制限も継承します。とりわけ、それはあなたの配列が含まれていないことを意味しundefined、または任意functionの。これらの両方はnullJSON.stringifyプロセス中に変換されます。などのその他の戦略では、(['cool','array']).slice()それらは変更されませんが、配列内のオブジェクトのディープクローンは行われません。したがって、トレードオフがあります。
Seth Holladay

27
パフォーマンスが非常に悪く、DOM、日付、正規表現、関数などの特別なオブジェクトやプロトタイプオブジェクトでは機能しません。循環参照をサポートしません。ディープクローンにはJSONを使用しないでください。
ユクレレ

17
最悪の方法!他のすべての問題が機能しない場合にのみ使用してください。遅いし、リソースも多くて、コメントですでに述べたJSONの制限はすべてある。それが25票を獲得した方法を想像することはできません。
Lukas Liesis

2
プリミティブを含む配列をディープコピーします。プロパティは、追加のプリミティブ/配列を含む配列です。そのためそれは大丈夫です。
Drenai

4
私はこれを私のブラウザ(Chrome 59.0.3071.115)で上記のDanの答えに対してテストしました。.slice()の約20倍の速度でした。n = 1000*1000; start = + new Date(); a = Array(n); var b = JSON.parse(JSON.stringify(a)) console.log(new Date() - start); // 221
ハリースティーブンス

29
var cloned_array = [].concat(target_array);

3
これが何をするか説明してください。
ジェドフォックス

8
このコードスニペットは質問に答えることがありますが、その方法や理由を説明するコンテキストは提供していません。答えを説明するために、1〜2文を追加することを検討してください。
ブランドンスクリプト、2016年

32
この種のコメントは嫌いです。それが何をするかは明らかです!
EscapeNetscape 2016年

6
単純な質問に対する単純な答えであり、読むべき大きな話はありません。私はこの種の答えが好きです+1
Achim

15
「私は速度についてだけ尋ねている」-この答えは速度について何の示唆も与えない。それが尋ねられる主な質問です。brandonscriptには良い点があります。これを回答と見なすには、さらに情報が必要です。しかし、それがより簡単な質問であれば、これは優れた答えになるでしょう。
TamusJRoyce 2017年

26

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

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


これを非常に高速に行う必要がある場合は、ガベージコレクタの追加コストを忘れないでください。セルオートマトンの各セルの各隣接配列をスライスを使用してコピーしていましたが、以前の配列を再利用して値をコピーするよりもはるかに低速でした。Chromeは、総時間の約40%がガベージコレクションに費やされたことを示しています。
drake7707 2013年

21

a.map(e => e)この仕事の別の選択肢です。今日の時点で.map()非常に高速に(ほぼ同じ速さで.slice(0)Firefoxではなく、Chromeで)。

一方、アレイは、アレイは、オブジェクトとオブジェクトが参照型であるため、スライスまたは連結方法のいずれも治癒しないであろう、多次元である場合...だから配列をクローニングするの適切な方法は、本発明である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));


悪くはありませんが、残念ながら、配列にObjectがある場合、これは機能しません:\ JSON.parse(JSON.stringify(myArray))は、この場合にうまく機能します。
GBMan

17

Arrayアレイのクローンを作成する最速の方法

この非常にわかりやすいユーティリティ関数を作成して、アレイのクローン作成にかかる時間をテストしました。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
この更新をありがとう@GilEpshtain


2
私はあなたの答えをベンチマークしてみましたが、私は非常に異なる結果を得ました:jsben.ch/o5nLG
mesqueeb

@mesqueeb、もちろん、マシンによってはテストが変わるかもしれません。ただし、テスト結果で回答を自由に更新してください。よくやった!
Lior Elrom

私はあなたの答えがとても好きですが、私はあなたのテストを試してみて、それarr => arr.slice()が最速だと思います。
Gil Epshtain

1
@LiorElrom、メソッドはシリアル化できないため、更新は正しくありません。たとえば、次のJSON.parse(JSON.stringify([function(){}]))ように出力されます[null]
Gil Epshtain

1
素晴らしいベンチマーク。私は2つのブラウザでは私のMac上でこれをテストしてみた:クロームバージョン81.0.4044.113とSafariのバージョン13.1(15609.1.20.111.8)と最速の拡散操作は次のとおりです。[...arr]4.653076171875msChromeのと8.565msサファリインチ Chromeの第二の高速は、スライス機能であるarr.slice()6.162109375msし、Safariで秒である[].concat(arr)13.018ms
edufinn

7

見てください:リンク。スピードではなく快適さです。ご覧のとおりプリミティブ型でのみslice(0)を使用できます

参照のコピーではなく、配列の独立したコピーを作成するには、配列スライスメソッドを使用できます。

例:

参照のコピーではなく、配列の独立したコピーを作成するには、配列スライスメソッドを使用できます。

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);

ソース:リンク


1
プリミティブ型のコメントは以下の製品に適用さforだけでなく、問題のループ。
user113716 2010

4
オブジェクトの配列をコピーする場合、オブジェクトを複製するのではなく、新しい配列が同じオブジェクトを参照することを期待します。
リンカーン

7

@Danが「この回答はすぐに古くなってしまいます。実際の状況を確認するにはベンチマークを使用してください」と述べたように、jsperf からの特定の回答が1つあります。

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

960,589オペレーション/秒で、次点a.concat()は578,129オペレーション/秒でした。これは60%です。

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


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


1
実際にはjsperfをリンクする必要があります。「while loop」テストを除いて、すべてのテストケースで新しい配列が作成されるため、考えているものは壊れています。
aleclarson

1
より正確な新しいjsperfを作成しました:jsperf.com/clone-array-3
aleclarson

60%何?60%高速ですか?
Peter Mortensen、

1
@PeterMortensen:587192は960589の〜60%(61.1 ...)です。–
serv-inc

7

ECMAScript 2015の方法 Spreadオペレーター:

基本的な例:

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);

参考文献


おそらく、スプレッドが速いのはタイプすることだけです。他の方法よりもパフォーマンスが低下します。
XT_Nova

3
あなたの議論についていくつかのリンクを提供してください。
Marian07

6

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

ここに画像の説明を入力してください

結果:

ここに画像の説明を入力してください


1
argumentsは適切な配列ではなく、コレクションcallを強制的sliceに実行するために使用しています。結果は誤解を招く可能性があります。
リンカーン

ええ、私は私の記事で、これらの統計はおそらくブロウザーが改善されると変化するであろうことを言及するつもりでしたが、それは一般的な考えを与えます。
kyndigs

2
@diugalde私は、コードを画像として投稿することが受け入れられる唯一の状況は、コードが潜在的に危険であり、コピー貼り付けされるべきではない場合だと思います。しかしこの場合、それはかなりばかげています。
フロリアンウェンデルボーン2016年

6

よりクリーンな解決策があります:

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

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


2
しかし、それは最速ですか?
Chris Wesseling 14

14
splice()おそらくより意味論的です。しかし、実際に適用すると、これはほとんど直感的ではありません。
Michael Piefel 2014年


3
あなたは使用することができますArray.ofし、長さを無視:Array.of.apply(Array, array)
オリオールを

6

.slice()は2次元配列では機能しないことに注意してください。次のような関数が必要です。

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

3
JavaScriptでは、2次元配列はありません。配列を含む配列があります。あなたがしようとしているのは、質問では必要のないディープコピーです。
Aloso

5

配列の長さに依存します。配列の長さが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ミリ秒以上かかります。


dup.pushでは間違っていますがa5、代わりdup[i] = に使用する必要があります
4esn0k


2
        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);

cloneNums[0][0]例での変更が変更をどのように伝播したかについて指摘することは有効です。nums[0][0]しかし、これnums[0][0]は、事実上cloneNums、spreadオペレーターによって参照がコピーされるオブジェクトだからです。つまり、この動作は、値(int、stringなどのリテラル)でコピーしているコードには影響しません。
Aditya MP

1

ベンチマークタイム!

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)


1

JavaScriptで配列を順序どおりに複製する高速な方法:

#1: array1copy = [...array1];

#2: array1copy = array1.slice(0);

#3: array1copy = array1.slice();

配列オブジェクトにJSONでシリアル化できないコンテンツ(関数、Number.POSITIVE_INFINITYなど)が含まれている場合は、

array1copy = JSON.parse(JSON.stringify(array1))


0

このコードに従うことができます。不変の方法配列クローン。これはアレイのクローニングに最適な方法です


const array = [1, 2, 3, 4]

const newArray = [...array]
newArray.push(6)
console.log(array)
console.log(newArray)

0

ES6では、単にSpread構文を利用できます

例:

let arr = ['a', 'b', 'c'];
let arr2 = [...arr];

スプレッド演算子は完全に新しい配列を生成するため、一方を変更しても他方には影響しないことに注意してください。

例:

arr2.push('d') // becomes ['a', 'b', 'c', 'd']
console.log(arr) // while arr retains its values ['a', 'b', 'c']
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.