Cara tercepat untuk menduplikasi array dalam JavaScript - slice vs. 'for' loop


634

Untuk menduplikasi array dalam JavaScript: manakah dari yang berikut ini yang lebih cepat digunakan?

Metode irisan

var dup_array = original_array.slice();

For lingkaran

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

Saya tahu kedua cara hanya melakukan salinan dangkal : jika original_array berisi referensi ke objek, objek tidak akan dikloning, tetapi hanya referensi yang akan disalin, dan oleh karena itu kedua array akan memiliki referensi ke objek yang sama. Tapi ini bukan inti dari pertanyaan ini.

Saya hanya bertanya tentang kecepatan.


3
jsben.ch/#/wQ9RU <= patokan untuk cara paling umum untuk mengkloning array
EscapeNetscape

Jawaban:


776

Setidaknya ada 5 (!) Cara untuk mengkloning array:

  • lingkaran
  • mengiris
  • Array.dari ()
  • concat
  • operator penyebaran (TERCEPAT)

Telah ada utas BENCHMARKS , menyediakan informasi berikut:

  • untuk blink browser slice()adalah metode tercepat, concat()sedikit lebih lambat, dan while loop2.4x lebih lambat.

  • untuk browser lain while loopadalah metode tercepat, karena browser tersebut tidak memiliki optimasi internal untuk slicedan concat.

Ini tetap berlaku di Jul 2016.

Di bawah ini adalah skrip sederhana yang dapat Anda salin-tempel ke konsol browser Anda dan jalankan beberapa kali untuk melihat gambar. Mereka menghasilkan milidetik, lebih rendah lebih baik.

sementara lingkaran

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

mengiris

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

Harap dicatat bahwa metode ini akan mengkloning objek Array itu sendiri, namun isi array disalin dengan referensi dan tidak dikloning dalam.

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

48
@ cept0 tidak ada emosi, hanya tolok ukur jsperf.com/new-array-vs-splice-vs-slice/31
Dan

2
@ Dan lalu apa? Hasil test case Anda: Firefox 30 nightly masih ~ 230% lebih cepat dari Chrome. Periksa kode sumber V8 splicedan Anda akan terkejut (sementara ...)
mate64

4
Sayangnya untuk array pendek jawabannya sangat berbeda . Misalnya mengkloning array pendengar sebelum memanggil mereka masing-masing. Array tersebut seringkali kecil, biasanya 1 elemen.
gman

6
Anda melewatkan metode ini:A.map(function(e){return e;});
wcochran

13
Anda sedang menulis tentang peramban yang berkedip . Bukankah berkedip hanya mesin tata letak, terutama mempengaruhi rendering HTML, dan karenanya tidak penting? Saya pikir kami lebih suka berbicara tentang V8, Spidermonkey dan teman-teman di sini. Hanya hal yang membingungkan saya. Cerahkan saya, jika saya salah.
Neonit

242

Secara teknis slice adalah cara tercepat. Namun , bahkan lebih cepat jika Anda menambahkan 0indeks mulai.

myArray.slice(0);

lebih cepat dari

myArray.slice();

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


Dan apakah myArray.slice(0,myArray.length-1);lebih cepat dari itu myArray.slice(0);?
jave.web

