web-dev-qa-db-fra.com

Appel de fonctions avec setTimeout ()

Tout simplement...

pourquoi

setTimeout('playNote('+currentaudio.id+', '+noteTime+')', delay);

fonctionne parfaitement, en appelant la fonction après le délai spécifié, mais

setTimeout(playNote(currentaudio.id,noteTime), delay);

appelle la fonction playNote en même temps?

(ces setTimeout () sont dans une boucle for)

ou, si mon explication est trop difficile à lire, quelle est la différence entre les deux fonctions?

36
Alex Hwang

Le premier formulaire que vous listez fonctionne, car il évaluera une chaîne à la fin de delay. Utiliser eval() n'est généralement pas une bonne idée, vous devriez donc éviter cela.

La deuxième méthode ne fonctionne pas, car vous exécutez immédiatement un objet fonction avec l'opérateur d'appel de fonction (). Ce qui finit par se produire, c'est que playNote est exécuté immédiatement si vous utilisez le formulaire playNote(...), ainsi rien ne se passera à la fin du délai.

Au lieu de cela, vous devez passer une fonction anonyme à setTimeout, le formulaire correct est donc:

setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);

Notez que vous transmettez à setTimeout une expression de fonction complète. Elle conservera donc la fonction anonyme et ne l'exécutera qu'à la fin du délai.

Vous pouvez également passer setTimeout une référence, car une référence n'est pas exécutée immédiatement, mais vous ne pouvez pas passer d'arguments:

setTimeout(playNote, delay);

Remarque:

Pour les événements répétés, vous pouvez utiliser setInterval() et vous pouvez définir setInterval() sur une variable et utiliser la variable pour arrêter l'intervalle avec clearInterval().

Vous dites que vous utilisez setTimeout() dans une boucle for. Dans de nombreuses situations, il est préférable d'utiliser setTimeout() dans une fonction récursive. En effet, dans une boucle for, les variables utilisées dans setTimeout() ne seront pas celles telles qu'elles étaient au début de setTimeout(), mais les variables telles qu'elles se trouvent après le délai d'activation de la fonction.

Utilisez simplement une fonction récursive pour contourner tout le problème.

Utilisation de la récursivité pour gérer des temps de retard variables:

  // Set original delay
var delay = 500;

  // Call the function for the first time, to begin the recursion.
playNote(xxx, yyy);

  // The recursive function
function playNote(theId, theTime)
{
    // Do whatever has to be done
    // ...

    // Have the function call itself again after a delay, if necessary
    //   you can modify the arguments that you use here. As an
    //   example I add 20 to theTime each time. You can also modify
    //   the delay. I add 1/2 a second to the delay each time as an example.
    //   You can use a condition to continue or stop the recursion

    delay += 500;

    if (condition)
    { setTimeout(function() { playNote(theID, theTime + 20) }, delay); }
}
64
Peter Ajtai

Essaye ça.

setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);
7
Daniel A. White

N'utilisez pas de timeout de chaîne. C'est efficace une eval, ce qui est une mauvaise chose. Cela fonctionne parce que currentaudio.id et noteTime sont convertis en représentations de chaînes d'eux-mêmes et cachés dans le code. Cela ne fonctionne que tant que ces valeurs ont toString()s qui génèrent une syntaxe littérale JavaScript qui va recréer la valeur, ce qui est vrai pour Number mais pas pour grand chose d'autre.

setTimeout(playNote(currentaudio.id, noteTime), delay);

c'est un appel de fonction. playNote est appelé immédiatement et le résultat renvoyé par la fonction (probablement undefined) est transmis à setTimeout(), et non ce que vous voulez.

Comme d'autres réponses le mentionnent, vous pouvez utiliser une expression de fonction inline avec une fermeture pour référencer currentaudio et noteTime:

setTimeout(function() {
    playNote(currentaudio.id, noteTime);
}, delay);

Cependant, si vous êtes dans une boucle et que currentaudio ou noteTime est différent à chaque fois, vous avez le problème de boucle de fermeture: la même variable sera référencée à chaque dépassement de délai; la même valeur à chaque fois, la valeur qui a été laissée dans la variable lorsque la boucle s'est terminée plus tôt.

Vous pouvez contourner ce problème avec un autre fermeture, en prenant une copie de la valeur de la variable pour chaque itération de la boucle:

setTimeout(function() {
    return function(currentaudio, noteTime) {
        playNote(currentaudio.id, noteTime);
    };
}(currentaudio, noteTime), delay);

mais cela devient un peu moche maintenant. Mieux vaut Function#bind, qui appliquera partiellement une fonction pour vous:

setTimeout(playNote.bind(window, currentaudio.id, noteTime), delay);

(window sert à définir la valeur de this dans la fonction, qui est une fonctionnalité de bind() dont vous n’avez pas besoin ici.)

Cependant, il s'agit d'une fonctionnalité ECMAScript Fifth Edition que tous les navigateurs ne prennent pas encore en charge. Donc, si vous voulez l'utiliser, vous devez d'abord modifier le support, par exemple:

// Make ECMA262-5 Function#bind work on older browsers
//
if (!('bind' in Function.prototype)) {
    Function.prototype.bind= function(owner) {
        var that= this;
        if (arguments.length<=1) {
            return function() {
                return that.apply(owner, arguments);
            };
        } else {
            var args= Array.prototype.slice.call(arguments, 1);
            return function() {
                return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
            };
        }
    };
}
4
bobince

Parce que le second, vous lui demandez d'appeler la fonction playNote first, puis de lui transmettre la valeur renvoyée à setTimeout.

1
dgnorton