破封 - 请帮我解决

时间:2011-01-03 13:29:05

标签: javascript closures

related question我发布了此代码。它几乎可以工作,但计数器没有。

我们能解决吗? (请不要jQuery)

<script type="text/javascript">
var intervals = [];
var counters = {
  "shoes":{"id":"shoe1","minutes":1,"seconds":5},
  "trousers":{"id":"trouser1","minutes":10,"seconds":0}
}; // generate this on the server and note there is no comma after the last item
window.onload = function() {
  for (var el in counters) { countdown(counters[el]) };
}

function countdown(element) {
    intervals[element.id] = setInterval(function() {
        var el = document.getElementById(element.id);
        var minutes = element.minutes;
        var seconds = element.seconds;
        if(seconds == 0) {
            if(minutes == 0) {
                el.innerHTML = "countdown's over!";                    
                clearInterval(intervals[element.id]);
                return;
            } else {
                minutes--;
                seconds = 60;
            }
        }
        if(minutes > 0) {
            var minute_text = minutes + (minutes > 1 ? ' minutes' : ' minute');
        } else {
            var minute_text = '';
        }
        var second_text = seconds > 1 ? 'seconds' : 'second';
        el.innerHTML = minute_text + ' ' + seconds + ' ' + second_text + ' remaining';
        seconds--;
    }, 1000);
}
</script>
shoes: <span id="shoe1"></span><br />
trousers: <span id="trouser1"></span><br />

5 个答案:

答案 0 :(得分:4)

请检查

<html>
    <body>
        <script type="text/javascript">
        var intervals = [];
        var counters = {
          "shoes":{"id":"shoe1","minutes":1,"seconds":5},
          "trousers":{"id":"trouser1","minutes":10,"seconds":0}
        }; // generate this on the server and note there is no comma after the last item
        window.onload = function() {
          for (var el in counters) { countdown(counters[el]) };
        }

        function countdown(element) {
            intervals[element.id] = setInterval(function() {
                var el = document.getElementById(element.id);
                var minutes = element.minutes;
                var seconds = element.seconds;

                if(seconds == 0) {
                    if(minutes == 0) {
                        el.innerHTML = "countdown's over!";                    
                        clearInterval(intervals[element.id]);
                        return;
                    } else {
                        minutes--;
                        seconds = 60;
                    }
                }
                if(minutes > 0) {
                    var minute_text = minutes + (minutes > 1 ? ' minutes' : ' minute');
                } else {
                    var minute_text = '';
                }
                var second_text = seconds > 1 ? 'seconds' : 'second';
                el.innerHTML = minute_text + ' ' + seconds + ' ' + second_text + ' remaining';
                seconds--;

                element.seconds = seconds;
                element.minutes = minutes;
            }, 1000);
        }
        </script>
        shoes: <span id="shoe1"></span><br />
        trousers: <span id="trouser1"></span><br />
    </body>
</html>

工作解决方案是here

编辑:脚本存在一个小问题。我修好了。

重新计算secondsminutes后,您必须在element对象中重新设置新值。

答案 1 :(得分:4)

你只需要从内部函数中取出minutesseconds变量声明,如下所示:

function countdown(element) {
    var minutes = element.minutes;
    var seconds = element.seconds;

    intervals[element.id] = setInterval(function() {
        var el = document.getElementById(element.id);
        if(seconds == 0) {
            if(minutes == 0) {
                el.innerHTML = "countdown's over!";                    
                clearInterval(intervals[element.id]);
                return;
            } else {
                minutes--;
                seconds = 60;
            }
        }
        if(minutes > 0) {
            var minute_text = minutes + (minutes > 1 ? ' minutes' : ' minute');
        } else {
            var minute_text = '';
        }
        var second_text = seconds > 1 ? 'seconds' : 'second';
        el.innerHTML = minute_text + ' ' + seconds + ' ' + second_text + ' remaining';
        seconds--;
    }, 1000);
}

当您调用countdown时,您希望获取每个倒计时的初始值,然后慢慢减少它们(闭包确保只要匿名函数需要它们,这些值将保持可用)。你之前做的是在每个滴答开始时重置倒计时值,所以倒计时从来没有机会......好吧,倒计时。

<强>更新

如果您需要在倒计时处于活动状态时更新window.counters内的值(虽然我不明白您为什么要这样做;如果您想对“当前”倒计时值执行任何有意义的操作,只需在匿名函数中执行),您可以在最后添加:

