字节面试官问粉丝,如何实现准时的setTimeout

沙海 2021年6月17日12:20:47Java评论19字数 5918阅读19分43秒阅读模式
摘要

智能摘要

智能摘要文章源自JAVA秀-https://www.javaxiu.com/33415.html

计数次数,就等于理想状态下的延迟,通过以下例子来查看我们计时器的准确性。类比真实的场景,对于一些倒计时以及动画来说都会造成时间的偏差都是不理想的。我们可以看到执行的时间和理想的时间非常相近,而那细微的差异应该就是线程通讯耗时。还是属于事件循环内,如果主线程有大量阻塞还是会让时间越差越大,因此这并不是个完美的方案。发现由于16.7 ms间隔执行,在使用间隔很小的定时器,很容易导致时间的不准确。文章源自JAVA秀-https://www.javaxiu.com/33415.html

原文约 2594 | 图片 25 | 建议阅读 6 分钟 | 评价反馈文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout

小黑格子屋 文章源自JAVA秀-https://www.javaxiu.com/33415.html

以下文章来源于秋风的笔记,作者蓝色的秋风文章源自JAVA秀-https://www.javaxiu.com/33415.html

文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

秋风的笔记文章源自JAVA秀-https://www.javaxiu.com/33415.html

一个爱技术爱生活的前端程序员,分享技术、生活。文章源自JAVA秀-https://www.javaxiu.com/33415.html

文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

来源:秋风的笔记文章源自JAVA秀-https://www.javaxiu.com/33415.html

作者:蓝色的秋风文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

最近一个粉丝去面字节,被面试官问到了这个问题来问我,一听感觉有点意思,于是对它进行了一番研究,可能研究的过程以及结果不一定是最好的,但是还是记录一下,为各位提供一些帮助。文章源自JAVA秀-https://www.javaxiu.com/33415.html

拿到这个问题,假设有这样的场景,我们需要用 setTimeout 做一个动画,并且需要控制他的频率,50ms 运行一次,首先我们先上图,来看看 setTimeout 的表现。文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

运行代码如下,通过一个计数器来记录每一次 setTimeout 的调用,而设定的间隔 * 计数次数,就等于理想状态下的延迟,通过以下例子来查看我们计时器的准确性。文章源自JAVA秀-https://www.javaxiu.com/33415.html

function timer() {   var speed = 50, // 设定间隔   counter = 1,  // 计数   start = new Date().getTime();      function instance()   {    var ideal = (counter * speed),    real = (new Date().getTime() - start);        counter++;    form.ideal.value = ideal; // 记录理想值    form.real.value = real;   // 记录真实值    var diff = (real - ideal);    form.diff.value = diff;  // 差值    window.setTimeout(function() { instance(); }, speed);   };      window.setTimeout(function() { instance(); }, speed);}timer();

而我们如果在 setTimeout 还未执行期间加入一些额外的代码逻辑,再来看看这个差值。文章源自JAVA秀-https://www.javaxiu.com/33415.html

...window.setTimeout(function() { instance(); }, speed);for(var x=1, i=0; i<10000000; i++) { x *= (i + 1); }}...

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

可以看出,这大大加剧了误差。文章源自JAVA秀-https://www.javaxiu.com/33415.html

可以看到随着时间的推移, setTimeout 实际执行的时间和理想的时间差值会越来越大,这就不是我们预期的样子。类比真实的场景,对于一些倒计时以及动画来说都会造成时间的偏差都是不理想的。文章源自JAVA秀-https://www.javaxiu.com/33415.html

那么,从这个现象来看一下,为什么 setTimeout 会不准时呢?文章源自JAVA秀-https://www.javaxiu.com/33415.html

因为我们的代码往往并不是只有一个 setTimeout,大多数会遇到以下情况。文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

详细要从浏览器的事件循环讲起,但是讲事件循环的文章太多了,文本就不再累赘地详细展开讲解。文章源自JAVA秀-https://www.javaxiu.com/33415.html