1
@ jave.web Anda; Anda baru saja menjatuhkan elemen terakhir dari array. Salinan penuh adalah array.slice (0) atau array.slice (0,
array.length

137

bagaimana dengan es6 way?

arr2 = [...arr1];

23
jika dikonversi dengan babel:[].concat(_slice.call(arguments))
CHAN

1
Tidak yakin dari mana argumentsdatangnya ... Saya pikir output babel Anda menggabungkan beberapa fitur yang berbeda. Itu lebih mungkin terjadi arr2 = [].concat(arr1).
Sterling Archer

3
@SterlingArcher arr2 = [].conact(arr1)berbeda dari arr2 = [...arr1]. [...arr1]sintaks akan mengonversi lubang menjadi undefined. Sebagai contoh arr1 = Array(1); arr2 = [...arr1]; arr3 = [].concat(arr1); 0 in arr2 !== 0 in arr3,.
tsh

1
Saya menguji ini di browser saya (Chrome 59.0.3071.115) terhadap jawaban Dan di atas. Itu lebih dari 10 kali lebih lambat dari .slice (). n = 1000*1000; start = + new Date(); a = Array(n); b = [...a]; console.log(new Date() - start); // 168
Harry Stevens

1
Masih tidak akan clone sesuatu seperti ini: [{a: 'a', b: {c: 'c'}}]. Jika cnilai diubah dalam array "duplikat", itu akan berubah dalam array asli, karena itu hanya salinan referensial, bukan klon.
Neurotransmitter

44

Cara termudah untuk mengkloning dalam Array atau Objek:

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

56
Catatan penting untuk pemula: karena ini tergantung pada JSON, ini juga mewarisi keterbatasannya. Di antara hal-hal lain, itu berarti array Anda tidak dapat berisi undefinedatau apa pun function. Keduanya akan dikonversi nulluntuk Anda selama JSON.stringifyproses berlangsung. Strategi lain, seperti (['cool','array']).slice()tidak akan mengubahnya tetapi juga tidak menduplikasi objek dalam array. Jadi ada tradeoff.
Seth Holladay

27
Perf sangat buruk dan tidak bekerja dengan objek khusus seperti DOM, tanggal, regexp, fungsi ... atau objek prototipe. Jangan mendukung referensi siklik. Anda seharusnya tidak pernah menggunakan JSON untuk klon dalam.
Yukulélé

17
cara terburuk! Hanya gunakan jika untuk beberapa masalah yang lainnya tidak berfungsi. Ini lambat, sumber dayanya sangat intensif dan memiliki semua batasan JSON yang telah disebutkan dalam komentar. Tidak bisa membayangkan bagaimana itu mendapat 25 suara.
Lukas Liesis

2
Ini menyalin array dengan primitif, dan di mana properti array dengan primitif / array lebih lanjut. Untuk itu tidak apa-apa.
Drenai

4
Saya menguji ini di browser saya (Chrome 59.0.3071.115) terhadap jawaban Dan di atas. Itu hampir 20 kali lebih lambat dari .slice (). n = 1000*1000; start = + new Date(); a = Array(n); var b = JSON.parse(JSON.stringify(a)) console.log(new Date() - start); // 221
Harry Stevens

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

3
Tolong jelaskan apa yang dilakukannya.
Jed Fox

8
Meskipun potongan kode ini dapat menjawab pertanyaan, namun tidak memberikan konteks apa pun untuk menjelaskan bagaimana atau mengapa. Pertimbangkan untuk menambahkan satu atau dua kalimat untuk menjelaskan jawaban Anda.
naskah merek

32
Saya benci komentar semacam ini. Sudah jelas apa fungsinya!
EscapeNetscape

6
Jawaban sederhana untuk pertanyaan sederhana, tidak ada berita besar untuk dibaca. Saya suka jawaban seperti ini +1
Achim

15
"Saya hanya bertanya tentang kecepatan" - Jawaban ini tidak menunjukkan kecepatan. Itulah pertanyaan utama yang diajukan. naskah merek memiliki poin yang bagus. Diperlukan lebih banyak informasi untuk menganggap ini sebagai jawaban. Tetapi jika itu adalah pertanyaan yang lebih sederhana, ini akan menjadi jawaban yang sangat baik.
TamusJRoyce

26

Saya mengumpulkan demo cepat: http://jsbin.com/agugo3/edit

Hasil saya di Internet Explorer 8 adalah 156, 782, dan 750, yang akan menunjukkan slicejauh lebih cepat dalam hal ini.


Jangan lupa biaya tambahan dari pengumpul sampah jika Anda harus melakukan ini dengan sangat cepat. Saya menyalin setiap array tetangga untuk setiap sel di automata seluler saya menggunakan slice dan itu jauh lebih lambat daripada menggunakan kembali array sebelumnya dan menyalin nilainya. Chrome menunjukkan sekitar 40% dari total waktu yang dihabiskan untuk mengumpulkan sampah.
drake7707

21

a.map(e => e)adalah alternatif lain untuk pekerjaan ini. Sampai hari .map()ini sangat cepat (hampir secepat.slice(0) ) di Firefox, tetapi tidak di Chrome.

Di sisi lain, jika array multi-dimensi, karena array adalah objek dan objek referensi jenis, tidak ada potongan atau concat metode akan menyembuhkan ... Jadi salah satu cara yang tepat kloning array adalah penemuan Array.prototype.clone()sebagai mengikuti.

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


Tidak buruk, tapi sayangnya ini tidak berfungsi jika Anda memiliki Object di array Anda: \ JSON.parse (JSON.stringify (myArray)) berfungsi lebih baik dalam kasus ini.
GBMan

17

🏁 Cara Tercepat untuk Mengkloning Array

Saya membuat fungsi utilitas yang sangat sederhana ini untuk menguji waktu yang diperlukan untuk mengkloning array. Ini tidak 100% dapat diandalkan namun dapat memberi Anda ide massal tentang berapa lama yang dibutuhkan untuk mengkloning array yang ada:

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

Dan menguji pendekatan yang berbeda:

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

UPDATE :
Catatan: dari semuanya, satu-satunya cara untuk mengkloning array adalah dengan menggunakan JSON.parse(JSON.stringify(arr)).

Yang mengatakan, jangan gunakan di atas jika array Anda mungkin menyertakan fungsi karena akan kembali null.
Terima kasih @GilEpshtain untuk pembaruan ini .


2
Saya mencoba membuat tolok ukur jawaban Anda dan saya mendapat hasil yang sangat berbeda: jsben.ch/o5nLG
mesqueeb

@mesqueeb, tes mungkin berubah, tergantung pada mesin Anda tentu saja. Namun, jangan ragu untuk memperbarui jawaban dengan hasil tes Anda. Kerja bagus!
Lior Elrom

Saya sangat menyukai jawaban Anda, namun saya mencoba tes Anda dan mendapatkan yang arr => arr.slice()tercepat.
Gil Epshtain

1
@LiorElrom, pembaruan Anda tidak benar, karena fakta bahwa metode tidak serial. Misalnya: JSON.parse(JSON.stringify([function(){}]))akan menampilkan[null]
Gil Epshtain

1
Tolok ukur yang bagus. Saya telah menguji ini di Mac saya di 2 browser: Chrome Versi 81.0.4044.113 dan Safari Versi 13.1 (15609.1.20.111.8) dan operasi penyebaran tercepat: [...arr]dengan 4.653076171875msdi Chrome dan 8.565msdi Safari. Cepat kedua di Chrome adalah fungsi irisan arr.slice()dengan 6.162109375msdan di Safari kedua adalah [].concat(arr)dengan 13.018ms.
edufinn

7

Lihatlah: Link . Ini bukan tentang kecepatan, tetapi kenyamanan. Selain seperti yang Anda lihat, Anda hanya dapat menggunakan slice (0) pada tipe primitif .

Untuk membuat salinan array yang independen daripada salinan referensi ke dalamnya, Anda bisa menggunakan metode slice array.

Contoh:

Untuk membuat salinan array yang independen daripada salinan referensi ke dalamnya, Anda bisa menggunakan metode slice array.

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

Untuk menyalin atau mengkloning suatu objek:

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

Sumber: tautan


1
The primitif jenis komentar berlaku untuk forlingkaran dalam pertanyaan juga.
user113716

4
jika saya menyalin array objek, saya akan mengharapkan array baru untuk referensi objek yang sama daripada mengkloning objek.
lincolnk

7

Seperti @Dan berkata "Jawaban ini menjadi usang dengan cepat. Gunakan tolok ukur untuk memeriksa situasi aktual", ada satu jawaban spesifik dari jsperf yang belum memiliki jawaban untuk dirinya sendiri: sementara :

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

memiliki 960.589 ops / detik dengan runnerup a.concat() di 578.129 ops / detik, yaitu 60%.

Ini adalah Firefox terbaru (40) 64 bit.


@aleclarson membuat patokan baru yang lebih andal.


1
Anda harus benar-benar menghubungkan jsperf. Yang Anda pikirkan rusak, karena array baru dibuat di setiap test case, kecuali tes 'while loop'.
aleclarson

1
Saya membuat jsperf baru yang lebih akurat: jsperf.com/clone-array-3
aleclarson

60% apa? 60% lebih cepat?
Peter Mortensen

1
@PeterMortensen: 587192 adalah ~ 60% (61.1 ...) dari 960589.
serv-inc

7

Cara ECMAScript 2015 dengan Spread operator:

Contoh dasar:

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

Coba di konsol browser:

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

Referensi


Mungkin satu-satunya hal yang cepat dengan penyebaran adalah mengetiknya. Ini kurang performan dibandingkan cara lain untuk melakukannya.
XT_Nova

3
Harap berikan beberapa tautan tentang argumen Anda.
Marian07

6

Itu tergantung pada browser. Jika Anda melihat di posting blog Array.prototype.slice vs pembuatan array manual , ada panduan kasar untuk kinerja masing-masing:

Masukkan deskripsi gambar di sini

Hasil:

Masukkan deskripsi gambar di sini


1
argumentsbukan array yang tepat dan dia gunakan calluntuk memaksa sliceberjalan di koleksi. hasilnya mungkin menyesatkan.
lincolnk

Ya saya bermaksud menyebutkan bahwa dalam posting saya bahwa statistik ini mungkin akan berubah sekarang dengan broswers membaik, tetapi memberikan gambaran umum.
kyndigs

2
@diugalde Saya pikir satu-satunya situasi di mana memposting kode sebagai gambar dapat diterima adalah ketika kode berpotensi berbahaya dan tidak boleh disalin-tempel. Namun dalam kasus ini, ini sangat konyol.
Florian Wendelborn

6

Ada solusi yang jauh lebih bersih:

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

Diperlukan pemeriksaan panjang, karena Arraykonstruktor berperilaku berbeda ketika dipanggil dengan tepat satu argumen.


2
Tetapi apakah ini yang tercepat?
Chris Wesseling

14
Lebih semantik daripada splice(), mungkin. Tapi sungguh, terapkan dan ini semua kecuali intuitif.
Michael Piefel

menunjukkan kinerja paling lambat pada chrome jsperf.com/new-array-vs-splice-vs-slice/113
chrismarx

3
Anda dapat menggunakan Array.ofdan mengabaikan panjangnya:Array.of.apply(Array, array)
Oriol

6

Ingat .slice () tidak akan berfungsi untuk array dua dimensi. Anda akan membutuhkan fungsi seperti ini:

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

3
Dalam Javascript tidak ada array dua dimensi. Hanya ada array yang berisi array. Apa yang Anda coba lakukan adalah salinan mendalam yang tidak diperlukan dalam pertanyaan.
Aloso

5

Itu tergantung pada panjang array. Jika panjang array <= 1.000.000, metode slicedan concatmengambil sekitar waktu yang sama. Tetapi ketika Anda memberikan rentang yang lebih luas, concatmetode ini menang.

Misalnya, coba kode ini:

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

Jika Anda menetapkan panjang original_array ke 1.000.000, slicemetode danconcat metode mengambil sekitar waktu yang sama (3-4 ms, tergantung pada angka acak).

Jika Anda menetapkan panjang original_array ke 10.000.000, maka slicemetode ini membutuhkan lebih dari 60 ms dan concatmetode ini membutuhkan lebih dari 20 ms.


dup.pushsalah a5, alih dup[i] = - alih harus digunakan
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 ] ]