var second_text = seconds > 1 ? 'seconds' : 'second';
el.innerHTML = minute_text + ' ' + seconds + ' ' + second_text + ' remaining';
seconds--;

// ADD THIS:
element.minutes = minutes;
element.seconds = seconds;

答案 2 :(得分:1)

你在间隔回调中减少了错误的变量。您无需执行seconds--minutes--,而是需要引用element成员。

intervals[element.id] = setInterval(function() {
    var el = document.getElementById(element.id);

    if(element.seconds == 0) {
        if(element.minutes == 0) {
            el.innerHTML = "countdown's over!";                    
            clearInterval(intervals[element.id]);
            return;
        } else {
            element.minutes--;
            element.seconds = 60;
        }
    }
    if(element.minutes > 0) {
        var minute_text = element.minutes + (element.minutes > 1 ? ' minutes' : ' minute');
    } else {
        var minute_text = '';
    }
    var second_text = element.seconds > 1 ? 'seconds' : 'second';
    el.innerHTML = minute_text + ' ' + element.seconds + ' ' + second_text + ' remaining';
    element.seconds--;
}, 1000);

答案 3 :(得分:1)

到目前为止,您提供的所有答案都无法处理一个简单的事实:setInterval 在您设置时可靠地发生。如果浏览器忙于执行其他操作,则可能会延迟甚至跳过。这意味着你不能依靠你的更新功能来减少剩余的秒数,你将很快开始从现实中漂移。也许那没关系,也许不是。在任何情况下都没有需要:只需计算超时发生的时间(例如,从现在开始的“鞋子”计数器的一分钟和五秒),然后在每次更新时计算多长时间你离开了。这样,如果一个间隔掉落或什么东西,你就不会漂移;你推迟了电脑的时钟。

这是一个程序版本:

// Note that to prevent globals, everything is enclosed in
// a function. In this case, we're using window.onload, but
// you don't have to wait that long (window.onload happens
// *very* late, after all images are loaded).
window.onload = function() {

  // Our counters
  var counters = {
    "shoes":{
      "id": "shoe1",
      "minutes": 1,
      "seconds":5
    },
    "trousers":{
      "id": "trouser1",
      "minutes": 10,
      "seconds":0
    }
  };

  // Start them
  var name;
  for (name in counters) {
    start(counters[name]);
  }

  // A function for starting a counter
  function start(counter) {
    // Find the time (in ms since The Epoch) at which
    // this item expires
    counter.timeout = new Date().getTime() +
                      (((counter.minutes * 60) + counter.seconds) * 1000);

    // Get this counter's target element
    counter.element = document.getElementById(counter.id);
    if (counter.element) {
      // Do the first update
      tick(counter);

      // Schedule the remaining ones to happen *roughly*
      // every quarter second. (Once a second will look
      // rough).
      counter.timer = setInterval(function() {
        tick(counter);
      }, 250);
    }
  }

  // Function to stop a counter
  function stop(counter) {
    if (counter.timer) {
      clearInterval(counter.timer);
      delete counter.timer;
    }
    delete counter.element;
  }

  // The function called on each "tick"
  function tick(counter) {
    var remaining, str;

    // How many seconds left?
    remaining = Math.floor(
      (counter.timeout - new Date().getTime()) / 1000
    );

    // Same as last time?
    if (remaining != counter.lastRemaining) {
      // No, do an update
      counter.lastRemaining = remaining;
      if (remaining <= 0) {
        // Done! Stop the counter.
        str = "done";
        alert("Stopped " + counter.id);
        stop(counter);
      }
      else {
        // More than a minute left?
        if (remaining >= 120) {
          // Yup, output a number
          str = Math.floor(remaining / 60) + " minutes";
        }
        else if (remaining >= 60) {
          // Just one minute left
          str = "one minute";
        }
        else {
          // Down to seconds!
          str = "";
        }

            // Truncate the minutes, just leave seconds (0..59)
        remaining %= 60;

        // Any seconds?
        if (remaining > 0) {
          // Yes, if there were minutes add an "and"
          if (str.length > 0) {
            str += " and ";
          }

          // If only one second left, use a word; else, 
          // a number
          if (remaining === 1) {
            str += "one second";
          }
          else {
            str += Math.floor(remaining) + " seconds";
              }
        }

        // Finish up
        str += " left";
      }

      // Write to the element
      counter.element.innerHTML = str;
    }
  }

};​

