Tại sao việc thay đổi Mảng trong JavaScript lại ảnh hưởng đến các bản sao của mảng?


83

Tôi đã viết đoạn mã JavaScript sau:

var myArray = ['a', 'b', 'c'];
var copyOfMyArray = myArray;
copyOfMyArray.splice(0, 1);
alert(myArray); // alerts ['b','c']
alert(copyOfMyArray); // alerts ['b','c']

var myNumber = 5;
var copyOfMyNumber = myNumber;
copyOfMyNumber = copyOfMyNumber - 1;
alert(myNumber); // alerts 5
alert(copyOfMyNumber); // alerts 4        

Đoạn mã này khai báo một biến myArrayvà đặt nó thành một giá trị mảng. Sau đó, nó khai báo một biến thứ hai copyOfMyArrayvà đặt nó thành myArray. Nó thực hiện một thao tác trên copyOfMyArrayvà sau đó cảnh báo cả myArraycopyOfMyArray. Bằng cách nào đó, khi tôi thực hiện một thao tác trên copyOfMyArray, có vẻ như thao tác tương tự được thực hiện trên myArray.

Sau đó, đoạn mã làm điều tương tự với một giá trị số: Nó khai báo một biến myNumbervà đặt nó thành một giá trị số. Sau đó, nó khai báo một biến thứ hai copyOfMyNumbervà đặt nó thành myNumber. Nó thực hiện một thao tác trên copyOfMyNumbervà sau đó cảnh báo cả myNumbercopyOfMyNumber. Ở đây, tôi nhận được hành vi mong đợi: các giá trị khác nhau cho myNumbercopyOfMyNumber.

Sự khác biệt giữa một mảng và một số trong JavaScript là gì mà dường như việc thay đổi một mảng sẽ thay đổi giá trị của một bản sao của mảng, trong khi việc thay đổi một số không làm thay đổi giá trị của một bản sao của số?

Tôi đoán rằng vì lý do nào đó, mảng được tham chiếu bằng tham chiếu và số theo giá trị, nhưng tại sao? Làm thế nào tôi có thể biết những hành vi mong đợi với các đối tượng khác?

Câu trả lời:


112

Một mảng trong JavaScript cũng là một đối tượng và các biến chỉ giữ một tham chiếu đến một đối tượng chứ không phải bản thân đối tượng. Do đó cả hai biến đều có tham chiếu đến cùng một đối tượng.

So sánh của bạn với ví dụ số không đúng btw. Bạn chỉ định một giá trị mới cho copyOfMyNumber. Nếu bạn gán một giá trị mới cho copyOfMyArraynó cũng sẽ không thay đổi myArray.

Bạn có thể tạo bản sao của một mảng bằng slice [docs] :

var copyOfMyArray = myArray.slice(0);

Nhưng lưu ý rằng điều này chỉ trả về một bản sao nông , tức là các đối tượng bên trong mảng sẽ không được nhân bản.


+1 - chỉ vì tò mò, có bất kỳ hạn chế nào bằng cách chỉ định myArray.slice(0);trực tiếp trong bối cảnh đó không?
jAndy 07/07/11

2
@Rice: Không, tôi chỉ chỉnh sửa để làm rõ. Nếu bạn muốn có một bản sao sâu sắc, bạn phải viết một cái gì đó của riêng bạn. Nhưng tôi chắc rằng bạn sẽ tìm thấy một kịch bản làm được điều này.
Felix Kling,

@FelixKling: Tôi không có ví dụ. Tôi chỉ hỏi vì bạn đã áp dụng phương pháp nguyên mẫu trước.
jAndy

@jAndy: Ah, do đó bạn gọi mà ... Tôi chỉ là một chút bối rối và tôi chỉ cách khác thường xuyên hơn thời gian gần đây;)
Felix Kling

23

Chà, câu trả lời có thể duy nhất - và câu trả lời đúng - là bạn không thực sự sao chép mảng. Khi bạn viết

var copyOfArray = array;

