Come estendere un array JavaScript esistente con un altro array, senza creare un nuovo array


974

Non sembra esserci un modo per estendere un array JavaScript esistente con un altro array, ovvero per emulare il extendmetodo Python .

Voglio ottenere quanto segue:

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

So che esiste un a.concat(b)metodo, ma crea un nuovo array invece di estendere semplicemente il primo. Vorrei un algoritmo che funzioni in modo efficiente quando aè significativamente più grande di b(cioè uno che non copia a).

Nota: questo non è un duplicato di Come aggiungere qualcosa a un array? - l'obiettivo qui è quello di aggiungere l'intero contenuto di un array all'altro e di farlo "sul posto", cioè senza copiare tutti gli elementi dell'array esteso.


5
Da @ commento di spazzolino da denti su una risposta: a.push(...b). È simile nel concetto alla risposta migliore, ma aggiornato per ES6.
tscizzle,

Risposte:


1501

Il .pushmetodo può accettare più argomenti. È possibile utilizzare l' operatore spread per passare tutti gli elementi del secondo array come argomenti a .push:

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

Se il tuo browser non supporta ECMAScript 6, puoi .applyinvece usare :

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

O forse, se pensi che sia più chiaro:

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

Si noti che tutte queste soluzioni falliranno con un errore di overflow dello stack se l'array bè troppo lungo (il problema inizia a circa 100.000 elementi, a seconda del browser).
Se non è possibile garantire che bsia abbastanza breve, è necessario utilizzare una tecnica standard basata su loop descritta nell'altra risposta.


3
Penso che questa sia la tua scommessa migliore. Qualsiasi altra cosa implicherà l'iterazione o un altro sforzo di apply ()
Peter Bailey,

44
Questa risposta fallirà se "b" (l'array da estendere) è grande (> 150000 voci circa in Chrome secondo i miei test). Dovresti usare un ciclo for, o ancora meglio usare la funzione "forEach" integrata su "b". Vedere la mia risposta: stackoverflow.com/questions/1374126/...
jcdude

35
@Deqing: il pushmetodo dell'array può accettare qualsiasi numero di argomenti, che vengono quindi inseriti nella parte posteriore dell'array. Quindi a.push('x', 'y', 'z')è una chiamata valida che si estenderà adi 3 elementi. applyè un metodo di qualsiasi funzione che accetta un array e usa i suoi elementi come se fossero tutti esplicitamente dati come elementi posizionali alla funzione. Quindi a.push.apply(a, ['x', 'y', 'z'])estenderebbe anche l'array di 3 elementi. Inoltre, applyaccetta un contesto come primo argomento (passiamo di anuovo lì a cui aggiungere a).
DzinX,

Nota: in questo scenario, il primo valore restituito non è [1,2,3,4,5] ma 5 mentre successivamente a == [1,2,3,4,5].
Jawo,

1
Ancora un altro un po 'meno confusione (e non molto di più) invocazione sarebbe: [].push.apply(a, b).
niry

259

Aggiornamento 2018 : Una risposta migliore è una versione più recente di mine : a.push(...b). Non votare più questo, poiché non ha mai veramente risposto alla domanda, ma è stato un hack del 2015 attorno al primo hit su Google :)


Per quelli che hanno semplicemente cercato "estensione di array JavaScript" e sono arrivati ​​qui, puoi usare molto bene Array.concat.

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

Concat restituirà una copia del nuovo array, poiché non voleva l'avvio del thread. Ma potrebbe non interessarti (sicuramente per la maggior parte degli usi questo andrà bene).


C'è anche un po 'di zucchero ECMAScript 6 per questo nella forma dell'operatore di diffusione:

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

(Copia anche.)


43
La domanda stava chiaramente affermando: "senza creare un nuovo array?"
Wilt

