Understanding JavaScript setTimeout and setInterva

2019-07-07 14:20发布

问题:

I need a bit of help understanding and learning how to control these functions to do what I intend for them to do

So basically I'm coming from a Java background and diving into JavaScript with a "Pong game" project. I have managed to get the game running with setInteval calling my main game loop every 20ms, so that's all ok. However I'm trying to implement a "countdown-to-begin-round" type of feature that basically makes a hidden div visible between rounds, sets it's innerHTML = "3" // then "2" then "1" then "GO!".

I initially attempted to do this by putting setTimeout in a 4-iteration for-loop (3,2,1,go) but always only displayed the last iteration. I tried tinkering for a bit but I keep coming back to the feeling that I'm missing a fundamental concept about how the control flows.

I'll post the relevant code from my program, and my question would be basically how is it that I'm writing my code wrong, and what do I need to know about setTimeout and setInterval to be able to fix it up to execute the way I intend it to. I'm interested in learning how to understand and master these calls, so although code examples would be awesome to help me understand and are obviously not unwelcome, but I just want to make it clear that I'm NOT looking for you to just "fix my code". Also, please no jQuery.

The whole program would be a big wall of code, so I'll try to keep it trimmed and relevant:

//this function is called from the html via onclick="initGame();"
function initGame(){

    usrScore = 0;
    compScore = 0;
    isInPlay = true;

    //in code not shown here, these objects all have tracking variables
    //(xPos, yPos, upperBound, etc) to update the CSS
    board = new Board("board");
    ball = new Ball("ball");
    lPaddle = new LPaddle("lPaddle");
    rPaddle = new RPaddle("rPaddle");

    renderRate = setInterval(function(){play();}, 20);

}

.

function initNewRound(){

    /*
     * a bunch of code to reset the pieces and their tracking variables(xPos, etc)    
     */

    //make my hidden div pop into visibility to display countdown (in center of board)
    count = document.getElementById("countdown");
    count.style.visibility = "visible"; 


     //*****!!!! Here's my issue !!!!*****//

    //somehow i ends up as -1 and that's what is displayed on screen
    //nothing else gets displayed except -1
    for(var i = 3; i >= 0; i--){
        setInterval(function(){transition(i);}, 1000);
    }
}

.

//takes initNewRound() for-loop var i and is intended to display 3, 2, 1, GO!
function transition(i){
    count.innerHTML = (i === 0) ? "Go" : i;
}

.

//and lastly my main game loop "play()" just for context
function play(){

    if(usrScore < 5 && compScore < 5){

        isInPlay = true;
        checkCollision();
        moveBall();
        moveRPaddle();

        if(goalScored()){
            isInPlay = false;
            initNewRound();
        }
    }

} 

Thanks a bunch for your advise, I'm pretty new to JavaScript so I really appreciate it.

回答1:

Expanding on cookie monster's comment, when you use setInterval in a loop, you are queueing up method executions that will run after the base code flow has completed. Rather than queue up multiple setInterval executions, you can queue up a single execution and use a variable closure or global counter to track the current count. In the example below, I used a global variable:

var i = 3 // global counter;
var counterInterval = null; // this will be the id of the interval so we can stop it

function initNewRound() {
    // do reset stuff

    counterInterval = setInterval(function () { transition() }, 1000); // set interval returns a ID number
}

// we don't need to worry about passing i, because it is global
function transition() {
    if (i > 0) {
        count.innerHTML = i;
    }
    else if (i === 0) {
        count.innerHTML = "Go!";
    }
    else {
        i = 4; // set it to 4, so we can do i-- as one line
        clearInterval(counterInterval); // this stops execution of the interval; we have to specify the id, so you don't kill the main game loop
    }

    i--;
}

Here is a Fiddle Demo



回答2:

The problem is in this code:

for(var i = 3; i >= 0; i--){
    setInterval(function(){transition(i);}, 1000);
}

When the code runs, it creates a new function 3 times, once for each loop, and then passes that function to setInterval. Each of these new functions refers to the variable i. When the first new function runs it first looks for a local variable (in it's own scope) called i. When it does not find it, it looks in the enclosing scope, and finds i has the value -1. In Javascript, variables are lexically scoped; an inner function may access the variables defined in the scope enclosing it. This concept is also known as "closure". This is probably the most confusing aspect of the language to learn, but is incredibly powerful once you understand it.

There is no need to resort to global variables, as you can keep i safely inside the enclosing scope:

function initNewRound(){
 var i = 3;
 var count = document.getElementById("countdown");
 count.style.visibility = "visible";

 var interval = setInterval(function(){
  //this function can see variables declared by the function that created it
  count.innerHTML = i || "Go"; //another good trick
  i-=1;
  i || clearInterval(interval); //stop the interval when i is 0
 },1000);
}

Each call to this function will create a new i, count and interval.