Cách mở rộng một mảng JavaScript hiện có với một mảng khác mà không cần tạo một mảng mới


973

Dường như không có cách nào để mở rộng một mảng JavaScript hiện có với một mảng khác, tức là mô phỏng extendphương thức của Python .

Tôi muốn đạt được những điều sau đây:

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

Tôi biết có một a.concat(b)phương thức, nhưng nó tạo ra một mảng mới thay vì chỉ mở rộng cái đầu tiên. Tôi muốn một thuật toán hoạt động hiệu quả khi alớn hơn đáng kể so với b( thuật toán không sao chép a).

Lưu ý: Đây không phải là bản sao của Cách nối một thứ gì đó vào một mảng? - mục tiêu ở đây là thêm toàn bộ nội dung của một mảng vào mảng khác và thực hiện "tại chỗ", tức là không sao chép tất cả các thành phần của mảng mở rộng.


5
Từ nhận xét của @ Bàn chải đánh răng về một câu trả lời : a.push(...b). Khái niệm này tương tự như câu trả lời hàng đầu, nhưng được cập nhật cho ES6.
tscizzle

Câu trả lời:


1500

Các .pushphương pháp có thể mất nhiều tranh cãi. Bạn có thể sử dụng toán tử trải để truyền tất cả các phần tử của mảng thứ hai làm đối số cho .push:

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

Nếu trình duyệt của bạn không hỗ trợ ECMAScript 6, bạn có thể sử dụng .applythay thế:

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

Hoặc có lẽ, nếu bạn nghĩ nó rõ ràng hơn:

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

Xin lưu ý rằng tất cả các giải pháp này sẽ thất bại với lỗi tràn ngăn xếp nếu mảng bquá dài (sự cố bắt đầu ở khoảng 100.000 phần tử, tùy thuộc vào trình duyệt).
Nếu bạn không thể đảm bảo bđủ ngắn, bạn nên sử dụng kỹ thuật dựa trên vòng lặp tiêu chuẩn được mô tả trong câu trả lời khác.


3
Tôi nghĩ rằng đây là đặt cược tốt nhất của bạn. Bất cứ điều gì khác sẽ liên quan đến việc lặp đi lặp lại hoặc nỗ lực áp dụng khác ()
Peter Bailey

44
Câu trả lời này sẽ thất bại nếu "b" (mảng cần mở rộng) lớn (> 150000 mục nhập trong Chrome theo các thử nghiệm của tôi). Bạn nên sử dụng vòng lặp for hoặc thậm chí tốt hơn là sử dụng chức năng "forEach" sẵn có trên "b". Xem câu trả lời của tôi: stackoverflow.com/questions/1374126/ Cách
jcdude

35
@De Khánh: pushPhương thức của Array có thể lấy bất kỳ số lượng đối số nào, sau đó được đẩy ra phía sau mảng. Vì vậy, a.push('x', 'y', 'z')một cuộc gọi hợp lệ sẽ mở rộng abởi 3 yếu tố. applylà một phương thức của bất kỳ hàm nào lấy một mảng và sử dụng các phần tử của nó như thể tất cả chúng được đưa ra rõ ràng như là các phần tử vị trí cho hàm. Vì vậy, a.push.apply(a, ['x', 'y', 'z'])cũng sẽ mở rộng mảng bằng 3 yếu tố. Ngoài ra, applylấy bối cảnh làm đối số đầu tiên (chúng tôi chuyển alại ở đó để nối thêm a).
DzinX

Lưu ý: trong trường hợp này, giá trị trả về đầu tiên không phải là [1,2,3,4,5] mà là 5 trong khi a == [1,2,3,4,5] sau đó.
jawo

1
Tuy nhiên, một cách gọi khác ít gây nhầm lẫn (và không lâu hơn nữa) sẽ là : [].push.apply(a, b).
niry

259

