1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        字節(jié)面試官問粉絲,如何實(shí)現(xiàn)準(zhǔn)時(shí)的setTimeout

        共 9691字,需瀏覽 20分鐘

         ·

        2021-06-21 22:55

        關(guān)注并將「趣談前端」設(shè)為星標(biāo)

        每早08:30按時(shí)推送技術(shù)干貨/優(yōu)秀開源/技術(shù)思維



        最近一個(gè)粉絲去面字節(jié),被面試官問到了這個(gè)問題來問我,一聽感覺有點(diǎn)意思,于是對它進(jìn)行了一番研究,可能研究的過程以及結(jié)果不一定是最好的,但是還是記錄一下,為各位提供一些幫助。

        拿到這個(gè)問題,假設(shè)有這樣的場景,我們需要用 setTimeout 做一個(gè)動(dòng)畫,并且需要控制他的頻率,50ms 運(yùn)行一次,首先我們先上圖,來看看 setTimeout 的表現(xiàn)。

        運(yùn)行代碼如下,通過一個(gè)計(jì)數(shù)器來記錄每一次 setTimeout 的調(diào)用,而設(shè)定的間隔 * 計(jì)數(shù)次數(shù),就等于理想狀態(tài)下的延遲,通過以下例子來查看我們計(jì)時(shí)器的準(zhǔn)確性。

        function timer({
           var speed = 50// 設(shè)定間隔
           counter = 1,  // 計(jì)數(shù)
           start = new Date().getTime();
           
           function instance()
           
        {
            var ideal = (counter * speed),
            real = (new Date().getTime() - start);
            
            counter++;
            form.ideal.value = ideal; // 記錄理想值
            form.real.value = real;   // 記錄真實(shí)值

            var diff = (real - ideal);
            form.diff.value = diff;  // 差值

            window.setTimeout(function({ instance(); }, speed);
           };
           
           window.setTimeout(function({ instance(); }, speed);
        }
        timer();

        而我們?nèi)绻?setTimeout 還未執(zhí)行期間加入一些額外的代碼邏輯,再來看看這個(gè)差值。

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

        可以看出,這大大加劇了誤差。

        可以看到隨著時(shí)間的推移, setTimeout 實(shí)際執(zhí)行的時(shí)間和理想的時(shí)間差值會越來越大,這就不是我們預(yù)期的樣子。類比真實(shí)的場景,對于一些倒計(jì)時(shí)以及動(dòng)畫來說都會造成時(shí)間的偏差都是不理想的。

        那么,從這個(gè)現(xiàn)象來看一下,為什么 setTimeout 會不準(zhǔn)時(shí)呢?

        因?yàn)槲覀兊拇a往往并不是只有一個(gè) setTimeout,大多數(shù)會遇到以下情況。

        詳細(xì)要從瀏覽器的事件循環(huán)講起,但是講事件循環(huán)的文章太多了,文本就不再累贅地詳細(xì)展開講解。

        視頻

        • https://www.youtube.com/watch?v=8aGhZQkoFbQ

        (國內(nèi)視頻 https://www.bilibili.com/video/av456657611/)

        建議看國外的中英對照字幕,國內(nèi)的翻譯準(zhǔn)確度一般

        相關(guān)文章

        • https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

        • 極客時(shí)間 - 李兵 - 15 | 消息隊(duì)列和事件循環(huán):頁面是怎么“活”起來的?https://time.geekbang.org/column/article/134456

        總結(jié)來說,因?yàn)闉g覽器頁面是有消息隊(duì)列和事件循環(huán)來驅(qū)動(dòng)的,創(chuàng)建一個(gè) setTimeout 的時(shí)候是將它推進(jìn)了一個(gè)隊(duì)列,并沒有立即執(zhí)行,只有本輪宏任務(wù)執(zhí)行完,才會去檢查當(dāng)前的消息隊(duì)列是否有有到期的任務(wù)。

        接下來我會用 4 這種方式來探索。

        while

        想得到準(zhǔn)確的,我們第一反應(yīng)就是如果我們能夠主動(dòng)去觸發(fā),獲取到最開始的時(shí)間,以及不斷去輪詢當(dāng)前時(shí)間,如果差值是預(yù)期的時(shí)間,那么這個(gè)定時(shí)器肯定是準(zhǔn)確的,那么用 while 可以實(shí)現(xiàn)這個(gè)功能。

        理解起來也很簡單:

        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 是單線程運(yùn)行,使用這樣的方式強(qiáng)行霸占線程會使得頁面進(jìn)入卡死狀態(tài),這樣的結(jié)果顯然是不合適的。

        Web Worker

        那么既然無法在當(dāng)前主線程避免這個(gè)誤差,我們能否另開一個(gè)線程去處理呢?當(dāng)然可以,JavaScript 也提供給我們這樣一個(gè)能力,通過 Web Worker 我們就可以在另一個(gè)線程來運(yùn)行我們的代碼。

        Web Worker為Web內(nèi)容在后臺線程中運(yùn)行腳本提供了一種簡單的方法。線程可以執(zhí)行任務(wù)而不干擾用戶界面。              -- 摘自MDN

        一個(gè) worker 的簡單的示例

        // main.js
        var myWorker = new Worker('worker.js');

        // 監(jiān)聽 worker
        myWorker.onmessage = function(e{
          result.textContent = e.data;
          console.log('Message received from worker');
        }
        first.onchange = function({
          // 向 worker 發(fā)送數(shù)據(jù)
          myWorker.postMessage([first.value,second.value]);
          console.log('Message posted to worker');
        }

        // worker.js
        onmessage = function(e{
          // 接受主線程的數(shù)據(jù)
          console.log('Message received from main script');
          var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
          console.log('Posting message back to main script');
          // 向主線程發(fā)送數(shù)據(jù)
          postMessage(workerResult);
        }

        那么接下來我們就要加 worker 和 while 相結(jié)合,以下為創(chuàng)建 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 中寫入一個(gè) while 循環(huán),當(dāng)達(dá)到我們的預(yù)取時(shí)間的時(shí)候,再向主線程發(fā)送一個(gè)完成事件,就不會因?yàn)橹骶€程的其他代碼的干擾而造成數(shù)據(jù)不準(zhǔn)的情況。

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

        我們來看一下實(shí)際的效果。

        我們可以看到執(zhí)行的時(shí)間和理想的時(shí)間非常相近,而那細(xì)微的差異應(yīng)該就是線程通訊耗時(shí)。

        我們再來看看加入額外的代碼邏輯的情況。

        ...
        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)

        時(shí)間明顯增加了一些,但是增加速度非常緩慢。

        雖然我們用 Web Worker 修復(fù)時(shí)間看似被解決了。但是一方面, worker 線程會被 while 給占用,導(dǎo)致無法接受到信息,多個(gè)定時(shí)器無法同時(shí)執(zhí)行,另一方面,由于 onmessage 還是屬于事件循環(huán)內(nèi),如果主線程有大量阻塞還是會讓時(shí)間越差越大,因此這并不是個(gè)完美的方案。

        requestAnimationFrame

        先來看看他的定義

        window.requestAnimationFrame() 告訴瀏覽器——你希望執(zhí)行一個(gè)動(dòng)畫,并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動(dòng)畫。該方法需要傳入一個(gè)回調(diào)函數(shù)作為參數(shù),該回調(diào)函數(shù)會在瀏覽器下一次重繪之前執(zhí)行,回調(diào)函數(shù)執(zhí)行次數(shù)通常是每秒60次,也就是每16.7ms 執(zhí)行一次,但是并不一定保證為 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)
            }
        }

        發(fā)現(xiàn)由于 16.7 ms 間隔執(zhí)行,在使用間隔很小的定時(shí)器,很容易導(dǎo)致時(shí)間的不準(zhǔn)確。

        再看看額外代碼的引入效果。

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

        略微加劇了誤差的增加,因此這種方案仍然不是一種好的方案。

        setTimeout 系統(tǒng)時(shí)間補(bǔ)償

        這個(gè)方案是在 stackoverflow 看到的一個(gè)方案,我們來看看此方案和原方案的區(qū)別

        原方案

        setTimeout系統(tǒng)時(shí)間補(bǔ)償

        當(dāng)每一次定時(shí)器執(zhí)行時(shí)后,都去獲取系統(tǒng)的時(shí)間來進(jìn)行修正,雖然每次運(yùn)行可能會有誤差,但是通過系統(tǒng)時(shí)間對每次運(yùn)行的修復(fù),能夠讓后面每一次時(shí)間都得到一個(gè)補(bǔ)償。

        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)); // 通過系統(tǒng)時(shí)間進(jìn)行修復(fù)

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

        再來看看加入額外的代碼邏輯的情況。

        依舊非常的穩(wěn)定,因此通過系統(tǒng)的時(shí)間補(bǔ)償,能夠讓我們的 setTimeout 變得更加準(zhǔn)時(shí),至此我們完成了如何讓 setTimeout 準(zhǔn)時(shí)的探索。

        好了我們最后來總結(jié)一下4種方案的優(yōu)缺點(diǎn)


        whileWeb WorkerrequestAnimationFramesetTimeout 系統(tǒng)時(shí)間補(bǔ)償
        準(zhǔn)確度
        主線程阻塞阻塞一般不阻塞不阻塞
        評分??????????????????????

        我們下期再見~

        參考

        https://segmentfault.com/q/1010000013909430

        https://stackoverflow.com/questions/196027/is-there-a-more-accurate-way-to-create-a-javascript-timer-than-settimeout

        ?? 看完三件事

        如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我三個(gè)小忙:

        • 點(diǎn)個(gè)【在看】,或者分享轉(zhuǎn)發(fā),讓更多的人也能看到這篇內(nèi)容

        • 關(guān)注公眾號【趣談前端】,不定期分享 前端工程化 可視化 / 低代碼 等技術(shù)文章。



        Dooring可視化搭建平臺數(shù)據(jù)源設(shè)計(jì)剖析

        10款2021年國外頂尖的lowcode開發(fā)平臺

        canvas圖像識取技術(shù)以及智能化設(shè)計(jì)的思考

        瀏覽 52
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产视频999| 国产一级在线观看| 欧美一级特黄A片免费看视频小说| 国产精品久久久久久亚洲影视| 日韩乱伦AV| 亚洲成a| 日韩一区二区三区精品| 人人操人人操人人操人人操| 欧美日韩亚洲另类| 尤物视频入口| 国产精品视频久久久久| 大鸡巴视频在线| 久久人妻无码中文字幕系列 | 蜜桃无码视频小说网站| 蝌蚪窝视频网| 麻豆午夜福利| 色色com| 亚洲免费精品视频| 日本大胆中出| 91久久久久久久久18| 精品福利在线观看| 欧美三级片在线播放| 中文字幕日韩欧美| 特级西西人体大胆无码| 国产成人免费在线观看| 91黄色电影| 91av免费观看| 91三级视频| 国产成人免费观看| 看毛片网址| 大鸡吧网| 国产操逼视频网站| 97人妻精品一区二区三区视频 | 成年人视频在线免费观看| 六月婷婷中文字幕| 91久热| 欧洲精品视频在线观看| 欧美久久性爱| 在线观看中文字幕AV| 熟女中文| 成人免费激情视频| 日本一级大片| 婷婷五月天基地| 欧美A片在线播放| 日韩成人区| 久久蝌蚪窝| 精品亚洲无码视频| 亚洲视频免费在线播放| 亚洲午夜精品视频| 一道本高清无码视频| 亚洲精品911| 91在线无码精品秘国产| 日批国产| 免费内射视频| 91豆花成人网站| 日本精品在线| 欧美一区视频| A片小视频| 无码福利| 亚洲国产高清视频| 嫩BBB槡BBBB槡BBB| 亚洲无码视频免费观看| 国产一区二区不卡| 亚洲无码影院| 天天搞天天曰在线观看| 永久免费看片视频5355| 91人妻无码一区二区久久| 一本色道精品久久一区二区三区| 性猛交╳XXX乱大交| 日韩欧美片| 三级片网页| 黄色爱爱视频| 日本熟妇无码一区二区| 美女久久久久| 日韩激情在线观看| 欧美sesese| 国产精品久久久久久久久久久久久| 天天草天天干| 成人无码交配视频国产网站 | 精品无码AV一区二区三区| 亚洲男女啪啪视频| 黄色动漫在线免费观看| 大香蕉性爱网| 成人精品一区二区区别解析| 婷婷狠狠干| 人妻二区| 无码精品一区二区| 亚韩无码| 色77777| 激情淫荡少妇| 亚洲无码操逼视频| 人妻无码在线观看| 欧美在线视频你懂的| 国产成人精品久久二区二区91| 在线观看日韩欧美| 人人看人人搂人人摸| 可以免费观看的av| 亚洲内射视频| 人人人人操| 天天干天天射天天操| 国产高清自拍| 色a视频| 成人午夜小电影| 中文字幕无码Av在线| 在线观看精品视频| 亚洲成人在线网| 91九色91蝌蚪91成人| 玖玖成人电影| 亚洲精品视频在线播放| 在线看a片| 五月天网址| 国产精品熟女| 婷婷五月激情小说| 久草综合网| 俺也来www俺也色com| 日韩AV无码一区二区三区| 欧美三级片在线视频| 亚洲人体视频| 3D动漫精品啪啪一区二区免费| 网址你懂的| 欧美亚洲黄色| A级片免费看| 欧美操逼在线观看| 日韩视频网址| 久久久久久久91| 九九热精品视频在线播放| 国产日批| 久草视频在线资源| 一级特黄大片录像i| 激情三区| 国产毛片777777| av资源免费观看| 国产精品福利视频| 内射视频网站| 影音先锋成人视频| 中文字幕在线视频无码| 51妺嘿嘿在线电影免费观看| 精品久久一区二区| 亚洲成人福利| 天天做天天爱| 特黄AAAAAAAA片视频| 国产福利美女网站| 国产自慰一区| 少妇推油呻吟白浆啪啪成人片 | 欧美日韩成人在线| 亚洲精品国产精品乱码视99| 中文字幕成人在线播放| 蜜桃人妻无码| 69福利视频| 国产淫语| 超碰久草| 久久黄色视| 97精品人妻一区二区三区香蕉农 | 色婷婷一二三精品A片| 免费中文字幕av| 天天操夜夜撸| 日韩精品无码一区二区| 亚洲精品一区二区三区蜜桃| 欧洲无码一区二区三区| 一区二区三区日本| 五月无码| 不卡中文字幕| 黄色视频| 国产欧美日韩综合精品| 久久成人在线| 日韩三级片无码| 暗呦网一区二区三区| 99在线视频观看| 久久久一级| 手机看片国产| 国产精品午夜福利视频| 怡红院男人的天堂| 亚洲无码成人| 亚洲视频99| 竹菊传媒一区二区三区| 精品91视频| 国产免费AV片| 国产免费一区二区三区网站免费| 久久久久久无码精品亚洲日韩麻豆| 免费作爱视频| 91视频专区| 操逼免费网站| 久久91人妻无码精品蜜桃HD| 内射视频网| 先锋影音在线| 日韩精品视频在线免费观看| 91人妻人人澡人人精品| 嫖中国站街老熟女HD| jt33免费观看高清| 91av免费| h网站在线观看| 亚洲图片在线播放| 骚逼免费观看| 久久精品99久久久久久| 亚洲一级A片| 亚洲AⅤ欧美AⅤ| AA视频网站| www九九热| 欧美人与禽乱婬A片| 亚洲色播放| 日日爱av| 亚洲国产精品二二三三区| 人人摸人人操人人| 最新国产激情视频| 国产丰满大乳无码免费播放| 欧洲第一无人区观看| 伊人导航| 日本在线一级片| 人人天天久久| 中文电视剧字幕在线播放免费视频| 国产精品秘久久久久久99| 在线中文字幕777| 亚洲影音先锋在线| 麻豆911| 在线观看中文字幕av| 精品乱子伦一区二区三区下载| 欧美久久久久久久| 国产一a毛一a毛A免费| 人妻一区二区三区| 黄色高清视频在线观看| www免费视频在线观看播放| 久久少妇视频| 成人福利小视频| 欧洲综合视频| 爱逼av| 在线视频亚洲| 伊人影院在线免费观看| 少妇高潮喷水| 激情网五月天| 人妻精品电影| 美女做爱视频网站| 操片免费| 欧美日韩国产尤物主播精品| 久久久久久久久毛片| 欧美第五页| 免费在线观看黄色视频| 国产欧美在线免费观看| 一本大道东京热AV| 精品一区二区视频| 欧美性爱天天| 国产人体视频| 国产一级a毛一级a做免费高清视频 | 久久99国产精品| 永久免费黄色| 黄色视频网站亚洲| 少妇搡BBBB搡BBB搡HD(| 熟女人妻人妻の视频| 欧美日韩操逼片| 久久国产亚洲| 国产精品自拍小视频| 久久久久久久香蕉视频| 天天干91| 日韩欧美精品在线观看| 亚洲综合社区在线| 日本免费版网站nba| 波多野结衣av在线播放| 最近中文字幕高清2019中文字幕| 久久综合无码内射国产| 日韩高清无码专区| 91av视频| 亚洲成人资源| 农村少妇久久久久久久| 欧美综合高清| 操熟女视频| 婷婷午夜精品久久久久久性色AV | 偷拍亚洲| 欧美18禁黄免费网站| 三级A片视频| 亚洲无码视频在线看| 一区性爱| 国产又粗又猛又爽又黄91精品 | 97人妻一区二区精品视频| 人人操在线播放| 特级毛片AAAAAA蜜桃| 欧美操逼在线| 国产色AV| 老太色HD色老太HD.| 欧美成人内射| 五月婷婷丁香| 五月婷婷综合激情| 久久成人网站| 综合成人在线| 国产99久久久| 成人精品三级AV在线看| 亚洲午夜影院在线| 成人在线A片| 日逼网址| 中文字幕日本| 国产精品无码AV| 亚洲AV无码一区二区三区少妇| 中文在线无码| 国产乱妇无码毛片A片在线看下载 日韩电影免费在线观看中文字幕 欧美性爱中文字幕 | 操逼视频免费看| 青青欧美| 91久久婷婷| 日韩乱码| 中文字幕+乱码+中文字幕电视剧| 国产视频福利| 91精品久久久久久久久久久久 | 天天视频狠狠狠狠|