W3docs

Décorateurs JavaScript et transfert d'appel : call & apply

Apprenez à écrire des fonctions décorateurs en JavaScript et à transférer des appels avec func.call et func.apply, avec mise en cache, bind et emprunt de méthodes.

Un décorateur est une fonction enveloppante : elle prend une autre fonction et retourne une nouvelle fonction qui ajoute un comportement — journalisation, mise en cache, mesure du temps, contrôle d'accès — autour de l'originale, sans toucher au code de celle-ci. Pour construire des décorateurs qui fonctionnent avec n'importe quelle fonction, il faut un moyen fiable d'appeler une fonction avec un this choisi et un ensemble d'arguments choisi. C'est précisément ce que func.call et func.apply fournissent.

Ce chapitre couvre les fonctions décorateurs (wrappers), le transfert de this et des arguments avec call/apply, la restauration d'un contexte perdu avec bind, et l'emprunt de méthodes.

Remarque : Il s'agit ici des décorateurs de fonctions — le patron courant disponible en JavaScript ordinaire aujourd'hui. Les décorateurs de classes préfixés par @, plus récents, constituent une fonctionnalité distincte et plus avancée (actuellement une proposition de stage 3 nécessitant un transpileur) et ne sont pas abordés ici.

Ce qu'est un décorateur

Un décorateur est une fonction qui enveloppe une fonction cible et retourne un remplaçant doté d'un comportement supplémentaire. Comme le wrapper présente la même interface externe, les appelants n'ont pas à changer.

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

Le wrapper est réutilisable, préserve l'original intact et peut être composé. Le problème ci-dessus est qu'il ne gère qu'une fonction prenant exactement deux arguments et pas de this. Pour envelopper n'importe quelle fonction, il faut transférer l'appel.

Un décorateur de mise en cache

Un décorateur courant dans la pratique met en cache les résultats afin qu'une fonction coûteuse ne s'exécute qu'une fois par entrée. Essayez :

javascript— editable

Cela fonctionne pour une fonction autonome. Mais dès que slow est une méthode qui utilise this, appeler func(x) la brise — le wrapper perd le contexte de l'objet. C'est là qu'interviennent call et apply.

Transférer l'appel : call et apply

call et apply invoquent tous deux une fonction avec un this explicitement choisi. Ils ne diffèrent que dans la façon dont les arguments sont passés :

  • func.call(thisArg, arg1, arg2, ...) — arguments listés individuellement.
  • func.apply(thisArg, argsArray) — arguments sous forme d'un seul array (ou objet de type array).

call

javascript— editable

apply

javascript— editable

Ces deux appels sont équivalents :

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

Utilisez call quand vous connaissez les arguments individuellement ; utilisez apply quand ils se trouvent déjà dans un array. Avec la syntaxe de décomposition (func.call(obj, ...args)), la distinction disparaît souvent — voir Paramètres rest et syntaxe de décomposition.

Transférer this avec call

Nous pouvons maintenant corriger le décorateur de mise en cache pour les méthodes. À l'intérieur du wrapper, this est l'objet sur lequel la méthode a été appelée, donc on le transfère avec func.call(this, x) :

javascript— editable

Sans func.call(this, x), l'appel interne serait func(x) et this serait perdu, ce qui ferait échouer this.someMethod().

Transférer tous les arguments avec apply

Pour une méthode avec plusieurs arguments, transférez tous les arguments en une seule fois. Le wrapper ne sait pas combien il y en a, il les lit depuis arguments et les transmet tous via func.apply(this, arguments) :

javascript— editable

Passer this et arguments tels quels s'appelle le transfert d'appel : le wrapper se comporte exactement comme l'original, avec simplement de la logique supplémentaire autour de lui.

Emprunt de méthodes

La fonction hash ci-dessus utilise une astuce. arguments est de type array-like (il possède des indices et une propriété length) mais ce n'est pas un véritable array, donc il n'a pas de join. Plutôt que de le convertir, on emprunte la méthode du array :

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

[].join est Array.prototype.join. L'appeler avec args comme this exécute la logique de join sur la valeur de type array-like. L'emprunt de méthodes permet de réutiliser des méthodes natives sur des objets qui ne sont pas de ce type.

bind et la perte de contexte

call et apply invoquent immédiatement. bind retourne en revanche une nouvelle fonction avec this définitivement fixé — utile quand l'appel est différé (un callback, un gestionnaire d'événement, un setTimeout).

Le problème que bind résout est la perte de contexte : détachez une méthode de son objet et this ne pointe plus vers lui.

javascript— editable

Pour approfondir la correction du contexte dans les callbacks et la différence entre bind, les fonctions fléchées et call/apply, voir Liaison de fonctions.

Quand utiliser lequel

ObjectifUtiliser
Appeler immédiatement avec un this choisi, arguments listés individuellementfunc.call(thisArg, a, b)
Appeler immédiatement avec un this choisi, arguments déjà dans un arrayfunc.apply(thisArg, args)
Obtenir une fonction à appeler plus tard avec this fixéfunc.bind(thisArg)
Réutiliser une méthode native sur un objet de type array-likel'emprunter : [].method.call(obj, …)

Conclusion

Les décorateurs enveloppent une fonction pour ajouter un comportement sans la modifier. Pour qu'un wrapper fonctionne avec n'importe quelle fonction — y compris les méthodes — transférez l'appel original avec func.call(this, ...) ou func.apply(this, arguments), utilisez bind quand l'appel est différé, et empruntez des méthodes natives quand un objet est seulement de type array-like. Ensemble, ces outils vous donnent des abstractions réutilisables et sûres vis-à-vis du contexte, comme le décorateur de mise en cache ci-dessus.

Lectures connexes : Méthodes d'objet, "this", Objet fonction, NFE, et Liaison de fonctions.

Pratique

Pratique
Quelles affirmations décrivent avec précision l'utilisation et les différences entre les méthodes `call` et `apply` en JavaScript ?
Quelles affirmations décrivent avec précision l'utilisation et les différences entre les méthodes `call` et `apply` en JavaScript ?
Was this page helpful?