简体   繁体   English

闭合破损-请帮助我修复

[英]Broken closure - please help me fix it

in a related question I have posted this code. 相关问题中,我已经发布了此代码。 It almost works, but the counter doesn't. 它几乎可以工作,但计数器不能工作。

Can we fix it? 我们可以解决吗? (no jQuery, please) (请不要使用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 />

Please check this 请检查一下

<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>

Working solution is here . 工作解决方案在这里

EDIT: There was a minor issue with the script. 编辑:脚本有一个小问题。 I've fixed it. 我已经解决了。

After recalculating the seconds and minutes you have to set the new values back in the element object. 重新计算secondsminutes ,必须将新值重新设置回element对象中。

You just need to take the minutes and seconds variable declarations out of the inner function, like this: 您只需要从内部函数中删除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);
}

When you call countdown , you want to fetch the initial values for each countdown and then slowly decrease them (closure makes sure that the values will stay available as long as the anonymous function requires them). 当您调用countdown ,您希望获取每个倒计时的初始值,然后慢慢减小它们(闭包确保只要匿名函数需要它们,这些值将保持可用)。 What you were doing before was reset the countdown values at the start of each tick, so the countdown never had a chance to... well, count down. 您之前所做的是在每个刻度线开始时重置倒数计时值,因此倒数计时永远没有机会...好吧,倒数计时。

Update: 更新:

If you need to update the values inside window.counters while the countdowns are active (although I don't see why you would want to do that; if you want to do anything meaningful with the "current" countdown values, just do it inside the anonymous function), you can simply add this at the end: 如果您需要在倒数计时处于活动状态时更新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;

You're decrementing the wrong variable inside your interval callback. 您正在递减间隔回调中的错误变量。 Instead of doing seconds-- and minutes-- you need to reference the element members. 您无需引用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);

All of the answers you've been given so far fail to handle one simple fact: setInterval does not happen reliably at the time you set. 所有你到目前为止一直在给出的答案无法处理一个简单的事实: setInterval 并不在您设定的时间发生可靠。 It can be delayed or even skipped if the browser is busy doing something else. 如果浏览器正忙于执行其他操作,则可能会延迟甚至跳过 This means you can't rely on your update function to decrement the number of seconds left, you'll start drifting from reality very quickly. 这意味着您不能依靠更新功能来减少剩余的秒数,您将很快开始脱离现实。 Maybe that's okay, maybe not. 也许还好,也许不行。 There's no need for it, in any case: Just calculate when the timeout occurs (eg, a minute and five seconds from now for your "shoes" counter), and then at each update, calculate how long you have left. 在任何情况下都不需要它:只需计算超时发生的时间(例如,“鞋子”计数器从现在起一分钟零五秒),然后在每次更新时计算您还剩下多长时间。 That way if an interval got dropped or something, you won't drift; 这样,如果间隔下降了或什么了,您就不会漂移。 you're deferring the computer's clock. 您正在推迟计算机的时钟。

Here's a procedural version: 这是程序版本:

// 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 现场例子

Here's an OOP version (using the module pattern so Counter can have named functions and a private one [ tick ]): 这是一个OOP版本(使用模块模式,因此Counter可以具有命名函数和私有的[ 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 . 更多关于闭包的信息

I think it'd make your code a lot cleaner and save you some if s if you would keep the timeout in code as seconds, rather than minutes and seconds: 我认为这将会使你的代码有很多清洁和节省一些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);
}​

(I'd post as a comment if it weren't for all the code...) (如果不是所有代码,我都会发表评论……)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM