Cara memperluas array JavaScript yang ada dengan array lain, tanpa membuat array baru

974

Tampaknya tidak ada cara untuk memperluas array JavaScript yang ada dengan array lain, yaitu meniru extendmetode Python .

Saya ingin mencapai yang berikut:

>>> a = [1, 2]
[1, 2]
>>> b = [3, 4, 5]
[3, 4, 5]
>>> SOMETHING HERE
>>> a
[1, 2, 3, 4, 5]

Saya tahu ada a.concat(b)metode, tetapi menciptakan array baru alih-alih hanya memperpanjang yang pertama. Saya ingin algoritma yang bekerja secara efisien bila ajauh lebih besar daripada b(yaitu yang tidak menyalin a).

Catatan: Ini bukan duplikat dari Bagaimana cara menambahkan sesuatu ke array? - tujuannya di sini adalah untuk menambahkan seluruh konten dari satu array ke array lainnya, dan untuk melakukannya "di tempat", yaitu tanpa menyalin semua elemen dari array yang diperluas.

DzinX
sumber
5
Dari @ komentar sikat gigi pada jawaban: a.push(...b). Konsepnya mirip dengan jawaban teratas, tetapi diperbarui untuk ES6.
tscizzle

Jawaban:

1501

The .pushMetode dapat mengambil beberapa argumen. Anda bisa menggunakan operator spread untuk meneruskan semua elemen array kedua sebagai argumen untuk .push:

>>> a.push(...b)

Jika browser Anda tidak mendukung ECMAScript 6, Anda dapat menggunakannya .apply:

>>> a.push.apply(a, b)

Atau mungkin, jika Anda pikir itu lebih jelas:

>>> Array.prototype.push.apply(a,b)

Harap dicatat bahwa semua solusi ini akan gagal dengan kesalahan stack overflow jika array bterlalu panjang (masalah dimulai pada sekitar 100.000 elemen, tergantung pada browser).
Jika Anda tidak dapat menjamin bahwa bitu cukup singkat, Anda harus menggunakan teknik berbasis loop standar yang dijelaskan dalam jawaban lainnya.

DzinX
sumber
3
Saya pikir ini adalah taruhan terbaik Anda. Hal lain akan melibatkan iterasi atau aktivitas lain dari aplikasi ()
Peter Bailey
44
Jawaban ini akan gagal jika "b" (array untuk memperpanjang) besar (> 150000 entri kira-kira di Chrome sesuai dengan pengujian saya). Anda harus menggunakan for for loop, atau bahkan lebih baik menggunakan fungsi "forEach" inbuilt pada "b". Lihat jawaban saya: stackoverflow.com/questions/1374126/…
jcdude
35
@Deqing: pushMetode Array dapat mengambil sejumlah argumen, yang kemudian didorong ke belakang array. Begitu a.push('x', 'y', 'z')juga panggilan yang valid yang akan diperpanjang aoleh 3 elemen. applyadalah metode dari setiap fungsi yang mengambil array dan menggunakan elemen-elemennya seolah-olah mereka semua diberikan secara eksplisit sebagai elemen posisi ke fungsi. Begitu a.push.apply(a, ['x', 'y', 'z'])juga akan memperluas array dengan 3 elemen. Selain itu, applygunakan konteks sebagai argumen pertama (kami sampaikan alagi di sana untuk ditambahkan ke a).
DzinX
Catatan: dalam skenario ini, nilai pengembalian pertama bukan [1,2,3,4,5] tetapi 5 sedangkan a == [1,2,3,4,5] sesudahnya.
jawo
1
Namun lain sedikit kurang membingungkan (dan tidak banyak lagi) doa akan menjadi: [].push.apply(a, b).
niry
259

Update 2018 : Sebuah jawaban yang lebih baik adalah salah satu yang lebih baru saya : a.push(...b). Jangan pilih yang ini lagi, karena tidak pernah benar-benar menjawab pertanyaan, tapi itu adalah hack 2015 di-hit-on-Google :)


Bagi mereka yang hanya mencari "memperpanjang array JavaScript" dan sampai di sini, Anda dapat menggunakannya dengan sangat baik Array.concat.

var a = [1, 2, 3];
a = a.concat([5, 4, 3]);

Concat akan mengembalikan salinan array baru, karena thread starter tidak mau. Tapi Anda mungkin tidak peduli (tentu untuk sebagian besar penggunaan ini tidak masalah).


