¿Qué hay tras las variables en JavaScript?

Yael Alberto Diaz Diaz
SoldAI
Published in
6 min readSep 11, 2020

¿Alguna vez te has topado bugs extraños, con objetos o arreglos mientras programabas en JavaScript? Por ejemplo un arreglo que debería estar lleno, pero le faltaban elementos y tú juras que están ahí. Yo sí, me ha pasado tanto en NodeJS como en VueJS y fueron un dolor de cabeza en su momento.

En este articulo te hablaré sobre un tema básico que toda persona que desarrolle debe comprender, para evitar errores en el programa. Estoy hablando de cómo funciona el mecanismo con el cual las variables son copiadas de un lugar a otro.

Los tipos de datos de un lenguaje describen los elementos básicos que pueden ser utilizados en un lenguaje.

Dependiendo de dónde busques, encontraras que en JavaScript (JS) hay entre 6 a 9 tipos de datos y estructuras con las cuales podemos trabajar. Nosotros definiremos 5 tipos de datos primitivos (datos que no son objetos y que no tienen métodos) y los tipos de datos no primitivos, conocidos como objetos, los cuales pueden dividirse en estructuras mas complejas.

Tipos de datos primitivos

  • Undefined
  • null
  • Boolean
  • String
  • Number

Tipos de datos no primitivos

Objects

  • Array
  • Date
  • Object
  • Set (entre otros).

Entonces ¿Cuál es la diferencia entre los tipos de datos primitivos y los objetos? La respuesta es la mutabilidad. Para entender este punto veamos lo siguiente.

Asignaciones de variables primitivas

El ejemplo a continuación, nos ayudará a entender el procedimiento que se sigue cuando realizamos una asignación de variables en JS.

Cuando el código anterior es ejecutado, JS realizará 3 cosas:

  1. Creará un identificador para la variable (varNumber, varString, por ejemplo).
  2. Separará una dirección de memoria.
  3. Asignará el valor a la dirección separada.

Hasta aquí todo bien, vemos que en la siguiente tabla están las variables que declaramos antes.

Ahora lo que haremos será asignar una variable a otra nueva, con el siguiente código:

Nuestra tabla habrá cambiado ya que se agregan mas variables, veamos como luce ahora…

Observamos que las dos variables nuevas contienen lo mismo, esto es lo que pasa cuando decimos que igualamos una variable a otra. Entonces, ¿qué pasa si cambiamos el valor de varCopyNumber?, ¿también cambiará el de varNumber? pues apuntan a la misma dirección de memoria… Probemos para ver qué sucede:

Ocurrió algo interesante al incrementar el contenido de varCopyNumber. Observemos la siguiente tabla:

Como podemos ver, la respuesta es un rotundo no. Y ¿Por qué no? Esto se debe a que los tipos de datos primitivos de JS son inmutables, por ende, cuando JS resuelva que varCopyNumber es ahora 11, se separará nueva memoria y apuntará a éste. Esto ocurrirá cada que actualicemos las variables con tipos de datos primitivos.

El lugar donde sucede lo anterior, es conocido como Call Stack. No detallaremos mucho su funcionamiento, sin embargo, podemos hacernos la idea de que es una pila en donde se irán depositando las variables que vamos utilizando.

También tenemos otra estructura en JS conocida como Heap, en la cual serán acomodados los objetos de forma dinámica. En esta región no tenemos que preocuparnos por separar más memoria para arreglos. Para entender cómo funciona debemos pasar a las asignaciones de variables no primitivas.

Asignaciones de variables no primitivas

Veamos el ejemplo de un arreglo de números simple:

Cuando el código anterior es ejecutado, JS realizará 4 cosas:

  1. Creará un identificador para la variable (array).
  2. Separará una dirección de memoria en el Stack.
  3. Se asignará el valor a una dirección en el Heap
  4. La dirección de en el Heap es guardada en el Stack.

A diferencia de las variables primitivas, en el stack no vemos el contenido de la variable como antes, en su lugar vemos la dirección del Heap donde están guardadas, puede parecer un poco confuso, pero sigamos adelante.

¿Qué pasa cuando modifico mi arreglo?

Lo que se modificará será el contenido en el Heap, esto le da la propiedad de mutabilidad a los objetos. Un ejemplo de lo práctico de esto, es que no tenemos que cuidar el tamaño del arreglo cada que hagamos un push, JS se encarga de separar la memoria, manteniendo la misma dirección en el stack.

Ahora veamos el siguiente caso:

¿Qué fue lo que ocurrió? Al hacer un copyArray.push(6) sobre copyArray, el Heap será modificado. Si observamos su contenido, veremos “[1,2,3,4,5,6]”, pero si revisamos también array nos encontraremos con exactamente el mismo contenido; “[1,2,3,4,5,6]”. Veamos la tabla para descubrir qué sucedió.

Como podemos ver, al realizar el push lo único que cambio fue el contenido en el Heap, por lo que tanto array como copyArray seguirán con la misma referencia en el stack, hasta que una de las dos sea sobrescrita con otra cosa.

Caso problemático

Es importante no tomar a la ligera este comportamiento en los objetos, ya que podría ocasionar comportamientos indeseados en nuestros programas. Veamos el siguiente ejemplo:

Según la información del método Object.Assign, se copiarán los valores de todas las propiedades numerables del objeto fuente. Entonces, ¿Cuál será el contenido de los dos objetos (persona y copyPersona)?

Ahora ya sabes porqué aunque el objeto persona fue copiado, el arreglo de tarjetas fue modificado en los dos objetos.

Solución practica

Algo sencillo de implementar es romper todas las referencias, con esto podemos lograr lo siguiente:

Si realizamos los mismos cambios que antes, pero en lugar de Object.Assign utilizamos lo anterior, podremos ver que los objetos están completamente aislados. Recuerda que si la eficiencia crucial en tu aplicación, deberás buscar mejores opciones para realizar la copia a profundidad.

Concluyendo, toda variable primitiva se copia por valor y los objetos por referencia. Si experimentas problemas con objetos modificados y no sabes el porqué, deberías primero revisar cuidadosamente cómo manipulas los objetos en tu aplicación.

Publicado originalmente en el blog de SoldAI.

--

--