10
@Wilt: questo è vero, la risposta dice perché :) Questo è stato il primo successo su Google da molto tempo. Anche le altre soluzioni erano brutte, volevano qualcosa di ideomatico e carino. Anche se al giorno d'oggi arr.push(...arr2)è il più nuovo e migliore e una risposta tecnicamente corretta (tm) a questa particolare domanda.
Odinho - Velmont,

7
Ti sento, ma ho cercato "Aggiungi array di javascript senza creare un nuovo array" , quindi è triste vedere che una risposta votata a 60 volte in alto non risponde alla domanda reale;) Non ho votato in basso, da quando lo hai chiarito nella tua risposta.
Wilt,

202

È necessario utilizzare una tecnica basata su loop. Altre risposte su questa pagina basate sull'uso.apply possono non riuscire per array di grandi dimensioni.

Un'implementazione basata su loop piuttosto concisa è:

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

È quindi possibile effettuare le seguenti operazioni:

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

La risposta di DzinX (usando push.apply) e altri .applymetodi basati falliscono quando l'array che stiamo aggiungendo è grande (i test mostrano che per me grande è> 150.000 voci circa in Chrome e> 500.000 voci in Firefox). Puoi vedere questo errore che si verifica in questo jsperf .

Si verifica un errore perché la dimensione dello stack di chiamate viene superata quando "Function.prototype.apply" viene chiamato con un array di grandi dimensioni come secondo argomento. (MDN ha una nota sui pericoli legati al superamento delle dimensioni dello stack di chiamate utilizzando Function.prototype.apply - vedere la sezione intitolata "Applica e funzioni integrate".)

Per un confronto veloce con altre risposte in questa pagina, dai un'occhiata a questo jsperf (grazie a EaterOfCode). L'implementazione basata su loop è simile per velocità all'utilizzo Array.push.apply, ma tende ad essere un po 'più lenta di Array.slice.apply.

È interessante notare che se l'array che si sta aggiungendo è scarso, il forEachmetodo basato sopra può sfruttare la scarsità e sovraperformare i .applymetodi basati; dai un'occhiata a questo jsperf se vuoi testarlo tu stesso.

A proposito, non essere tentato (come lo ero io!) Di abbreviare ulteriormente l'implementazione di ForEach per:

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

perché questo produce risultati spazzatura! Perché? Perché Array.prototype.forEachfornisce tre argomenti alla funzione che chiama: questi sono: (element_value, element_index, source_array). Tutti questi saranno forEachinseriti nel tuo primo array per ogni iterazione se usi "forEach (this.push, this)"!


1
ps per verificare se other_array è davvero un array, scegliere una delle opzioni descritte qui: stackoverflow.com/questions/767486/...
jcdude

6
Buona risposta. Non ero a conoscenza di questo problema. Ho citato la tua risposta qui: stackoverflow.com/a/4156156/96100
Tim Down

2
.push.applyè in realtà molto più veloce che .forEachin v8, il più veloce è ancora un loop in linea.
Benjamin Gruenbaum,

1
@BenjaminGruenbaum - potresti pubblicare un link ad alcuni risultati che lo mostrano? Per quanto posso vedere dai risultati raccolti sul jsperf collegato alla fine di questo commento, l'utilizzo .forEachè più veloce rispetto .push.applya Chrome / Chromium (in tutte le versioni dalla v25). Non sono stato in grado di testare la v8 in modo isolato, ma se hai, collega i tuoi risultati. Vedi jsperf: jsperf.com/array-extending-push-vs-concat/5
jcdude

1
@jcdude il tuo jsperf non è valido. come tutti gli elementi nel tuo array sono undefined, quindi .forEachli salterà, rendendolo il più veloce. .spliceè in realtà il più veloce ?! jsperf.com/array-extending-push-vs-concat/18
EaterOfCode

153

Mi sento il più elegante in questi giorni è:

arr1.push(...arr2);

L' articolo MDN sull'operatore di diffusione menziona questo modo zuccherino in ES2015 (ES6):

Una spinta migliore

Esempio: push viene spesso utilizzato per inviare un array alla fine di un array esistente. In ES5 questo è spesso fatto come:

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