Live example

这是一个OOP版本(使用模块模式,Counter可以named functions和私有[tick]):

// A Counter constructor function
var Counter = (function() {
  var p;

  // The actual constructor (our return value)
  function Counter(id, minutes, seconds) {
    this.id = id;
    this.minutes = minutes || 0;
    this.seconds = seconds || 0;
  }

      // Shortcut to the prototype
  p = Counter.prototype;

  // Start a counter
  p.start = Counter_start;
  function Counter_start() {
    var me = this;

    // Find the time (in ms since The Epoch) at which
    // this item expires
    this.timeout = new Date().getTime() +
                      (((this.minutes * 60) + this.seconds) * 1000);

    // Get this counter's target element
    this.element = document.getElementById(this.id);
    if (this.element) {
      // Do the first update
      tick(this);

      // Schedule the remaining ones to happen *roughly*
      // every quarter second. (Once a second will look
      // rough).
      this.timer = setInterval(function() {
        tick(me);
      }, 250);
    }
  }

  // Stop a counter
  p.stop = Counter_stop;
  function Counter_stop() {
    if (this.timer) {
      clearInterval(this.timer);
      delete this.timer;
    }
    delete this.element;
  }

  // The function we use to update a counter; not exported
  // on the Counter prototype because we only need one for
  // all counters.
  function tick(counter) {
    var remaining, str;

    // How many seconds left?
    remaining = Math.floor(
      (counter.timeout - new Date().getTime()) / 1000
    );

    // Same as last time?
    if (remaining != counter.lastRemaining) {
      // No, do an update
      counter.lastRemaining = remaining;
      if (remaining <= 0) {
        // Done! Stop the counter.
        str = "done";
        alert("Stopped " + counter.id);
        stop(counter);
      }
      else {
        // More than a minute left?
        if (remaining >= 120) {
          // Yup, output a number
          str = Math.floor(remaining / 60) + " minutes";
        }
        else if (remaining >= 60) {
          // Just one minute left
          str = "one minute";
        }
        else {
          // Down to seconds!
          str = "";
        }

        // Truncate the minutes, just leave seconds (0..59)
        remaining %= 60;

        // Any seconds?
        if (remaining > 0) {
          // Yes, if there were minutes add an "and"
          if (str.length > 0) {
            str += " and ";
          }

          // If only one second left, use a word; else, 
          // a number
          if (remaining === 1) {
            str += "one second";
          }
          else {
            str += Math.floor(remaining) + " seconds";
          }
        }

        // Finish up
        str += " left";
      }

      // Write to the element
      counter.element.innerHTML = str;
    }
  }

  // Return the constructor function reference. This
  // gets assigned to the external var, which is how
  // everyone calls it.
  return Counter;
})();

// Note that to prevent globals, everything is enclosed in
// a function. In this case, we're using window.onload, but
// you don't have to wait that long (window.onload happens
// *very* late, after all images are loaded).
window.onload = function() {

  // Our counters
  var counters = {
    "shoes":    new Counter("shoe1", 1, 5),
    "trousers": new Counter("trouser1", 10, 0)
  };

  // Start them
  var name;
  for (name in counters) {
    counters[name].start();
  }

};​

Live example

More about closures here

答案 4 :(得分:0)

我认为如果您将代码中的超时保持为秒,而不是分钟和秒,它会使您的代码更加清晰并节省一些if

var intervals = [];
var counters = {
  "shoes":{"id":"shoe1","seconds":65},
  "trousers":{"id":"trouser1","seconds":600}
}; // generate this on the server and note there is no comma after the last item

window.onload = function() {
  for (var el in counters) { countdown(counters[el]) };
}

function countdown(element) {
    intervals[element.id] = setInterval(function() {
        var el = document.getElementById(element.id);

        if(element.seconds == 0) {
            el.innerHTML = "countdown's over!";                    
            clearInterval(intervals[element.id]);
            return;
        }

        var minutes = (element.seconds - (element.seconds % 60)) / 60;
        if(minutes > 0) {
            var minute_text = minutes + (minutes > 1 ? ' minutes' : ' minute');
        } else {
            var minute_text = '';
        }

        var second_text = (element.seconds%60) > 1 ? 'seconds' : 'second';
        el.innerHTML = minute_text + ' ' + (element.seconds%60) + ' ' + second_text + ' remaining';

        element.seconds--;

    }, 1000);
}​

(如果不是所有代码,我会发表评论......)