28 votes

Fermetures récursives en JavaScript

Disons que j'ai quelque chose comme

 function animate(param)
{
    // ...
    if (param < 10)
        setTimeout(function () { animate(param + 1) }, 100);
}

animate(0);
 

Est-ce à dire que chaque instance de la fonction des données locales sera conservée en mémoire jusqu'à ce que l'animation se termine, c'est-à-dire jusqu'à ce que param atteigne 10?

S'il est vrai que les instances sont conservées en mémoire, existe-t-il une meilleure façon de procéder? Je sais, passer du code textuel à setTimeout () résout le problème, mais dans mon cas, il existe des objets parmi les arguments de fonction qui ne peuvent pas être représentés facilement sous forme de chaîne.

12voto

Wladimir Palant Points 34829

Non, tout au plus deux instances de la fonction locale de données aura lieu en mémoire à un moment donné dans le temps. Voici l'ordre des événements:

  1. animate(0) est appelé.
  2. Une fermeture avec param == 0 est créé, il empêche à présent de cette variable d'être libéré.
  3. Temporisation des feux, animate(1) est appelé.
  4. Nouvelle fermeture avec param == 1 est créé, il est désormais éviter cette variable d'être libéré.
  5. La première fermeture termine son exécution, à ce stade, il n'est plus référencé et peut être libéré. Les variables locales de la première animate() appel peut également être libéré.
  6. Répétez à partir de l'étape 3, maintenant avec animate(2).

6voto

jAndy Points 93076

En fait, vous ne créez pas une fonction récursive là. En invoquant setTimeout de ses pas s'appelant elle-même plus. La seule fermeture de la créés ici est la fonction anonyme pour setTimeout, une fois exécuté le garbage collector de reconnaître que la référence à l'instance précédente pouvez obtenir nettoyé. C'est probablement ce qui n'arrivera pas instantanément, mais vous avez vraiment ne pouvez pas créer d' stack overflow l'aide que. Permet de visualiser cet exemple:

function myFunc() {
   var bigarray = new Array(10000).join('foobar');

   setTimeout(myFunc, 200);
}
myFunc();

En regardant l'utilisation de la mémoire de votre navigateur. Il va pousser constamment, mais après un certain temps (20 à 40 secondes pour moi) il va avoir complètement nettoyé de nouveau. D'autre part, si nous créons un véritable récursivité comme ceci:

function myFunc() {
   var bigarray = new Array(10000).join('foobar');

   myFunc();
}
myFunc();

Notre utilisation de la mémoire va croître, les navigateurs se verrouille et nous avons finalement créer qu' stack overflow. Javascript ne pas mettre en œuvre la Queue de la récursivité, donc on va se retrouver avec un dépassement de capacité dans tous les cas.


mise à jour

cela y ressemblait comme je me suis trompé dans mon premier exemple. Ce comportement est vrai uniquement si aucune fonction-le contexte est appelé (à l'aide d'une fonction anonyme, par exemple). Si nous ré-écriture qui, comme

function myFunc() {
   var bigarray = new Array(10000).join('foobar');

   setTimeout(function() {
     myFunc();
   }, 200);
}
myFunc();

Navigateur de mémoire ne semble pas sortir plus. Grandit jamais. C'est peut-être que tout intérieure-closured conserve une référence à l' bigarray. Mais de toute façon.

4voto

T.J. Crowder Points 285826

Seul le contexte lié à l'appel le plus récent à l' animate seront retenus (en théorie, c'est tout pour le garbage collector). Lors de l' animate crée la fonction anonyme, la fonction anonyme obtient une référence pour le contexte de cet appel à la animate et donc que le contexte reste dans la mémoire. Lorsque le délai d'attente se produit, la minuterie du code de référence de la fonction anonyme est libéré, ce qui libère de cette fonction dans le contexte de l'. Un nouveau contexte a été créé, mais le nouveau contexte ne fait pas référence à l'ancien, afin de ne pas garder l'ancien dans la mémoire.

La clé pour cela est que la liaison du contexte de la fonction qui se passe lorsque la fonction est créée, et non pas lorsqu'elle est appelée.

2voto

nickf Points 185423

Oui, dans votre exemple, chaque fois que la fonction d'animation est exécutée, une nouvelle fonction est créée avec sa propre portée de fermeture.

Pour éviter les fermetures multiples, vous pouvez essayer quelque chose comme ceci:

 function animate (param) {
    function doIt () {
        param++;
        if (param < 10) {
            setTimeout(doIt, 100);
        }
    };
    setTimeout(doIt, 100);
}
 

-1voto

James Gaunt Points 9541

Que diriez-vous

 function animate(param)
{
  //....
  if(param < 10)
    animateTimeout(param);
}

function animateTimeout(param)
{
   setTimout(function() { animate(param + 1) }, 100 );
}
 

De cette façon, vous ne stockez pas les données locales dans // ... en attendant le délai d'expiration.

Je ne sais pas si vous pensez qu'il y a plus de problème ici qu'il n'y en a vraiment cependant. Votre code n'est pas récursif en ce qu'il entraînera 10 chaînes de fermetures profondes car la fermeture 1 est fermée une fois le deuxième appel effectué pour l'animation. Chaque fermeture n'existe que pour la durée de vie d'un setTimeout.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X