setInterval fires only once

2019-09-06 06:16发布

I'm trying to create a self contained timer where all variables are inside a object.

For the most part it works but I can get this to fire once. What am I missing?

    function Countdown()
    {
        this.appId = null;
        this.timerId = null;
        this.seconds = null;

        this.decrementCounter = function (instant)
        {
            if (instant == null)
                return;

            instant.tick();
            if (instant.seconds === 0)
            {
                instant.tickEnd();
                instant.stop();
            }
            instant.seconds--;
        };
        this.tick = function ()
        {
            var xx = this.appId
        };
        this.tickEnd = function ()
        {
            var xx = this.appId
        };
        this.start = function ()
        {
            clearInterval(this.timerId);
            this.timerId = setInterval(this.decrementCounter(this), 1000);
        };
        this.stop = function ()
        {
            clearInterval(this.timerId);
        };
    }

5条回答
手持菜刀,她持情操
2楼-- · 2019-09-06 06:58

You are calling the function, not assigning a reference

this.timerId = setInterval(this.decrementCounter(this), 1000); 

Seems weird you are passing in "this" as an argument...

use bind()

this.timerId = setInterval(this.decrementCounter.bind(this, this), 1000); 

or a closure

var that = this;
this.timerId = setInterval(function(){that.decrementCounter(that); }, 1000); 
查看更多
倾城 Initia
3楼-- · 2019-09-06 07:09

I provided an answer to another question that creates a simple stopwatch.

You could modify the code slightly to count down instead of up.

Here's a jsbin demo.


Original snippet

var Stopwatch = function(elem, options) {

  var timer       = createTimer(),
      startButton = createButton("start", start),
      stopButton  = createButton("stop", stop),
      resetButton = createButton("reset", reset),
      offset,
      clock,
      interval;

  // default options
  options = options || {};
  options.delay = options.delay || 1;

  // append elements     
  elem.appendChild(timer);
  elem.appendChild(startButton);
  elem.appendChild(stopButton);
  elem.appendChild(resetButton);

  // initialize
  reset();

  // private functions
  function createTimer() {
    return document.createElement("span");
  }

  function createButton(action, handler) {
    var a = document.createElement("a");
    a.href = "#" + action;
    a.innerHTML = action;
    a.addEventListener("click", function(event) {
      handler();
      event.preventDefault();
    });
    return a;
  }

  function start() {
    if (!interval) {
      offset   = Date.now();
      interval = setInterval(update, options.delay);
    }
  }

  function stop() {
    if (interval) {
      clearInterval(interval);
      interval = null;
    }
  }

  function reset() {
    clock = 0;
    render();
  }

  function update() {
    clock += delta();
    render();
  }

  function render() {
    timer.innerHTML = clock/1000; 
  }

  function delta() {
    var now = Date.now(),
        d   = now - offset;

    offset = now;
    return d;
  }

  // public API
  this.start  = start;
  this.stop   = stop;
  this.reset  = reset;
};
查看更多
萌系小妹纸
4楼-- · 2019-09-06 07:11

In the following bit of code:

this.timerId = setInterval(this.decrementCounter(this), 1000);

You are executing this immediately:

this.decrementCounter(this)

And so the return value of that, is what is being called by setInterval each second. Usually you would want to pass a function closure, like this:

var _this = this; //make sure your variable is available in the scope that follows
this.timerId = setInterval(function() { this.decrementCounter(_this); }, 1000);

Then, when your timer is executed, it's the function which is called, and that function then executes what you want. The other option is to pass the first parameter as a string such as "callAFunction('blah')" which will be evaluated each second and executed, but I believe the above is that you want.

查看更多
Fickle 薄情
5楼-- · 2019-09-06 07:13

I modified your code a bit and changed the line containing setInterval to this:

this.timerId = setInterval((function(scope) {return function() {scope.decrementCounter(scope);};})(this), 1000);

The functions run inside of setInterval run in the window scope. It only runs once, because you don't pass the function itself just the result of it. You need to return the actual function or pass an anonymous function which calls it.

jsfiddle demo: http://jsfiddle.net/2gLdL/1/

查看更多
聊天终结者
6楼-- · 2019-09-06 07:16

Try this:

setInterval(function(){Countdown()},500);
查看更多
登录 后发表回答