Чому зміна масиву в JavaScript впливає на копії масиву?


83

Я написав такий JavaScript:

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        

Цей код оголошує змінну myArrayі встановлює для неї значення масиву. Потім він оголошує другу змінну copyOfMyArrayі встановлює для неї значення myArray. Він виконує операцію, copyOfMyArrayа потім попереджає і, myArrayі copyOfMyArray. Якось, коли я виконую операцію на copyOfMyArray, здається, що та сама операція виконується на myArray.

Потім код робить те саме з числовим значенням: він оголошує змінну myNumberі встановлює їй числове значення. Потім він оголошує другу змінну copyOfMyNumberі встановлює для неї значення myNumber. Він виконує операцію, copyOfMyNumberа потім попереджає і, myNumberі copyOfMyNumber. Тут я отримую очікувану поведінку: різні значення для myNumberі copyOfMyNumber.

У чому різниця між масивом і числом у JavaScript, що, здається, зміна масиву змінює значення копії масиву, де як зміна числа не змінює значення копії числа?

Я здогадуюсь, що з якихось причин на масив посилаються посиланням, а на число - значенням, але чому? Як я можу знати, якої поведінки чекати з іншими предметами?

Відповіді:


112

Масив у JavaScript також є об'єктом, і змінні містять лише посилання на об'єкт, а не на сам об'єкт. Таким чином, обидві змінні мають посилання на один і той же об'єкт.

Порівняння з прикладом числа невірно, до речі. Ви присвоюєте нове значення copyOfMyNumber. Якщо ви призначите йому нове значення, copyOfMyArrayвоно також не зміниться myArray.

Ви можете створити копію масиву за допомогою slice [docs] :

var copyOfMyArray = myArray.slice(0);

Але зауважте, що це повертає лише поверхневу копію, тобто об’єкти всередині масиву не будуть клоновані.


+1 - просто з цікавості, чи є якийсь недолік, призначаючи myArray.slice(0);безпосередньо в цьому контексті?
jAndy

2
@Rice: Ні, я просто редагую, щоб пояснити. Якщо ви хочете глибоку копію, вам доведеться написати щось самостійно. Але я впевнений, що ви знайдете сценарій, який це робить.
Фелікс Клінг

@FelixKling: Я не маю прикладу. Я просто запитував, тому що ви спочатку застосували метод прототипу.
jAndy

@jAndy: А, отже, ти посилався на це ... Я просто трохи розгубився, а нещодавно я частіше в інший бік;)
Фелікс Клінг

23

Ну, єдина можлива відповідь - і правильна - полягає в тому, що ви насправді не копіюєте масив. Коли пишеш

var copyOfArray = array;

ви призначаєте посилання на той самий масив в іншу змінну. Іншими словами, вони обидва вказують на один і той же об’єкт.


Я б сказав, що ви призначаєте не точно вказівник посилання, ви присвоюєте як копію посилання. Оскільки якщо ви передаєте функцію obj у функцію та намагаєтесь замінити її іншим новим об’єктом усередині функції, ви не зміните початковий об’єкт.
Кашесандр

1
@kashesandr так, "присвоєння посилання" означає "призначення копії посилання", це правда. Однак два рівні посилання завжди рівні, як і два екземпляри числа 5завжди рівні.
Пойнті

16

Тож усі тут продемонстрували, чому це відбувається, - я просто хотів кинути рядок і повідомити, як я зміг це виправити - досить легко:

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

Це використовує ... синтаксис поширення.

Джерело розповсюдження синтаксису

EDIT: Що стосується чому це, і відповісти на ваше запитання:

У чому різниця між масивом і числом у JavaScript, що, здається, зміна масиву змінює значення копії масиву, де як зміна числа не змінює значення копії числа?

Відповідь полягає в тому, що в JavaScript масиви та об’єкти можна змінювати , тоді як рядки, числа та інші примітиви незмінні . Коли ми виконуємо таке завдання, як:

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

copyOfMyArray насправді є лише посиланням на myArray, а не фактичною копією.

Я б рекомендував цю статтю: Що таке незмінні та змінні структури даних? , заглибитися в тему.

Глосарій MDN: змінний


2
Саме те, що я шукав.
Fabien TheSolution

6

Клонування об'єктів -

A loop / array.pushдає результат, подібний до array.slice(0)або array.clone(). Всі значення передаються за посиланням, але оскільки більшість примітивних типів даних незмінні , подальші операції дають бажаний результат - «клон». Звичайно, це не стосується об'єктів та масивів, які дозволяють модифікувати оригінальне посилання (вони є змінними типами).

Візьмемо такий приклад:

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

Всі операції, що виконуються над індексами newArray, дають бажаний результат, крім кінцевого (об'єкта), який, оскільки він копіюється за допомогою посилання, також буде мутувати originalArray [3].

https://jsfiddle.net/7ajz2m6w/

Зверніть увагу, що array.slice(0) and array.clone()страждає від цього самого обмеження.

Одним із способів вирішити це є ефективне клонування об'єкта під час послідовності натискань:

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

https://jsfiddle.net/e5hmnjp0/

ура


3

У JS оператор "=" копіює вказівник на область пам'яті масиву. Якщо ви хочете скопіювати масив в інший, вам потрібно скористатися функцією Клонування.

Для цілих чисел відрізняється, оскільки вони є примітивним типом.

С.


1

Все копіюється за посиланням, крім примітивних типів даних (рядки та числа IIRC).


Це неправда. Всі завдання присвоюють посилання. Рядки та числа незмінні.
Слакс

1

У вас немає копій.
У вас є кілька змінних, що містять один і той же масив.

Так само у вас є кілька змінних, що містять одне і те ж число.

Коли ви пишете copyOfMyNumber = ..., ви вводите нове число у змінну.
Це як писати copyOfMyArray = ....

Коли ви пишете copyOfMyArray.splice, ви змінюєте вихідний масив .
Це неможливо з числами, оскільки числа незмінні та не можуть бути змінені,


1

Створіть фільтр вихідного масиву в arrayCopy. Таким чином, зміни до нового масиву не вплинуть на початковий масив.

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']

Сподіваюся, це допоможе.


1

Проблема з поверхневою копією полягає в тому, що всі об'єкти не клоновані, натомість вони отримують посилання. Отже, array.slice (0) буде працювати нормально лише з літеральним масивом, але він не буде робити поверхневу копію з масивом об'єктів. У цьому випадку один із способів - це ..

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

Ви можете додати деяку обробку помилок залежно від ваших випадків і використовувати щось подібне до наведеної нижче функції для вирішення проблеми. Будь ласка, коментуйте будь-які помилки / проблеми / ідеї щодо ефективності.

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

Масив або об’єкт у javascript завжди містить одне і те ж посилання, якщо ви не клонуєте або копіюєте. Ось приклад:

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

Для клонування об'єкта ми можемо використовувати .clone () у jquery та angular.copy (), ці функції створять новий об'єкт з іншими посиланнями. Якщо ви знаєте більше функцій для цього, скажіть мені, дякую!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.