In ES6 con diffusione questo diventa:

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

Nota che arr2non può essere enorme (mantienilo sotto circa 100000 articoli), perché lo stack di chiamate trabocca, come da risposta di jcdude.


1
Solo un avvertimento per un errore che ho appena fatto. La versione ES6 è molto vicina arr1.push(arr2). Questo può essere un problema del genere, quindi arr1 = []; arr2=['a', 'b', 'd']; arr1.push(arr2)il risultato è una matrice di matrici arr1 == [['a','b','d']]anziché due matrici combinate. È un errore facile da fare. Preferisco la tua seconda risposta qui sotto per questo motivo. stackoverflow.com/a/31521404/4808079
Seph Reed

C'è una comodità quando si usa .concatè che il secondo argomento non deve essere un array. Ciò può essere ottenuto combinando l'operatore spread con concat, ad esempioarr1.push(...[].concat(arrayOrSingleItem))
Steven Pribilinskiy il

Questo è Javascript moderno ed elegante per i casi in cui l'array di destinazione non è vuoto.
timbo,

È abbastanza veloce?
Roel

@RoelDelosReyes: Sì.
odinho - Velmont

46

Prima di tutto alcune parole su apply()in JavaScript per aiutare a capire perché lo usiamo:

Il apply()metodo chiama una funzione con un determinato thisvalore e argomenti forniti come una matrice.

Push prevede un elenco di elementi da aggiungere all'array. Il apply()metodo, tuttavia, accetta gli argomenti previsti per la chiamata di funzione come un array. Questo ci consente di inserire facilmente pushgli elementi di un array in un altro array con il builtinpush() metodo .

Immagina di avere questi array:

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

e semplicemente fai questo:

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

Il risultato sarà:

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

La stessa cosa può essere fatta in ES6 usando l'operatore spread (" ...") in questo modo:

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

Al momento più breve e migliore, ma non completamente supportato in tutti i browser.

Inoltre, se vuoi spostare tutto dall'array ba a, svuotandolo bnel processo, puoi farlo:

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

e il risultato sarà il seguente:

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

1
o while (b.length) { a.push(b.shift()); }giusto?
LarsW

37

Se vuoi usare jQuery, c'è $ .merge ()

Esempio:

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

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


1
Come posso trovare l'implementazione (nel codice sorgente) di jQuery.merge()?
ma11hew28,

Mi ci sono voluti 10 minuti: jQuery è open source e la fonte è pubblicata su Github. Ho fatto una ricerca per "unire" e più avanti ho trovato quella che sembrava essere la definizione della funzione di unione nel file core.js, vedere: github.com/jquery/jquery/blob/master/src/core.js#L331
AMN

19

Mi piace il a.push.apply(a, b)metodo sopra descritto e, se lo desideri, puoi sempre creare una funzione di libreria come questa:

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

e usalo in questo modo

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

a.append(b)

6
Il metodo push.apply non deve essere utilizzato in quanto può causare un overflow dello stack (e quindi non riuscire) se l'argomento "array" è un array di grandi dimensioni (ad esempio> ~ 150000 voci in Chrome). Si dovrebbe usare "array.forEach" - vedi la mia risposta: stackoverflow.com/a/17368101/1280629
jcdude

12

È possibile farlo utilizzando splice():

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

Ma nonostante sia più brutto non è più veloce di push.apply, almeno non in Firefox 3.0.


9
Ho trovato la stessa cosa, la giuntura non fornisce miglioramenti delle prestazioni per spingere ogni articolo fino a quando circa 10.000 array di elementi jsperf.com/splice-vs-push
Drew

1
+1 Grazie per aver aggiunto questo e il confronto delle prestazioni, mi ha risparmiato lo sforzo di testare questo metodo.
jjrv,

1
L'uso di splice.apply o push.apply può fallire a causa di un overflow dello stack se l'array b è grande. Essi sono anche più lento rispetto all'utilizzo di un "per" o loop "forEach" - vedere questo jsPerf: jsperf.com/array-extending-push-vs-concat/5 e la mia risposta stackoverflow.com/questions/1374126/...
jcdude

