Câu hỏi Sao chép mảng theo giá trị trong JavaScript


Khi sao chép một mảng trong JavaScript sang mảng khác:

var arr1 = ['a','b','c'];
var arr2 = arr1;
arr2.push('d');  //Now, arr1 = ['a','b','c','d']

Tôi nhận ra rằng arr2 đề cập đến cùng một mảng với arr1, chứ không phải mảng mới, độc lập. Làm thế nào tôi có thể sao chép mảng để có được hai mảng độc lập?


1345
2017-09-20 13:38


gốc


Có vẻ như trong Chrome 53 và Firefox 48, chúng tôi có hiệu suất tuyệt vời cho slice và splice hoạt động và nhà điều hành lan truyền mới và Array.from có triển khai chậm hơn nhiều. Nhìn vào perfjs.fnfo - Pencroff
jsben.ch/#/wQ9RU <= điểm chuẩn này cung cấp tổng quan về các cách khác nhau để sao chép mảng - EscapeNetscape
jsperf.com/flat-array-copy - Walle Cyril
Đối với mảng này (một trong đó có chứa các chuỗi nguyên thủy), bạn có thể sử dụng var arr2 = arr1.splice(); để sao chép sâu, nhưng kỹ thuật này sẽ không hoạt động nếu các phần tử trong mảng của bạn chứa các cấu trúc theo nghĩa đen (tức là [] hoặc là {}) hoặc các đối tượng nguyên mẫu (tức là function () {}, new, v.v.) Xem câu trả lời của tôi bên dưới để biết thêm giải pháp. - tfmontague
Đó là năm 2017, vì vậy bạn có thể cân nhắc sử dụng các tính năng ES6: let arr2 = [...arr1];  developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… - Hinrich


Các câu trả lời:


Dùng cái này:

var newArray = oldArray.slice();

Về cơ bản, slice () hoạt động nhân bản mảng và trả về tham chiếu đến mảng mới. Cũng lưu ý rằng:

Đối với các tham chiếu, các chuỗi và các số (và không phải là đối tượng thực tế), slice sao chép các tham chiếu đối tượng vào mảng mới. Cả mảng ban đầu và mảng mới đều tham chiếu đến cùng một đối tượng. Nếu một đối tượng tham chiếu thay đổi, các thay đổi sẽ hiển thị cho cả mảng mới và mảng gốc.

Các nguyên tố như chuỗi và số không thay đổi nên các thay đổi đối với chuỗi hoặc số là không thể.


2161
2017-09-20 13:41



Về hiệu suất các kiểm tra jsPerf sau đây thực sự cho thấy rằng var arr2 = arr1.slice () chỉ là nhanh như var arr2 = arr1.concat (); JSPerf: jsperf.com/copy-array-slice-vs-concat/5 và jsperf.com/copy-simple-array . Kết quả của jsperf.com/array-copy/5 làm tôi ngạc nhiên đến mức tôi tự hỏi liệu mã kiểm thử có hợp lệ hay không. - Cohen
Mặc dù điều này đã nhận được một tấn upvotes, nó xứng đáng khác vì nó mô tả đúng tài liệu tham khảo trong JS, đó là loại hiếm, không may. - Wayne Burkett
@ GáborImre bạn muốn thêm toàn bộ thư viện chỉ để dễ đọc? Có thật không? Tôi chỉ cần thêm một bình luận nếu tôi quan tâm đến khả năng đọc. Xem: var newArray = oldArray.slice (); // Clone oldArray vào newArray - dudewad
@ GáborImre tôi nhận được điều đó, chắc chắn. Nhưng trả lời một vấn đề kỹ thuật cụ thể bằng cách bao gồm toàn bộ thư viện theo ý kiến ​​của tôi là không hữu ích, đó là thiết kế bloat. Tôi thấy các nhà phát triển làm điều đó rất nhiều, và sau đó bạn kết thúc với một dự án bao gồm toàn bộ khuôn khổ để thay thế việc phải viết một hàm duy nhất. Tuy nhiên, tôi chỉ là M.O. - dudewad
@dudewad Nói chung bạn đúng. Trong trường hợp này, toàn bộ trải nghiệm JS của tôi đề xuất sử dụng lib này trong hầu như mọi dự án. Lodash cho mảng hoặc đối tượng, như Moment cho các ngày, như Express cho máy chủ NodeJS, như Angular hoặc React cho giao diện người dùng. - Gábor Imre


Trong Javascript, các kỹ thuật sao chép sâu phụ thuộc vào các phần tử trong một mảng.
Hãy bắt đầu ở đó.

Ba loại yếu tố

Các phần tử có thể là: giá trị chữ, cấu trúc theo nghĩa đen hoặc nguyên mẫu.

// Literal values (type1)
var booleanLiteral = true;
var numberLiteral = 1;
var stringLiteral = 'true';

