Как расширить существующий массив JavaScript другим массивом, не создавая новый массив

974

Кажется, не существует способа расширить существующий массив JavaScript другим массивом, то есть эмулировать extendметод Python .

Я хочу добиться следующего:

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

Я знаю, что есть a.concat(b)метод, но он создает новый массив вместо простого расширения первого. Я хотел бы алгоритм, который работает эффективно, когда aзначительно больше, чем b(то есть тот, который не копирует a).

Примечание: это не дубликат Как добавить что-либо в массив? - цель здесь состоит в том, чтобы добавить все содержимое одного массива в другой и сделать это «на месте», т.е. без копирования всех элементов расширенного массива.

DzinX
источник
5
От @ комментария зубной щетки на ответ: a.push(...b). По концепции он похож на лучший ответ, но обновлен для ES6.
tscizzle 14.09.16

Ответы:

1501

.pushМетод может принимать несколько аргументов. Вы можете использовать оператор распространения, чтобы передать все элементы второго массива в качестве аргументов .push:

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

Если ваш браузер не поддерживает ECMAScript 6, вы можете использовать .applyвместо этого:

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

Или, может быть, если вы думаете, что это яснее:

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

Обратите внимание, что все эти решения завершатся с ошибкой переполнения стека, если массив bслишком длинный (проблема начинается с 100 000 элементов, в зависимости от браузера).
Если вы не можете гарантировать, что bон достаточно короткий, вам следует использовать стандартную методику, основанную на циклах, описанную в другом ответе.

DzinX
источник
3
Я думаю, что это ваша лучшая ставка. Все остальное будет включать в себя итерацию или другое применение apply ()
Питер Бейли
44
Этот ответ потерпит неудачу, если «b» (массив для расширения) будет большим (> 150000 записей в Chrome приблизительно в соответствии с моими тестами). Вы должны использовать цикл for или, что еще лучше, использовать встроенную функцию forEach для b. Смотрите мой ответ: stackoverflow.com/questions/1374126/…
jcdude
35
@Deqing: pushметод массива может принимать любое количество аргументов, которые затем помещаются в конец массива. Поэтому a.push('x', 'y', 'z')действительный вызов , который будет распространяться aна 3 -х элементов. applyявляется методом любой функции, которая принимает массив и использует его элементы, как если бы они все были заданы явно как позиционные элементы для функции. Так a.push.apply(a, ['x', 'y', 'z'])же расширил бы массив на 3 элемента. Кроме того, applyв качестве первого аргумента принимает контекст (мы aснова передаем его для добавления a).
DzinX
Примечание: в этом сценарии первое возвращаемое значение - не [1,2,3,4,5], а 5, а после == [1,2,3,4,5].
Jawo
1
Еще одна чуть менее запутанной (и не намного дольше) вызов будет: [].push.apply(a, b).
Нири
260

Обновление 2018 : Лучший ответ новее один из моих : a.push(...b). Не одобряйте это больше, так как он никогда не отвечал на вопрос, но это был хак 2015 года вокруг первого попадания в Google :)


Для тех, кто просто искал "Расширение массива JavaScript" и получил здесь, вы можете очень хорошо использовать Array.concat.

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

Concat вернет копию нового массива, поскольку запускатель потоков не захотел. Но вам может быть все равно (конечно, для большинства видов использования это будет хорошо).


Для этого есть также хороший сахар ECMAScript 6 в виде оператора распространения:

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

(Это также копирует.)

Одино - Вельмонт
источник
43
Вопрос был четко сформулирован: «без создания нового массива?»
Уилт
10
@Wilt: Это правда, ответ говорит, почему, хотя :) Это было первым хитом в Google долгое время. Также другие решения были безобразны, хотели чего-то идеологического и хорошего. Хотя в настоящее время arr.push(...arr2)новее и лучше и технически правильно (тм) ответ на этот конкретный вопрос.
Одино - Вельмонт,
7
Я слышал вас, но я искал "javascript append array без создания нового массива" , потом грустно видеть, что 60-кратный ответ с высоким рейтингом не отвечает фактическому вопросу;) Я не понижал голос, так как вы ясно дали понять в своем ответе.
Уил
@Wilt, и я искал "JS Расширить массив с массивом" и попал сюда, так что спасибо :)
Давид
202

Вы должны использовать технику на основе цикла. Другие ответы на этой странице, основанные на использовании, .applyмогут не работать для больших массивов.

Довольно краткая реализация, основанная на циклах:

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

Затем вы можете сделать следующее:

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

Ответ DzinX (с использованием push.apply) и других .applyоснованных на нем методов завершается неудачей, когда добавляемый нами массив большой (тесты показывают, что для меня большой размер составляет> 150 000 записей в Chrome и> 500 000 записей в Firefox). Вы можете увидеть эту ошибку, возникающую в этом jsperf .

Ошибка возникает из-за превышения размера стека вызовов при вызове Function.prototype.apply с большим массивом в качестве второго аргумента. (У MDN есть примечание об опасностях превышения размера стека вызовов с помощью Function.prototype.apply - см. Раздел «Применение и встроенные функции».)

