this, call, apply y bind en JavaScript

En esta página
Enterate del próximo post
Un mail cuando publico algo nuevo. Sin spam y te podés dar de baja cuando quieras.

Un mail cuando publico algo nuevo. Sin spam y te podés dar de baja cuando quieras.
El post anterior sobre class terminó anunciando este problema: un método compartido en un prototipo, o en cualquier objeto, no sabe de antemano a qué apunta this dentro suyo. Lo decide la llamada, no la definición.
this se decide en la llamada, no en la definición"use strict";
const contador = {
total: 0,
sumar() {
this.total++;
},
};
contador.sumar();
contador.total; // 1Llamado como contador.sumar(), this adentro de sumar es contador. No porque sumar esté escrito adentro del objeto: porque se lo llamó con la forma objeto.metodo().
const sumarSuelto = contador.sumar;
sumarSuelto();
// TypeError: Cannot read properties of undefined (reading 'total')// El mismo código sin "use strict"
const sumarSuelto = contador.sumar;
sumarSuelto();
globalThis.total; // NaN ← this pasó a ser el objeto global, total no existía ahíSeparar el método del objeto no mueve el código: mueve la forma en que se lo llama. sumarSuelto() se llama como una función suelta, así que this deja de ser contador. En estricto, this es undefined y explota apenas se lo usa. En sloppy, this es el objeto global, y el bug queda escondido en un NaN en vez de un error.
call y apply llaman la función fijando this a manofunction saludar(saludo) {
return `${saludo}, soy ${this.nombre}`;
}
const persona = { nombre: "Fernando" };
saludar.call(persona, "Hola"); // "Hola, soy Fernando"
saludar.apply(persona, ["Hola"]); // "Hola, soy Fernando"Los dos hacen lo mismo: ejecutan saludar ahí mismo, con persona como this. La única diferencia es cómo reciben el resto de los argumentos. call los recibe sueltos, uno por uno. apply los recibe juntos, en un array.
function sumarTodos() {
return Array.prototype.reduce.call(arguments, (acumulado, n) => acumulado + n, 0);
}
sumarTodos(1, 2, 3); // 6Acá está el uso real, más allá del ejemplo de juguete: arguments se parece a un array pero no lo es, así que no tiene reduce propio. call permite pedirle prestado el reduce de Array.prototype y ejecutarlo con arguments como si fuera el array de siempre.
bind fija this para siempre, sin ejecutar todavíaconst saludarFernando = saludar.bind(persona);
saludarFernando("Hola"); // "Hola, soy Fernando"
typeof saludarFernando; // "function", no ejecutó nada todavíabind no llama a la función: devuelve una función nueva, con this ya fijado, para llamar cuando haga falta. Es la herramienta indicada para el problema del método suelto de la primera sección, por ejemplo al pasar un método como callback.
const otraPersona = { nombre: "Ana" };
const intentoDoble = saludarFernando.bind(otraPersona);
intentoDoble("Hola"); // "Hola, soy Fernando" ← el segundo bind no cambió nadaUna vez ligada, una función ligada no se puede volver a ligar. Intentar un segundo bind no tira error, pero tampoco hace nada: this ya quedó fijo la primera vez.
this propiofunction Persona(nombre) {
this.nombre = nombre;
this.saludar = () => `Hola, soy ${this.nombre}`;
}
const persona = new Persona("Fernando");Una arrow function no crea su propio this: usa el que ya existía en el lugar donde está escrita. Acá this adentro de la arrow es el mismo this de Persona, capturado en el momento en que se definió, no en el momento en que se llama.
const otraPersona = { nombre: "Ana" };
persona.saludar.call(otraPersona); // "Hola, soy Fernando" ← call no puede cambiar el this de una arrowEsto es lo que distingue a una arrow function de todo lo visto hasta acá: ni call, ni apply, ni bind pueden cambiarle el this. No es que lo ignoren por las dudas: una arrow function no tiene ese mecanismo interno para empezar, así que no hay nada que fijar.
El post anterior mostró que una función fábrica no sufre ningún problema de this porque cada método es una función nueva por cada llamada, mientras que class comparte un único método en el prototipo. Se puede tener lo mejor de las dos cosas usando una arrow function como campo de la clase:
class Contador {
total = 0;
sumar = () => {
this.total++;
};
}
const contador = new Contador();
const sumarSuelto = contador.sumar;
sumarSuelto();
contador.total; // 1, funcionó igual que llamado como métodosumar acá ya no vive en Contador.prototype: es una propiedad propia de cada instancia, igual que #saldo en el post anterior, porque los campos de clase se crean por instancia. Eso significa volver a pagar el costo de memoria de la función fábrica (una función nueva por cada objeto), pero a cambio ese método se puede pasar suelto, como callback, sin perder nunca su this.
thisno es una propiedad del método: es un parámetro implícito que cada llamada decide de nuevo.callyapplylo fijan una vez,bindlo fija para siempre, y una arrow function directamente no participa de esa decisión.
Si estás empezando, quedate con esto: antes de pasar un método como callback, preguntate qué forma se va a usar para llamarlo. Si la respuesta es "no lo sé todavía" o "una función suelta", bind o una arrow function como campo de clase evitan el bug antes de que aparezca.