ChatGPT解决这个技术问题 Extra ChatGPT

How to extend an existing JavaScript array with another array, without creating a new array

There doesn't seem to be a way to extend an existing JavaScript array with another array, i.e. to emulate Python's extend method.

I want to achieve the following:

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

I know there's a a.concat(b) method, but it creates a new array instead of simply extending the first one. I'd like an algorithm that works efficiently when a is significantly larger than b (i.e. one that does not copy a).

Note: This is not a duplicate of How to append something to an array? -- the goal here is to add the whole contents of one array to the other, and to do it "in place", i.e. without copying all elements of the extended array.

From @Toothbrush's comment on an answer: a.push(...b). It's similar in concept to the top answer, but updated for ES6.
>>> a.push(...b)

A
Arsen Khachaturyan

The .push method can take multiple arguments. You can use the spread operator to pass all the elements of the second array as arguments to .push:

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

If your browser does not support ECMAScript 6, you can use .apply instead:

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

Or perhaps, if you think it's clearer:

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

Please note that all these solutions will fail with a stack overflow error if array b is too long (trouble starts at about 100,000 elements, depending on the browser).
If you cannot guarantee that b is short enough, you should use a standard loop-based technique described in the other answer.


I think this is your best bet. Anything else is going to involve iteration or another exertion of apply()
This answer will fail if "b" (the array to extend by) is large (> 150000 entries approx in Chrome according to my tests). You should use a for loop, or even better use the inbuilt "forEach" function on "b". See my answer: stackoverflow.com/questions/1374126/…
@Deqing: Array's push method can take any number of arguments, which are then pushed to the back of the array. So a.push('x', 'y', 'z') is a valid call that will extend a by 3 elements. apply is a method of any function that takes an array and uses its elements as if they were all given explicitly as positional elements to the function. So a.push.apply(a, ['x', 'y', 'z']) would also extend the array by 3 elements. Additionally, apply takes a context as the first argument (we pass a again there to append to a).
Note: in this scenario, first return value isn't [1,2,3,4,5] but 5 while a == [1,2,3,4,5] afterward.
Yet another slightly less confusing (and not much longer) invocation would be: [].push.apply(a, b).
D
Dan Dascalescu

Update 2018: A better answer is a newer one of mine: a.push(...b). Don't upvote this one anymore, as it never really answered the question, but it was a 2015 hack around first-hit-on-Google :)

For those that simply searched for "JavaScript array extend" and got here, you can very well use Array.concat.

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

Concat will return a copy the new array, as thread starter didn't want. But you might not care (certainly for most kind of uses this will be fine).

There's also some nice ECMAScript 6 sugar for this in the form of the spread operator:

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

(It also copies.)


The question was clearly stating: "without creating a new array?"
@Wilt: This is true, the answer says why though :) This was first hit on Google a long time. Also the other solutions were ugly, wanted something ideomatic and nice. Though nowadays the arr.push(...arr2) is newer and better and a Technically Correct(tm) answer to this particular question.
I hear you, but I searched for "javascript append array without creating a new array", then it is sad to see that a 60 times upvoted answer that is high up doesn't answer the actual question ;) I didn't downvote, since you made it clear in your answer.
@Wilt and I searched "js extend array with array" and got here so thank you :)
P
Peter Mortensen

You should use a loop-based technique. Other answers on this page that are based on using .apply can fail for large arrays.

A fairly terse loop-based implementation is:

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

You can then do the following:

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

DzinX's answer (using push.apply) and other .apply based methods fail when the array that we are appending is large (tests show that for me large is > 150,000 entries approx in Chrome, and > 500,000 entries in Firefox). You can see this error occurring in this jsperf.

An error occurs because the call stack size is exceeded when 'Function.prototype.apply' is called with a large array as the second argument. (MDN has a note on the dangers of exceeding call stack size using Function.prototype.apply - see the section titled "apply and built-in functions".)

For a speed comparison with other answers on this page, check out this jsperf (thanks to EaterOfCode). The loop-based implementation is similar in speed to using Array.push.apply, but tends to be a little slower than Array.slice.apply.

Interestingly, if the array you are appending is sparse, the forEach based method above can take advantage of the sparsity and outperform the .apply based methods; check out this jsperf if you want to test this for yourself.

By the way, do not be tempted (as I was!) to further shorten the forEach implementation to:

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

because this produces garbage results! Why? Because Array.prototype.forEach provides three arguments to the function it calls - these are: (element_value, element_index, source_array). All of these will be pushed onto your first array for every iteration of forEach if you use "forEach(this.push, this)"!


