W3docs

Decoradores y reenvío en JavaScript: call y apply

Aprende a escribir funciones decoradoras en JavaScript y reenviar llamadas con func.call y func.apply, incluyendo un decorador de caché, bind y préstamo de métodos.

Un decorador es una función envolvente: toma otra función y devuelve una nueva función que añade comportamiento — registro, caché, temporización, verificaciones de acceso — alrededor de la original, sin tocar su código. Para construir decoradores que funcionen con cualquier función, necesitas una forma fiable de llamar a una función con un this elegido y un conjunto de argumentos elegido. Eso es exactamente lo que proporcionan func.call y func.apply.

Este capítulo cubre las funciones decoradoras (envolventes), el reenvío de this y argumentos con call/apply, la restauración del contexto perdido con bind y el préstamo de métodos.

Nota: Esto trata sobre los decoradores de funciones — el patrón cotidiano disponible en JavaScript puro hoy en día. Los decoradores de clases con prefijo @ son una característica separada y más avanzada (actualmente una propuesta en Stage 3 que requiere un transpilador), y no se cubren aquí.

Qué es un decorador

Un decorador es una función que envuelve una función objetivo y devuelve un reemplazo con comportamiento adicional. Como el envoltorio tiene la misma forma exterior, los llamadores no tienen que cambiar.

function sum(a, b) {
  return a + b;
}

function logged(func) {
  return function (a, b) {
    console.log(`calling with ${a}, ${b}`);
    return func(a, b);
  };
}

const loggedSum = logged(sum);
console.log(loggedSum(2, 3));
// calling with 2, 3
// 5

El envoltorio es reutilizable, mantiene el original intacto y puede apilarse. El problema del ejemplo anterior es que solo maneja una función que toma exactamente dos argumentos y ningún this. Para envolver cualquier función, reenviamos la llamada.

Un decorador de caché

Un decorador habitual en el mundo real almacena en caché los resultados para que una función costosa se ejecute solo una vez por entrada. Pruébalo:

javascript— editable

Esto funciona para una función independiente. Pero en el momento en que slow es un método que usa this, llamar a func(x) lo rompe — el envoltorio pierde el contexto del objeto. Ahí es donde entran call y apply.

Reenvío de la llamada: call y apply

call y apply invocan una función con un this elegido explícitamente. Solo difieren en cómo se pasan los argumentos:

  • func.call(thisArg, arg1, arg2, ...) — argumentos enumerados individualmente.
  • func.apply(thisArg, argsArray) — argumentos como un único array (o similar a un array).

call

javascript— editable

apply

javascript— editable

Estas dos llamadas son equivalentes:

func.call(obj, 1, 2, 3);
func.apply(obj, [1, 2, 3]);

Usa call cuando conoces los argumentos individualmente; usa apply cuando ya los tienes en un array. Con la sintaxis spread (func.call(obj, ...args)) la distinción a menudo desaparece — consulta Parámetros rest y sintaxis spread.

Reenvío de this con call

Ahora podemos arreglar el decorador de caché para métodos. Dentro del envoltorio, this es el objeto sobre el que se llamó el método, así que lo reenviamos con func.call(this, x):

javascript— editable

Sin func.call(this, x), la llamada interna sería func(x) y se perdería this, por lo que this.someMethod() fallaría.

Reenvío de todos los argumentos con apply

Para un método con varios argumentos, reenvía todos los argumentos de una vez. El envoltorio no sabe cuántos hay, así que los lee de arguments y los pasa todos con func.apply(this, arguments):

javascript— editable

Pasar this y arguments directamente se denomina reenvío de llamada: el envoltorio se comporta exactamente como el original, solo con lógica adicional a su alrededor.

Préstamo de métodos

La función hash anterior usa un truco. arguments es similar a un array (tiene índices y length) pero no es un array real, por lo que no tiene join. En lugar de convertirlo, tomamos prestado el método del array:

function hash(args) {
  return [].join.call(args, ',');
}
console.log(hash([3, 5])); // "3,5"

[].join es Array.prototype.join. Llamarlo con args como this ejecuta la lógica de join sobre el valor similar a un array. El préstamo de métodos permite reutilizar métodos integrados en objetos que no son de ese tipo.

bind y pérdida de contexto

call y apply invocan de inmediato. bind en cambio devuelve una nueva función con this fijado permanentemente — útil cuando la llamada ocurre más tarde (un callback, un manejador de eventos, un setTimeout).

El problema que bind resuelve es la pérdida de contexto: desvincula un método de su objeto y this deja de apuntar a él.

javascript— editable

Para una mirada más detallada sobre cómo fijar el contexto en callbacks y la diferencia entre bind, las funciones flecha y call/apply, consulta Enlace de funciones.

Cuándo usar cada uno

ObjetivoUsar
Llamar ahora con this elegido, argumentos enumerados individualmentefunc.call(thisArg, a, b)
Llamar ahora con this elegido, argumentos ya en un arrayfunc.apply(thisArg, args)
Obtener una función para llamar después con this fijofunc.bind(thisArg)
Reutilizar un método integrado en un objeto similar a un arraypréstamo: [].method.call(obj, …)

Conclusión

Los decoradores envuelven una función para añadir comportamiento sin modificarla. Para que un envoltorio funcione con cualquier función — incluyendo métodos — reenvía la llamada original con func.call(this, ...) o func.apply(this, arguments), usa bind cuando la llamada se difiere, y toma prestados métodos integrados cuando un objeto es solo similar a un array. Juntos, estos ofrecen abstracciones reutilizables y seguras con el contexto, como el decorador de caché anterior.

Lectura relacionada: Métodos de objeto, "this", Objeto función, NFE y Enlace de funciones.

Práctica

Práctica
¿Cuáles afirmaciones describen con precisión el uso y las diferencias entre los métodos `call` y `apply` en JavaScript?
¿Cuáles afirmaciones describen con precisión el uso y las diferencias entre los métodos `call` y `apply` en JavaScript?
Was this page helpful?