El comportamiento de this en JavaScript es, probablemente, la fuente número uno de bugs raros para quien viene de otros lenguajes (en Java o Python, this/self se comporta de forma mucho más predecible). Van los casos que más se repiten por aquí.
1. Perder el contexto en un callback. El clásico:
const objeto = {
nombre: 'Ana',
saludar: function () {
setTimeout(function () {
console.log('Hola ' + this.nombre); // undefined, no "Ana"
}, 1000);
}
};
Dentro de la función normal que le pasas a setTimeout, this ya no es objeto. La solución moderna es usar un arrow function, que no tiene su propio this y hereda el del contexto donde se define:
setTimeout(() => {
console.log('Hola ' + this.nombre); // ahora si funciona
}, 1000);
2. Usar arrow function donde hace falta un this dinámico. Es el error contrario y menos conocido: en un manejador de evento de un objeto, si usas arrow function pierdes el this que apunta al elemento que disparó el evento:
boton.addEventListener('click', () => {
console.log(this); // NO es el boton, es el "this" del contexto exterior
});
Aquí toca usar function normal si necesitas que this sea el elemento que recibió el clic.
3. Extraer un método de un objeto y perder su this.
const objeto = { nombre: 'Ana', saludar() { console.log(this.nombre); } };
const funcionSuelta = objeto.saludar;
funcionSuelta(); // this ya no es "objeto", el metodo se desconecta de el
Si necesitas pasar el método como referencia (por ejemplo a un addEventListener) y que siga apuntando al objeto original, hace falta bind: const funcionAtada = objeto.saludar.bind(objeto);.
¿A alguien más le costó pillar la diferencia entre arrow functions y functions normales respecto a esto? Contad vuestra propia batallita.
David Carrero
Añado un cuarto caso que se parece al primero pero pilla a bastante gente: pasar un método de una clase como callback directamente, sin arrow function ni bind:
class Contador {
valor = 0;
incrementar() {
this.valor++;
}
}
const contador = new Contador();
boton.addEventListener('click', contador.incrementar); // this ya no es "contador" al ejecutarse
Al pasar contador.incrementar como referencia, el método se «desconecta» de la instancia igual que en el ejemplo 3 del hilo, solo que aquí está camuflado dentro de una clase. La solución más cómoda hoy en día es declarar el método como class field con arrow function, que fija this en el momento de crear la instancia:
class Contador {
valor = 0;
incrementar = () => {
this.valor++;
}
}
Así puedes pasar contador.incrementar a cualquier sitio sin preocuparte de perder el contexto.
Bruno C.
Dato curioso relacionado: en modo estricto ('use strict', que es automático en módulos ES y en clases), si llamas a una función normal «suelta» sin ningún objeto delante, this vale undefined en vez de apuntar silenciosamente al objeto global (window en el navegador). Eso en realidad ayuda a detectar antes estos bugs: en vez de que el código falle de forma sutil escribiendo en variables globales sin que te enteres, revienta con un error claro de «no se puede leer propiedad de undefined» en el momento exacto donde this no es lo que esperabas.
Jota CT