¿Por qué el cambio de una matriz en JavaScript afecta las copias de la matriz?

83

He escrito el siguiente 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        

Este código declara una variable myArrayy la establece en un valor de matriz. Luego declara una segunda variable copyOfMyArrayy la establece en myArray. Realiza una operación en copyOfMyArrayy luego alerta a ambos myArrayy copyOfMyArray. De alguna manera, cuando realizo una operación en copyOfMyArray, parece que se realiza la misma operación en myArray.

Luego, el código hace lo mismo con un valor numérico: declara una variable myNumbery la establece en un valor numérico. Luego declara una segunda variable copyOfMyNumbery la establece en myNumber. Realiza una operación en copyOfMyNumbery luego alerta a ambos myNumbery copyOfMyNumber. Aquí, obtengo el comportamiento esperado: diferentes valores para myNumbery copyOfMyNumber.

¿Cuál es la diferencia entre una matriz y un número en JavaScript que parece que cambiar una matriz cambia el valor de una copia de la matriz, mientras que cambiar un número no cambia el valor de una copia del número?

Supongo que, por alguna razón, se hace referencia a la matriz por referencia y al número por valor, pero ¿por qué? ¿Cómo puedo saber qué comportamiento esperar con otros objetos?

Río vivian
fuente

Respuestas:

112

Una matriz en JavaScript también es un objeto y las variables solo contienen una referencia a un objeto, no al objeto en sí. Por tanto, ambas variables tienen una referencia al mismo objeto.

Su comparación con el ejemplo del número no es correcta por cierto. Asigna un nuevo valor a copyOfMyNumber. Si le asigna un nuevo valor copyOfMyArray, tampoco cambiará myArray.

Puede crear una copia de una matriz usando slice [docs] :

var copyOfMyArray = myArray.slice(0);

Pero tenga en cuenta que esto solo devuelve una copia superficial , es decir, los objetos dentro de la matriz no se clonarán.

Felix Kling
fuente
+1 - solo por curiosidad, ¿hay algún inconveniente al asignar myArray.slice(0);directamente en ese contexto?
jAndy
2
@Rice: No, solo edito para aclarar. Si desea una copia profunda, debe escribir algo por su cuenta. Pero estoy seguro de que encontrará un guión que haga esto.
Felix Kling
@FelixKling: No tengo un ejemplo. Solo preguntaba porque aplicaste primero el método del prototipo.
jAndy
@jAndy: Ah, así que te refieres a eso ... Estaba un poco confundido y he estado al revés más a menudo recientemente;)
Felix Kling
23

Bueno, la única respuesta posible, y la correcta, es que en realidad no está copiando la matriz. Cuando escribes

var copyOfArray = array;

estás asignando una referencia a la misma matriz en otra variable. Ambos apuntan al mismo objeto, en otras palabras.

Puntiagudo
fuente
Yo diría que no está asignando exactamente un puntero de referencia, lo está asignando como una copia de la referencia. Ya que si pasa obj a function e intenta reemplazarlo con otro objeto nuevo dentro de la función, no cambiará el objeto original.
kashesandr
1
@kashesandr sí, "asignar una referencia" significa "asignar una copia de una referencia", eso es cierto. Sin embargo, dos referencias iguales son siempre iguales, al igual que dos instancias del número 5son siempre iguales.
Puntiagudo
16

Entonces, todos aquí han hecho un gran trabajo al explicar por qué está sucediendo esto; solo quería escribir una línea y hacerle saber cómo pude solucionarlo, con bastante facilidad:

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

Esto está usando la sintaxis ... spread.

Propagar fuente de sintaxis

EDITAR: En cuanto al por qué de esto, y para responder a su pregunta:

¿Cuál es la diferencia entre una matriz y un número en JavaScript que parece que cambiar una matriz cambia el valor de una copia de la matriz, mientras que cambiar un número no cambia el valor de una copia del número?

La respuesta es que en JavaScript, las matrices y los objetos son mutables , mientras que las cadenas, los números y otras primitivas son inmutables . Cuando hacemos una tarea como:

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

copyOfMyArray es realmente solo una referencia a myArray, no una copia real.

Recomendaría este artículo, ¿Qué son las estructuras de datos inmutables y mutables? , para profundizar en el tema.

Glosario de MDN: Mutable

Salvatore
fuente
2
Exactamente lo que estaba buscando.
Fabien TheSolution
6

Clonación de objetos -

A loop / array.pushproduce un resultado similar a array.slice(0)o array.clone(). Todos los valores se pasan por referencia, pero dado que la mayoría de los tipos de datos primitivos son inmutables , las operaciones posteriores producen el resultado deseado: un 'clon'. Esto no es cierto para los objetos y matrices, por supuesto, que permiten la modificación de la referencia original (son tipos mutables).

Tome el siguiente ejemplo:

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

Todas las operaciones que se ejecutan en los índices newArray producen el resultado deseado, excepto el (objeto) final, que, debido a que se copia por referencia, también mutará el originalArray [3].

https://jsfiddle.net/7ajz2m6w/

Tenga en cuenta que array.slice(0) and array.clone()sufre de esta misma limitación.

Una forma de resolver esto es clonando efectivamente el objeto durante la secuencia de inserción:

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

https://jsfiddle.net/e5hmnjp0/

salud

Bosworth99
fuente
3

En JS, el operador "=" copia el puntero al área de memoria de la matriz. Si desea copiar una matriz en otra, debe usar la función Clonar.

Para enteros es diferente porque son un tipo primitivo.

S.

DonCallisto
fuente
1

Todo se copia por referencia, excepto los tipos de datos primitivos (cadenas y números IIRC).

Quentin
fuente
Eso no es cierto. Todas las asignaciones asignan referencias. Las cadenas y los números son inmutables.
SLaks
1

No tienes copias.
Tiene varias variables que contienen la misma matriz.

Del mismo modo, tiene varias variables que contienen el mismo número.

Cuando escribe copyOfMyNumber = ..., está poniendo un nuevo número en la variable.
Eso es como escribir copyOfMyArray = ....

Cuando escribe copyOfMyArray.splice, está modificando la matriz original .
Eso no es posible con los números porque los números son inmutables y no se pueden modificar,

SLaks
fuente
1

Cree un filtro de la matriz original en arrayCopy. De modo que los cambios en la nueva matriz no afectarán a la matriz original.

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

Espero eso ayude.

Apilar
fuente
1

El problema con la copia superficial es que todos los objetos no se clonan, sino que obtienen una referencia, por lo que array.slice (0) funcionará bien solo con una matriz literal, pero no hará una copia superficial con una matriz de objetos. En ese caso, una forma es ...

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
Omprakash Sharma
fuente
0

Puede agregar un poco de manejo de errores según sus casos y usar algo similar a la siguiente función para resolver el problema. Por favor comente si hay errores / problemas / ideas de eficiencia.

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
fuente
-1

Una matriz o un objeto en javascript siempre tiene la misma referencia a menos que clone o copie. Aquí hay un ejemplo:

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

Para la clonación de objetos, podemos usar .clone () en jquery y angular.copy (), estas funciones crearán un nuevo objeto con otra referencia. Si conoce más funciones para hacer eso, por favor dígame, ¡gracias!

Albert Xu
fuente