
call() y apply() ejecutan inmediatamente una función con un this elegido; call recibe los argumentos uno a uno y apply los recibe en un array. bind() no ejecuta nada: devuelve una función nueva cuyo this (y, si hace falta, los primeros argumentos) queda fijado para siempre. Se usa cuando un método se pasa como callback y pierde su objeto.
Todas las funciones de JavaScript tienen tres métodos, call(), apply() y bind(), que sirven para lo mismo: decidir cuánto vale this en el momento en que la función se ejecuta. Para entender para qué sirven hay que entender antes el problema que resuelven: en JavaScript, this no depende del sitio donde se escribe una función, sino de la forma en que se la llama.
El problema: this cambia según la llamada
Con cuatro reglas basta para predecir this en casi todos los casos:
- Llamada a un método
objet.fonction():thisesobjet. - Llamada simple
fonction():thisesundefineden modo estricto (módulos, clases) y el objeto globalwindowen modo no estricto. - Llamada con
new:thises el objeto que se está construyendo. - Llamada a través de
call,applyo de una función enlazada conbind:thises lo que tú hayas decidido.
Las arrow functions se escapan de estas reglas: no tienen this propio y conservan el del código que las rodea.
El caso que duele en la práctica es el paso de la regla 1 a la regla 2:
const person = {
name: 'Sarah',
hello() {
return `Bonjour ${this.name}`;
},
};
person.hello(); // 'Bonjour Sarah' — llamada a un método, this = person
const detached = person.hello;
detached(); // TypeError en modo estricto: this vale undefined
// ('Bonjour undefined' o 'Bonjour ' en modo no estricto)La función no ha cambiado. Solo ha cambiado la manera de llamarla, y this con ella. Es exactamente lo que ocurre cuando se pasa un método a setTimeout, a addEventListener o a map.
call(): llamar con un this elegido
fonction.call(thisArg, arg1, arg2, …) ejecuta la función de inmediato, con this igual a thisArg y los argumentos pasados uno a uno.
function presenter(age, ville) {
return `${this.name}, ${age} ans, ${ville}`;
}
const person = { name: 'Sarah' };
presenter.call(person, 30, 'Lille'); // 'Sarah, 30 ans, Lille'
presenter.call({ name: 'Léa' }, 25, 'Paris'); // 'Léa, 25 ans, Paris'
const detached = person.hello; // el método del ejemplo anterior
detached.call(person); // 'Bonjour Sarah' — this queda restablecidoUn uso clásico: reutilizar un método de array sobre algo que no lo es.
Array.prototype.slice.call('abc'); // ['a', 'b', 'c']
// Hoy se escribe más bien:
Array.from('abc'); // ['a', 'b', 'c']apply(): lo mismo, con los argumentos en un array
fonction.apply(thisArg, [arg1, arg2, …]) hace exactamente lo mismo que call, pero recibe los argumentos en forma de array. Práctico cuando ya llegan agrupados.
presenter.apply(person, [30, 'Lille']); // 'Sarah, 30 ans, Lille'
const notes = [3, 9, 4];
Math.max.apply(null, notes); // 9 — Math.max espera argumentos separados, no un arrayDesde ES2015, el operador spread hace que apply rara vez sea necesario: Math.max(...notes) y presenter.call(person, ...args) hacen el mismo trabajo y se leen mejor. apply se encuentra sobre todo en código escrito antes.
bind(): fijar this para más tarde
Mientras que call y apply ejecutan de inmediato, bind no ejecuta nada. Devuelve una función nueva cuyo this queda fijado de forma definitiva. Es la herramienta de los callbacks: preparas la función ahora y otro la llamará más tarde.
const bound = person.hello.bind(person);
bound(); // 'Bonjour Sarah', sea cual sea el contexto de la llamada
bound.call({ name: 'X' }); // 'Bonjour Sarah' — gana bind, call ya no puede cambiar this
bound.name; // 'bound hello'El caso típico: un método pasado como callback
class Compteur {
constructor() {
this.n = 0;
}
incrementer() {
this.n += 1;
return this.n;
}
}
const compteur = new Compteur();
// Sin bind — el método está separado de su objeto
const inc = compteur.incrementer;
inc(); // TypeError: Cannot read properties of undefined (reading 'n')
// Con bind — this queda fijado a compteur
const incLie = compteur.incrementer.bind(compteur);
incLie(); // 1
incLie(); // 2El mismo esquema se aplica a los temporizadores y a los eventos del navegador:
setTimeout(compteur.incrementer.bind(compteur), 1000);
bouton.addEventListener('click', compteur.incrementer.bind(compteur));
// Para poder quitar el escuchador después, guarda la referencia enlazada:
const onClick = compteur.incrementer.bind(compteur);
bouton.addEventListener('click', onClick);
bouton.removeEventListener('click', onClick);Sin bind, addEventListener llamaría a incrementer con this igual al botón pulsado: this.n sería undefined y el contador no contaría nada.
bind() para la aplicación parcial
Los argumentos que se pasan a bind después de this también quedan fijados. La función devuelta espera los siguientes.
const presenterSarah = presenter.bind(person, 30);
presenterSarah('Lille'); // 'Sarah, 30 ans, Lille' — solo queda por dar el último argumento
const add = (a, b) => a + b;
const add5 = add.bind(null, 5); // this no se usa: basta con null
add5(3); // 8Es una forma sencilla de especializar una función genérica sin escribir una función envoltorio.
Arrow functions: cuando bind sobra
Una arrow function captura el this del lugar donde se escribe y luego ya no lo suelta. Dos consecuencias.
Primero, call, apply y bind no pueden cambiar su this:
const arrow = () => this;
arrow.call({ a: 1 }); // el this del módulo, no { a: 1 }Después, y este es el uso habitual, una arrow declarada dentro de un método o de una clase conserva el this correcto sin bind:
class Compteur {
n = 0;
// Propiedad arrow: this se captura de una vez por todas
incrementer = () => {
this.n += 1;
return this.n;
};
}
const c = new Compteur();
const inc = c.incrementer;
inc(); // 1 — funciona sin bind
setTimeout(() => c.incrementer(), 1000); // o una arrow en la llamadaLa otra cara de la moneda: una propiedad arrow se recrea en cada instancia, mientras que un método clásico se comparte a través del prototipo. Con unos pocos objetos da igual; con miles de instancias, el método enlazado una sola vez en el constructor (this.incrementer = this.incrementer.bind(this)) ahorra memoria.
this pasado como segundo argumento
Varios métodos de array aceptan directamente el valor de this que hay que usar en el callback, lo que evita un bind:
const config = { k: 10 };
[1, 2, 3].map(function (x) { return this.k * x; }, config); // [10, 20, 30]forEach, filter, some y every tienen el mismo segundo argumento. Con una arrow function, ese argumento sobra.
Tabla comparativa
call | apply | bind | |
|---|---|---|---|
| Ejecuta la función | de inmediato | de inmediato | no: devuelve una función |
| Argumentos | uno a uno | en un array | uno a uno, fijados para las llamadas futuras |
| Uso típico | tomar prestado un método | desplegar un array (sustituido por ...) | callbacks, eventos, aplicación parcial |
| Efecto sobre una arrow function | ninguno sobre this | ninguno sobre this | ninguno sobre this, los argumentos sí quedan fijados |
Errores frecuentes y cómo leerlos
- «Cannot read properties of undefined (reading ‘n’)» en un método de clase: el método se ha separado de su instancia (regla 2). Enlázalo con
bindo decláralo como propiedad arrow. - Un escuchador de eventos que no modifica el estado:
thises el elemento del DOM, no tu objeto. Mismo remedio. - Un
bindque parece no hacer nada: la función es una arrow. Quita elbind, sobra. removeEventListenerque no quita nada: cadabindcrea una función nueva; hay que pasar la misma referencia enlazada al añadirlo y al quitarlo.
Para continuar, las clases en JavaScript vuelven sobre los métodos y el prototipo, y el capítulo Las funciones del tutorial pone las bases si el vocabulario de este artículo todavía te suena nuevo. En PHP la cuestión se plantea de otra forma: $this nunca cambia de objeto.
Errores frecuentes
bouton.addEventListener('click', compteur.incrementer) llama a la función con this igual al botón, no a compteur. Escribe compteur.incrementer.bind(compteur) o una arrow function.this propio: call, apply y bind no lo modifican. Solo se tienen en cuenta los argumentos que pasa bind.bind crea una función nueva en cada llamada. En un bucle o en un renderizado repetido, enlaza una sola vez en el constructor (o declara el método como propiedad arrow).f.call(obj, [1, 2]) pasa un único argumento, el array. Para desplegar un array está apply, o bien f.call(obj, ...tableau).

