Javascript closure “stores” value at the wrong tim

2019-01-25 13:52发布

问题:

I'm trying to have a counter increase gradually. The following works:

function _award(points){    
  var step = 1;
  while(points){
    var diff = Math.ceil(points / 10);
    setTimeout( "_change_score_by("+diff+");" /* sigh */,
                step * 25);
    points -= diff;
    step++;
  }
}

However, it uses an implicit eval. Evil! Let's use a closure instead, right?

function _award(points){    
  var step = 1;
  while(points){
    var diff = Math.ceil(points / 10);
    setTimeout( function(){ _change_score_by(diff); },
                step * 25);
    points -= diff;
    step++;
  }
}

Obviously, this doesn't work. All closures created catch the last value diff has had in the function -- 1. Hence, all anonymous functions will increase the counter by 1 and, for example, _award(100) will increase the score by 28 instead.

How can I do this properly?

回答1:

This is a known problem. But you can easily create a closure on each loop iteration:

(function(current_diff) {
    setTimeout(function() {_change_score_by(current_diff);},
               step * 25);
})(diff);


回答2:

Solving the Closure Loop Problem gets less messy with ECMAScript Fifth Edition's Function#bind method:

setTimeout(_change_score_by.bind(window, diff), step*25);

You can hack this feature into browsers that don't support it yet to get the benefit now.



回答3:

Nikita's approach works (thanks!), but personally I prefer changing it to this more explicit way:

function _schedule_score_change(diff, step){
  setTimeout( function(){ _change_score_by(diff) },
              step*25 );
}

function _award(points){    
  var step = 1;
  while(points){
    var diff = Math.ceil(points / 10);
    _schedule_score_change(diff, step);
    points -= diff;
    step++;
  }
}