4

Questa soluzione funziona per me (utilizzando l'operatore spread di 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);


1

È possibile creare un polyfill per estensione come ho di seguito. Si aggiungerà all'array; sul posto e restituire se stesso, in modo da poter concatenare altri metodi.

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


0

Combinare le risposte ...

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

9
No, non è necessario farlo. Un ciclo for che usa forEach è più veloce dell'uso di push.apply e funziona indipendentemente dalla lunghezza dell'array da estendere. Dai un'occhiata alla mia risposta rivista: stackoverflow.com/a/17368101/1280629 In ogni caso, come fai a sapere che 150000 è il numero giusto da utilizzare per tutti i browser? Questo è un fondente.
jcdude,

lol. la mia risposta è irriconoscibile, ma sembra essere una combo di altri trovati al momento - cioè un riassunto. nessun
problema

0

Un'altra soluzione per unire più di due 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]);
  }
 }
};

Quindi chiama e stampa come:

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

L'output sarà: Array [1, 2, 3, 4, 5, 6, 7]


-1

La risposta è semplicissima.

>>> 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 agisce in modo molto simile alla concatenazione di stringhe JavaScript. Restituirà una combinazione del parametro inserito nella funzione concat alla fine dell'array su cui si chiama la funzione. Il punto cruciale è che devi assegnare il valore restituito a una variabile o si perde. Quindi per esempio

a.concat(b);  <--- This does absolutely nothing since it is just returning the combined arrays, but it doesn't do anything with it.

2
Come indicato chiaramente nei documenti di Array.prototype.concat()MDN, questo restituirà un nuovo array , non verrà aggiunto all'array esistente come chiaramente richiesto da OP.
Appassisci il

-2

Utilizzare Array.extendanziché Array.pushper> 150.000 record.

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

-6

Super semplice, non conta su operatori di spread o si applica, se questo è un problema.

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

Dopo aver eseguito alcuni test delle prestazioni su questo, è terribilmente lento, ma risponde alla domanda per quanto riguarda la non creazione di un nuovo array. Concat è significativamente più veloce, anche di jQuery$.merge() supplica.

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


4
Dovresti usare .forEachpiuttosto che .mapse non stai usando il valore di ritorno. Trasmette meglio l'intenzione del tuo codice.
David,

@david - a ciascuno il proprio. Preferisco la sinossi di .mapma potresti anche farlo b.forEach(function(x) { a.push(x)} ). In effetti l'ho aggiunto al test jsperf ed è un pelo più veloce della mappa. Ancora super lento.
elPastor,

3
Questo non è un caso di preferenza. Questi metodi "funzionali" hanno ciascuno un significato specifico associato ad essi. Chiamare .mapqui sembra molto strano. Sarebbe come chiamare b.filter(x => a.push(x)). Funziona, ma confonde il lettore implicando che qualcosa sta accadendo quando non lo è.
David,

2
@elPastor dovrebbe valere la pena, perché un altro tipo come me si siederebbe e si griderebbe la nuca chiedendosi cos'altro sta succedendo lì nel tuo codice che non riesce a vedere. Nella maggior parte dei casi ordinari, è la chiarezza delle intenzioni che conta, non la performance. Ti preghiamo di rispettare il tempo di coloro che leggono il tuo codice, perché potrebbero essere molti quando sei solo uno. Grazie.
Dmytro Y.

1
@elPastor So esattamente cosa .map()fa e questo è il problema. Questa conoscenza mi farebbe pensare che hai bisogno dell'array risultante, altrimenti sarebbe una buona cosa usare .forEach()per rendere il lettore cauto sugli effetti collaterali della funzione di callback. A rigor di termini, la tua soluzione crea un ulteriore array di numeri naturali (risultati di push), mentre la domanda afferma: "senza creare un nuovo array".
Dmytro Y.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.