2021-05-13 如何實現(xiàn)比 setTimeout 快 80 倍的定時器纲仍?

如何實現(xiàn)比 setTimeout 快 80 倍的定時器呀袱?

在瀏覽器中,setTimeout()/setInterval() 的每調(diào)用一次定時器的最小間隔是 4ms郑叠,這通常是由于函數(shù)嵌套導(dǎo)致(嵌套層級達到一定深度)压鉴。

簡單來說,5 層以上的定時器嵌套會導(dǎo)致至少 4ms 的延遲锻拘。

用如下代碼做個測試:

let a = performance.now();
setTimeout(() => {
  let b = performance.now();
  console.log(b - a);
  setTimeout(() => {
    let c = performance.now();
    console.log(c - b);
    setTimeout(() => {
      let d = performance.now();
      console.log(d - c);
      setTimeout(() => {
        let e = performance.now();
        console.log(e - d);
        setTimeout(() => {
          let f = performance.now();
          console.log(f - e);
          setTimeout(() => {
            let g = performance.now();
            console.log(g - f);
          }, 0);
        }, 0);
      }, 0);
    }, 0);
  }, 0);
}, 0);

// 1.3500000350177288
// 1.244999933987856
// 1.38000026345253
// 1.2050000950694084
// 4.724999889731407
// 5.309999920427799

探索

假設(shè)我們就需要一個「立刻執(zhí)行」的定時器呢?有什么辦法繞過這個 4ms 的延遲嗎击蹲,上面那篇 MDN 文檔的角落里有一些線索:

如果想在瀏覽器中實現(xiàn) 0ms 延時的定時器署拟,你可以參考這里[3]所說的 window.postMessage()

postMessage 來實現(xiàn)真正 0 延遲的定時器:

(function() {
  var timeouts = [];
  var messageName = 'zero-timeout-message';

  // 保持 setTimeout 的形態(tài)歌豺,只接受單個函數(shù)的參數(shù)推穷,延遲始終為 0。
  function setZeroTimeout(fn) {
    timeouts.push(fn);
    window.postMessage(messageName, '*');
  }

  function handleMessage(event) {
    if (event.source == window && event.data == messageName) {
      event.stopPropagation();
      if (timeouts.length > 0) {
        var fn = timeouts.shift();
        fn();
      }
    }
  }

  window.addEventListener('message', handleMessage, true);

  // 把 API 添加到 window 對象上
  window.setZeroTimeout = setZeroTimeout;
})();

由于 postMessage 的回調(diào)函數(shù)的執(zhí)行時機和 setTimeout 類似类咧,都屬于宏任務(wù)馒铃,所以可以簡單利用 postMessageaddEventListener('message') 的消息通知組合蟹腾,來實現(xiàn)模擬定時器的功能。

再利用上面的嵌套定時器的例子來跑一下測試:

// 0.3850003704428673
// 0.23999996483325958
// 0.15999982133507729
// 0.3349999897181988
// 0.169999897480011
// 0.135000329464674

全部在 0.1 ~ 0.3 毫秒級別区宇,而且不會隨著嵌套層數(shù)的增多而增加延遲娃殖。

測試

從理論上來說,由于 postMessage 的實現(xiàn)沒有被瀏覽器引擎限制速度议谷,一定是比 setTimeout 要快的炉爆。但空口無憑,咱們用數(shù)據(jù)說話卧晓。

作者設(shè)計了一個實驗方法芬首,就是分別用 postMessage 版定時器和傳統(tǒng)定時器做一個遞歸執(zhí)行計數(shù)函數(shù)的操作,看看同樣計數(shù)到 100 分別需要花多少時間逼裆。讀者也可以在這里自己跑一下測試[4]郁稍。

