二谈javascript中的定时器
侧边栏壁纸
  • 累计撰写 1,061 篇文章
  • 累计收到 0 条评论

二谈javascript中的定时器

加速器之家
2024-10-21 / 0 评论 / 8 阅读 / 正在检测是否收录...

在以前的文章【javascript 中的定时器】中,简单的介绍了一下 setTimeout()和 setInterval()两个定时器方法的使用和原理。不过在昨天给我的 node 即时聊天系统添加消息提示时,发现了定时器新的特性。当然,这对于我来说是新的发现,其实这些东西早就已经存在了。

1. 最小运行时间间隔 #

在 setTimeout()和 setInterval()我们能够设定时间间隔,来让下个事件大致发生在哪个时间段。假如我们设置时间间隔是 0 的话,那是不是就会在 0ms 之后执行呢,也就是立即执行。我们可以采用下面的代码输出一下:

function get() {
  var timer = null;
  var date = null;
  var diff = 0;
  var last = 0;
  var now = 0;
  var nums = 0;
  var color = "#000";
  var process = document.getElementById("process");
  timer = setInterval(function () {
    date = new Date();
    now = date.getTime();
    diff = now - last; // 前后两个时间差
    last = now;
    nums++;
    color = diff === 0 ? "#f00" : "#000";
    process.innerHTML +=
      "" +
      now +
      ' (' +
diff +
")"; if (nums >= 100) { clearInterval(timer); } }, 0); } get();

我们把每次执行 setInterval()前后的时间差打印到屏幕中(以下数据使用 chrome 42.0.2311.90 版本测试):

1429545782409 (1)
1429545782412 (3)
1429545782414 (2)
1429545782420 (6)
1429545782425 (5)
1429545782430 (5)
1429545782437 (7)
1429545782443 (6)
1429545782449 (6)
1429545782454 (5)
1429545782460 (6)
1429545782466 (6)
1429545782471 (5)
1429545782476 (5)
...

从打印出的数据可以看出,setInterval()的时间间隔为 0ms 时,输出的时间差基本都在 1~10ms 之间,也是能在可以接受的范围内。IE11 下的测试与 chrome 的数据基本一致,而在 firefox 下能够出现0的时间差。

2. 标签不可见时的定时器间隔 #

其实不管是把时间间隔设定为 0ms 还是其他的时间间隔,运行时都会有时间误差的,比设定的间隔多 1~16ms 毫秒左右,有的时候还会相差更多。

我们有时会在某个场合对标题进行闪动,提示给用户当前标签页有新消息产生:

var backup = document.title; // 存储原标题
function blink() {
  document.title = document.title == backup ? "【有新消息】" : backup;
}
blink();
timer = setInterval(blink, 500);

上面的代码能够进行 500ms 的标题轮流闪动,当我们处在当前标签页时,基本感觉不出定时器产生的误差。可是如果我们切换到其他的标签页或者最小化时,我们就能够看到,标题的闪动变慢了很多,差不多提升到 1000ms 左右了。

为了更加准确的记录时间间隔的变化,我们特此将上面的代码进行如下的补充,标题进行闪动时记录当前的毫秒时间戳,同时标记出当前标签页可见时的状态和不可见时的状态【查看演示】:

// 标题闪动
function blinkTile(title, timeout) {
  var self = this;
  var timer = null;
  var backup = document.title;
  var last = 0;
  var process = document.getElementById("process");

  self.init = function (title, timeou) {
    if (title != undefined) {
      self.title = title;
    }
    self.timeout = timeout == undefined ? 500 : timeout;
  };

  self.start = function () {
    self.stop();

    function blink() {
      document.title = document.title == backup ? self.title : backup;
      self.check();
    }
    blink();
    timer = setInterval(blink, self.timeout);
  };

  self.stop = function () {
    if (timer != null) {
      document.title = backup;
      clearInterval(timer);
      timer = null;
    }
  };

  // 打印时间差,同时让滚动条在最下边
  self.check = function () {
    var date = new Date();
    var now = date.getTime();
    var diff = now - last;
    last = now;
    process.innerHTML += "" + now + " (" + diff + ")";
    process.scrollTop = process.scrollHeight;
  };

  self.init(title, timeout);
}
var blink = new blinkTile("【新消息】", 500);
blink.start();

// 标签页的可见状态
var hidden, state, visibilityChange;
if (typeof document.hidden !== "undefined") {
  hidden = "hidden";
  visibilityChange = "visibilitychange";
  state = "visibilityState";
} else if (typeof document.mozHidden !== "undefined") {
  hidden = "mozHidden";
  visibilityChange = "mozvisibilitychange";
  state = "mozVisibilityState";
} else if (typeof document.msHidden !== "undefined") {
  hidden = "msHidden";
  visibilityChange = "msvisibilitychange";
  state = "msVisibilityState";
} else if (typeof document.webkitHidden !== "undefined") {
  hidden = "webkitHidden";
  visibilityChange = "webkitvisibilitychange";
  state = "webkitVisibilityState";
}

var process = document.getElementById("process");
// 添加监听器,在title里显示状态变化
document.addEventListener(
  visibilityChange,
  function () {
    if (document[state] == "hidden") {
      process.innerHTML += '

====== 离开 ======

'
; } else { process.innerHTML += '

++++++ 回来 ++++++

'
; } }, false );

运行后,我们能够看到程序记录下的数据有(以下仅是部分数据):

1429547223336 (505)
1429547223837 (501)
====== 离开 ======
1429547225296 (1459)
1429547226296 (1000)
1429547227296 (1000)
1429547228297 (1001)
++++++ 回来 ++++++
1429547229137 (840)
1429547229637 (500)

我们很清楚的看到,当标签页不可见时(“离开”后),时间差上升了 1000ms 左右;标签页可见时(“回来”后),时间差又恢复到了 500ms 左右。不过在标签页刚切换完的时候,时间差的变化比较大,后来就趋于稳定了。其实浏览器为了在标签页不可见时减少 CPU 的利用率和电池等的消耗,特地将时间间隔进行提高。

不过这里要指出的是,在 IE11 下,标签的可见状态不会影响定时器的时间间隔。

3. 如何解决时间间隔会变化的问题(2016 年 1 月 14 日更新) #

在上面的章节中,介绍了setIntervalsetTimeout的时间间隔会随着标签页的可见性发生变化。但是在有的情形下,是不希望时间间隔发生变化的,那该如何解决呢。

受下面评论的启发,使用web worker可以解决这个这个问题,但是因为 web worker 是 html5 里的标准,低版本的浏览器是支持不了的。

当前页面:

// 创建一个worker实例
var worker = new Worker("worker.js");

// 向worker.js发送信息
worker.postMessage("hello world");

var last = 0;
// 接收从worker.js发送的信息,存储在event.data中
worker.onmessage = function (event) {
  var diff = event.data - last;
  last = event.data;
  $("#content").append(diff + "
"
); }; // 报错信息 worker.onerror = function (error) { console.log(error.filename, error.lineno, error.message); };

worker.js:

// 接收前端页面发送过来的信息,存储在event.data中
onmessage = function (event) {
  var data = event.data;
  setInterval(function () {
    // 向前端页面发送信息
    postMessage(Date.now());
  }, 500);
};

在上面的例子中,可以看到,当前页面中的 js 和 worker.js 都是是通过postMessageonmessage进行互相通信的。当我们执行这些代码时,无论当前页面是否可见,setInterval 每次的间隔都是**500**。

因此,我们可以在worker.onmessage中进行相应的 DOM 操作。

0

评论

博主关闭了当前页面的评论