Для сравнения скорости с другими ответами на этой странице, проверьте этот jsperf (благодаря EaterOfCode). Реализация на основе циклов похожа по скорости на использование Array.push.apply, но имеет тенденцию быть немного медленнее, чем Array.slice.apply.

Интересно, что если массив, который вы добавляете, является разреженным, forEachоснованный метод выше может использовать преимущества разреженности и опережать .applyоснованные методы; Проверьте этот jsperf, если вы хотите проверить это для себя.

Между прочим, не поддавайтесь искушению (как я!) Дополнительно сократить реализацию forEach до:

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

потому что это приводит к результатам мусора! Почему? Потому что Array.prototype.forEachпредоставляет три аргумента вызываемой функции - это: (element_value, element_index, source_array). Все они будут помещены в ваш первый массив для каждой итерации, forEachесли вы используете «forEach (this.push, this)»!

jcdude
источник
1
ps, чтобы проверить, является ли other_array массивом, выберите один из вариантов, описанных здесь: stackoverflow.com/questions/767486/…
jcdude
6
Хороший ответ. Я не знал об этой проблеме. Я процитировал ваш ответ здесь: stackoverflow.com/a/4156156/96100
Тим Даун
2
.push.applyна самом деле намного быстрее, чем .forEachв v8, самый быстрый - все еще встроенный цикл.
Бенджамин Грюнбаум
1
@BenjaminGruenbaum - не могли бы вы опубликовать ссылку на некоторые результаты, показывающие это? Насколько я могу судить по результатам, собранным в jsperf, связанном в конце этого комментария, использование .forEachпроисходит быстрее, чем .push.applyв Chrome / Chromium (во всех версиях начиная с v25). Я не смог протестировать v8 изолированно, но если у вас есть, пожалуйста, свяжите свои результаты. См. Jsperf: jsperf.com/array-extending-push-vs-concat/5
jcdude
1
@jcdude ваш jsperf недействителен. как и все элементы в вашем массиве undefined, так .forEachчто пропустить их, что делает его самым быстрым. .spliceна самом деле самый быстрый ?! jsperf.com/array-extending-push-vs-concat/18
EaterOfCode
154

Я чувствую себя самым элегантным в эти дни:

arr1.push(...arr2);

В статье MDN об операторе спреда в ES2015 (ES6) упоминается этот приятный сладкий путь:

Лучший толчок

Пример: push часто используется для перемещения массива в конец существующего массива. В ES5 это часто делается так:

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

В ES6 с распространением это становится:

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

Обратите внимание, что он arr2не может быть огромным (не более 100 000 элементов), потому что стек ответов переполняется, согласно ответу jcdude.

Одино - Вельмонт
источник
1
Просто предупреждение от ошибки, которую я только что сделал. Версия ES6 очень близка к arr1.push(arr2). Это может быть проблемой, arr1 = []; arr2=['a', 'b', 'd']; arr1.push(arr2)в результате чего получается массив массивов, arr1 == [['a','b','d']]а не два вместе взятых. Это простая ошибка. По этой причине я предпочитаю ваш второй ответ ниже. stackoverflow.com/a/31521404/4808079
Сеф Рид
При использовании удобно .concat, что второй аргумент не должен быть массивом. Это может быть достигнуто путем объединения оператора распространения с concat, напримерarr1.push(...[].concat(arrayOrSingleItem))
Стивен Pribilinskiy
Это современный элегантный Javascript для случаев, когда целевой массив не пустой.
Тимбо
Это достаточно быстро?
Roel
@RoelDelosReyes: Да.
Одино - Велмонт
46

Сначала несколько слов о apply()JavaScript, чтобы понять, почему мы его используем:

apply()Метод вызывает функцию с заданным thisзначением, и при условии , аргументы в виде массива.

Push ожидает список элементов для добавления в массив. Однако apply()метод принимает ожидаемые аргументы для вызова функции в виде массива. Это позволяет нам легко помещать pushэлементы одного массива в другой с помощью встроенного push()метода.

Представьте, что у вас есть эти массивы:

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

и просто сделайте это:

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

Результатом будет:

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

То же самое можно сделать в ES6 с помощью оператора распространения (" ...") следующим образом:

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

Короче и лучше, но не полностью поддерживается на данный момент во всех браузерах.

Также, если вы хотите переместить все из массива bв aпустую bв процессе, вы можете сделать это:

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

и результат будет следующим:

a = [1, 2, 3, 4, 5, 6, 7];
b = [];
Алиреза
источник
1
или while (b.length) { a.push(b.shift()); }правильно?
LarsW
37

Если вы хотите использовать jQuery, есть $ .merge ()

Пример:

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

Результат: a = [1, 2, 3, 4, 5]

Жюль Колле
источник
1
Как я могу найти реализацию (в исходном коде) jQuery.merge()?
ma11hew28
Это заняло у меня 10 минут - jQuery с открытым исходным кодом и опубликован на Github. Я выполнил поиск «слияния» и далее обнаружил то, что, по-видимому, было определением функции слияния в файле core.js, см. По адресу: github.com/jquery/jquery/blob/master/src/core.js#L331.
атп
19

