Як розширити існуючий масив JavaScript іншим масивом, не створюючи новий масив


970

Здається, не існує способу розширити існуючий масив 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).

Примітка. Це не дублікат того, як додати щось до масиву? - мета тут - додати весь вміст одного масиву до іншого і зробити це "на місці", тобто без копіювання всіх елементів розширеного масиву.


5
Від @ коментаря зубної щітки на відповідь: a.push(...b). Це за концепцією схоже на головну відповідь, але оновлено для ES6.
tscizzle

Відповіді:


1497

.pushМетод може приймати кілька аргументів. Ви можете використовувати оператор розповсюдження для передачі всіх елементів другого масиву як аргументів .push:

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

Якщо ваш браузер не підтримує ECMAScript 6, ви можете використовувати .applyнатомість:

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

А може, якщо ви думаєте, що це зрозуміліше:

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

Зверніть увагу, що всі ці рішення вийдуть з ладу з помилкою переповнення стека, якщо масив bзанадто довгий (проблема починається приблизно з 100 000 елементів, залежно від браузера).
Якщо ви не можете гарантувати, що bце досить коротко, вам слід скористатися стандартною технікою на основі циклу, описаною в іншій відповіді.


3
Я думаю, що це ваша найкраща ставка. Все інше буде пов'язане з ітерацією чи іншим навантаженням на застосування ()
Пітер Бейлі,

44
Ця відповідь буде невдалою, якщо "b" (масив, який слід розширити на), великий (> 150000 записів, приблизно в Chrome відповідно до моїх тестів). Ви повинні використовувати цикл for, або ще краще використовувати вбудовану функцію "forEach" на "b". Дивіться моя відповідь: stackoverflow.com/questions/1374126 / ...
jcdude

35
@Deqing: pushметод Array може приймати будь-яку кількість аргументів, які потім висуваються на задню частину масиву. Отже 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).
niry

259

Оновлення 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
Питання чітко проголошувало: "без створення нового масиву?"
Wilt

10
@Wilt: Це правда, у відповіді сказано, чому хоч :) Це вперше потрапило в Google. Також інші рішення були некрасивими, хотіли чогось ідейного та приємного. Хоча сьогодні arr.push(...arr2)це новіше та краще, і це технічно правильне (tm) відповідь на це конкретне питання.
odinho - Велмонт

7
Я чую вас, але я шукав "javascript-масив додавання без створення нового масиву" , то сумно бачити, що відповідь, що відповідає високій нагорі в 60 разів, не відповідає дійсному питанню; оскільки ви зрозуміли у своїй відповіді.
Зітхне

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)"!


1
пс для випробування дійсно other_array є масивом, виберіть один з варіантів , описаних тут: stackoverflow.com/questions/767486 / ...
jcdude

6
Хороша відповідь. Я не знав цієї проблеми. Я тут цитував вашу відповідь: stackoverflow.com/a/4156156/96100
Tim Down

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

152

Я вважаю себе найелегантнішим сьогодні:

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
Seph Reed

Зручність при використанні .concatполягає в тому, що другий аргумент не повинен бути масивом. Це може бути досягнуто шляхом об'єднання оператора поширення з concat, наприкладarr1.push(...[].concat(arrayOrSingleItem))
Стівен Pribilinskiy

Це сучасний, елегантний Javascript для випадків, коли цільовий масив не порожній.
тембо

Це досить швидко?
Roel

@RoelDelosReyes: Так.
odinho - Велмонт

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
Я виявив те ж саме, сплайс не забезпечує підвищення продуктивності для натискання кожного елемента, поки приблизно 10 000 масивів елементів jsperf.com/splice-vs-push
Дрю

1
+1 Дякую за додавання цього та порівняння продуктивності, врятувало мене від тестування цього методу.
jjrv

1
Використання splice.apply або push.apply може не вдатися через переповнення стека, якщо масив b великий. Вони також повільніше , ніж використання «для» або петлі «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

Ви можете створити поліфіл для розширення, як у мене нижче. Він додасть до масиву; на місці і повернути себе, щоб ви могли застосувати інші методи.

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

9
Ні, в цьому немає необхідності. Для циклу використання 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]


-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, це поверне новий масив , він не додаватиметься до існуючого масиву, оскільки ОП явно просив.
Заповіт

-2

Використовуйте Array.extendзамість Array.pushдля> 150 000 записів.

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


4
Ви повинні використовувати, .forEachа не .mapякщо ви не використовуєте значення повернення. Це краще передає наміри вашого коду.
Девід

@david - кожному своє. Я віддаю перевагу синаксисі, .mapале ви також можете це зробити b.forEach(function(x) { a.push(x)} ). Насправді я додав, що до тесту jsperf це волосся швидше, ніж карта. Ще супер повільно.
elPastor

3
Це не випадок переваги. Кожен з цих "функціональних" методів має специфічне значення, пов'язане з ними. Телефонувати .mapсюди виглядає дуже дивно. Це було б як дзвонити b.filter(x => a.push(x)). Це працює, але бентежить читача, маючи на увазі, що щось відбувається, коли це не так.
Девід

2
@elPastor це повинно коштувати вашого часу, тому що якийсь інший хлопець, як я, сидів би і дряпав потилицю, цікавившись, що ще там відбувається у вашому коді, він не бачить. У більшості звичайних випадків важлива чіткість намірів, а не виконання. Будь ласка, поважайте час тих, хто читає ваш код, бо їх може бути багато, коли ви лише один. Дякую.
Дмитро Ю.

1
@elPastor Я точно знаю, що .map()робить, і це питання. Ці знання змусять мене думати, що вам потрібен масив, інакше було б корисно використовувати .forEach()читача обережність щодо побічних ефектів функції зворотного виклику. Строго кажучи, ваше рішення створює додатковий масив натуральних чисел (результатів push), тоді як у запитанні зазначено: "без створення нового масиву".
Дмитро Ю.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.