Por valor vs por referencia en JavaScript

Lupo Montero
4 min readApr 20, 2018

--

En JavaScript, cuando asignamos un valor a una variable, o pasamos un argumento a una función, este proceso siempre se hace “por valor” (by value en inglés). Estrictamente hablando, JavaScript no nos ofrece la opción de pasar o asignar “por referencia” (by reference en inglés), como en otros lenguajes. Lo interesante en nuestro caso, es que cuando una variable hace referencia a un objeto (Object, Array o Function), el “valor” es la referencia en sí.

Hasta el Capitán América entiende sobre referencias.

Cuando asignamos valores primitivos (Boolean, Null, Undefined, Number, String y Symbol), el valor asignado es una copia del valor que estamos asignando. Pero cuando asignamos valores NO primitivos o complejos (Object, Array y Function), JavaScript copia “la referencia”, lo que implica que no se copia el valor en sí, si no una referencia a través de la cual accedemos al valor original.

Asignando valores

Asignando valores primitivos

let a = 'hola';
let b = a; // asignamos valor de `a` a `b`.
a += '!'; // cambiamos valor de `a` añadiendo ! al finalconsole.log(a); // hola!
console.log(b); // hola

En el ejemplo de arriba vemos cómo al asignarle a b el valor de a (let b = a;), éstas se mantienen independientes. Cada variable guarda su propio valor. Por eso al modificar a, vemos que b permanece igual.

Asignando valores complejos

const a = [1, 2, 3];
const b = a;
a.push(4);console.log(a); // [ 1, 2, 3, 4 ]
console.log(b); // [ 1, 2, 3, 4 ]

En el código de arriba vemos cómo al asignar el valor de un array en una variable, el valor en sí no se copia, lo que se asigna es una referencia. Por eso, cuando modificamos el valor de cualquiera de las variables (a ób) estamos afectando al mismo valor, y los cambios se verán reflejados en ambas.

Pasando valores a una función

Pasando valores primitivos

Cuando pasamos un valor primitivo como argumento a una función, el valor que recibimos dentro de la función es siempre una “copia”, lo que implica que cualquier mutación o re-asignación de los parámetros dentro de una función no afecta al valor en el contexto de invocación (fuera de la función).

const foo = (str) => {
str = 'otra cosa';
return str;
};
const aString = 'a';
console.log(foo(aString)); //=> 'otra cosa'
console.log(aString); // => 'a'

Pasando valores complejos

Ahora imaginemos una función donde pasamos una array como argumento:

const foo = (arr) => {
const results = [];
while (arr.length) {
results.push(doSomethingWithArrayItem(arr.shift()));
}
return results;
};
const arr = [1, 2, 3, 4];
foo(arr);
console.log(arr); // []

Como vemos, cuando modificamos (mutamos) el argumento/parámetro arr estamos modificando el mismo valor al que hace referencia la variable original a. Por ello, para evitar efectos colaterales (⚠️), normalmente evitamos modificar directamente los valores de los argumentos pasados a una función. Así, la función anterior podría ser refactorizada de la siguiente manera para implementar la misma lógica, pero sin modificar el array que recibe como argumento:

const foo = (arr) => {
const copy = arr.slice(0); // hacemos una copia del arreglo
const results = [];
while (copy.length) {
results.push(doSomethingWithArrayItem(copy.shift()));
}
return results;
};
const arr = [1, 2, 3, 4];
foo(arr);
console.log(arr); // [1, 2, 3, 4]

Dado el ejemplo, no podemos dejar de mencionar que también podemos implementar la misma lógica (evitando mutar el arreglo original) de una manera mucho más elegante y declarativa usando Array.prototype.map — al fin y al cabo estamos en 2018 🤖:

const foo = arr => arr.map(doSomethingWithArrayItem);

ℹ️ Referencias circulares

A la hora de hablar de referencias, vale la pena mencionar un tipo de referencia especial, aquellas en las que un objeto contiene una referencia a sí mismo (alguien dijo recursividad?). Veamos un ejemplo:

const a = [1, 2, 3];
a.push(a);
console.log(a);

El código de arriba declara un arreglo a con tres elementos (1, 2 y 3) al que después le añadimos un cuarto elemento, que es una referencia a sí mismo (a) 😱!!

Cuando corremos el código en el navegador, en la consola vemos lo siguiente:

El mismo código ejecutado con Node.js nos da el siguiente output:

[ 1, 2, 3, [Circular] ]

Como vemos, a la computadora no le importa este tipo de recursión, pero a más de uno le ha dado dolor de cabeza tratando de visualizar este tipo de estructuras.

Este tipo de referencias se llaman referencias circulares y son relativamente comunes en objetos compuestos que contienen otros objetos que pueden a su vez tener referencias al primero. A la hora de implementar ciertas soluciones recursivas vamos a tener que tener especial cuidado con este tipo de referencias para evitar entrar en ciclos infinitos.

--

--