Newsletter

bind(), call() y apply() en JavaScript: entender y dominar this

Por qué this cambia de valor según cómo se llame a una función y cómo lo fijan call(), apply() y bind(): ejemplos ejecutados, el caso de los callbacks y de las clases, y comparación con las arrow functions.

bind(), call() y apply() en JavaScript: cambiar el this
Respuesta rápida

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:

  1. Llamada a un método objet.fonction(): this es objet.
  2. Llamada simple fonction(): this es undefined en modo estricto (módulos, clases) y el objeto global window en modo no estricto.
  3. Llamada con new: this es el objeto que se está construyendo.
  4. Llamada a través de call, apply o de una función enlazada con bind: this es 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:

probleme.js
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.

call.js
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 restablecido

Un uso clásico: reutilizar un método de array sobre algo que no lo es.

javascript
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.

apply.js
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 array

Desde 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.

bind.js
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

compteur.js
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();   // 2

El mismo esquema se aplica a los temporizadores y a los eventos del navegador:

javascript
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.

partiel.js
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);                          // 8

Es 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:

javascript
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:

fleche.js
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 llamada

La 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:

javascript
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 bind o decláralo como propiedad arrow.
  • Un escuchador de eventos que no modifica el estado: this es el elemento del DOM, no tu objeto. Mismo remedio.
  • Un bind que parece no hacer nada: la función es una arrow. Quita el bind, sobra.
  • removeEventListener que no quita nada: cada bind crea 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

Pasar un método como callback sin bind 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.
Creer que bind cambia el this de una arrow function Una arrow function no tiene this propio: call, apply y bind no lo modifican. Solo se tienen en cuenta los argumentos que pasa bind.
Llamar a bind en cada renderizado 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).
Confundir call y apply con la misma lista de argumentos f.call(obj, [1, 2]) pasa un único argumento, el array. Para desplegar un array está apply, o bien f.call(obj, ...tableau).

JavaScript

Damien Flandrin Desarrollador web desde 2010, creador de Gekkode y de Email Impact. Cada artículo se prueba en un proyecto real antes de publicarse. Contacto
Newsletter

Las nuevas pruebas, tutoriales y proyectos, por correo.

Pruebas reproducibles, código versionado, resultados fechados. Nunca spam.