Décorateur d'accélération
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é enms
. 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:
- 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. - Puis, alors que la souris continue d’avancer, il ne se passe plus rien jusqu’à
100ms
. La variante décorée ignore les appels. - À la fin de
100ms
– une autreupdate
se produit avec les dernières coordonnées. - Enfin, la souris s’arrête quelque part. La variante décorée attend que
100ms
expire, puis lanceupdate
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.
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
.
- Lors du premier appel, le
wrapper
exécute simplementfunc
et définit l’état de charge (isThrottled = true
). - 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. - Après
ms
millisecondes,setTimeout
se déclenche. L’état de charge est supprimé (isThrottled = false
), et si nous avions ignoré les appels, alorswrapper
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.