Ada juga beberapa gula ECMAScript 6 yang bagus untuk ini dalam bentuk operator spread:

const a = [1, 2, 3];
const b = [...a, 5, 4, 3];

(Ini juga salinan.)

odinho - Velmont
sumber
43
Pertanyaannya jelas menyatakan: "tanpa membuat array baru?"
Layu
10
@Wilt: Ini benar, jawabannya mengatakan mengapa :) Ini pertama kali dipukul di Google. Juga solusi lain jelek, menginginkan sesuatu yang ideomatis dan menyenangkan. Meskipun saat arr.push(...arr2)ini lebih baru dan lebih baik dan jawaban yang benar secara teknis untuk pertanyaan khusus ini.
odinho
7
Saya mendengar Anda, tetapi saya mencari "javascript append array tanpa membuat array baru" , maka sedih melihat bahwa 60 kali jawaban tervvotasikan yang tinggi tidak menjawab pertanyaan yang sebenarnya;) Saya tidak downvote, karena Anda membuatnya jelas dalam jawaban Anda.
Layu
202

Anda harus menggunakan teknik berbasis loop. Jawaban lain pada halaman ini yang didasarkan pada penggunaan .applydapat gagal untuk array besar.

Implementasi berbasis loop yang cukup singkat adalah:

Array.prototype.extend = function (other_array) {
    /* You should include a test to check whether other_array really is an array */
    other_array.forEach(function(v) {this.push(v)}, this);
}

Anda kemudian dapat melakukan hal berikut:

var a = [1,2,3];
var b = [5,4,3];
a.extend(b);

Jawaban DzinX (menggunakan push.apply) dan .applymetode berbasis lainnya gagal ketika array yang kita tambahkan besar (tes menunjukkan bahwa bagi saya besar adalah> 150.000 entri kira-kira di Chrome, dan> 500.000 entri di Firefox). Anda dapat melihat kesalahan ini terjadi di jsperf ini .

Terjadi kesalahan karena ukuran tumpukan panggilan terlampaui ketika 'Function.prototype.apply' dipanggil dengan array besar sebagai argumen kedua. (MDN memiliki catatan tentang bahaya melebihi ukuran tumpukan panggilan menggunakan Function.prototype.apply - lihat bagian berjudul "Terapkan dan fungsi bawaan ".)

Untuk perbandingan kecepatan dengan jawaban lain di halaman ini, lihat jsperf ini (terima kasih kepada EaterOfCode). Implementasi berbasis loop serupa dalam kecepatan untuk menggunakan Array.push.apply, tetapi cenderung sedikit lebih lambat daripada Array.slice.apply.

Menariknya, jika array yang Anda tambahkan jarang, forEachmetode berbasis di atas dapat mengambil keuntungan dari sparsity dan mengungguli .applymetode berbasis; lihat jsperf ini jika Anda ingin menguji sendiri.

By the way, jangan tergoda (seperti saya!) Untuk lebih mempersingkat implementasi forEach ke:

Array.prototype.extend = function (array) {
    array.forEach(this.push, this);
}

karena ini menghasilkan hasil sampah! Mengapa? Karena Array.prototype.forEachmenyediakan tiga argumen untuk fungsi yang dipanggil - ini adalah: (element_value, element_index, source_array). Semua ini akan didorong ke array pertama Anda untuk setiap iterasi forEachjika Anda menggunakan "forEach (this.push, this)"!

jcdude
sumber
1
ps untuk menguji apakah other_array benar-benar array, pilih salah satu opsi yang dijelaskan di sini: stackoverflow.com/questions/767486/…
jcdude
6
Jawaban yang bagus. Saya tidak menyadari masalah ini. Saya mengutip jawaban Anda di sini: stackoverflow.com/a/4156156/96100
Tim Down
2
.push.applysebenarnya jauh lebih cepat daripada .forEachdi v8, yang tercepat masih merupakan inline loop.
Benjamin Gruenbaum
1
@BenjaminGruenbaum - dapatkah Anda memposting tautan ke beberapa hasil yang menunjukkan hal itu? Sejauh yang saya bisa lihat dari hasil yang dikumpulkan di jsperf yang tertaut di akhir komentar ini, menggunakan .forEachlebih cepat daripada .push.applydi Chrome / Chromium (di semua versi sejak v25). Saya belum dapat menguji v8 secara terpisah, tetapi jika Anda memiliki harap tautkan hasil Anda. Lihat jsperf: jsperf.com/array-extending-push-vs-concat/5
jcdude
1
@jcdude jsperf Anda tidak valid. karena semua elemen dalam array Anda undefined, maka .forEachakan melewatkannya, menjadikannya yang tercepat. .splicesebenarnya yang tercepat ?! jsperf.com/array-extending-push-vs-concat/18
EaterOfCode
153