视频文章源自JAVA秀-https://www.javaxiu.com/33415.html

(国内视频 https://www.bilibili.com/video/av456657611/)文章源自JAVA秀-https://www.javaxiu.com/33415.html

建议看国外的中英对照字幕,国内的翻译准确度一般文章源自JAVA秀-https://www.javaxiu.com/33415.html

相关文章文章源自JAVA秀-https://www.javaxiu.com/33415.html

  • https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/文章源自JAVA秀-https://www.javaxiu.com/33415.html

  • 极客时间 - 李兵 - 15 | 消息队列和事件循环:页面是怎么“活”起来的?https://time.geekbang.org/column/article/134456文章源自JAVA秀-https://www.javaxiu.com/33415.html

总结来说,因为浏览器页面是有消息队列和事件循环来驱动的,创建一个 setTimeout 的时候是将它推进了一个队列,并没有立即执行,只有本轮宏任务执行完,才会去检查当前的消息队列是否有有到期的任务。文章源自JAVA秀-https://www.javaxiu.com/33415.html

接下来我会用 4 这种方式来探索。文章源自JAVA秀-https://www.javaxiu.com/33415.html

while

想得到准确的,我们第一反应就是如果我们能够主动去触发,获取到最开始的时间,以及不断去轮询当前时间,如果差值是预期的时间,那么这个定时器肯定是准确的,那么用 while 可以实现这个功能。文章源自JAVA秀-https://www.javaxiu.com/33415.html

理解起来也很简单:文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

i文章源自JAVA秀-https://www.javaxiu.com/33415.html

代码如下:文章源自JAVA秀-https://www.javaxiu.com/33415.html

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文章源自JAVA秀-https://www.javaxiu.com/33415.html

显然这样的方式很精确,但是我们知道 js 是单线程运行,使用这样的方式强行霸占线程会使得页面进入卡死状态,这样的结果显然是不合适的。文章源自JAVA秀-https://www.javaxiu.com/33415.html

Web Worker

那么既然无法在当前主线程避免这个误差,我们能否另开一个线程去处理呢?当然可以,JavaScript 也提供给我们这样一个能力,通过 Web Worker 我们就可以在另一个线程来运行我们的代码。文章源自JAVA秀-https://www.javaxiu.com/33415.html

Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。              -- 摘自MDN文章源自JAVA秀-https://www.javaxiu.com/33415.html

一个 worker 的简单的示例文章源自JAVA秀-https://www.javaxiu.com/33415.html

// main.jsvar myWorker = new Worker('worker.js');// 监听 workermyWorker.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.jsonmessage = 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 部分文章源自JAVA秀-https://www.javaxiu.com/33415.html

// 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 循环,当达到我们的预取时间的时候,再向主线程发送一个完成事件,就不会因为主线程的其他代码的干扰而造成数据不准的情况。文章源自JAVA秀-https://www.javaxiu.com/33415.html

let isStart = false;function timer() {    worker.onmessage = function (e) {       cb()        if (isStart) {            worker.postMessage(speed);        }     }    worker.postMessage(speed);}

我们来看一下实际的效果。文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

我们可以看到执行的时间和理想的时间非常相近,而那细微的差异应该就是线程通讯耗时。文章源自JAVA秀-https://www.javaxiu.com/33415.html

我们再来看看加入额外的代码逻辑的情况。文章源自JAVA秀-https://www.javaxiu.com/33415.html

...if (isStart) {   worker.postMessage(speed);}for (var x = 1, i = 0; i < 10000000; i++) { x *= (i + 1); }...

![](https://s3.qiufengh.com/blog/2021-04-20 23.16.44.gif)文章源自JAVA秀-https://www.javaxiu.com/33415.html

时间明显增加了一些,但是增加速度非常缓慢。文章源自JAVA秀-https://www.javaxiu.com/33415.html

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

requestAnimationFrame

先来看看他的定义文章源自JAVA秀-https://www.javaxiu.com/33415.html

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

我们也可以尝试一下将它来模拟 setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

// 模拟代码function setTimeout2 (cb, delay) {    let startTime = Date.now()    loop()      function loop () {      const now = Date.now()      if (now - startTime >= delay) {        cb();        return;      }      requestAnimationFrame(loop)    }}

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

发现由于 16.7 ms 间隔执行,在使用间隔很小的定时器,很容易导致时间的不准确。文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

再看看额外代码的引入效果。文章源自JAVA秀-https://www.javaxiu.com/33415.html

... window.setInterval2(function () { instance(); }, speed);}for (var x = 1, i = 0; i < 10000000; i++) { x *= (i + 1); }...

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

略微加剧了误差的增加,因此这种方案仍然不是一种好的方案。文章源自JAVA秀-https://www.javaxiu.com/33415.html

setTimeout 系统时间补偿

这个方案是在 stackoverflow 看到的一个方案,我们来看看此方案和原方案的区别文章源自JAVA秀-https://www.javaxiu.com/33415.html

原方案文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

setTimeout系统时间补偿文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

当每一次定时器执行时后,都去获取系统的时间来进行修正,虽然每次运行可能会有误差,但是通过系统时间对每次运行的修复,能够让后面每一次时间都得到一个补偿。文章源自JAVA秀-https://www.javaxiu.com/33415.html

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);    form.diff.value = diff;    window.setTimeout(function() { instance(); }, (speed - diff)); // 通过系统时间进行修复   };      window.setTimeout(function() { instance(); }, speed);}

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