function runtest() {
  var output = document.getElementById('output');
  var outputText = document.createTextNode('');
  output.appendChild(outputText);
  function printOutput(line) {
    outputText.data += line + '\n';
  }

  var i = 0;
  var startTime = Date.now();
  // 通過遞歸 setZeroTimeout 達到 100 計數(shù)
  // 達到 100 后切換成 setTimeout 來實驗
  function test1() {
    if (++i == 100) {
      var endTime = Date.now();
      printOutput(
        '100 iterations of setZeroTimeout took ' + (endTime - startTime) + ' milliseconds.'
      );
      i = 0;
      startTime = Date.now();
      setTimeout(test2, 0);
    } else {
      setZeroTimeout(test1);
    }
  }

  setZeroTimeout(test1);

  // 通過遞歸 setTimeout 達到 100 計數(shù)
  function test2() {
    if (++i == 100) {
      var endTime = Date.now();
      printOutput(
        '100 iterations of setTimeout(0) took ' + (endTime - startTime) + ' milliseconds.'
      );
    } else {
      setTimeout(test2, 0);
    }
  }
}

直接放結(jié)論,這個差距不固定胜宇,在我的 mac 上用無痕模式排除插件等因素的干擾后耀怜,以計數(shù)到 100 為例,大概有 80 ~ 100 倍的時間差距掸屡。在我硬件更好的臺式機上封寞,甚至能到 200 倍以上。

Performance 面板

只是看冷冰冰的數(shù)字還不夠過癮仅财,我們打開 Performance 面板狈究,看看更直觀的可視化界面中,postMessage 版的定時器和 setTimeout 版的定時器是如何分布的盏求。

image.png

這張分布圖非常直觀的體現(xiàn)出了我們上面所說的所有現(xiàn)象抖锥,左邊的 postMessage 版本的定時器分布非常密集,大概在 5ms 以內(nèi)就執(zhí)行完了所有的計數(shù)任務(wù)碎罚。

而右邊的 setTimeout 版本相比較下分布的就很稀疏了磅废,而且通過上方的時間軸可以看出,前四次的執(zhí)行間隔大概在 1ms 左右荆烈,到了第五次就拉開到 4ms 以上拯勉。

總結(jié)

通過本文,你大概可以了解如下幾個知識點:

  1. setTimeout 的 4ms 延遲歷史原因憔购,具體表現(xiàn)宫峦。
  2. 如何通過 postMessage 實現(xiàn)一個真正 0 延遲的定時器。

原文鏈接:如何實現(xiàn)比 setTimeout 快 80 倍的定時器玫鸟?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末导绷,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子屎飘,更是在濱河造成了極大的恐慌妥曲,老刑警劉巖贾费,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異檐盟,居然都是意外死亡褂萧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門遵堵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箱玷,“玉大人,你說我怎么就攤上這事陌宿∥悖” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵壳坪,是天一觀的道長舶得。 經(jīng)常有香客問我,道長爽蝴,這世上最難降的妖魔是什么沐批? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮蝎亚,結(jié)果婚禮上九孩,老公的妹妹穿的比我還像新娘。我一直安慰自己发框,他們只是感情好躺彬,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著梅惯,像睡著了一般宪拥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上铣减,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天她君,我揣著相機與錄音,去河邊找鬼葫哗。 笑死缔刹,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的劣针。 我是一名探鬼主播桨螺,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼酿秸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起魏烫,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤辣苏,失蹤者是張志新(化名)和其女友劉穎肝箱,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稀蟋,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡煌张,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了退客。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骏融。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖萌狂,靈堂內(nèi)的尸體忽然破棺而出档玻,到底是詐尸還是另有隱情,我是刑警寧澤茫藏,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布误趴,位于F島的核電站,受9級特大地震影響务傲,放射性物質(zhì)發(fā)生泄漏凉当。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一售葡、第九天 我趴在偏房一處隱蔽的房頂上張望看杭。 院中可真熱鬧,春花似錦挟伙、人聲如沸楼雹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烘豹。三九已至,卻和暖如春诺祸,著一層夾襖步出監(jiān)牢的瞬間携悯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工筷笨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留憔鬼,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓胃夏,卻偏偏與公主長得像轴或,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子仰禀,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容