Почему изменение массива в 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 07
2
@Rice: Нет, я просто редактирую, чтобы уточнить. Если вам нужна глубокая копия, вы должны написать что-нибудь самостоятельно. Но я уверен, что вы найдете сценарий, который это сделает.
Felix Kling
@FelixKling: У меня нет примера. Я просто спросил, потому что вы сначала применили метод прототипа.
jAndy 07
@jAndy: Ах, ты упомянул об этом ... Я просто немного запутался, а в последнее время я просто наоборот чаще;)
Феликс Клинг
23

Что ж, единственный возможный ответ - и правильный - это то, что вы на самом деле не копируете массив. Когда ты пишешь

var copyOfArray = array;

вы назначаете ссылку на тот же массив другой переменной. Другими словами, они оба указывают на один и тот же объект.

Заостренный
источник
Я бы сказал, что вы не назначаете точно ссылочный указатель, вы назначаете как копию ссылки. Поскольку, если вы передадите obj функции и попытаетесь заменить его другим новым объектом внутри функции, вы не измените исходный объект.
kashesandr
1
@kashesandr да, «присвоение ссылки» означает «присвоение копии ссылки», это правда. Однако две равные ссылки всегда равны, так же как два экземпляра числа 5всегда равны.
Pointy
16

Итак, все здесь хорошо поработали, объяснив, почему это происходит - я просто хотел сказать пару слов и сообщить, как мне удалось это исправить - довольно легко:

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

Это использует синтаксис ... распространения.

Источник синтаксиса распространения

РЕДАКТИРОВАТЬ: Относительно того, почему это, и чтобы ответить на ваш вопрос:

В чем разница между массивом и числом в 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, дают желаемый результат, за исключением final (object), который, поскольку он копируется по ссылке, также изменяет 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/

ура

Bosworth99
источник
3

В JS оператор "=" копирует указатель в область памяти массива. Если вы хотите скопировать массив в другой, вы должны использовать функцию Clone.

Для целых чисел все по-другому, потому что они примитивный тип.

С.

ДонКаллисто
источник
1

Все копируется по ссылке, кроме примитивных типов данных (строки и числа IIRC).

Квентин
источник
Это неправда. На все задания присваиваем ссылки. Строки и числа неизменны.
SLaks 07
1

У вас нет копий.
У вас есть несколько переменных, содержащих один и тот же массив.

Точно так же у вас есть несколько переменных, содержащих одно и то же число.

Когда вы пишете copyOfMyNumber = ..., вы помещаете в переменную новое число.
Это как писатьcopyOfMyArray = ... .

Когда вы пишете copyOfMyArray.splice, вы изменяете исходный массив .
Это невозможно с числами, потому что числа неизменяемы и не могут быть изменены.

SLaks
источник
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;
}
Erjim
источник
-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 (), эти функции создадут новый объект с другой ссылкой. Если вам известны другие функции для этого, сообщите мне, спасибо!

Альберт Сюй
источник