Cập nhật 2018 : Một câu trả lời tốt hơn là một câu trả lời mới hơn của tôi : a.push(...b). Đừng upvote cái này nữa, vì nó không bao giờ thực sự trả lời câu hỏi, nhưng đó là một bản hack năm 2015 xung quanh lần đầu tiên truy cập vào Google :)


Đối với những người chỉ cần tìm kiếm "mảng JavaScript mở rộng" và đã đến đây, bạn có thể sử dụng rất tốt Array.concat.

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

Concat sẽ trả về một bản sao của mảng mới, vì trình khởi động luồng không muốn. Nhưng bạn có thể không quan tâm (chắc chắn đối với hầu hết các loại sử dụng, điều này sẽ ổn).


Ngoài ra còn có một số đường ECMAScript 6 đẹp cho dạng này dưới dạng toán tử trải:

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

(Nó cũng sao chép.)


43
Câu hỏi được nêu rõ: "không tạo ra một mảng mới?"
Héo

10
@Wilt: Đây là sự thật, câu trả lời cho biết tại sao mặc dù vậy :) Đây là lần đầu tiên xuất hiện trên Google trong một thời gian dài. Ngoài ra các giải pháp khác là xấu xí, muốn một cái gì đó ý thức hệ và tốt đẹp. Mặc dù ngày nay, arr.push(...arr2)phiên bản mới hơn và tốt hơn và là câu trả lời đúng về mặt kỹ thuật (tm) cho câu hỏi cụ thể này.
odinho - Velmont

7
Tôi nghe thấy bạn, nhưng tôi đã tìm kiếm "mảng nối thêm javascript mà không tạo ra một mảng mới" , thật buồn khi thấy câu trả lời được nâng lên 60 lần mà không trả lời được câu hỏi thực tế;) Tôi không downvote, kể từ khi bạn làm cho nó rõ ràng trong câu trả lời của bạn.
Héo

202

Bạn nên sử dụng một kỹ thuật dựa trên vòng lặp. Các câu trả lời khác trên trang này dựa trên việc sử dụng .applycó thể thất bại đối với các mảng lớn.

Một triển khai dựa trên vòng lặp khá ngắn gọn là:

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

Sau đó, bạn có thể làm như sau:

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

Câu trả lời của DzinX (sử dụng push.apply) và các .applyphương thức dựa trên khác không thành công khi mảng mà chúng tôi đang nối thêm lớn (các thử nghiệm cho thấy đối với tôi lớn là> 150.000 mục nhập trong Chrome và> 500.000 mục trong Firefox). Bạn có thể thấy lỗi này xảy ra trong jsperf này .

Xảy ra lỗi vì kích thước ngăn xếp cuộc gọi bị vượt quá khi 'Function.prototype.apply' được gọi với một mảng lớn làm đối số thứ hai. (MDN có một lưu ý về sự nguy hiểm của việc vượt quá kích thước ngăn xếp cuộc gọi bằng cách sử dụng Function.prototype.apply - xem phần có tiêu đề "các hàm áp dụng và tích hợp".)

Để so sánh tốc độ với các câu trả lời khác trên trang này, hãy xem jsperf này (nhờ EaterOfCode). Việc thực hiện dựa trên vòng lặp có tốc độ tương tự như sử dụng Array.push.apply, nhưng có xu hướng chậm hơn một chút so với Array.slice.apply.

Thật thú vị, nếu mảng bạn đang nối thêm thưa thớt, forEachphương thức dựa trên có thể tận dụng độ thưa thớt và vượt trội so với các .applyphương thức dựa trên; kiểm tra jsperf này nếu bạn muốn tự mình kiểm tra điều này.

Nhân tiện, đừng bị cám dỗ (như tôi đã từng!) Để rút ngắn hơn nữa việc thực hiện forEach thành:

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

