Si es nuevo en JavaScript, es probable que ya se haya encontrado con algunos de los comportamientos divertidos que este lenguaje tiene para ofrecer (gráfico A). Al principio, estas rarezas pueden parecer ridículas y frustrantes, pero prometo que hay un método detrás de toda esa locura.
Uno de los obstáculos más difíciles de superar, en mi humilde opinión, es la diferencia entre pasar por valor frente a pasar por referencia. ¿Por qué es tan complicado este concepto? Para empezar, ciertamente puede llegar bastante lejos sin comprender realmente cómo interactúa JavaScript con los valores primitivos y los valores de referencia. Esto puede, y la mayoría de las veces, resultará en una tonelada de errores que son difíciles de rastrear y molestos de arreglar. En segundo lugar, este es un concepto que aparecerá en las entrevistas técnicas, por lo que no entenderlo contará como una gran bandera roja en su contra.
no temas, compañero lector! Que comience la educación
Tipos de datos primitivos
En JavaScript, podemos dividir los tipos de datos en dos cubos diferentes, tipos de datos primitivos y objetos.
Hay seis tipos de datos primitivos en JavaScript: string
, number
, boolean
, undefined
, null
, y symbol
a partir de ES6.
Los tipos de datos primitivos se pasan, o copian, por valor y son inmutables, lo que significa que el valor existente no se puede alterar de la manera en que un array u objeto pueden. Echemos un vistazo al código a continuación para ver esto en acción.
Aquí se han creado dos variables, x = 10
y y = x
. Dado que 10
es un número y un valor primitivo, cuando establecemos y = x
, en realidad estamos copiando el valor, es decir, 10
, y asignándolo a y
. También podemos visualizar esto usando la tabla a continuación.
Si vamos a cambiar el valor de x
, veríamos que y
conserva su valor de 10
. De nuevo, esto se debe a que los valores primitivos se copian, por lo que el valor de y
es independiente del valor de x
. Piensa en ello como hacer una fotocopia de una foto. Después de hacer la copia, tienes dos imágenes idénticas: un original y un facsímil. Si cortara el original por la mitad, solo el original se alteraría y el facsímil permanecería exactamente igual.
Objetos de Referencia
Objetos, por otro lado, se pasa por referencia y seleccione una ubicación en la memoria para el valor, no el valor en sí mismo. Echemos un vistazo a esto en nuestro código.
En este ejemplo, x
es ahora un objeto que apunta a {dog: "poodle"}
. Cuando creamos la variable y
y le asignamos el valor de x
, ahora podemos aprovechar las diversas propiedades de x
que incluye el valor de dog
, es decir, "poodle"
. Esta parece exactamente la misma lógica utilizada para los valores primitivos, pero echemos un vistazo a nuestro práctico gráfico a continuación para ver la diferencia sutil, pero importante.
Ahora este gráfico se ve un poco diferente de cuando nuestro variables x
y y
celebró tipos de datos simples. En esta versión, vemos que los valores para x
y y
no son tipos de datos, sino referencias a direcciones en memoria, ¡la misma dirección de hecho! Ahora echemos un vistazo a lo que sucede a x
si queremos agregar una nueva propiedad de size
a y
…
x
todavía devuelve un objeto, sino que ahora tiene una propiedad adicional de size
también! De nuevo, esto se debe a que tanto x
como y
apuntan al mismo objeto de referencia, por lo que cualquier cambio realizado en una variable será visible en la otra.
Para ayudarme a recordar este concepto, me gusta pensar en los valores de referencia como una casa y las variables como personas que viven en esa casa. Todos los residentes (variables) pueden decir «Tengo una casa» y apuntar a la misma casa. Si un solo residente decide que quiere pintar la casa de amarillo, entonces todos los residentes ahora tienen una casa amarilla porque es compartida.
Echemos un vistazo a un ejemplo más que contiene una variedad de objetos de referencia.
En este código, comenzamos con una variable person
que contiene las propiedades de name
, age
, y hobbies
. Cuando imprimimos este objeto en la consola, obtenemos exactamente lo que esperamos, el mismo objeto que acabamos de crear.
A continuación, tenemos una función llamada changePerson
que toma un argumento, hace algunos cambios y luego devuelve un objeto. Cuando creamos la variable thirdPerson
, invocamos la funciónchangePerson
pasando nuestro objeto original de person
a ella. Lo interesante es lo que sucede cuando imprimimos en la consola thirdPerson
y person
de nuevo.
Observe que console.log(thirdPerson)
devuelve un objeto completamente nuevo con propiedades nuevas. Ahora mira lo que devuelve console.log(person)
. Esto es similar a nuestro objeto original, pero contiene nuevos valores de propiedad que se introdujeron en nuestra función changePerson
.
Comprobar la igualdad
Finalmente, echemos un vistazo a cómo se comportan los tipos de datos primitivos y los objetos de referencia con los operadores de igualdad.
Cuando se trata de tipos de datos primitivos, no importa lo que esté a la derecha del signo =
, siempre y cuando los valores sean los mismos. Podemos ver esto arriba con variables a
y b
que se escriben de manera diferente pero se evalúan con el mismo valor cuando usamos el ===
, el operador de igualdad estricta.
Lo contrario es cierto en el segundo ejemplo para dog
y cat
. Si bien puede parecer que contienen valores idénticos, es una matriz y un objeto de referencia, lo que significa que
===
está verificando si dog
y cat
tienen la misma referencia al valor en memoria. Por otro lado, bird === dog
es verdadero porque comparten el mismo objeto de referencia.
Conclusión
Y con esto concluye nuestra introducción a pasar por valor vs pasar por referencia. Hay más temas que se pueden cubrir bajo este paraguas fundamental, incluido lo que sucede cuando una referencia se sobrescribe o se pierde, cómo copiar una referencia para crear un nuevo objeto Y asegurarse de que la copia sea una copia profunda, solo por nombrar algunos. Recomendaría revisar los recursos que utilicé a continuación, los cuales abordan algunos de estos temas adicionales con mayor detalle.