Saya merasa yang paling elegan hari ini adalah:

arr1.push(...arr2);

The Artikel MDN pada operator penyebaran menyebutkan ini cara manis yang bagus di ES2015 (ES6):

Dorongan yang lebih baik

Contoh: push sering digunakan untuk mendorong array ke akhir array yang ada. Dalam ES5 ini sering dilakukan sebagai:

var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
// Append all items from arr2 onto arr1
Array.prototype.push.apply(arr1, arr2);

Dalam ES6 dengan spread, ini menjadi:

var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);

Perhatikan bahwa arr2tidak boleh besar (simpan di bawah sekitar 100.000 item), karena tumpukan panggilan meluap, sesuai jawaban jcdude.

odinho - Velmont
sumber
1
Hanya peringatan dari kesalahan yang baru saja aku buat. Versi ES6 sangat dekat arr1.push(arr2). Ini bisa menjadi masalah seperti sehingga arr1 = []; arr2=['a', 'b', 'd']; arr1.push(arr2)hasilnya menjadi array array arr1 == [['a','b','d']]daripada dua array yang digabungkan. Ini adalah kesalahan yang mudah dibuat. Saya lebih suka jawaban kedua Anda di bawah ini karena alasan ini. stackoverflow.com/a/31521404/4808079
Seph Reed
Ada kenyamanan saat menggunakan .concatadalah bahwa argumen kedua tidak boleh berupa array. Ini dapat dicapai dengan menggabungkan operator spread dengan concat, mis.arr1.push(...[].concat(arrayOrSingleItem))
Steven Pribilinskiy
Ini adalah Javascript modern dan elegan untuk kasus-kasus di mana array target tidak kosong.
timbo
Apakah ini cukup cepat?
Roel
@RoelDelosReyes: Ya.
odinho
46

Pertama beberapa kata tentang apply()dalam JavaScript untuk membantu memahami mengapa kami menggunakannya:

The apply()metode panggilan fungsi dengan diberikan thisnilai, dan argumen yang disediakan sebagai array.

Push mengharapkan daftar item untuk ditambahkan ke array. The apply()metode, bagaimanapun, mengambil argumen diharapkan untuk panggilan fungsi sebagai array. Ini memungkinkan kita untuk dengan mudah pushelemen dari satu array ke array lain dengan push()metode bawaan .

Bayangkan Anda memiliki susunan ini:

var a = [1, 2, 3, 4];
var b = [5, 6, 7];

dan cukup lakukan ini:

Array.prototype.push.apply(a, b);

Hasilnya adalah:

a = [1, 2, 3, 4, 5, 6, 7];

Hal yang sama dapat dilakukan di ES6 menggunakan operator spread (" ...") seperti ini:

a.push(...b); //a = [1, 2, 3, 4, 5, 6, 7]; 

Lebih pendek dan lebih baik tetapi tidak sepenuhnya didukung di semua browser saat ini.

Juga jika Anda ingin memindahkan semuanya dari array bke a, mengosongkan bdalam proses, Anda dapat melakukan ini:

while(b.length) {
  a.push(b.shift());
} 

dan hasilnya adalah sebagai berikut:

a = [1, 2, 3, 4, 5, 6, 7];
b = [];
Alireza
sumber
1
atau while (b.length) { a.push(b.shift()); }benar?
LarsW
37

Jika Anda ingin menggunakan jQuery, ada $ .merge ()

Contoh:

a = [1, 2];
b = [3, 4, 5];
$.merge(a,b);

Hasil: a = [1, 2, 3, 4, 5]

Jules Colle
sumber
1
Bagaimana saya bisa menemukan implementasi (dalam kode sumber) jQuery.merge()?
ma11hew28
Butuh waktu 10 menit - jQuery adalah sumber terbuka dan sumber dipublikasikan di Github. Saya melakukan pencarian untuk "menggabungkan" dan selanjutnya menemukan apa yang tampaknya menjadi definisi fungsi gabungan dalam file core.js, lihat: github.com/jquery/jquery/jquery/blob/master/src/core.js#L331
amn
19