bởi vì điều này tạo ra kết quả rác! Tại sao? Bởi vì Array.prototype.forEachcung cấp ba đối số cho hàm mà nó gọi - đó là: (Element_value, Element_index, source_array). Tất cả những thứ này sẽ được đẩy lên mảng đầu tiên của bạn cho mỗi lần lặp forEachnếu bạn sử dụng "forEach (this.push, this)"!


1
ps để kiểm tra xem other_array có thực sự là một mảng hay không, chọn một trong các tùy chọn được mô tả ở đây: stackoverflow.com/questions/767486/
mẹo

6
Câu trả lời tốt. Tôi đã không nhận thức được vấn đề này. Tôi đã trích dẫn câu trả lời của bạn ở đây: stackoverflow.com/a/4156156/96100
Tim Down

2
.push.applythực sự nhanh hơn nhiều so với .forEachtrong v8, nhanh nhất vẫn là một vòng lặp nội tuyến.
Benjamin Gruenbaum

1
@BenjaminGruenbaum - bạn có thể gửi một liên kết đến một số kết quả cho thấy điều đó? Theo như tôi có thể thấy từ các kết quả được thu thập tại jsperf được liên kết ở cuối nhận xét này, việc sử dụng .forEachnhanh hơn .push.applytrong Chrome / Chromium (trong tất cả các phiên bản kể từ v25). Tôi đã không thể kiểm tra v8 một cách cô lập, nhưng nếu bạn vui lòng liên kết kết quả của mình. Xem jsperf: jsperf.com/array-extending-push-vs-concat/5
jcdude

1
@jcdude jsperf của bạn không hợp lệ. vì tất cả các phần tử trong mảng của bạn là undefined, vì vậy .forEachsẽ bỏ qua chúng, làm cho nó nhanh nhất. .splicethực sự là nhanh nhất?! jsperf.com/array-extending-push-vs-concat/18
EaterOfCode

153

Tôi cảm thấy thanh lịch nhất những ngày này là:

arr1.push(...arr2);

Các bài viết MDN trên hành lan rộng đề cập đến cách này có đường ngơi thoải mái tại ES2015 (ES6):

Đẩy tốt hơn

Ví dụ: đẩy thường được sử dụng để đẩy một mảng đến cuối của một mảng hiện có. Trong ES5, điều này thường được thực hiện như sau:

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

Trong ES6 với sự lây lan, điều này trở thành:

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

Xin lưu ý rằng arr2không thể lớn (giữ dưới 100 000 mục), vì ngăn xếp cuộc gọi tràn ra, theo câu trả lời của jcdude.


1
Chỉ là một cảnh báo từ một sai lầm tôi vừa thực hiện. Phiên bản ES6 rất gần arr1.push(arr2). Đây có thể là một vấn đề như vậy vì vậy arr1 = []; arr2=['a', 'b', 'd']; arr1.push(arr2)kết quả là một mảng các mảng arr1 == [['a','b','d']]chứ không phải là hai mảng kết hợp. Đó là một sai lầm dễ dàng để thực hiện. Tôi thích câu trả lời thứ hai của bạn dưới đây vì lý do này. stackoverflow.com/a/31521404/4808079
Seph Reed

Có một thuận tiện khi sử dụng .concatlà đối số thứ hai không phải là một mảng. Điều này có thể đạt được bằng cách kết hợp toán tử lây lan với concat, ví dụarr1.push(...[].concat(arrayOrSingleItem))
Steven Pribilinskiy

Đây là Javascript hiện đại, thanh lịch cho các trường hợp mảng mục tiêu không trống.
timbo

Điều này có đủ nhanh không?
Roel

@RoelDelosReyes: Có.
odinho - Velmont

46

Trước tiên, một vài từ về apply()JavaScript để giúp hiểu lý do tại sao chúng tôi sử dụng nó:

Các apply()phương pháp gọi một hàm với một định thisgiá trị, và đối số cung cấp như một mảng.

