Query explained:
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
).
How to extend an existing JavaScript array with another array, without creating a new array?
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.
JavaScript array extend:
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.)
Answer #3:
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);
The first 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).
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)
For a speed comparison with other answers on this page. 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.
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)”!
Answer #4:
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).
How to extend array in JavaScript?
First, a few words about apply()
in JavaScript to help understand why we use it:
The
apply()
method calls a function with a giventhis
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 = [];
Final answer:
Overview
a.push(...b)
– limited, fast, modern syntaxa.push.apply(a, b)
– limited, fasta = a.concat(b)
unlimited, slow ifa
is largefor (let i in b) { a.push(b[i]); }
– unlimited, slow ifb
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).
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
This surprised me: I figured 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 b
s but without breaking if it does get large; however, when you approach the limit, concat
is a bit faster again.
Hope you learned something from this post.
Follow Programming Articles for more!