JavaScript
call, apply y bind
Compara call, apply y bind para controlar this, enviar argumentos, crear funciones ligadas y conservar referencias en callbacks o listeners.
- Última actualización
- Actualizada
- Nivel
- Aplicación
- Fundamentos de programación
JavaScript
Compara call, apply y bind para controlar this, enviar argumentos, crear funciones ligadas y conservar referencias en callbacks o listeners.
call, apply y bind permiten controlar el this de una función tradicional.
function greet(greeting) {
return `${greeting}, ${this.name}`;
}
const user = {
name: "Nicolás",
};greet.call(user, "Hello");
greet.apply(user, ["Hello"]);
const greetUser = greet.bind(user);
greetUser("Hello");function introduce(role, city) {
return `${this.name} is a ${role} from ${city}`;
}
introduce.call(
{ name: "Nicolás" },
"developer",
"Bogotá",
);El primer argumento es this. Los demás se entregan uno por uno a la función.
Forma general:
functionValue.call(thisArg, argument1, argument2);apply recibe los argumentos en una colección similar a un array.
introduce.apply(
{ name: "Nicolás" },
["developer", "Bogotá"],
);Forma general:
functionValue.apply(thisArg, argumentsList);Es útil cuando los argumentos ya están agrupados.
const values = [10, 4, 20];
Math.max.apply(null, values); // 20Hoy spread suele ser más claro:
Math.max(...values); // 20bind no ejecuta la función. Devuelve otra con un this fijado.
const user = {
name: "Nicolás",
};
const greetUser = greet.bind(user);
greetUser("Hello");
greetUser("Welcome");La nueva función puede almacenarse, pasarse o ejecutarse después.
| Método | Ejecuta ahora | Argumentos | Resultado |
|---|---|---|---|
| `call` | Sí | Separados | Retorno de la función |
| `apply` | Sí | En una lista | Retorno de la función |
| `bind` | No | Puede preconfigurarlos | Nueva función |
function multiply(first, second) {
return first * second;
}
const double = multiply.bind(null, 2);
double(5); // 10
double(20); // 40El primer argumento real de multiply ya quedó configurado como 2.
Esto se parece a partial application: se crea una función especializada a partir de otra.
Una alternativa explícita con closure:
const double = (value) => multiply(2, value);Esta forma puede comunicar mejor la intención cuando this no importa.
class Counter {
constructor() {
this.value = 0;
}
increment() {
this.value++;
}
}
const counter = new Counter();
button.addEventListener(
"click",
counter.increment.bind(counter),
);La función ligada conserva la instancia.
Pero cada llamada a bind crea una función distinta.
button.addEventListener(
"click",
counter.increment.bind(counter),
);
button.removeEventListener(
"click",
counter.increment.bind(counter),
);Esto no elimina el listener porque las funciones no son la misma referencia.
Guárdala:
const boundIncrement = counter.increment.bind(counter);
button.addEventListener("click", boundIncrement);
button.removeEventListener("click", boundIncrement);function readName() {
return this.name;
}
const first = readName.bind({ name: "First" });
const second = first.bind({ name: "Second" });
second(); // "First"El primer binding de this permanece.
Sí pueden añadirse más argumentos preconfigurados en binds posteriores.
const readName = () => this.name;
readName.call({ name: "Nicolás" });La arrow continúa usando su this léxico. call, apply y bind pueden entregar argumentos, pero no reemplazan su this.
function showThis() {
"use strict";
return this;
}
showThis.call(null); // null
showThis.call(undefined); // undefinedEn código no estricto existen conversiones históricas al objeto global o wrappers de primitivos. Evita depender de ellas.
Cuando una función no usa this, suele verse:
multiply.call(null, 2, 5);Pero una llamada normal es más clara:
multiply(2, 5);const person = {
name: "Nicolás",
};
function greet() {
return `Hello, ${this.name}`;
}
greet.call(person);También se han prestado métodos nativos:
Array.prototype.slice.call(arrayLike);Hoy existen opciones más claras para muchos casos:
Array.from(arrayLike);No dependas de prestar métodos internos de objetos que no controlas sin conocer su contrato. Algunos métodos requieren slots internos y no funcionan solo con una forma parecida.
Map.prototype.get.call({}, "key"); // TypeErrorUn objeto normal no posee el estado interno de un Map.
Reflect.apply(greet, user, ["Hello"]);Es una alternativa funcional a greet.apply(user, ["Hello"]).
Puede ser útil en código genérico porque no depende de obtener el método apply desde la propia función.
Una función ligada puede seguir utilizándose con new si la función original es constructible.
function User(role, name) {
this.role = role;
this.name = name;
}
const Developer = User.bind(null, "developer");
const user = new Developer("Nicolás");Cuando se usa new, el this fijado por bind se ignora y se utiliza la nueva instancia. Los argumentos preconfigurados sí se conservan.
user instanceof User; // true
user instanceof Developer; // trueEste comportamiento se entiende mejor después de estudiar new y prototipos.
const method = object.method;Copia la referencia a la misma función, pero no conserva el receptor.
const boundMethod = object.method.bind(object);Crea una función nueva con el receptor fijado.
Elige según el contrato. Un bind innecesario crea identidades nuevas y puede complicar memoización o eliminación de listeners.
const logger = {
application: "DomiSys",
write(level, message) {
console.log(
`[${this.application}] [${level}] ${message}`,
);
},
};
const logError = logger.write.bind(
logger,
"ERROR",
);
logError("Could not load products");La función conserva el logger y preconfigura el nivel.
bind ejecuta inmediatamente.apply con argumentos separados.call cambie el this de una arrow function.bind crea una identidad nueva.new utilizará el this fijado por bind.call ejecuta con argumentos separados.apply ejecuta con una lista de argumentos.bind devuelve una función nueva.bind puede preconfigurar this y argumentos.this.this léxico.bind.¿Por qué no se elimina el listener?
button.addEventListener(
"click",
handler.bind(service),
);
button.removeEventListener(
"click",
handler.bind(service),
);Cada llamada a bind crea una función nueva. removeEventListener necesita exactamente la misma referencia registrada.
Funciones constructoras y new explica cómo JavaScript crea una instancia, conecta su prototipo y decide qué valor retornar.