retour au cours

Décorateur d'accélération

importance: 5

Créez un décorateur “d’accélération” throttle(f, ms) – qui retourne un wrapper.

Lorsqu’il est appelé plusieurs fois, il passe l’appel à f au maximum une fois par ms millisecondes.

La différence avec debounce est que c’est un décorateur complètement différent :

  • debounce exécute la fonction une fois après la période de “cooldown”. Bon pour traiter le résultat final.
  • throttle ne l’exécute pas plus souvent que le temps donné en ms. Bon pour les mises à jour régulières qui ne devraient pas être très fréquentes.

En d’autres termes, throttle est comme une secrétaire qui accepte les appels téléphoniques, mais qui dérange le patron (appellez le f réel) pas plus d’une fois par ms millisecondes.

Examinons l’application réelle pour mieux comprendre cette exigence et voir d’où elle vient.

Par exemple, nous voulons suivre les mouvements de la souris.

Dans le navigateur, nous pouvons configurer une fonction à exécuter à chaque mouvement de la souris et obtenir l’emplacement du pointeur à mesure qu’il se déplace. Pendant une utilisation active de la souris, cette fonction est généralement utilisée très souvent et peut atteindre 100 fois par seconde (toutes les 10 ms). Nous aimerions mettre à jour certaines informations sur la page Web lorsque le pointeur se déplace.

… Mais la mise à jour de la fonction update() est trop lourde pour tous les micro-mouvements. Il est également inutile de mettre à jour plus d’une fois toutes les 100 ms.

Nous allons donc l’envelopper dans le décorateur : utilisez throttle(update, 100) comme fonction à exécuter à chaque déplacement de souris à la place de update() d’origine. Le décorateur sera appelé souvent, mais update() sera appelé au maximum une fois toutes les 100 ms.

Visuellement, cela ressemblera à ceci:

  1. Pour le premier mouvement de souris, la variante décorée passe l’appel à update. Cela est important, l’utilisateur voit notre réaction à leur mouvement immédiatement.
  2. Puis, alors que la souris continue d’avancer, il ne se passe plus rien jusqu’à 100ms. La variante décorée ignore les appels.
  3. À la fin de 100ms – une autre update se produit avec les dernières coordonnées.
  4. Enfin, la souris s’arrête quelque part. La variante décorée attend que 100ms expire, puis lance update avec les dernières coordonnées. Donc, peut-être le plus important, les coordonnées finales de la souris sont traitées.

Un exemple de code:

function f(a) {
  console.log(a);
}

// f1000 passe les appels à f au maximum une fois toutes les 1000 ms
let f1000 = throttle(f, 1000);

f1000(1); // montre 1
f1000(2); // (étranglement, 1000ms pas encore écoulée)
f1000(3); // (étranglement, 1000ms pas encore écoulée)

// quand 1000ms expirent...
// ...sort 3, la valeur intermédiaire 2 a été ignorée

P.S. Les arguments et le contexte this transmis à f1000 doivent être transmis à f d’origine.

Open a sandbox with tests.

function throttle(func, ms) {

  let isThrottled = false,
    savedArgs,
    savedThis;

  function wrapper() {

    if (isThrottled) { // (2)
      savedArgs = arguments;
      savedThis = this;
      return;
    }
    isThrottled = true;

    func.apply(this, arguments); // (1)

    setTimeout(function() {
      isThrottled = false; // (3)
      if (savedArgs) {
        wrapper.apply(savedThis, savedArgs);
        savedArgs = savedThis = null;
      }
    }, ms);
  }

  return wrapper;
}

Un appel à throttle(func, ms) retourne wrapper.

  1. Lors du premier appel, le wrapper exécute simplement func et définit l’état de charge (isThrottled = true).
  2. Dans cet état, tous les appels sont mémorisés dans savedArgs/savedThis. Veuillez noter que le contexte et les arguments sont d’égale importance et doivent être mémorisés. Nous avons besoin d’eux simultanément pour reproduire l’appel.
  3. Après ms millisecondes, setTimeout se déclenche. L’état de charge est supprimé (isThrottled = false), et si nous avions ignoré les appels, alors wrapper est exécuté avec les derniers arguments et contextes mémorisés.

La 3ème étape n’exécute pas func, mais wrapper, car nous devons non seulement exécuter func, mais encore une fois entrer dans l’état de charge et configurer le délai d’expiration pour le réinitialiser.

Ouvrez la solution avec des tests dans une sandbox.