bạn đang gán một tham chiếu cho cùng một mảng vào một biến khác. Nói cách khác, cả hai đều đang trỏ vào cùng một đối tượng.


Tôi muốn nói rằng bạn không chỉ định chính xác con trỏ tham chiếu, bạn đang chỉ định giống như một bản sao của tham chiếu. Vì nếu bạn truyền obj cho hàm và cố gắng thay thế nó bằng một đối tượng mới khác bên trong hàm, bạn sẽ không thay đổi đối tượng ban đầu.
kashandr

1
@kashandr vâng, "gán tham chiếu" có nghĩa là "gán một bản sao của tham chiếu", điều đó đúng. Tuy nhiên, hai tham chiếu bằng nhau luôn bằng nhau, giống như hai trường hợp của số 5luôn bằng nhau.
Pointy

16

Vì vậy, mọi người ở đây đã làm rất tốt trong việc giải thích lý do tại sao điều này lại xảy ra - tôi chỉ muốn thả một dòng và cho bạn biết làm thế nào tôi có thể sửa lỗi này - khá dễ dàng:

thingArray = ['first_thing', 'second_thing', 'third_thing']
function removeFirstThingAndPreserveArray(){
  var copyOfThingArray = [...thingArray]
  copyOfThingArray.shift();
  return copyOfThingArray;
}

Đây là sử dụng cú pháp ... spread.

Nguồn cú pháp Spread

CHỈNH SỬA: Về lý do tại sao của điều này và để trả lời câu hỏi của bạn:

Sự khác biệt giữa một mảng và một số trong JavaScript là gì mà dường như việc thay đổi một mảng sẽ thay đổi giá trị của một bản sao của mảng, trong khi việc thay đổi một số không làm thay đổi giá trị của một bản sao của số?

Câu trả lời là trong JavaScript, mảng và đối tượng có thể thay đổi được , trong khi chuỗi và số và các nguyên thủy khác là bất biến . Khi chúng tôi thực hiện một nhiệm vụ như:

var myArray = ['a', 'b', 'c']; var copyOfMyArray = myArray;

copyOfMyArray thực sự chỉ là một tham chiếu đến myArray, không phải là một bản sao thực sự.

Tôi muốn giới thiệu bài viết này, Cấu trúc dữ liệu bất biến và có thể thay đổi là gì? , để tìm hiểu sâu hơn về chủ đề này.

Bảng chú giải thuật ngữ MDN: Có thể thay đổi


2
Chính xác những gì tôi đang tìm kiếm.
Fabien TheSolution

6

Nhân bản đối tượng -

A loop / array.pushtạo ra kết quả tương tự với array.slice(0)hoặc array.clone(). Tất cả các giá trị đều được chuyển qua tham chiếu, nhưng vì hầu hết các kiểu dữ liệu nguyên thủy là bất biến , các hoạt động tiếp theo tạo ra kết quả mong muốn - một 'bản sao'. Tất nhiên, điều này không đúng với các đối tượng và mảng, cho phép sửa đổi tham chiếu gốc (chúng là các kiểu có thể thay đổi).

Lấy ví dụ sau:

const originalArray = [1, 'a', false, {foor: 'bar'}]
const newArray = [];

originalArray.forEach((v, i) => {
    newArray.push(originalArray[i]);
});

newArray[0] = newArray[0] + 1;
newArray[1] = 'b';
newArray[2] = true;
newArray[3] = Object.assign(newArray[3], {bar: 'foo'});

Các hoạt động chạy trên các chỉ mục newArray đều tạo ra kết quả mong muốn, ngoại trừ cuối cùng (đối tượng), vì nó được sao chép bằng tham chiếu, cũng sẽ thay đổi nguyên gốcArray [3].

https://jsfiddle.net/7ajz2m6w/

Lưu ý rằng array.slice(0) and array.clone()bị hạn chế tương tự.

Một cách để giải quyết vấn đề này là nhân bản đối tượng một cách hiệu quả trong chuỗi push:

