4种方案详解如何实现准时的setTimeout

接下来我会用 4 这种方式来探索。

while


想得到准确的,我们第一反应就是如果我们能够主动去触发,获取到最开始的时间,以及不断去轮询当前时间,如果差值是预期的时间,那么这个定时器肯定是准确的,那么用 while 可以实现这个功能。

理解起来也很简单:

i

代码如下:

function timer(time) {

const startTime = Date.now();

while(true) {

const now = Date.now();

if(now - startTime >= time) {

console.log(‘误差’, now - startTime - time);

return;

}

}

}

timer(5000);

打印:误差 0

显然这样的方式很精确,但是我们知道 js 是单线程运行,使用这样的方式强行霸占线程会使得页面进入卡死状态,这样的结果显然是不合适的。

Web Worker


那么既然无法在当前主线程避免这个误差,我们能否另开一个线程去处理呢?当然可以,JavaScript 也提供给我们这样一个能力,通过 Web Worker 我们就可以在另一个线程来运行我们的代码。

Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。              – 摘自MDN

一个 worker 的简单的示例

// main.js

var myWorker = new Worker(‘worker.js’);

// 监听 worker

myWorker.onmessage = function(e) {

result.textContent = e.data;

console.log(‘Message received from worker’);

}

first.onchange = function() {

// 向 worker 发送数据

myWorker.postMessage([first.value,second.value]);

console.log(‘Message posted to worker’);

}

// worker.js

onmessage = function(e) {

// 接受主线程的数据

console.log(‘Message received from main script’);

var workerResult = 'Result: ’ + (e.data[0] * e.data[1]);

console.log(‘Posting message back to main script’);

// 向主线程发送数据

postMessage(workerResult);

}

那么接下来我们就要加 worker 和 while 相结合,以下为创建 worker 部分

// worker生成器

const createWorker = (fn, options) => {

const blob = new Blob([‘(’ + fn.toString() + ‘)()’]);

const url = URL.createObjectURL(blob);

if (options) {

return new Worker(url, options);

}

return new Worker(url);

}

// worker 部分

const worker = createWorker(function () {

onmessage = function (e) {

const date = Date.now();

while (true) {

const now = Date.now();

if(now - date >= e.data) {

postMessage(1);

return;

}

}

}

})

我们通过在 worker 中写入一个 while 循环,当达到我们的预取时间的时候,再向主线程发送一个完成事件,就不会因为主线程的其他代码的干扰而造成数据不准的情况。

let isStart = false;

function timer() {

worker.onmessage = function (e) {

cb()

if (isStart) {

worker.postMessage(speed);

}

}

worker.postMessage(speed);

}

我们来看一下实际的效果。

我们可以看到执行的时间和理想的时间非常相近,而那细微的差异应该就是线程通讯耗时。

我们再来看看加入额外的代码逻辑的情况。

if (isStart) {

worker.postMessage(speed);

}

for (var x = 1, i = 0; i < 10000000; i++) { x *= (i + 1); }

![]( )

时间明显增加了一些,但是增加速度非常缓慢。

虽然我们用 Web Worker 修复时间看似被解决了。但是一方面, worker 线程会被 while 给占用,导致无法接受到信息,多个定时器无法同时执行,另一方面,由于 onmessage 还是属于事件循环内,如果主线程有大量阻塞还是会让时间越差越大,因此这并不是个完美的方案。

requestAnimationFrame


先来看看他的定义

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行,回调函数执行次数通常是每秒60次,也就是每16.7ms 执行一次,但是并不一定保证为 16.7 ms。

我们也可以尝试一下将它来模拟 setTimeout

// 模拟代码

function setTimeout2 (cb, delay) {

let startTime = Date.now()

loop()

function loop () {

const now = Date.now()

if (now - startTime >= delay) {

cb();

return;

}

requestAnimationFrame(loop)

}

}

发现由于 16.7 ms 间隔执行,在使用间隔很小的定时器,很容易导致时间的不准确。

再看看额外代码的引入效果。

window.setInterval2(function () { instance(); }, speed);

}

for (var x = 1, i = 0; i < 10000000; i++) { x *= (i + 1); }

略微加剧了误差的增加,因此这种方案仍然不是一种好的方案。

setTimeout 系统时间补偿


这个方案是在 stackoverflow 看到的一个方案,我们来看看此方案和原方案的区别

原方案

setTimeout系统时间补偿

当每一次定时器执行时后,都去获取系统的时间来进行修正,虽然每次运行可能会有误差,但是通过系统时间对每次运行的修复,能够让后面每一次时间都得到一个补偿。

function timer() {

var speed = 500,

counter = 1,

start = new Date().getTime();

function instance()

{

var ideal = (counter * speed),

real = (new Date().getTime() - start);

counter++;

var diff = (real - ideal);

ES6

  • 列举常用的ES6特性:

  • 箭头函数需要注意哪些地方?

  • let、const、var

  • 拓展:var方式定义的变量有什么样的bug?

  • Set数据结构

  • 拓展:数组去重的方法

  • 箭头函数this的指向。

  • 手写ES6 class继承。

微信小程序

  • 简单描述一下微信小程序的相关文件类型?

  • 你是怎么封装微信小程序的数据请求?

  • 有哪些参数传值的方法?

  • 你使用过哪些方法,来提高微信小程序的应用速度?

  • 小程序和原生App哪个好?

  • 简述微信小程序原理?

  • 分析微信小程序的优劣势

  • 怎么解决小程序的异步请求问题?

  • 14
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值