// Literal structures (type2)
var arrayLiteral = [];
var objectLiteral = {};

// Prototypes (type3)
var booleanPrototype = new Bool(true);
var numberPrototype = new Number(1);
var stringPrototype = new String('true');
var arrayPrototype = new Array();
var objectPrototype = new Object(); # or "new function () {}"

Từ những phần tử này, chúng ta có thể tạo ba kiểu mảng.

// 1) Array of literal-values (boolean, number, string) 
var type1 = [true, 1, "true"];

// 2) Array of literal-structures (array, object)
var type2 = [[], {}];

// 3) Array of prototype-objects (function)
var type3 = [function () {}, function () {}];

Kỹ thuật sao chép sâu phụ thuộc vào ba loại mảng

Dựa trên các loại phần tử trong mảng, chúng ta có thể sử dụng các kỹ thuật khác nhau để sao chép sâu.

Javascript deep copy techniques by element types

  • Mảng giá trị chữ (loại1)
    Các myArray.splice(0), myArray.slice()myArray.concat() kỹ thuật có thể được sử dụng để mảng bản sao sâu với các giá trị bằng chữ (boolean, number, và string); nơi Slice có hiệu suất cao hơn Concat (http://jsperf.com/duplicate-array-slice-vs-concat/3).

  • Mảng giá trị chữ (loại1) và cấu trúc chữ (loại2)
    Các JSON.parse(JSON.stringify(myArray)) kỹ thuật có thể được sử dụng để sao chép sâu các giá trị văn bản (boolean, number, string) và các cấu trúc chữ (mảng, đối tượng), nhưng không phải là các đối tượng nguyên mẫu.

  • Tất cả các mảng (type1, type2, type3)
    JQuery $.extend(myArray) kỹ thuật có thể được sử dụng để sao chép sâu tất cả các loại mảng. Thư viện như Dấu gạch dưới và Lo-dash cung cấp các chức năng sao chép sâu tương tự jQuery  $.extend(), nhưng có hiệu suất thấp hơn. Đáng ngạc nhiên hơn, $.extend() có hiệu suất cao hơn JSON.parse(JSON.stringify(myArray)) kỹ thuật http://jsperf.com/js-deep-copy/15.
    Và đối với những nhà phát triển né tránh thư viện của bên thứ ba (như jQuery), bạn có thể sử dụng chức năng tùy chỉnh sau; có hiệu suất cao hơn $ .extend và sao chép sâu tất cả các mảng.

    function copy(o) {
      var output, v, key;
      output = Array.isArray(o) ? [] : {};
      for (key in o) {
        v = o[key];
        output[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
      }
      return output;
    }  
    

Vì vậy, để trả lời câu hỏi ...

Câu hỏi 

var arr1 = ['a','b','c'];
var arr2 = arr1;

Tôi nhận ra rằng arr2 đề cập đến cùng một mảng như arr1, chứ không phải là một   mảng độc lập mới. Làm thế nào tôi có thể sao chép mảng để có được hai   mảng độc lập?

Câu trả lời 

Bởi vì arr1 là một mảng các giá trị theo nghĩa đen (số boolean, số hoặc chuỗi), bạn có thể sử dụng bất kỳ kỹ thuật sao chép sâu nào được thảo luận ở trên, slice có hiệu suất cao nhất.

// Highest performance for deep copying literal values
arr2 = arr1.slice();

// Any of these techniques will deep copy literal values as well,
//   but with lower performance.
arr2 = arr1.splice(0);
arr2 = arr1.concat();
arr2 = JSON.parse(JSON.stringify(arr1));
arr2 = $.extend(true, [], arr1); // jQuery.js needed
arr2 = _.extend(arr1); // Underscore.js needed
arr2 = _.cloneDeep(arr1); // Lo-dash.js needed
arr2 = copy(arr1); // Custom-function needed - as provided above

365
2018-05-08 08:36



Câu trả lời của Saket không sử dụng splice nó sử dụng slice. Rất khác nhau - James Montagne
Nhiều cách tiếp cận này không hoạt động tốt. Sử dụng toán tử gán có nghĩa là bạn phải gán lại giá trị gốc của arr1. Rất hiếm khi đó là trường hợp. Sử dụng splice xóa bỏ arr1, do đó, đó không phải là một bản sao cả. Sử dụng JSON sẽ thất bại nếu bất kỳ giá trị nào trong mảng là hàm hoặc có nguyên mẫu (chẳng hạn như Date). - Dancrumb
Tôi nghĩ rằng đây sẽ là câu trả lời đúng cho câu hỏi. slice, concat có vấn đề - Manu M
Tại sao ghép (0)? Nó không phải là slice ()? Tôi nghĩ rằng nó không phải là để sửa đổi mảng ban đầu, mà mối nối nào. @JamesMontagne - helpse
mối nối sẽ tạo ra con trỏ đến các phần tử trong mảng ban đầu (bản sao nông). splice (0) sẽ cấp phát bộ nhớ mới (bản sao sâu) cho các phần tử trong mảng là số hoặc chuỗi và tạo con trỏ cho tất cả các loại phần tử khác (bản sao nông). Bằng cách chuyển giá trị bắt đầu bằng 0 cho phương thức hàm nối, nó sẽ không ghép bất kỳ phần tử nào khỏi mảng ban đầu, và do đó nó không sửa đổi nó. - tfmontague


Không cần jQuery ... Ví dụ làm việc

var arr2 = arr1.slice()

Điều này sao chép mảng từ vị trí bắt đầu 0 thông qua phần cuối của mảng.

Điều quan trọng cần lưu ý là nó sẽ hoạt động như mong đợi đối với các kiểu nguyên thủy (chuỗi, số, vv), và cũng giải thích hành vi mong đợi đối với các kiểu tham chiếu ...

Nếu bạn có một loạt các kiểu tham chiếu, hãy nói kiểu Object. Mảng sẽ được sao chép, nhưng cả hai mảng sẽ chứa tham chiếu đến cùng Object'S. Vì vậy, trong trường hợp này có vẻ như mảng được sao chép bằng tham chiếu mặc dù mảng thực sự được sao chép.


146
2017-09-20 13:39



Cảm ơn ví dụ. Tôi tò mò, đây là loại sao chép được gọi là một bản sao sâu? - ayjay
Không, đây không phải là một bản sao sâu. - jondavidjohn


Bạn có thể sử dụng chênh lệch mảng ... để sao chép mảng.

const itemsCopy = [...items];

Ngoài ra nếu muốn tạo một mảng mới với mảng hiện tại là một phần của nó:

var parts = ['shoulders', 'knees'];
var lyrics = ['head', ...parts, 'and', 'toes'];

Mảng lây lan hiện nay được hỗ trợ trong tất cả các trình duyệt chính nhưng nếu bạn cần sử dụng hỗ trợ kiểu cũ hơn hoặc babel và biên dịch sang ES5.

Thông tin thêm về spread


138
2017-07-03 14:46



Điều này nên được upvoted - SobiborTreblinka
Hoan hô cho câu trả lời ES6. - reid
Các lát nên được phản đối so với các nhà điều hành lan rộng toàn năng! - Chang
Ahh, đây là những gì tôi đang tìm kiếm. - Hinrich
chỉ là một nhận xét, điều này chỉ hoạt động cho các loại nguyên thủy, mảng các đối tượng sẽ được sao chép nông - gajo


Một thay thế cho slice Là concat, có thể được sử dụng theo 2 cách. Việc đầu tiên trong số này có lẽ dễ đọc hơn vì hành vi dự định rất rõ ràng:

var array2 = [].concat(array1);

Phương pháp thứ hai là:

var array2 = array1.concat();

Cohen (trong các ý kiến) chỉ ra rằng phương pháp sau này có hiệu suất tốt hơn.

Cách làm việc này là concat phương thức tạo một mảng mới bao gồm các phần tử trong đối tượng mà nó được gọi theo sau bởi các phần tử của bất kỳ mảng nào được truyền vào nó như là các đối số. Vì vậy, khi không có đối số nào được thông qua, nó chỉ đơn giản là sao chép mảng.

Lee Penkman, cũng trong phần bình luận, chỉ ra rằng nếu có cơ hội array1 Là undefined, bạn có thể trả về một mảng trống như sau:

var array2 = [].concat(array1 || []);

Hoặc, đối với phương thức thứ hai:

var array2 = (array1 || []).concat();

Lưu ý rằng bạn cũng có thể làm điều này với slice: var array2 = (array1 || []).slice();.


67
2017-10-18 00:11



Trên thực tế bạn cũng có thể làm: var array2 = array1.concat (); Nó nhanh hơn rất nhiều về hiệu suất. (JSPerf: jsperf.com/copy-simple-array và jsperf.com/copy-array-slice-vs-concat/5 - Cohen
Cần lưu ý rằng nếu mảng 1 không phải là mảng thì [].concat(array1) trả về [array1] ví dụ. nếu không xác định, bạn sẽ nhận được [undefined]. Đôi khi tôi làm var array2 = [].concat(array1 || []); - lee penkman
Concat không tạo tham chiếu đến đối tượng mà tạo một mảng mới. Đây là những gì tôi cần - Shyamal Parikh


Đây là cách tôi đã thực hiện nó sau khi thử nhiều cách tiếp cận:

var newArray = JSON.parse(JSON.stringify(orgArray));

Điều này sẽ tạo một bản sao sâu mới không liên quan đến bản sao đầu tiên (không phải bản sao nông).

Ngoài ra điều này rõ ràng sẽ không sao chép các sự kiện và chức năng, nhưng điều tốt bạn có thể làm điều đó trong một dòng, và nó có thể được sử dụng cho bất kỳ loại đối tượng nào (mảng, chuỗi, số, đối tượng ...)


40
2018-04-23 13:32



Đây là điều tốt nhất. Tôi sử dụng cùng một phương pháp một thời gian dài trước đây và nghĩ rằng không có ý nghĩa hơn trong các vòng đệ quy cũ của trường học - Vladimir Kharlampidi
Lưu ý rằng tùy chọn này không xử lý các cấu trúc giống như đồ thị: bị treo khi có chu kỳ và không giữ nguyên các tham chiếu được chia sẻ. - Ruben
Điều này cũng không thành công cho những thứ như Date, hoặc thực sự, bất cứ thứ gì có nguyên mẫu. Ngoài ra, undefineds được chuyển đổi thành nullS. - Dancrumb
Không có ai đủ can đảm để bình luận về sự thiếu hiệu quả trong cả CPU và bộ nhớ của serializing cho văn bản và sau đó phân tích cú pháp trở lại một đối tượng? - Lawrence Dol
Giải pháp này là giải pháp duy nhất hoạt động. Sử dụng slice () thực sự là một giải pháp giả mạo.


Một số phương pháp được đề cập hoạt động tốt khi làm việc với các kiểu dữ liệu đơn giản như số hoặc chuỗi, nhưng khi mảng chứa các đối tượng khác thì các phương thức này không thành công. Khi chúng ta cố gắng truyền bất kỳ đối tượng nào từ mảng này sang mảng khác, nó được truyền dưới dạng tham chiếu chứ không phải đối tượng.

Thêm mã sau vào tệp JavaScript của bạn:

Object.prototype.clone = function() {
    var newObj = (this instanceof Array) ? [] : {};
    for (i in this) {
        if (i == 'clone') 
            continue;
        if (this[i] && typeof this[i] == "object") {
            newObj[i] = this[i].clone();
        } 
        else 
            newObj[i] = this[i]
    } return newObj;
};

Và chỉ cần sử dụng

var arr1 = ['val_1','val_2','val_3'];
var arr2 = arr1.clone()

Nó sẽ hoạt động.


17
2017-12-12 16:22



tôi nhận được lỗi này khi tôi thêm mã này vào trang của tôi 'Uncaught RangeError: Kích thước ngăn xếp cuộc gọi tối đa đã vượt quá' - sawe
Bạn đã thấy lỗi này trên trình duyệt nào? - sarvesh singh
Lời xin lỗi của tôi, lỗi này xảy ra trong chrome nếu arr1 không được khai báo. vì vậy tôi sao chép-dán mã trên, và tôi nhận được lỗi, tuy nhiên, nếu tôi khai báo mảng arr1, sau đó tôi không nhận được lỗi. Bạn có thể cải thiện câu trả lời bằng cách khai báo arr1 ngay trên arr2, tôi thấy có một vài 'chúng tôi' ở đó không nhận ra rằng chúng tôi phải khai báo arr1 (một phần vì khi tôi đánh giá câu trả lời của bạn, tôi đã vội vàng và cần một cái gì đó 'chỉ hoạt động') - sawe
.slice() vẫn hoạt động tốt ngay cả khi bạn có các đối tượng trong mảng của mình: jsfiddle.net/edelman/k525g - Jason
@ Jason nhưng các đối tượng vẫn chỉ vào cùng một đối tượng để thay đổi một cái sẽ thay đổi cái kia. jsfiddle.net/k525g/1 - Samuel


Từ ES2015,

var arr2 = [...arr1];

14
2017-12-24 07:14





Cá nhân tôi nghĩ Array.from là một giải pháp dễ đọc hơn. Nhân tiện, hãy cẩn thận với sự hỗ trợ của trình duyệt.

//clone
let x = [1,2,3];
let y = Array.from(x);

//deep clone
let clone = arr => Array.from(arr,item => Array.isArray(item) ? clone(item) : item);
let x = [1,[],[[]]];
let y = clone(x);

14
2018-03-09 12:01



Vâng, điều này rất dễ đọc. Các .slice() giải pháp hoàn toàn không trực quan. Cám ơn vì cái này. - Banago


Nếu bạn đang ở trong một môi trường ECMAScript 6, sử dụng Spread Operator bạn có thể làm theo cách này:

var arr1 = ['a','b','c'];
var arr2 = [...arr1]; //copy arr1
arr2.push('d');

console.log(arr1)
console.log(arr2)
<script src="http://www.wzvang.com/snippet/ignore_this_file.js"></script>


10
2017-07-03 09:31