originalArray.forEach((v, i) => {
    const val = (typeof v === 'object') ? Object.assign({}, v) : v;
    newArray.push(val);
});

https://jsfiddle.net/e5hmnjp0/

hoan hô


3

Trong JS, toán tử "=" sao chép con trỏ vào vùng nhớ của mảng. Nếu bạn muốn sao chép một mảng vào một mảng khác, bạn phải sử dụng hàm Clone.

Đối với số nguyên thì khác vì chúng là một kiểu nguyên thủy.

S.


1

Mọi thứ đều được sao chép bằng tham chiếu ngoại trừ các kiểu dữ liệu nguyên thủy (chuỗi và số IIRC).


Điều đó không đúng. Tất cả các bài tập chỉ định tài liệu tham khảo. Chuỗi và số là bất biến.
SLaks vào

1

Bạn không có bất kỳ bản sao nào.
Bạn có nhiều biến giữ cùng một mảng.

Tương tự, bạn có nhiều biến giữ cùng một số.

Khi bạn viết copyOfMyNumber = ..., bạn đang đặt một số mới vào biến.
Đó là giống như viết copyOfMyArray = ....

Khi bạn viết copyOfMyArray.splice, bạn đang sửa đổi mảng ban đầu .
Điều đó không thể xảy ra với số vì số là bất biến và không thể sửa đổi,


1

Tạo một bộ lọc của mảng ban đầu trong arrayCopy. Vì vậy, những thay đổi đối với mảng mới sẽ không ảnh hưởng đến mảng ban đầu.

var myArray = ['a', 'b', 'c'];
var arrayCopy = myArray.filter(function(f){return f;})
arrayCopy.splice(0, 1);
alert(myArray); // alerts ['a','b','c']
alert(arrayCopy); // alerts ['b','c']

Hy vọng nó giúp.


1

Vấn đề với bản sao cạn là tất cả các đối tượng không được sao chép, thay vào đó nó nhận được tham chiếu. Vì vậy, array.slice (0) sẽ chỉ hoạt động tốt với mảng chữ, nhưng nó sẽ không sao chép nông với mảng đối tượng. Trong trường hợp đó, một cách là ..

var firstArray = [{name: 'foo', id: 121}, {name: 'zoo', id: 321}];
var clonedArray = firstArray.map((_arrayElement) => Object.assign({}, _arrayElement));  
console.log(clonedArray);
// [{name: 'foo', id: 121}, {name: 'zoo', id: 321}]  // shallow copy

0

Bạn có thể thêm một số xử lý lỗi tùy thuộc vào trường hợp của mình và sử dụng một cái gì đó tương tự như hàm sau để giải quyết vấn đề. Vui lòng bình luận cho bất kỳ lỗi / vấn đề / ý tưởng hiệu quả.

function CopyAnArray (ari1) {
   var mxx4 = [];
   for (var i=0;i<ari1.length;i++) {
      var nads2 = [];
      for (var j=0;j<ari1[0].length;j++) {
         nads2.push(ari1[i][j]);
      }
      mxx4.push(nads2);
   }
   return mxx4;
}

-1

Một mảng hoặc một đối tượng trong javascript luôn giữ cùng một tham chiếu trừ khi bạn sao chép hoặc sao chép. Đây là một bản đồ ngoại vi:

http://plnkr.co/edit/Bqvsiddke27w9nLwYhcl?p=preview

// for showing that objects in javascript shares the same reference

var obj = {
  "name": "a"
}

var arr = [];

//we push the same object
arr.push(obj);
arr.push(obj);

//if we change the value for one object
arr[0].name = "b";

//the other object also changes
alert(arr[1].name);

Đối với đối tượng clone, chúng ta có thể sử dụng .clone () trong jquery và angle.copy (), các hàm này sẽ tạo đối tượng mới với tham chiếu khác. Nếu bạn biết thêm chức năng để làm điều đó, xin vui lòng cho tôi biết, cảm ơn!

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.