Мне нравится a.push.apply(a, b)метод, описанный выше, и если вы хотите, вы всегда можете создать библиотечную функцию следующим образом:

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

и использовать это так

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

a.append(b)
Пиццайола Горгонзола
источник
6
Метод push.apply не должен использоваться, поскольку он может вызвать переполнение стека (и, следовательно, сбой), если ваш аргумент «массив» является большим массивом (например,> ~ 150000 записей в Chrome). Вы должны использовать "array.forEach" - см. Мой ответ: stackoverflow.com/a/17368101/1280629
jcdude
12

Это можно сделать с помощью splice():

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

Но несмотря на то, что он уродлив, он не быстрее push.apply, по крайней мере, в Firefox 3.0.

Рафал Доугирд
источник
9
Я обнаружил то же самое: сплайс не обеспечивает повышения производительности для каждого элемента до тех пор, пока не будет около 10000 массивов элементов. Jsperf.com/splice-vs-push
Дрю
1
+1 Спасибо за добавление этого и сравнения производительности, сэкономил мне усилия на тестировании этого метода.
2012 года
1
При использовании splice.apply или push.apply может произойти сбой из-за переполнения стека, если массив b большой. Они также работают медленнее, чем использование цикла for или forEach - см. JsPerf: jsperf.com/array-extending-push-vs-concat/5 и мой ответ stackoverflow.com/questions/1374126/…
jcdude
4

Это решение работает для меня (используя оператор распространения 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

Вы можете создать Polyfill для расширения, как у меня ниже. Это добавит в массив; на месте и вернуть себя, так что вы можете связать другие методы.

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

Объединяя ответы ...

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
источник
9
Нет, в этом нет необходимости. Цикл for, использующий forEach, быстрее, чем использование push.apply, и работает независимо от длины расширяемого массива. Взгляните на мой исправленный ответ: stackoverflow.com/a/17368101/1280629 В любом случае, как вы узнаете, что 150000 - это правильное число для использования во всех браузерах? Это выдумка.
jcdude
лол. мой ответ до неузнаваемости, но, кажется, это какая-то комбинация других найденных в то время - то есть резюме. не беспокойтесь
zCoder
0

Еще одно решение для объединения более двух массивов

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

Затем позвоните и распечатайте как:

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

Выход будет: Array [1, 2, 3, 4, 5, 6, 7]

user3126309
источник
-1

Ответ супер прост.

>>> 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 действует очень похоже на конкатенацию строк JavaScript. Он вернет комбинацию параметра, который вы поместили в функцию concat в конце массива, для которого вы вызываете функцию. Суть в том, что вы должны присвоить возвращаемое значение переменной, иначе оно будет потеряно. Так например

a.concat(b);  <--- This does absolutely nothing since it is just returning the combined arrays, but it doesn't do anything with it.
Тимоти Трусдейл
источник
2
Как четко указано в документации для Array.prototype.concat()MDN, это вернет новый массив , он не будет добавлен к существующему массиву, как явно просил OP.
Уил
-2

Используйте Array.extendвместо Array.push> 150000 записей.

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

Супер просто, не рассчитывать на операторов спреда или применять, если это проблема.

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

После выполнения некоторых тестов производительности это очень медленно, но отвечает на вопрос о том, чтобы не создавать новый массив. Concat работает значительно быстрее, даже jQuery $.merge()делает это.

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

elPastor
источник
4
Вы должны использовать, .forEachа не .mapесли вы не используете возвращаемое значение. Это лучше передает смысл вашего кода.
Дэвид
@ Давид - каждому свое. Я предпочитаю Синакс, .mapно вы также можете сделать b.forEach(function(x) { a.push(x)} ). На самом деле я добавил это в тест jsperf, и это на волосок быстрее, чем map. Все еще супер медленно.
elPastor
3
Это не случай предпочтения. Эти «функциональные» методы имеют специфическое значение, связанное с ними. Звонить .mapсюда выглядит очень странно. Это было бы как звонить b.filter(x => a.push(x)). Это работает, но это сбивает с толку читателя, подразумевая, что что-то происходит, когда это не так.
Дэвид
2
@elPastor это должно стоить вашего времени, потому что какой-то другой парень, как я, сидел бы и царапал затылок, задаваясь вопросом, что еще происходит там в вашем коде, который он не может видеть. В большинстве обычных случаев важна ясность намерений, а не производительность. Пожалуйста, уважайте время тех, кто читает ваш код, потому что их может быть много, когда вы один. Спасибо.
Дмитрий Юрьевич
1
@elPastor Я точно знаю, что .map()делает, и это проблема. Это знание заставит меня думать, что вам нужен результирующий массив, иначе было бы неплохо использовать его, .forEach()чтобы заставить читателя быть осторожным с побочными эффектами функции обратного вызова. Строго говоря, ваше решение создает дополнительный массив натуральных чисел (результаты push), а вопрос гласит: «без создания нового массива».
Дмитрий Юрьевич