this, bind, call y apply en JavaScript
En esta lección
- La regla de oro: importa cómo llamas, no dónde escribes
- Los casos uno por uno
- El problema: perder el this
- Solución 1: envolver en una flecha
- Solución 2: bind
- Solución 3: métodos con flecha en una clase
- call y apply: elegir el this en una llamada
- El truco antiguo: const self = this
- Cuándo elegir cada solución
- Errores frecuentes
- Resumen
this es una palabra especial que, dentro de una función, apunta a “el objeto con el que se está trabajando”. Lo has usado ya en objetos y clases, y casi siempre hace lo que esperas. Pero hay situaciones en las que de repente vale undefined y el programa falla con un error que desconcierta. En esta lección verás la regla que decide cuánto vale this en cada caso, por qué se “pierde” al pasar un método como callback y cómo arreglarlo con funciones flecha, bind, call y apply.
Conviene que tengas claros los closures y las funciones flecha antes de seguir.
La regla de oro: importa cómo llamas, no dónde escribes
En casi todos los lenguajes con objetos (Java, PHP, Python), this (o self) es siempre el objeto al que pertenece el método. JavaScript es distinto: el valor de this se decide en el momento de la llamada, según cómo se llame a la función. La misma función puede tener un this distinto cada vez.
Para las funciones normales (las que se escriben con function o con la sintaxis corta de método), la pregunta que debes hacerte es: ¿qué hay a la izquierda del punto cuando se llama?
function presentarse() {
return `Soy ${this.nombre}`;
}
const ana = { nombre: "Ana", presentarse };
const luis = { nombre: "Luis", presentarse };
console.log(ana.presentarse()); // Soy Ana
console.log(luis.presentarse()); // Soy Luis
Es la misma función guardada en dos objetos. En ana.presentarse(), a la izquierda del punto está ana, así que this es ana. En luis.presentarse(), es luis.
Los casos uno por uno
1. Llamada como método
Es el caso normal y el que usarás el 90 % de las veces:
const bici = {
modelo: "Orbea Alma",
kilometros: 1200,
describir() {
return `${this.modelo} con ${this.kilometros} km`;
},
};
console.log(bici.describir()); // Orbea Alma con 1200 km
this es bici porque la llamada es bici.describir().
2. Función suelta
Si llamas a una función sin nada delante del punto, no hay objeto. Qué vale entonces this depende del modo:
- En modo estricto vale
undefined. - En modo no estricto (el de los scripts antiguos) vale el objeto global:
windowen el navegador.
"use strict";
function quienSoy() {
return this;
}
console.log(quienSoy()); // undefined
El modo estricto es una versión más exigente de JavaScript que convierte despistes en errores. Se activa escribiendo "use strict"; al principio del archivo, pero normalmente no hace falta: los módulos y las clases están siempre en modo estricto. Como hoy casi todo el código moderno usa módulos, quédate con esto: una función suelta tiene this igual a undefined.
Nota: que en modo no estricto
thisseawindowes peor, no mejor. Si una función suelta hacethis.total = 0, en vez de dar error crea sin querer una variable global llamadatotal.
3. Dentro de una clase
En una clase, this dentro del constructor es el objeto nuevo que se está creando con new. Dentro de los métodos, se aplica la regla del punto: es el objeto con el que llamas al método.
class Temporizador {
constructor(nombre) {
this.nombre = nombre; // this = el objeto que crea new
this.segundos = 0;
}
avanzar() {
this.segundos++; // this = el objeto de antes del punto
return `${this.nombre}: ${this.segundos} s`;
}
}
const plancha = new Temporizador("Plancha");
console.log(plancha.avanzar()); // Plancha: 1 s
Como las clases están en modo estricto, si sacas un método de la clase y lo llamas suelto, this es undefined.
4. Manejadores de eventos
Cuando registras una función normal con addEventListener, el navegador la llama con this igual al elemento que tiene el manejador (el mismo que evento.currentTarget):
const boton = document.querySelector("#guardar");
boton.addEventListener("click", function () {
this.textContent = "Guardado"; // this = boton
});
Funciona, pero es más claro usar el parámetro del evento, que no depende de cómo esté escrita la función:
boton.addEventListener("click", (evento) => {
evento.currentTarget.textContent = "Guardado";
});
5. Funciones flecha
Las funciones flecha no tienen this propio. Usan el this del sitio donde se escribieron, igual que usan cualquier otra variable de fuera (es un closure). Por eso son perfectas para callbacks dentro de un método:
const ruta = {
nombre: "Ruta del Cares",
etapas: ["Poncebos", "Caín"],
listar() {
return this.etapas.map((etapa) => `${this.nombre}: ${etapa}`);
},
};
console.log(ruta.listar());
// [ 'Ruta del Cares: Poncebos', 'Ruta del Cares: Caín' ]
La flecha de dentro de map usa el this de listar, que es ruta. Si hubieras escrito function (etapa) { ... }, map la llamaría como función suelta y this.nombre fallaría.
Por la misma razón, una flecha no sirve como método: su this sería el de fuera del objeto, no el objeto.
const mal = {
nombre: "Ejemplo",
saludar: () => `Hola, ${this.nombre}`, // this NO es mal
};
Tabla de casos
| Cómo se llama | Qué vale this |
|---|---|
objeto.metodo() | objeto |
funcion() suelta, modo estricto o módulo | undefined |
funcion() suelta, script no estricto | window (el objeto global) |
new Clase() | El objeto nuevo |
Manejador con function en addEventListener | El elemento que escucha |
| Función flecha | El this de donde se escribió |
funcion.call(obj) / apply(obj) / bind(obj) | obj |
El problema: perder el this
Ahora que conoces la regla, el error más famoso de JavaScript tiene explicación. Mira esto:
const describir = bici.describir;
describir();
// TypeError: Cannot read properties of undefined (reading 'modelo')
Al hacer const describir = bici.describir copias solo la función, no su relación con bici. Cuando la llamas, no hay nada a la izquierda del punto, así que es una función suelta: this es undefined y this.modelo revienta.
Esto casi nunca lo harás a propósito. Lo que sí harás es pasar un método como callback, que es exactamente lo mismo: la otra función recibe la función “pelada” y la llama suelta.
const contador = {
cuenta: 0,
sumar() {
this.cuenta++;
return this.cuenta;
},
};
function ejecutar(fn) {
return fn(); // la llama suelta
}
ejecutar(contador.sumar);
// TypeError: Cannot read properties of undefined (reading 'cuenta')
Los casos reales donde te pasará:
setTimeout(contador.sumar, 1000); // se pierde
boton.addEventListener("click", contador.sumar); // this será el botón
datos.forEach(carrito.anadir); // se pierde
El del botón es especialmente traicionero: no da error al registrarlo, pero dentro de sumar el this es el botón, así que this.cuenta++ crea una propiedad cuenta en el botón y tu objeto no cambia.
Solución 1: envolver en una flecha
La forma más sencilla y la más usada: pasa una flecha que llame al método con su punto:
console.log(ejecutar(() => contador.sumar())); // 1
setTimeout(() => contador.sumar(), 1000);
boton.addEventListener("click", () => contador.sumar());
Ahora quien recibe el callback llama a la flecha, y la flecha hace contador.sumar(), con contador a la izquierda del punto. Además, si el método necesita argumentos, se los pasas con naturalidad: () => carrito.anadir(producto, 2).
Solución 2: bind
Todas las funciones tienen el método bind(objeto), que devuelve una función nueva con el this fijado para siempre:
const sumarAtado = contador.sumar.bind(contador);
console.log(ejecutar(sumarAtado)); // 2
Da igual cómo se llame después sumarAtado: su this siempre será contador. Eso tiene una consecuencia: un bind no se puede deshacer ni cambiar con otro bind.
function saludar(saludo, signo) {
return `${saludo}, ${this.nombre}${signo}`;
}
const saludarAAna = saludar.bind(ana, "Qué tal");
console.log(saludarAAna("?")); // Qué tal, Ana?
console.log(saludarAAna.bind(luis)("!")); // Qué tal, Ana! (sigue siendo Ana)
Fíjate en que bind también puede fijar los primeros argumentos. Aquí "Qué tal" queda fijado como saludo, y al llamar solo hay que pasar el signo. Esto se llama aplicación parcial, y sirve aunque la función no use this (en ese caso se pasa null como primer argumento):
function precioConIva(iva, precio) {
return +(precio * (1 + iva)).toFixed(2);
}
const conIvaGeneral = precioConIva.bind(null, 0.21);
console.log(conIvaGeneral(100)); // 121
bind y removeEventListener
Hay un detalle importante si quitas manejadores: bind crea una función nueva cada vez, así que esto no funciona:
boton.addEventListener("click", contador.sumar.bind(contador));
boton.removeEventListener("click", contador.sumar.bind(contador)); // no quita nada
Son dos funciones distintas. Guarda la función atada en una variable y usa esa misma en los dos sitios.
Solución 3: métodos con flecha en una clase
En las clases puedes declarar un método como propiedad con una flecha. Así cada objeto tiene su propia copia con el this ya fijado, y puedes pasarlo como callback directamente:
class Marcador {
puntos = 0;
anotar = () => {
this.puntos++;
return this.puntos;
};
}
const local = new Marcador();
const fn = local.anotar;
console.log(fn()); // 1 (no se pierde el this)
Es cómodo en componentes con muchos manejadores de eventos (lo verás en frameworks como React), pero tiene un coste: cada objeto guarda su propia copia de la función en vez de compartirla. Úsalo solo en los métodos que de verdad vayas a pasar como callback.
call y apply: elegir el this en una llamada
bind devuelve una función para usarla más tarde. Si lo que quieres es llamar ahora mismo con un this concreto, tienes call y apply. Hacen lo mismo y solo cambian en cómo se pasan los argumentos:
console.log(saludar.call(ana, "Hola", "!")); // Hola, Ana!
console.log(saludar.apply(luis, ["Buenas", "."])); // Buenas, Luis.
| Método | Qué hace | Argumentos |
|---|---|---|
fn.call(obj, a, b) | Llama ya, con this = obj | Uno a uno |
fn.apply(obj, [a, b]) | Llama ya, con this = obj | En un array |
fn.bind(obj, a) | No llama: devuelve una función nueva | Fija los primeros |
Un truco para recordarlo: apply, array. En código moderno se usan poco. apply se usaba para pasar un array como argumentos sueltos (Math.max.apply(null, notas)), pero hoy eso se escribe con spread: Math.max(...notas). Los verás sobre todo en bibliotecas y en código antiguo.
Las flechas ignoran call, apply y bind
Como una flecha no tiene this propio, no hay nada que cambiar:
const flecha = () => this;
console.log(flecha.call(ana) === ana); // false
El truco antiguo: const self = this
Antes de que existieran las flechas (2015), para usar el this de un método dentro de un callback se guardaba en una variable:
const pedido = {
productos: ["pan", "leche"],
imprimir() {
const self = this; // también verás "that" o "_this"
this.productos.forEach(function (p) {
console.log(self.productos.length, p);
});
},
};
Funciona gracias a los closures. Si lo ves en código antiguo, ya sabes qué hace; en código nuevo, usa una flecha.
Cuándo elegir cada solución
- Callback puntual (un
setTimeout, unforEach, un evento que no vas a quitar): flecha que envuelve,() => obj.metodo(). - Manejador que luego tendrás que quitar: guarda en una variable una función atada con
bind(o una flecha) y usa esa variable enaddEventListenery enremoveEventListener. - Clase cuyos métodos se pasan mucho como callback: propiedades con flecha.
- Fijar argumentos de una función:
bindcon aplicación parcial, o simplemente una flecha.
Errores frecuentes
- Escribir un método con flecha:
{ total: 0, sumar: () => this.total++ }no toca el objeto. Usa la sintaxis cortasumar() { ... }. - Pasar
obj.metodocomo callback: asetTimeout,addEventListener,forEachothen. Se pierde elthis. Envuélvelo:() => obj.metodo(). - Pasar
obj.metodo()con paréntesis: eso ejecuta el método al momento y pasa su resultado, no la función. - Usar
functiondentro de un método para un callback demap,filterosetTimeout: dentro,thisya no es el objeto. Usa una flecha. - Esperar que
bindcambie la función original: devuelve una función nueva; la original sigue igual. - Quitar un manejador con otro
bind: cadabindcrea una función distinta; guarda la que registraste. - Pensar que
thisdentro de un manejador es el elemento pulsado: es el elemento que escucha (currentTarget). El pulsado esevento.target, que puede ser un hijo.
Resumen
| Situación | this | Consejo |
|---|---|---|
obj.metodo() | obj | El caso normal |
fn() suelta | undefined (estricto) | Es el origen de casi todos los errores |
| Flecha | El de fuera | Ideal para callbacks dentro de métodos |
new Clase() | El objeto nuevo | En constructor y métodos |
addEventListener con function | El elemento que escucha | Mejor evento.currentTarget |
fn.bind(obj) | obj para siempre | Devuelve una función nueva |
fn.call(obj, ...) / apply(obj, [...]) | obj en esa llamada | Llaman al momento |
- El valor de
thislo decide la forma de llamar, no el sitio donde se escribió la función (salvo en las flechas). - Pasar un método como callback lo separa de su objeto: envuélvelo en una flecha o usa
bind.
En la siguiente lección empezarás a trabajar a fondo con texto: cadenas.
Pruébalo tú
Cambia el código y pulsa Ejecutar (o Ctrl + Enter).
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.