p.s. to test whether other_array really is an array, choose one of the options described here: stackoverflow.com/questions/767486/…
Good answer. I wasn't aware of this problem. I've cited your answer here: stackoverflow.com/a/4156156/96100
.push.apply is actually much faster than .forEach in v8, the fastest is still an inline loop.
@BenjaminGruenbaum - could you post a link to some results showing that? As far as I can see from the results collected at the jsperf linked at the end of this comment, using .forEach is faster than .push.apply in Chrome/Chromium (in all versions since v25). I've not been able to test v8 in isolation, but if you have please link your results. See jsperf: jsperf.com/array-extending-push-vs-concat/5
@jcdude your jsperf is invalid. as all elements in your array are undefined, so .forEach will skip them, making it the fastest. .splice is actually the fastest?! jsperf.com/array-extending-push-vs-concat/18
o
odinho - Velmont

I feel the most elegant these days is:

arr1.push(...arr2);

The MDN article on the spread operator mentions this nice sugary way in ES2015 (ES6):

A better push Example: push is often used to push an array to the end of an existing array. In ES5 this is often done as: 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 with spread this becomes: var arr1 = [0, 1, 2]; var arr2 = [3, 4, 5]; arr1.push(...arr2);

Do note that arr2 can't be huge (keep it under about 100 000 items), because the call stack overflows, as per jcdude's answer.


Just a warning from a mistake I just made. The ES6 version is very close to arr1.push(arr2). This can be a problem like so arr1 = []; arr2=['a', 'b', 'd']; arr1.push(arr2) the result being an array of arrays arr1 == [['a','b','d']] rather than two arrays combined. It is an easy mistake to make. I prefer your second answer below for this reason. stackoverflow.com/a/31521404/4808079
There's a convenience when using .concat is that the second argument must not be an array. This can be achieved by combining spread operator with concat, e.g. arr1.push(...[].concat(arrayOrSingleItem))
This is modern, elegant Javascript for cases where the target array is not empty.
Is this fast enough?
L
Luc

Overview

a.push(...b) - limited, fast, modern syntax

a.push.apply(a, b) - limited, fast

a = a.concat(b) unlimited, slow if a is large

for (let i in b) { a.push(b[i]); } - unlimited, slow if b is large

Each snippet modifies a to be extended with b.

The "limited" snippets pass each array element as an argument, and the maximum number of arguments you can pass to a function is limited. From that link, it seems that a.push(...b) is reliable until there are about 32k elements in b (the size of a does not matter).

Relevant MDN documentation: spread syntax, .apply(), .concat(), .push()

Speed considerations

Every method is fast if both a and b are small, so in most web applications you'll want to use push(...b) and be done with it.

If you're handling more than a few thousand elements, what you want to do depends on the situation:

you're adding a few elements to a large array → push(...b) is very fast

you're adding many elements to a large array → concat is slightly faster than a loop

you're adding many elements to a small array → concat is much faster than a loop

you're usually adding only a few elements to any size array → loops are about as fast as the limited methods for small additions, but will never throw an exception even if it is not the most performant when you add many elements

you're writing a wrapper function to always get the maximum performance → you'll need to check the lengths of the inputs dynamically and choose the right method, perhaps calling push(...b_part) (with slices of the big b) in a loop.

This surprised me: I thought a=a.concat(b) would be able to do a nice memcpy of b onto a without bothering to do individual extend operations as a.push(...b) would have to do, thus always being the fastest. Instead, a.push(...b) is much, much faster especially when a is large.

The speed of different methods was measured in Firefox 88 on Linux using:

a = [];
for (let i = 0; i < Asize; i++){
  a.push(i);
}
b = [];
for (let i = 0; i < Bsize; i++){
  b.push({something: i});
}
t=performance.now();
// Code to test
console.log(performance.now() - t);

Parameters and results:

 ms | Asize | Bsize | code
----+-------+-------+------------------------------
 ~0 |  any  |  any  | a.push(...b)
 ~0 |  any  |  any  | a.push.apply(a, b)
480 |   10M |    50 | a = a.concat(b)
  0 |   10M |    50 | for (let i in b) a.push(b[i])
506 |   10M |  500k | a = a.concat(b)
882 |   10M |  500k | for (let i in b) a.push(b[i])
 11 |    10 |  500k | a = a.concat(b)
851 |    10 |  500k | for (let i in b) a.push(b[i])

Note that a Bsize of 500 000 is the largest value accepted by all methods on my system, that's why it is smaller than Asize.

All tests were run multiple times to see if the results are outliers or representative. The fast methods are almost immeasurable in just one run using performance.now(), of course, but since the slow methods are so obvious and the two fast methods both work on the same principle, we needn't bother repeating it a bunch of times to split hairs.

