JavaScript offre une flexibilité exceptionnelle lors du traitement des fonctions. Ils peuvent être distribués, utilisés comme objets, et maintenant nous allons voir comment transférer des appels entre eux et les décorer .
Disons que nous avons une fonction slow(x)
qui consomme beaucoup de CPU, mais dont les résultats sont stables. En d’autres termes, pour le même x
il renvoie toujours le même résultat.
Si la fonction est appelée souvent, nous souhaiterons peut-être mettre en cache (mémoriser) les résultats pour éviter de consacrer plus de temps aux recalculs.
Mais au lieu d’ajouter cette fonctionnalité dans slow()
nous allons créer une fonction wrapper qui ajoute la mise en cache. Comme nous le verrons, cela présente de nombreux avantages.
Voici le code et les explications suivent :
fonction lente (x) { // il peut y avoir ici un travail très gourmand en CPU alert(`Appelé avec ${x}`); renvoyer x ; } fonction cachingDecorator(func) { laissez cache = new Map(); fonction de retour (x) { if (cache.has(x)) { // s'il y a une telle clé dans le cache retourner cache.get(x); // lit le résultat } soit résultat = func(x); // sinon appelle func cache.set(x, résultat); // et met en cache (mémorise) le résultat renvoyer le résultat ; } ; } lent = cachingDecorator(lent); alerte( lente(1) ); // slow(1) est mis en cache et le résultat est renvoyé alert( "Encore : " + slow(1) ); // résultat slow(1) renvoyé depuis le cache alerte( lente(2) ); // slow(2) est mis en cache et le résultat est renvoyé alert( "Encore : " + slow(2) ); // résultat slow(2) renvoyé depuis le cache
Dans le code ci-dessus, cachingDecorator
est un décorateur : une fonction spéciale qui prend une autre fonction et modifie son comportement.
L'idée est que nous pouvons appeler cachingDecorator
pour n'importe quelle fonction, et il renverra le wrapper de mise en cache. C'est génial, car nous pouvons avoir de nombreuses fonctions qui pourraient utiliser une telle fonctionnalité, et tout ce que nous avons à faire est de leur appliquer cachingDecorator
.
En séparant la mise en cache du code de la fonction principale, nous simplifions également le code principal.
Le résultat de cachingDecorator(func)
est un « wrapper » : function(x)
qui « enveloppe » l'appel de func(x)
dans une logique de mise en cache :
À partir d'un code extérieur, la fonction slow
encapsulée fait toujours la même chose. Un aspect de mise en cache vient d'être ajouté à son comportement.
Pour résumer, il y a plusieurs avantages à utiliser un cachingDecorator
distinct au lieu de modifier le code de slow
lui-même :
Le cachingDecorator
est réutilisable. Nous pouvons l'appliquer à une autre fonction.
La logique de mise en cache est distincte, elle n'augmente pas la complexité du slow
lui-même (s'il y en avait).
Nous pouvons combiner plusieurs décorateurs si besoin (d'autres décorateurs suivront).
Le décorateur de mise en cache mentionné ci-dessus n'est pas adapté pour travailler avec des méthodes objet.
Par exemple, dans le code ci-dessous, worker.slow()
cesse de fonctionner après la décoration :
// nous allons créer une mise en cache worker.slow laissez travailleur = { uneMéthode() { renvoyer 1 ; }, lent(x) { // tâche effrayante et gourmande en CPU ici alert("Appelé avec " + x); return x * this.someMethod(); // (*) } } ; // même code qu'avant fonction cachingDecorator(func) { laissez cache = new Map(); fonction de retour (x) { si (cache.has(x)) { retourner cache.get(x); } soit résultat = func(x); // (**) cache.set(x, résultat); renvoyer le résultat ; } ; } alert( travailleur.slow(1) ); // la méthode originale fonctionne travailleur.slow = cachingDecorator(worker.slow); // maintenant, mets-le en cache alert( travailleur.slow(2) ); // Oups ! Erreur : Impossible de lire la propriété « someMethod » de non définie
L'erreur se produit dans la ligne (*)
qui tente d'accéder this.someMethod
et échoue. Voyez-vous pourquoi ?
La raison en est que le wrapper appelle la fonction d'origine comme func(x)
dans la ligne (**)
. Et, lorsqu'elle est appelée ainsi, la fonction obtient this = undefined
.
Nous observerions un symptôme similaire si nous essayions de courir :
let func = travailleur.slow; fonction(2);
Ainsi, le wrapper transmet l'appel à la méthode d'origine, mais sans le contexte this
. D'où l'erreur.
Réparons-le.
Il existe une méthode de fonction intégrée spéciale func.call(context, …args) qui permet d'appeler une fonction définissant explicitement this
.
La syntaxe est :
func.call(contexte, arg1, arg2, ...)
Il exécute func
en fournissant le premier argument comme this
et le suivant comme arguments.
Pour faire simple, ces deux appels font presque la même chose :
fonction(1, 2, 3); func.call(obj, 1, 2, 3)
Ils appellent tous les deux func
avec les arguments 1
, 2
et 3
. La seule différence est que func.call
définit également this
sur obj
.
A titre d'exemple, dans le code ci-dessous, nous appelons sayHi
dans le contexte de différents objets : sayHi.call(user)
exécute sayHi
en fournissant this=user
, et la ligne suivante définit this=admin
:
fonction direSalut() { alert(ce.nom); } let user = { nom : "John" } ; let admin = { nom : "Admin" } ; // utilise call pour passer différents objets comme "this" sayHi.call(utilisateur); // John sayHi.call( admin ); // Administrateur
Et ici, nous utilisons call
to call say
avec le contexte et la phrase donnés :
fonction dire (phrase) { alert(this.name + ': ' + phrase); } let user = { nom : "John" } ; // l'utilisateur devient ceci et "Bonjour" devient le premier argument say.call(utilisateur, "Bonjour" ); // Jean : Bonjour
Dans notre cas, nous pouvons utiliser call
dans le wrapper pour passer le contexte à la fonction d'origine :
laissez travailleur = { uneMéthode() { renvoyer 1 ; }, lent(x) { alert("Appelé avec " + x); return x * this.someMethod(); // (*) } } ; fonction cachingDecorator(func) { laissez cache = new Map(); fonction de retour (x) { si (cache.has(x)) { retourner cache.get(x); } let result = func.call(this, x); // "this" est passé correctement maintenant cache.set(x, résultat); renvoyer le résultat ; } ; } travailleur.slow = cachingDecorator(worker.slow); // maintenant, mets-le en cache alert( travailleur.slow(2) ); // travaux alert( travailleur.slow(2) ); // fonctionne, n'appelle pas l'original (mis en cache)
Maintenant, tout va bien.
Pour que tout soit clair, voyons plus en profondeur comment this
se transmet :
Après la décoration worker.slow
se trouve maintenant la function (x) { ... }
.
Ainsi, lorsque worker.slow(2)
est exécuté, le wrapper obtient 2
comme argument et this=worker
(c'est l'objet avant le point).
À l'intérieur du wrapper, en supposant que le résultat n'est pas encore mis en cache, func.call(this, x)
transmet le this
( =worker
) actuel et l'argument actuel ( =2
) à la méthode d'origine.
Rendons maintenant cachingDecorator
encore plus universel. Jusqu'à présent, il ne fonctionnait qu'avec des fonctions à un seul argument.
Maintenant, comment mettre en cache la méthode multi-arguments worker.slow
?
laissez travailleur = { lent (min, max) { renvoyer min + max ; // un processeur effrayant est supposé } } ; // doit mémoriser les appels avec le même argument travailleur.slow = cachingDecorator(worker.slow);
Auparavant, pour un seul argument x
nous pouvions simplement cache.set(x, result)
pour enregistrer le résultat et cache.get(x)
pour le récupérer. Mais maintenant, nous devons nous souvenir du résultat d'une combinaison d'arguments (min,max)
. La Map
native prend une valeur unique uniquement comme clé.
De nombreuses solutions sont possibles :
Implémentez une nouvelle structure de données de type carte (ou utilisez une tierce partie) qui est plus polyvalente et permet plusieurs clés.
Utilisez des cartes imbriquées : cache.set(min)
sera une Map
qui stocke la paire (max, result)
. Nous pouvons donc obtenir result
sous la forme cache.get(min).get(max)
.
Joignez deux valeurs en une seule. Dans notre cas particulier, nous pouvons simplement utiliser une chaîne "min,max"
comme clé Map
. Pour plus de flexibilité, nous pouvons permettre de fournir une fonction de hachage au décorateur, qui sait créer une valeur parmi plusieurs.
Pour de nombreuses applications pratiques, la 3ème variante est suffisante, nous nous y tiendrons donc.
Nous devons également transmettre non seulement x
, mais tous les arguments de func.call
. Rappelons que dans une function()
nous pouvons obtenir un pseudo-tableau de ses arguments sous forme arguments
, donc func.call(this, x)
doit être remplacé par func.call(this, ...arguments)
.
Voici un cachingDecorator
plus puissant :
laissez travailleur = { lent (min, max) { alert(`Appelé avec ${min},${max}`); renvoyer min + max ; } } ; fonction cachingDecorator (func, hachage) { laissez cache = new Map(); fonction de retour() { let key = hash(arguments); // (*) if (cache.has(key)) { return cache.get(clé); } let result = func.call(this, ...arguments); // (**) cache.set(clé, résultat); renvoyer le résultat ; } ; } fonction hachage (arguments) { retourner args[0] + ',' + args[1]; } travailleur.slow = cachingDecorator(worker.slow, hachage); alert( travailleur.slow(3, 5) ); // travaux alert( "Encore " + travailleur.slow(3, 5) ); // pareil (mis en cache)
Maintenant, cela fonctionne avec n'importe quel nombre d'arguments (bien que la fonction de hachage doive également être ajustée pour autoriser n'importe quel nombre d'arguments. Une façon intéressante de gérer cela sera abordée ci-dessous).
Il y a deux changements :
Dans la ligne (*)
il appelle hash
pour créer une seule clé à partir arguments
. Ici, nous utilisons une simple fonction de « jointure » qui transforme les arguments (3, 5)
en clé "3,5"
. Les cas plus complexes peuvent nécessiter d'autres fonctions de hachage.
Ensuite (**)
utilise func.call(this, ...arguments)
pour transmettre à la fois le contexte et tous les arguments obtenus par le wrapper (pas seulement le premier) à la fonction d'origine.
Au lieu de func.call(this, ...arguments)
nous pourrions utiliser func.apply(this, arguments)
.
La syntaxe de la méthode intégrée func.apply est :
func.apply (contexte, arguments)
Il exécute la func
en définissant this=context
et en utilisant un objet de type tableau args
comme liste d'arguments.
La seule différence de syntaxe entre call
et apply
est que call
attend une liste d'arguments, tandis que apply
prend avec eux un objet de type tableau.
Ces deux appels sont donc presque équivalents :
func.call(contexte, ...args); func.apply(contexte, arguments);
Ils effectuent le même appel de func
avec un contexte et des arguments donnés.
Il n'y a qu'une différence subtile concernant args
:
La syntaxe spread ...
permet de passer args
itérables comme liste à call
.
L' apply
n'accepte que args
de type tableau .
… Et pour les objets à la fois itérables et de type tableau, comme un vrai tableau, nous pouvons utiliser n'importe lequel d'entre eux, mais apply
sera probablement plus rapide, car la plupart des moteurs JavaScript l'optimisent mieux en interne.
Passer tous les arguments ainsi que le contexte à une autre fonction est appelé transfert d'appel .
C'est la forme la plus simple :
laissez wrapper = fonction() { return func.apply(this, arguments); } ;
Lorsqu'un code externe appelle un tel wrapper
, il est impossible de le distinguer de l'appel de la fonction d'origine func
.
Apportons maintenant une autre amélioration mineure à la fonction de hachage :
fonction hachage (arguments) { retourner args[0] + ',' + args[1]; }
Pour l'instant, cela ne fonctionne que sur deux arguments. Ce serait mieux s'il pouvait coller n'importe quel nombre d' args
.
La solution naturelle serait d'utiliser la méthode arr.join :
fonction hachage (arguments) { return args.join(); }
…Malheureusement, cela ne fonctionnera pas. Parce que nous appelons hash(arguments)
et que l'objet arguments
est à la fois itérable et semblable à un tableau, mais pas un vrai tableau.
Donc appeler join
échouerait, comme nous pouvons le voir ci-dessous :
fonction hachage() { alert( arguments.join() ); // Erreur : arguments.join n'est pas une fonction } hachage(1, 2);
Il existe néanmoins un moyen simple d'utiliser la jointure de tableau :
fonction hachage() { alert( [].join.call(arguments) ); // 1,2 } hachage(1, 2);
L'astuce s'appelle l'emprunt de méthode .
Nous prenons (empruntons) une méthode join à partir d'un tableau régulier ( [].join
) et utilisons [].join.call
pour l'exécuter dans le contexte d' arguments
.
Pourquoi ça marche ?
C'est parce que l'algorithme interne de la méthode native arr.join(glue)
est très simple.
Tiré de la spécification presque « tel quel » :
Laissez glue
être le premier argument ou, s'il n'y a pas d'argument, alors une virgule ","
.
Soit result
une chaîne vide.
Ajoutez this[0]
au result
.
Ajoutez glue
et this[1]
.
Ajoutez glue
et this[2]
.
…Faites-le jusqu'à ce que les éléments this.length
soient collés.
Renvoie result
.
Donc, techniquement, il prend this
et joint this[0]
, this[1]
…etc ensemble. Il est intentionnellement écrit d'une manière qui autorise tout this
de tableau (ce n'est pas une coïncidence, de nombreuses méthodes suivent cette pratique). C'est pourquoi cela fonctionne également avec this=arguments
.
Il est généralement prudent de remplacer une fonction ou une méthode par une fonction décorée, à une petite exception près. Si la fonction d'origine avait des propriétés, comme func.calledCount
ou autre, alors celle décorée ne les fournira pas. Parce que c'est un emballage. Il faut donc être prudent si on les utilise.
Par exemple, dans l'exemple ci-dessus, si la fonction slow
avait des propriétés, alors cachingDecorator(slow)
est un wrapper sans elles.
Certains décorateurs peuvent proposer leurs propres propriétés. Par exemple, un décorateur peut compter combien de fois une fonction a été invoquée et combien de temps cela a pris, et exposer ces informations via les propriétés du wrapper.
Il existe un moyen de créer des décorateurs qui conservent l'accès aux propriétés de la fonction, mais cela nécessite l'utilisation d'un objet Proxy
spécial pour envelopper une fonction. Nous en discuterons plus tard dans l'article Proxy et Reflect.
Decorator est un wrapper autour d'une fonction qui modifie son comportement. Le travail principal reste assuré par la fonction.
Les décorateurs peuvent être considérés comme des « caractéristiques » ou des « aspects » qui peuvent être ajoutés à une fonction. Nous pouvons en ajouter un ou en ajouter plusieurs. Et tout cela sans changer son code !
Pour implémenter cachingDecorator
, nous avons étudié les méthodes :
func.call(context, arg1, arg2…) – appelle func
avec un contexte et des arguments donnés.
func.apply(context, args) – appelle func
en passant context
comme this
et args
de type tableau dans une liste d'arguments.
Le renvoi d'appel générique se fait généralement avec apply
:
laissez wrapper = fonction() { return original.apply(this, arguments); } ;
Nous avons également vu un exemple d' emprunt de méthode lorsque nous prenons une méthode d'un objet et l' call
dans le contexte d'un autre objet. Il est assez courant de prendre des méthodes de tableau et de les appliquer aux arguments
. L’alternative consiste à utiliser un objet de paramètres de repos qui est un vrai tableau.
Il y a de nombreux décorateurs dans la nature. Vérifiez dans quelle mesure vous les avez obtenus en résolvant les tâches de ce chapitre.
importance : 5
Créez un spy(func)
qui doit renvoyer un wrapper qui enregistre tous les appels à fonctionner dans sa propriété calls
.
Chaque appel est enregistré sous forme d'un tableau d'arguments.
Par exemple:
fonction travail(a, b) { alerte( a + b ); // le travail est une fonction ou une méthode arbitraire } travail = espion (travail); travail(1, 2); // 3 travailler(4, 5); // 9 pour (laisser les arguments de work.calls) { alert( 'call:' + args.join() ); // "appel :1,2", "appel :4,5" }
PS Ce décorateur est parfois utile pour les tests unitaires. Sa forme avancée est sinon.spy
dans la bibliothèque Sinon.JS.
Ouvrez un bac à sable avec des tests.
Le wrapper renvoyé par spy(f)
doit stocker tous les arguments, puis utiliser f.apply
pour transférer l'appel.
fonction espion (func) { wrapper de fonction(...arguments) { // utilisant ...args au lieu d'arguments pour stocker un tableau "réel" dans wrapper.calls wrapper.calls.push(args); return func.apply(this, args); } wrapper.calls = []; emballage de retour ; }
Ouvrez la solution avec des tests dans un bac à sable.
importance : 5
Créez un delay(f, ms)
qui retarde chaque appel de f
de ms
millisecondes.
Par exemple:
fonction f(x) { alerte(x); } // crée des wrappers soit f1000 = retard(f, 1000); soit f1500 = retard(f, 1500); f1000("test"); // affiche "test" après 1000 ms f1500("test"); // affiche "test" après 1500 ms
En d'autres termes, delay(f, ms)
renvoie une variante « retardée de ms
» de f
.
Dans le code ci-dessus, f
est fonction d'un seul argument, mais votre solution doit transmettre tous les arguments et le contexte this
.
Ouvrez un bac à sable avec des tests.
La solution :
fonction retard (f, ms) { fonction de retour() { setTimeout(() => f.apply(this, arguments), ms); } ; } soit f1000 = délai (alerte, 1000) ; f1000("test"); // affiche "test" après 1000 ms
Veuillez noter comment une fonction fléchée est utilisée ici. Comme nous le savons, les fonctions fléchées n'ont pas leurs propres this
et arguments
, donc f.apply(this, arguments)
prend this
et arguments
du wrapper.
Si nous passons une fonction régulière, setTimeout
l'appellerait sans arguments et this=window
(en supposant que nous soyons dans le navigateur).
Nous pouvons toujours passer le this
en utilisant une variable intermédiaire, mais c'est un peu plus fastidieux :
fonction retard (f, ms) { fonction de retour(...arguments) { laissez saveThis = ceci ; // stocke ceci dans une variable intermédiaire setTimeout(fonction() { f.apply(savedThis, args); // utilisez-le ici }, MS); } ; }
Ouvrez la solution avec des tests dans un bac à sable.
importance : 5
Le résultat du décorateur debounce(f, ms)
est un wrapper qui suspend les appels à f
jusqu'à ce qu'il y ait ms
millisecondes d'inactivité (aucun appel, « période de refroidissement »), puis invoque f
une fois avec les derniers arguments.
En d'autres termes, debounce
est comme une secrétaire qui accepte des « appels téléphoniques » et attend jusqu'à ce qu'elle reste silencieuse pendant ms
millisecondes. Et alors seulement, il transfère les dernières informations d'appel au « patron » (appelle le f
réel).
Par exemple, nous avions une fonction f
et l'avons remplacée par f = debounce(f, 1000)
.
Ensuite, si la fonction encapsulée est appelée à 0 ms, 200 ms et 500 ms, et qu'il n'y a aucun appel, alors le f
réel ne sera appelé qu'une seule fois, à 1 500 ms. C'est-à-dire : après la période de refroidissement de 1 000 ms à compter du dernier appel.
…Et il obtiendra les arguments du tout dernier appel, les autres appels sont ignorés.
Voici le code (utilise le décorateur anti-rebond de la bibliothèque Lodash) :
soit f = _.debounce (alerte, 1000); fa"); setTimeout( () => f("b"), 200); setTimeout( () => f("c"), 500); // la fonction anti-rebond attend 1000 ms après le dernier appel puis exécute : alert("c")
Maintenant un exemple pratique. Disons que l'utilisateur tape quelque chose et que nous aimerions envoyer une requête au serveur une fois la saisie terminée.
Cela ne sert à rien d'envoyer la requête pour chaque caractère saisi. Au lieu de cela, nous aimerions attendre, puis traiter l'ensemble du résultat.
Dans un navigateur Web, nous pouvons configurer un gestionnaire d'événements – une fonction appelée à chaque modification d'un champ de saisie. Normalement, un gestionnaire d'événements est appelé très souvent, pour chaque clé saisie. Mais si nous le debounce
de 1 000 ms, il ne sera alors appelé qu'une seule fois, 1 000 ms après la dernière entrée.
Dans cet exemple réel, le gestionnaire place le résultat dans une case ci-dessous, essayez-le :
Voir? La deuxième entrée appelle la fonction anti-rebond, son contenu est donc traité 1 000 ms après la dernière entrée.
Ainsi, debounce
est un excellent moyen de traiter une séquence d'événements : qu'il s'agisse d'une séquence de pressions sur des touches, de mouvements de souris ou autre chose.
Il attend le temps imparti après le dernier appel, puis exécute sa fonction, qui peut traiter le résultat.
La tâche consiste à implémenter un décorateur debounce
.
Astuce : cela ne fait que quelques lignes si vous y réfléchissez :)
Ouvrez un bac à sable avec des tests.
fonction anti-rebond (func, ms) { laissez le délai expirer ; fonction de retour() { clearTimeout(délai d'attente); timeout = setTimeout(() => func.apply(this, arguments), ms); } ; }
Un appel à debounce
renvoie un wrapper. Lorsqu'il est appelé, il planifie l'appel de fonction d'origine après ms
donné et annule le délai d'attente précédent.
Ouvrez la solution avec des tests dans un bac à sable.
importance : 5
Créez un throttle(f, ms)
– qui renvoie un wrapper.
Lorsqu'il est appelé plusieurs fois, il transmet l'appel à f
au maximum une fois par ms
milliseconde.
Par rapport au décorateur anti-rebond, le comportement est complètement différent :
debounce
exécute la fonction une fois après la période de « refroidissement ». Bon pour traiter le résultat final.
throttle
ne le fait pas fonctionner plus souvent que le temps ms
. Idéal 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 dérange le patron (appelle le f
) pas plus d'une fois par ms
milliseconde.
Vérifions l'application réelle pour mieux comprendre cette exigence et voir d'où elle vient.
Par exemple, nous souhaitons suivre les mouvements de la souris.
Dans un navigateur, nous pouvons configurer une fonction pour qu'elle s'exécute à chaque mouvement de la souris et obtenir l'emplacement du pointeur à mesure qu'il se déplace. Lors d'une utilisation active de la souris, cette fonction s'exécute généralement très fréquemment, peut être quelque chose comme 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 mettre à jour la fonction update()
est trop lourd pour le faire à chaque micro-mouvement. Cela n'a également aucun sens 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 mouvement de la souris au lieu de la update()
d'origine. Le décorateur sera appelé souvent, mais transmettra l'appel à update()
au maximum une fois toutes les 100 ms.
Visuellement, cela ressemblera à ceci :
Au premier mouvement de la souris, la variante décorée passe immédiatement l'appel à update
. C'est important, l'utilisateur voit immédiatement notre réaction à son mouvement.
Puis, au fur et à mesure que la souris avance, jusqu'à 100ms
, rien ne se passe. La variante décorée ignore les appels.
Au bout de 100ms
– une autre update
a lieu avec les dernières coordonnées.
Puis finalement, la souris s'arrête quelque part. La variante décorée attend l'expiration de 100ms
, puis exécute update
avec les dernières coordonnées. Donc, ce qui est très important, les coordonnées finales de la souris sont traitées.
Un exemple de code :
fonction f(a) { console.log(a); } // f1000 transmet les appels à f au maximum une fois toutes les 1000 ms soit f1000 = manette des gaz (f, 1000); 1 000 f(1 ); // affiche 1 f1000(2); // (limitation, 1000 ms pas encore sorti) f1000(3); // (limitation, 1000 ms pas encore sorti) // quand le délai de 1000 ms expire... // ...sorties 3, la valeur intermédiaire 2 a été ignorée
Les arguments PS et le contexte this
à f1000
doivent être transmis au f
d'origine.
Ouvrez un bac à sable avec des tests.
fonction accélérateur (func, ms) { soit isThrottled = false, Args enregistrés, enregistréCeci ; fonction wrapper() { si (isThrottled) { // (2) saveArgs = arguments ; saveThis = ceci ; retour; } isThrottled = vrai ; func.apply(ce, arguments); // (1) setTimeout(fonction() { isThrottled = faux ; // (3) si (Argsenregistrés) { wrapper.apply (savedThis, saveArgs); saveArgs = saveThis = null ; } }, MS); } emballage de retour ; }
Un appel à throttle(func, ms)
renvoie wrapper
.
Lors du premier appel, le wrapper
exécute simplement func
et définit l'état de refroidissement ( isThrottled = true
).
Dans cet état, tous les appels sont mémorisés dans savedArgs/savedThis
. Veuillez noter que le contexte et les arguments sont tout aussi importants et doivent être mémorisés. Nous en avons besoin simultanément pour reproduire l'appel.
Après ms
millisecondes, setTimeout
se déclenche. L'état de refroidissement est supprimé ( isThrottled = false
) et, si nous avions ignoré les appels, 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 refroidissement et configurer le délai d'attente pour le réinitialiser.
Ouvrez la solution avec des tests dans un bac à sable.