Saya suka a.push.apply(a, b)metode yang dijelaskan di atas, dan jika mau, Anda selalu dapat membuat fungsi perpustakaan seperti ini:

Array.prototype.append = function(array)
{
    this.push.apply(this, array)
}

dan gunakan seperti ini

a = [1,2]
b = [3,4]

a.append(b)
Pizzaiola Gorgonzola
sumber
6
Metode push.apply tidak boleh digunakan karena dapat menyebabkan stack overflow (dan karena itu gagal) jika argumen "array" Anda adalah array besar (mis. ~ 150000 entri di Chrome). Anda harus menggunakan "array.forEach" - lihat jawaban saya: stackoverflow.com/a/17368101/1280629
jcdude
12

Dimungkinkan untuk melakukannya menggunakan splice():

b.unshift(b.length)
b.unshift(a.length)
Array.prototype.splice.apply(a,b) 
b.shift() // Restore b
b.shift() // 

Tetapi meskipun lebih jelek itu tidak lebih cepat daripada push.apply, setidaknya tidak di Firefox 3.0.

Rafał Dowgird
sumber
9
Saya telah menemukan hal yang sama, sambungan tidak memberikan peningkatan kinerja untuk mendorong setiap item hingga sekitar 10.000 elemen array jsperf.com/splice-vs-push
Drew
1
+1 Terima kasih telah menambahkan ini dan perbandingan kinerja, menyelamatkan saya dari upaya menguji metode ini.
jjrv
1
Menggunakan salah satu splice.apply atau push.apply bisa gagal karena stack overflow jika array b besar. Mereka juga lebih lambat daripada menggunakan loop "for" atau "forEach" - lihat jsPerf ini: jsperf.com/array-extending-push-vs-concat/5 dan jawaban saya stackoverflow.com/questions/1374126/…
jcdude
4

Solusi ini cocok untuk saya (menggunakan operator spread dari ECMAScript 6):

let array = ['my', 'solution', 'works'];
let newArray = [];
let newArray2 = [];
newArray.push(...array); // Adding to same array
newArray2.push([...array]); // Adding as child/leaf/sub-array
console.log(newArray);
console.log(newArray2);

Pankaj Shrivastava
sumber
1

Anda dapat membuat polyfill untuk ekstensi seperti yang saya miliki di bawah ini. Ini akan menambah array; di tempat dan kembalikan sendiri, sehingga Anda dapat memadukan metode lain.

if (Array.prototype.extend === undefined) {
  Array.prototype.extend = function(other) {
    this.push.apply(this, arguments.length > 1 ? arguments : other);
    return this;
  };
}

function print() {
  document.body.innerHTML += [].map.call(arguments, function(item) {
    return typeof item === 'object' ? JSON.stringify(item) : item;
  }).join(' ') + '\n';
}
document.body.innerHTML = '';

var a = [1, 2, 3];
var b = [4, 5, 6];

print('Concat');
print('(1)', a.concat(b));
print('(2)', a.concat(b));
print('(3)', a.concat(4, 5, 6));

print('\nExtend');
print('(1)', a.extend(b));
print('(2)', a.extend(b));
print('(3)', a.extend(4, 5, 6));
body {
  font-family: monospace;
  white-space: pre;
}

Pak Polywhirl
sumber
0

Menggabungkan jawaban ...

Array.prototype.extend = function(array) {
    if (array.length < 150000) {
        this.push.apply(this, array)
    } else {
        for (var i = 0, len = array.length; i < len; ++i) {
            this.push(array[i]);
        };
    }  
}
zCoder
sumber
9
Tidak, tidak perlu melakukan ini. A untuk loop menggunakan forEach lebih cepat daripada menggunakan push.apply, dan bekerja tidak peduli berapa panjang array untuk memperpanjang. Lihat jawaban saya yang telah direvisi: stackoverflow.com/a/17368101/1280629 Bagaimanapun, bagaimana Anda tahu bahwa 150000 adalah angka yang tepat untuk digunakan untuk semua browser? Ini adalah fudge.
jcdude
lol. jawaban saya tidak dapat dikenali, tetapi tampaknya merupakan kombinasi dari yang lain yang ditemukan pada saat itu - yaitu ringkasan. jangan khawatir
zCoder
0

