24 de mayo de 2025

Función bind: vinculación de funciones

Al pasar métodos de objeto como devoluciones de llamada, por ejemplo a setTimeout, se genera un problema conocido: la “pérdida de this”.

En este capítulo veremos las formas de solucionarlo.

Pérdida de “this”

Ya hemos visto ejemplos de pérdida de this. Una vez que se pasa hacia algún lugar un método separado de su objeto, this se pierde.

Así es como puede suceder con setTimeout:

let user = {
  firstName: "John",
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

setTimeout(user.sayHi, 1000); // Hello, undefined!

Como podemos ver, el resultado no muestra “John” como this.firstName ¡sino undefined!

Esto se debe a que setTimeout tiene la función user.sayHi, separada del objeto. La última línea se puede reescribir como:

let f = user.sayHi;
setTimeout(f, 1000); // user pierde el contexto

El método setTimeout en el navegador es un poco especial: establece this = window para la llamada a la función (para Node.js, this se convierte en el objeto temporizador (timer), pero realmente no importa aquí). Entonces, en this.firstName intenta obtener window.firstName, que no existe. En otros casos similares, this simplemente se vuelve undefined.

La tarea es bastante típica: queremos pasar un método de objeto a otro lugar (aquí, al planificador) donde se llamará. ¿Cómo asegurarse de que se llamará en el contexto correcto?

Solución 1: un contenedor (wrapper en inglés)

La solución más simple es usar una función contenedora que la envuelva:

let user = {
  firstName: "John",
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

setTimeout(function() {
  user.sayHi(); // Hello, John!
}, 1000);

Ahora funciona, porque recibe a user del entorno léxico externo, y luego llama al método normalmente.

Aquí hacemos lo mismo, pero de otra manera:

setTimeout(() => user.sayHi(), 1000); // Hello, John!

Se ve bien, pero aparece una ligera vulnerabilidad en nuestra estructura de código…

¿Qué pasa si antes de que se dispare setTimeout (¡hay un segundo retraso!) user cambia el valor? Entonces, de repente, ¡llamará al objeto equivocado!

let user = {
  firstName: "John",
  sayHi() {
    alert(`Hola, ${this.firstName}!`);
  }
};

setTimeout(() => user.sayHi(), 1000);

// ...el valor de user cambia en 1 segundo
user = {
  sayHi() { alert("¡Otro user en setTimeout!"); }
};

// ¡Otro user en setTimeout!

La siguiente solución garantiza que tal cosa no sucederá.

Solución 2: bind (vincular)

Las funciones proporcionan un método incorporado