问题
setTimeout在某一次写一个倒计时功能时,当浏览器缩小或者离开当前页面,倒计时会不准确,进入休眠模式,执行间隔比预期要慢。
为何不使用setInterval
先看下《JavaScript高级程序设计(第三版)》中对定时器解释
设定一个 150ms 后执行的定时器不代表到了 150ms 代码就立刻执行,它表示代码会在 150ms 后被加入到队列中。如果在这个时间点上,队列中没有其他东西,那么这段代码就会被执行。
也就是说定时器会在你任务队列中没用执行任务时才会将你的定时器添加到任务队列中。
这样,他会带来一些问题:
- 无视代码错误
setInterval有个讨厌的习惯,即对自己调用的代码是否报错这件事漠不关心。换句话说,如果setInterval执行的代码由于某种原因出了错,它还会持续不断(不管不顾)地调用该代码。
- 无视网络延迟
假设你每隔一段时间就通过Ajax轮询一次服务器,看看有没有新数据(注意:如果你真的这么做了,那恐怕你做错了;建议使用“补偿性轮询”(backoff polling)[1])。而由于某些原因(服务器过载、临时断网、流量剧增、用户带宽受限,等等),你的请求要花的时间远比你想象的要长。但setInterval不在乎。它仍然会按定时持续不断地触发请求,最终你的客户端网络队列会塞满Ajax调用。
- 不保证执行
与setTimeout不同,你并不能保证到了时间间隔,代码就准能执行。如果你调用的函数需要花很长时间才能完成,那某些调用会被直接忽略。
使用“web Workers”来解决
Web worker 是运行在后台的 JavaScript,不会影响页面的性能。
当在 HTML 页面中执行脚本时,页面是不可响应的,直到脚本已完成。
Web worker 是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能。您可以继续做任何愿意做的事情:点击、选取内容等等,而此时 web worker 运行在后台。
worker提供了一个方法和一个事件处理:
postMessage() 方法和onmessage事件处理
以一个倒计时60秒为例
// 创建一个Worker实例
this.worker = new Worker("./handleCenter.js");
// 初始化,否则不会执行
this.worker.postMessage("init");
// 监听Worker收到的消息
this.worker.onmessage = e => {
this.timeNum = e.data;
// 当倒计时为1时
if (this.timeNum == 1) {
/**
* 需要处理的逻辑
*/
}
};
handleCenter.js
// 创建一个60秒的倒计时
let timer = 60;
onmessage = function(evt) {
const fun = () => {
// 轮循处理timer
setTimeout(() => {
if (timer > 1) {
--timer;
} else {
timer = 60;
}
/**
* 满足及时条件时发送消息,通知Worker你可以做一些事了
* self指向worker
*/
self.postMessage(timer);
fun();
}, 1000);
};
fun();
};
离开的时候别忘了销毁
destroyed() {
this.worker.terminate()
}