Solusi lain untuk menggabungkan lebih dari dua array

var a = [1, 2],
    b = [3, 4, 5],
    c = [6, 7];

// Merge the contents of multiple arrays together into the first array
var mergeArrays = function() {
 var i, len = arguments.length;
 if (len > 1) {
  for (i = 1; i < len; i++) {
    arguments[0].push.apply(arguments[0], arguments[i]);
  }
 }
};

Kemudian panggil dan cetak sebagai:

mergeArrays(a, b, c);
console.log(a)

Output akan menjadi: Array [1, 2, 3, 4, 5, 6, 7]

pengguna3126309
sumber
-1

Jawabannya sangat sederhana.

>>> a = [1, 2]
[1, 2]
>>> b = [3, 4, 5]
[3, 4, 5]
>>> SOMETHING HERE
(The following code will combine the two arrays.)

a = a.concat(b);

>>> a
[1, 2, 3, 4, 5]

Concat bertindak sangat mirip dengan penggabungan string JavaScript. Ini akan mengembalikan kombinasi parameter yang Anda masukkan ke fungsi concat di akhir array yang Anda panggil fungsi. Intinya adalah bahwa Anda harus menetapkan nilai yang dikembalikan ke variabel atau hilang. Jadi misalnya

a.concat(b);  <--- This does absolutely nothing since it is just returning the combined arrays, but it doesn't do anything with it.
Timothy Trousdale
sumber
2
Seperti yang dinyatakan dengan jelas dalam dokumen untuk Array.prototype.concat()di MDN ini akan mengembalikan Array baru , itu tidak akan menambahkan ke array yang ada seperti OP jelas bertanya.
Layu
-2

Gunakan Array.extendalih-alih Array.pushuntuk> 150.000 rekaman.

if (!Array.prototype.extend) {
  Array.prototype.extend = function(arr) {
    if (!Array.isArray(arr)) {
      return this;
    }

    for (let record of arr) {
      this.push(record);
    }

    return this;
  };
}
Mahdi Pedram
sumber
-6

Sangat sederhana, tidak mengandalkan operator yang tersebar atau berlaku, jika itu masalah.

b.map(x => a.push(x));

Setelah menjalankan beberapa tes kinerja, ini sangat lambat, tetapi menjawab pertanyaan sehubungan dengan tidak membuat array baru. Concat secara signifikan lebih cepat, bahkan jQuery $.merge()mengatasinya.

https://jsperf.com/merge-arrays19b/1

elPastor
sumber
4
Anda harus menggunakan .forEachdaripada .mapjika Anda tidak menggunakan nilai kembali. Lebih baik menyampaikan maksud kode Anda.
david
@david - untuk masing-masing. Saya lebih suka synax .maptetapi Anda juga bisa melakukannya b.forEach(function(x) { a.push(x)} ). Sebenarnya saya menambahkan itu ke tes jsperf dan itu rambut lebih cepat dari peta. Masih sangat lambat.
elPastor
3
Ini bukan kasus preferensi. Metode 'fungsional' ini masing-masing memiliki makna spesifik yang terkait dengannya. Menelepon ke .mapsini terlihat sangat aneh. Itu seperti menelepon b.filter(x => a.push(x)). Ini berfungsi, tetapi membingungkan pembaca dengan menyiratkan sesuatu sedang terjadi padahal tidak.
david
2
@ ElPastor, itu sepadan dengan waktu Anda, karena beberapa pria lain seperti saya akan duduk dan menggaruk tengkuknya bertanya-tanya apa lagi yang terjadi di kode Anda yang tidak dapat dilihatnya. Dalam kebanyakan kasus biasa, kejelasan niatlah yang penting, bukan kinerja. Harap hormati waktu mereka yang membaca kode Anda, karena mereka mungkin banyak ketika Anda hanya satu. Terima kasih.
Dmytro Y.
1
@ ElPastor Saya tahu persis apa yang .map()terjadi dan itu masalahnya. Pengetahuan ini akan membuat saya berpikir Anda memerlukan array yang dihasilkan, jika tidak maka akan menjadi hal yang baik untuk digunakan .forEach()untuk membuat pembaca berhati-hati tentang efek samping dari fungsi callback. Secara tegas, solusi Anda menciptakan array tambahan bilangan asli (hasil push), sedangkan pertanyaannya menyatakan: "tanpa membuat array baru".
Dmytro Y.