The concat method is always slow if either array is large, but the loop is only slow if it has to do a lot of function calls and doesn't care how large a is. A loop is thus similar to push(...b) or push.apply for small bs but without breaking if it does get large; however, when you approach the limit, concat is a bit faster again.


E
Eric Robinson

First a few words about apply() in JavaScript to help understand why we use it:

The apply() method calls a function with a given this value, and arguments provided as an array.

Push expects a list of items to add to the array. The apply() method, however, takes the expected arguments for the function call as an array. This allows us to easily push the elements of one array into another array with the builtin push() method.

Imagine you have these arrays:

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

and simply do this:

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

The result will be:

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

The same thing can be done in ES6 using the spread operator ("...") like this:

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

Shorter and better but not fully supported in all browsers at the moment.

Also if you want to move everything from array b to a, emptying b in the process, you can do this:

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

and the result will be as follows:

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

or while (b.length) { a.push(b.shift()); }, right?
J
Jules Colle

If you want to use jQuery, there is $.merge()

Example:

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

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


How can I find the implementation (in the source code) of jQuery.merge()?
Took me 10 minutes -- jQuery is open source and the source is published on Github. I did a search for "merge" and further on located what appeared to be the definition of the merge function in the core.js file, see: github.com/jquery/jquery/blob/master/src/core.js#L331
Not to be confused with operations that merge sorted lists, such as clhs.lisp.se/Body/f_merge.htm
c
casillas

I like the a.push.apply(a, b) method described above, and if you want you can always create a library function like this:

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

and use it like this

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

a.append(b)

The push.apply method should not be used as it can cause a stack overflow (and therefore fail) if your "array" argument is a large array (e.g. > ~150000 entries in Chrome). You should use "array.forEach" - see my answer: stackoverflow.com/a/17368101/1280629
In ES6, just use a.push(...b)
P
Peter Mortensen

It is possible to do it using splice():

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

But despite being uglier it is not faster than push.apply, at least not in Firefox 3.0.


I have found the same thing, splice doesn't provide performance enhancements to pushing each item until about 10,000 element arrays jsperf.com/splice-vs-push
+1 Thanks for adding this and the performance comparison, saved me the effort of testing this method.
Using either splice.apply or push.apply can fail due to a stack overflow if array b is large. They are also slower than using a "for" or "forEach" loop - see this jsPerf: jsperf.com/array-extending-push-vs-concat/5 and my answer stackoverflow.com/questions/1374126/…
P
Peter Mortensen

This solution works for me (using the spread operator of 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);


v
vitaly-t

I'm adding this answer, because despite the question stating clearly without creating a new array, pretty much every answer just ignores it.

Modern JavaScript works well with arrays and alike as iterable objects. This makes it possible to implement a version of concat that builds upon that, and spans the array data across its parameters logically.

The example below makes use of iter-ops library that features such logic:

import {pipe, concat} from 'iter-ops';

const i = pipe(
    originalArray,
    concat(array2, array3, array4, ...)
); //=> Iterable

for(const a of i) {
    console.log(a); // joint values from all arrays
}

Above, no new array is created. Operator concat will iterate through the original array, then will automatically continue into array2, then array3, and so on, in the specified order.

This is the most efficient way of joining arrays in terms of performance and memory usage.

And if, at the end, you decide to convert it into an actual physical array, you can do so via the standard spread operator:

const fullArray = [...i]; // pulls all values from iterable, into a new array

g
gman

as the top voted answer says, a.push(...b) is probably the correct answer taking into account the size limit issue.

On the other hand, some of the answers on performance seem out of date.

These numbers below are for 2022-05-20

https://i.stack.imgur.com/c1nKN.png

https://i.stack.imgur.com/WMwGg.png

https://i.stack.imgur.com/Jqt90.png

from here

At appears that push is fastest across the board in 2022. That may change in the future.

Answers ignoring the question (generating a new array) are missing the point. Lots of code might need/want to modify an array in place given there can be other references to the same array

let a = [1, 2, 3];
let b = [4, 5, 6];
let c = a;
a = a.concat(b);    // a and c are no longer referencing the same array

Those other references could be deep in some object, something that was captured in a closure, etc...

As a probably bad design but as an illustration, imagine you had

const carts = [
  { userId: 123, cart: [item1, item2], },
  { userId: 456, cart: [item1, item2, item3], },
];

and a function

function getCartForUser(userId) {
  return customers.find(c => c.userId === userId);
}

Then you want to add items to the cart