再来看看加入额外的代码逻辑的情况。文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

依旧非常的稳定,因此通过系统的时间补偿,能够让我们的 setTimeout 变得更加准时,至此我们完成了如何让 setTimeout 准时的探索。文章源自JAVA秀-https://www.javaxiu.com/33415.html

好了我们最后来总结一下4种方案的优缺点文章源自JAVA秀-https://www.javaxiu.com/33415.html

whileWeb WorkerrequestAnimationFramesetTimeout 系统时间补偿
准确度
主线程阻塞阻塞一般不阻塞不阻塞
评分⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️

我们下期再见~文章源自JAVA秀-https://www.javaxiu.com/33415.html

参考

https://segmentfault.com/q/1010000013909430文章源自JAVA秀-https://www.javaxiu.com/33415.html

https://stackoverflow.com/questions/196027/is-there-a-more-accurate-way-to-create-a-javascript-timer-than-settimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

-End-字节面试官问粉丝,如何实现准时的setTimeout

文章源自JAVA秀-https://www.javaxiu.com/33415.html

文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

一文搞懂JS中的“值传递”和“引用传递”文章源自JAVA秀-https://www.javaxiu.com/33415.html

文章源自JAVA秀-https://www.javaxiu.com/33415.html

文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

一张图对比阿里、腾讯、快手的企业文化文章源自JAVA秀-https://www.javaxiu.com/33415.html

文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

2021年6月程序员工资统计,平均15052元文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout 可乐记得加冰,爱我就要置顶 字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

字节面试官问粉丝,如何实现准时的setTimeout素质三连biubiubiu~字节面试官问粉丝,如何实现准时的setTimeout文章源自JAVA秀-https://www.javaxiu.com/33415.html

继续阅读
速蛙云 - 极致体验,强烈推荐!!!购买套餐就免费送各大视频网站会员!快速稳定、独家福利社、流媒体稳定解锁!速度快,全球上网、视频、游戏加速、独立IP均支持!基础套餐性价比很高!这里不多说,我一直正在使用,推荐购买:https://www.javaxiu.com/59919.html
weinxin
资源分享QQ群
本站是JAVA秀团队的技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
沙海
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定