Push mong đợi một danh sách các mục để thêm vào mảng. Các apply()phương pháp, tuy nhiên, có những lập luận dự kiến cho cuộc gọi chức năng như một mảng. Điều này cho phép chúng ta dễ dàng pushcác phần tử của một mảng thành một mảng khác với push()phương thức dựng sẵn .

Hãy tưởng tượng bạn có những mảng này:

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

và chỉ cần làm điều này:

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

Kết quả sẽ là:

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

Điều tương tự có thể được thực hiện trong ES6 bằng cách sử dụng toán tử trải (" ...") như thế này:

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

Hiện tại ngắn hơn và tốt hơn nhưng không được hỗ trợ đầy đủ trong tất cả các trình duyệt.

Ngoài ra nếu bạn muốn chuyển mọi thứ từ mảng bsang a, làm trống btrong quy trình, bạn có thể làm điều này:

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

và kết quả sẽ như sau:

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

1
hay while (b.length) { a.push(b.shift()); }, phải không?
LarsW

37

Nếu bạn muốn sử dụng jQuery, có $ .merge ()

Thí dụ:

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

Kết quả: a = [1, 2, 3, 4, 5]


1
Làm thế nào tôi có thể tìm thấy việc thực hiện (trong mã nguồn) của jQuery.merge()?
ma11hew28

Mất 10 phút - jQuery là nguồn mở và nguồn được xuất bản trên Github. Tôi đã thực hiện tìm kiếm "hợp nhất" và tìm hiểu thêm về định nghĩa của hàm hợp nhất trong tệp core.js, xem: github.com/jquery/jquery/blob/master/src/core.js#L331
Amn

19

Tôi thích a.push.apply(a, b)phương pháp được mô tả ở trên và nếu bạn muốn, bạn luôn có thể tạo một hàm thư viện như thế này:

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

và sử dụng nó như thế này

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

a.append(b)

6
Không nên sử dụng phương thức push.apply vì nó có thể gây ra tràn ngăn xếp (và do đó không thành công) nếu đối số "mảng" của bạn là một mảng lớn (ví dụ> ~ 150000 mục trong Chrome). Bạn nên sử dụng "mảng.forEach" - xem câu trả lời của tôi: stackoverflow.com/a/17368101/1280629
jcdude

12

Có thể làm điều đó bằng cách sử dụng splice():

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

Nhưng mặc dù xấu hơn nhưng nó không nhanh hơn push.apply, ít nhất là không có trong Firefox 3.0.


9
Tôi đã tìm thấy điều tương tự, mối nối không cung cấp những cải tiến hiệu suất để đẩy từng hạng mục cho đến khoảng 10.000 mảng yếu tố jsperf.com/splice-vs-push
Drew

1
+1 Cảm ơn bạn đã thêm điều này và so sánh hiệu suất, đã tiết kiệm cho tôi nỗ lực thử nghiệm phương pháp này.
jjrv

1
Sử dụng splice.apply hoặc push.apply có thể không thành công do tràn ngăn xếp nếu mảng b lớn. Họ cũng là chậm hơn so với sử dụng một "cho" hoặc "foreach" loop - xem jsPerf này: jsperf.com/array-extending-push-vs-concat/5 và câu trả lời của tôi stackoverflow.com/questions/1374126/...
jcdude

4