Jadi, untuk menghindari skenario ini terjadi, gunakan

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

        const cloneArr = Array.from(arr);

Ini adalah hal yang valid untuk menunjukkan tentang bagaimana perubahan cloneNums[0][0]dalam contoh Anda menyebarkan perubahan ke nums[0][0]- tetapi itu karena nums[0][0]secara efektif objek yang referensi disalin ke cloneNumsoleh operator spread. Singkatnya, perilaku ini tidak akan memengaruhi kode tempat kami menyalin menurut nilai (int, string, dll. Literal).
Aditya MP

1

Waktu benchmark!

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>

Patokan akan berjalan selama 10-an karena Anda mengklik tombol.

Hasil saya:

Chrome (mesin 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 (Mesin 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)

Kode pemenang:

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

Mesin pemenang:

SpiderMonkey (Mozilla / Firefox)


1

Cara cepat untuk menduplikasi array dalam JavaScript dalam Urutan:

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

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

#3: array1copy = array1.slice();

Jika objek array Anda mengandung beberapa konten JSON-non-serializable (fungsi, Number.POSITIVE_INFINITY, dll.) Lebih baik untuk digunakan

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


0

Anda dapat mengikuti kode ini. Klon array cara yang tidak berubah. Ini adalah cara sempurna untuk mengatur kloning


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

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

0

Di ES6, Anda cukup menggunakan sintaks Penyebaran .

Contoh:

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

Harap perhatikan bahwa operator spread menghasilkan array yang sama sekali baru, jadi memodifikasi yang satu tidak akan memengaruhi yang lain.

Contoh:

arr2.push('d') // becomes ['a', 'b', 'c', 'd']
console.log(arr) // while arr retains its values ['a', 'b', 'c']
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.