const cart = getCartForUser(userId);
if (cart) {
  cart.concat(newItems);      // FAIL 😢
  cart.push(...newItems);     // Success! 🤩
}

As an aside, the answers suggesting modifying Array.prototype are arguably bad adivce. Changing the native prototypes is bascially a landmine in your code. Another implementation maybe be different than yours and so it will break your code or you'll break their code expecting the other behavior. This includes if/when a native implmentation gets added that clashes with yours. You might say "I know what I'm using so no issue" and that might be true at the moment and you're a single dev but add a second dev and they can't read your mind. And, you are that second dev in a few years when you've forgotten and then graft some other library (analytics?, logging?, ...) on to your page and forget the landmind you left in the code.

This is not just theory. There are countless stories on the net of people running into these landmines.

Arguably there are just a few safe uses for modifying a native object's prototype. One is to polyfill an existing and specified implementation in an old browser. In that case, the spec is defined, the spec is implemented is shipping in new browsers, you just want to get the same behavior in old browsers. That's pretty safe. Pre-patching (spec in progress but not shipping) is arguably not safe. Specs change before shipping.


z
zCoder

Combining the answers...

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

No, there is no need to do this. A for loop using forEach is faster than using push.apply, and works no matter what the length of the array to extend by is. Have a look at my revised answer: stackoverflow.com/a/17368101/1280629 In any case, how do you know that 150000 is the right number to use for all browsers? This is a fudge.
lol. my answer is unrecognizable, but appears to be some combo of others found at the time - i.e. a summary. no worries
M
Mr. Polywhirl

You can create a polyfill for extend as I have below. It will add to the array; in-place and return itself, so that you can chain other methods.

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


u
user3126309

Another solution to merge more than two arrays

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

Then call and print as:

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

Output will be: Array [1, 2, 3, 4, 5, 6, 7]


P
Peter Mortensen

The answer is super simple.

>>> 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 acts very similarly to JavaScript string concatenation. It will return a combination of the parameter you put into the concat function on the end of the array you call the function on. The crux is that you have to assign the returned value to a variable or it gets lost. So for example

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

As clearly stated in the docs for Array.prototype.concat() at MDN this will return a new Array, it will not append to the existing array as OP was clearly asking.
M
Mahdi Pedram

Use Array.extend instead of Array.push for > 150,000 records.

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

N
Nancy Brown

You can do that by simply adding new elements to the array with the help of the push() method.

let colors = ["Red", "Blue", "Orange"]; console.log('Array before push: ' + colors); // append new value to the array colors.push("Green"); console.log('Array after push : ' + colors);

Another method is used for appending an element to the beginning of an array is the unshift() function, which adds and returns the new length. It accepts multiple arguments, attaches the indexes of existing elements, and finally returns the new length of an array:

let colors = ["Red", "Blue", "Orange"]; console.log('Array before unshift: ' + colors); // append new value to the array colors.unshift("Black", "Green"); console.log('Array after unshift : ' + colors);

There are other methods too. You can check them out here.


This adds a value to an array. It does not add an array to an array (in-place) as per the question and as Python's list extend method does.
F
Felix

Another option, if you have lodash installed:

 import { merge } from 'lodash';

 var arr1 = merge(arr1, arr2);

I don't think this works on arrays like this. Please provide a link to a working snippet.
e
elPastor

Super simple, does not count on spread operators or apply, if that's an issue.

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

After running some performance tests on this, it's terribly slow, but answers the question in regards to not creating a new array. Concat is significantly faster, even jQuery's $.merge() whoops it.

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


You should use .forEach rather than .map if you're not using the return value. It better conveys the intention of your code.
@david - to each their own. I prefer the synax of .map but you could also do b.forEach(function(x) { a.push(x)} ). In fact I added that to the jsperf test and it's a hair faster than map. Still super slow.
This isn't a case of preference. These 'functional' methods each have a specific meaning associated with them. Calling .map here looks very weird. It would be like calling b.filter(x => a.push(x)). It works, but it confuses the reader by implying something is happening when it's not.
@elPastor it should be worth your time, because some other guy like me would sit and scratch the nape of his neck wondering what else is happening there in your code he can't see. In most ordinary cases, it's the clarity of intent that matters, not the performance. Please respect the time of those who read your code, cause they might be many when you're just one. Thanks.
@elPastor I know exactly what .map() does and that is the issue. This knowledge would make me think you need the resulting array, otherwise it would be a good thing to use .forEach() to make the reader cautious about the side-effects of the callback function. Strictly speaking your solution creates an additional array of natural numbers (results of push), while the question states: "without creating a new array".