Giải pháp này hiệu quả với tôi (sử dụng toán tử trải rộng của 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

Bạn có thể tạo một polyfill để mở rộng như tôi có bên dưới. Nó sẽ thêm vào mảng; tại chỗ và trả về chính nó, để bạn có thể xâu chuỗi các phương thức khác.

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

Kết hợp các câu trả lời ...

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
Không, không cần phải làm điều này. Vòng lặp for sử dụng forEach nhanh hơn so với sử dụng push.apply và hoạt động bất kể độ dài của mảng cần kéo dài là bao nhiêu. Hãy xem câu trả lời sửa đổi của tôi: stackoverflow.com/a/17368101/1280629 Trong mọi trường hợp, làm thế nào để bạn biết rằng 150000 là con số phù hợp để sử dụng cho tất cả các trình duyệt? Đây là một sự sai lầm.
jcdude

cười lớn. Câu trả lời của tôi là không thể nhận ra, nhưng dường như là một số kết hợp của những người khác được tìm thấy tại thời điểm đó - tức là một bản tóm tắt. không phải lo lắng
zCoder

0

Một giải pháp khác để hợp nhất nhiều hơn hai mảng

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

Sau đó gọi và in như:

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

Đầu ra sẽ là: Array [1, 2, 3, 4, 5, 6, 7]


-1

Câu trả lời là siêu đơn giản.

>>> 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 hoạt động rất giống với nối chuỗi JavaScript. Nó sẽ trả về một tổ hợp tham số bạn đặt vào hàm concat ở cuối mảng bạn gọi hàm trên. Điểm mấu chốt là bạn phải gán giá trị trả về cho một biến hoặc nó bị mất. Ví dụ

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

2
Như đã nêu rõ trong các tài liệu Array.prototype.concat()tại MDN, điều này sẽ trả về một Mảng mới , nó sẽ không nối vào mảng hiện tại như OP đã hỏi rõ ràng.
Héo

-2

Sử dụng Array.extendthay vì Array.pushcho 150.000 hồ sơ.

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

Siêu đơn giản, không tính vào các toán tử lây lan hoặc áp dụng, nếu đó là một vấn đề.

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

Sau khi chạy một số bài kiểm tra hiệu năng về điều này, nó rất chậm, nhưng trả lời câu hỏi liên quan đến việc không tạo ra một mảng mới. Concat nhanh hơn đáng kể, ngay cả jQuery cũng kiểm tra $.merge()nó.

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


4
Bạn nên sử dụng .forEachthay vì .mapnếu bạn không sử dụng giá trị trả về. Nó tốt hơn truyền đạt ý định của mã của bạn.
david

@david - để riêng của họ. Tôi thích synax của .mapnhưng bạn cũng có thể làm b.forEach(function(x) { a.push(x)} ). Trong thực tế, tôi đã thêm nó vào thử nghiệm jsperf và nó nhanh hơn bản đồ. Vẫn siêu chậm.
elPastor

3
Đây không phải là một trường hợp ưu tiên. Mỗi phương thức 'chức năng' này đều có một ý nghĩa cụ thể liên quan đến chúng. Gọi .mapở đây trông rất kỳ lạ. Nó sẽ giống như gọi b.filter(x => a.push(x)). Nó hoạt động, nhưng nó gây nhầm lẫn cho người đọc bằng cách ngụ ý điều gì đó đang xảy ra khi nó không.
david

2
@elPastor nó đáng để bạn dành thời gian, bởi vì một số người khác như tôi sẽ ngồi gãi gáy tự hỏi có gì khác đang xảy ra trong mã của bạn mà anh ta không thể nhìn thấy. Trong hầu hết các trường hợp thông thường, đó là sự rõ ràng của ý định quan trọng, không phải hiệu suất. Hãy tôn trọng thời gian của những người đọc mã của bạn, vì họ có thể là nhiều khi bạn chỉ là một. Cảm ơn.
Dmytro Y.

1
@elPastor Tôi biết chính xác những gì .map()và đó là vấn đề. Kiến thức này sẽ khiến tôi nghĩ rằng bạn cần mảng kết quả, nếu không nó sẽ là một điều tốt để sử dụng .forEach()để làm cho người đọc thận trọng về tác dụng phụ của chức năng gọi lại. Nói một cách chính xác, giải pháp của bạn tạo ra một mảng số tự nhiên (kết quả push), trong khi câu hỏi nêu rõ: "không tạo ra một mảng mới".
Dmytro Y.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.