JavaScript 事件循環竟還能這樣玩!

JavaScript 是一種單線程的編程語言,這意味著它一次只能執行一個任務。為了能夠處理異步操作,JavaScript 使用了一種稱為事件循環(Event Loop)的機制。
本文將深入探討事件循環的工作原理,并展示如何基于這一原理實現一個更為準確的 setTimeoutsetInterval

什么是事件循環?

事件循環是 JavaScript 運行時環境中處理異步操作的核心機制。它允許 JavaScript 在執行任務時不會阻塞主線程,從而實現非阻塞 I/O 操作。

為了理解事件循環,首先需要了解以下幾個關鍵概念:

  1. 調用棧(Call Stack)

    • 調用棧是一個 LIFO(后進先出)結構,用于存儲當前執行的函數調用。當一個函數被調用時,它會被推入調用棧,當函數執行完畢后,它會從調用棧中彈出。
  2. 任務隊列(Task Queue)

    • 任務隊列存儲了所有等待執行的任務,這些任務通常是異步操作的回調函數,例如 setTimeoutsetInterval、I/O 操作等。當調用棧為空時,事件循環會從任務隊列中取出一個任務并將其推入調用棧執行。
  3. 微任務隊列(Microtask Queue)

    • 微任務隊列存儲了所有等待執行的微任務,這些微任務通常是 Promise 的回調函數、MutationObserver 等。微任務隊列的優先級高于任務隊列,當調用棧為空時,事件循環會優先處理微任務隊列中的所有任務,然后再處理任務隊列中的任務。
事件循環的工作原理

事件循環的工作原理可以簡化為以下幾個步驟:

  1. 執行調用棧中的任務

    • JavaScript 引擎會從調用棧中取出并執行最頂層的任務,直到調用棧為空。
  2. 處理微任務隊列

    • 當調用棧為空時,事件循環會檢查微任務隊列。如果微任務隊列中有任務,會依次取出并執行,直到微任務隊列為空。
  3. 處理任務隊列

    • 當調用棧和微任務隊列都為空時,事件循環會檢查任務隊列。如果任務隊列中有任務,會取出一個任務并將其推入調用棧執行。
  4. 重復上述步驟

    • 事件循環會不斷重復上述步驟,確保所有任務都能被及時處理。
示例

以下是一個簡單的示例,展示事件循環的工作原理:

console.log('Start');setTimeout(() => {console.log('Timeout callback');
}, 0);Promise.resolve().then(() => {console.log('Promise callback');
});console.log('End');

輸出結果:

Start
End
Promise callback
Timeout callback

解釋如下:

  1. 同步任務:首先執行同步任務,console.log('Start')console.log('End') 被推入調用棧并立即執行。
  2. 微任務Promise.resolve().then 創建了一個微任務,該微任務被推入微任務隊列。
  3. 任務setTimeout 創建了一個任務,該任務被推入任務隊列。
  4. 處理微任務:同步任務執行完畢后,調用棧為空,事件循環檢查微任務隊列并執行所有微任務,因此輸出 Promise callback
  5. 處理任務:微任務隊列為空后,事件循環檢查任務隊列并執行所有任務,因此輸出 Timeout callback
為什么 setTimeout 不準確?

JavaScript 中的 setTimeoutsetInterval 是基于事件循環和任務隊列的,因此它們的執行時間可能會受到以下幾個因素的影響,從而導致不準確:

  1. 事件循環機制

    • JavaScript 是單線程的,所有代碼的執行都是在一個事件循環中進行的。事件循環會依次處理任務隊列中的任務。
    • 如果前面的任務執行時間較長,或者任務隊列中有很多任務,定時器的回調函數就會被延遲執行。
  2. 任務隊列的優先級

    • 瀏覽器的任務隊列有不同的優先級,例如用戶交互事件、渲染更新等任務的優先級通常高于 setTimeoutsetInterval
    • 這意味著即使定時器到期,如果有其他高優先級任務在執行,定時器的回調函數也會被延遲執行。
  3. JavaScript 引擎的限制

    • JavaScript 引擎通常會對最小時間間隔進行限制。例如,在瀏覽器環境中,嵌套的 setTimeout 調用的最小時間間隔通常是 4 毫秒。
    • 這意味著即使你設置了一個非常短的時間間隔,實際執行的時間間隔也可能會比你設置的時間更長。
  4. 系統性能和負載

    • 系統的性能和當前負載也會影響定時器的準確性。如果系統負載較高,任務的執行時間可能會被進一步延遲。

為了更直觀地理解這一點,可以考慮以下示例:

console.log('Start');setTimeout(() => {console.log('Timeout callback');
}, 1000);const start = Date.now();
while (Date.now() - start < 2000) {// 模擬一個耗時2秒的任務
}console.log('End');

在這個示例中,setTimeout 的回調函數設置為 1 秒后執行,但由于在主線程上有一個耗時 2 秒的任務,導致定時器的回調函數被延遲到這個任務執行完畢后才執行。

因此,實際執行時間會遠遠超過 1 秒。

實現一個更準確的 setTimeout

為了實現更精確的定時器,可以結合 Date 對象和遞歸的 setTimeout 來實現更高精度的定時器。

以下是一個實現準時 setTimeout 的例子:

function preciseTimeout(callback, delay) {const start = Date.now();function loop() {const now = Date.now();const elapsed = now - start;const remaining = delay - elapsed;if (remaining <= 0) {callback();} else {setTimeout(loop, remaining);}}setTimeout(loop, delay);
}// 使用示例
preciseTimeout(() => {console.log('This is a precise timeout callback');
}, 1000); // 1秒

在這個實現中:

  1. 獲取當前時間 start
  2. loop 函數中不斷計算已經過去的時間 elapsed 和剩余時間 remaining
  3. 如果剩余時間 remaining 小于等于 0,就調用回調函數 callback
  4. 如果剩余時間 remaining 大于 0,就使用 setTimeout 遞歸調用 loop 函數。

這種方法能比直接使用 setTimeout 更精確地執行定時任務。

進一步優化

上面的代碼還可以進一步優化,可以考慮使用 requestAnimationFrame 來實現更高精度的定時器。

requestAnimationFrame 是專門為動畫設計的,它會在瀏覽器下一次重繪之前調用指定的回調函數。由于瀏覽器的重繪通常是每秒 60 次(即每 16.67 毫秒一次),所以使用 requestAnimationFrame 可以實現更高精度的定時器。

以下是使用 requestAnimationFrame 實現的高精度定時器:

function preciseTimeout(callback, delay) {const start = Date.now();function loop() {const now = Date.now();const elapsed = now - start;if (elapsed >= delay) {callback();} else {requestAnimationFrame(loop);}}requestAnimationFrame(loop);
}// 使用示例
preciseTimeout(() => {console.log('This is a precise timeout callback');
}, 1000); // 1秒

在這個實現中,requestAnimationFrame 會在每次瀏覽器重繪之前調用 loop 函數,從而實現更高精度的定時器。

實現一個更準確的 setInterval

同樣地,我們可以通過結合 Date 對象和遞歸的 setTimeout 來實現更高精度的 setInterval。以下是一個實現準時 setInterval 的例子:

function preciseInterval(callback, interval) {let expected = Date.now() + interval;function step() {const now = Date.now();const drift = now - expected;if (drift >= 0) {callback();expected += interval;}setTimeout(step, interval - drift);}setTimeout(step, interval);
}// 使用示例
preciseInterval(() => {console.log('This is a precise interval callback');
}, 1000); // 每秒

在這個實現中:

  1. 設置預期的下一次執行時間 expected
  2. step 函數中不斷計算當前時間 now 和預期時間 expected 之間的偏差 drift
  3. 如果偏差 drift 大于等于 0,就調用回調函數 callback,并更新預期時間 expected
  4. 使用 setTimeout 遞歸調用 step 函數,并根據偏差 drift 調整下一次調用的時間間隔。
進一步優化

為了進一步優化,可以考慮使用 requestAnimationFrame 來實現更高精度的定時器。requestAnimationFrame 是專門為動畫設計的,它會在瀏覽器下一次重繪之前調用指定的回調函數。由于瀏覽器的重繪通常是每秒 60 次(即每 16.67 毫秒一次),所以使用 requestAnimationFrame 可以實現更高精度的定時器。

那我們使用 requestAnimationFrame 來實現的高精度 setInterval

function preciseSetInterval(callback, interval) {let expected = performance.now() + interval;function step() {const drift = performance.now() - expected;if (drift >= 0) {callback();expected += interval;}requestAnimationFrame(step);}requestAnimationFrame(step);
}// 使用示例
preciseSetInterval(() => {console.log('This runs every 2 seconds with higher precision');
}, 2000);

總結

事件循環是 JavaScript 處理異步操作的核心機制,通過調用棧、任務隊列和微任務隊列的協調工作,實現了非阻塞 I/O 操作。

雖然 setTimeout 的定時精度受到事件循環的影響,但通過結合 Date 對象和遞歸的 setTimeout,或者使用 requestAnimationFrame,可以實現更為準確的定時器。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/21661.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/21661.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/21661.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

git-commit-id-plugin maven插件筆記(git commitId跟蹤工具)

文章目錄 maven依賴git.properties 例子 代碼版本管理比較混亂&#xff0c;如何記錄呢? 一種是手動記錄&#xff0c;也可以實現&#xff0c;顯得有點笨。 也可以通過插件。 maven依賴 <plugin><groupId>pl.project13.maven</groupId><artifactId>git…

面試題系列:Python是什么?使用Python有什么好處?你對 PEP 8 有什么理解?

###面試題系列:Python是什么?使用Python有什么好處?你對 PEP 8 有什么理解? 1、Python是什么? Python是一門動態的(dynamic)且強類型(strong)語言 延伸: 1)靜態類型語言和動態類型語言的判別的標準 如果類型檢查發生在編譯階段(compile time),那么是靜態類型語言(s…

【數據分享】水體分布與五級水系和流域矢量數據+2000-2022年植被指數(NDVI)數據(全國/分省/分市)

1. 數據介紹 數據分為3個層次結構&#xff0c;分別為省、地級市、縣。其中&#xff0c;省級水體31個&#xff08;不包含香港、臺灣等&#xff09;&#xff0c; 地級市水體366個&#xff0c;縣級市水體2847個。每一個文件夾中都包含該省、地級市或者縣的水體矢量數據、行政邊界…

數學建模 —— 灰色系統(4)

目錄 什么是灰色系統&#xff1f; 一、灰色關聯分析 1.1 灰色關聯分析模型 1.2 灰色關聯因素和關聯算子集 1.2.1 灰色關聯因素 1.2.2 關聯算子集 1.3 灰色關聯公理與灰色關聯度 1.3.1 灰色關聯度 1.3.2 灰色關聯度計算步驟 1.4 廣義關聯度 1.4.1 灰色絕對關聯…

一文讀懂GDPR

GDPR將對人們的網絡足跡、使用的APP和服務如何保護或利用這些數據產生重大影響。 下面我們將對有關GDPR人們最關心的問題進行解讀。 GDPR是什么&#xff1f; 一般數據保護條例&#xff08;General Data Protection Regulation&#xff09;是一項全面的法律&#xff0c;賦予了…

風電Weibull+隨機出力!利用ARMA模型隨機生成風速+風速Weibull分布程序代碼!

前言 隨著能源問題日益突出&#xff0c;風力發電等以可再生能源為基礎的發電技術越來越受到關注。建立能夠正確反映實際風速特性的風速模型是研究風力發電系統控制策略以及并網運行特性的重要基礎叫。由于風速的隨機性和波動性&#xff0c;系統中的機械設備和電氣設備以及電網…

計算機網絡⑩ —— Linux系統如何收發網絡包

轉載于小林coding&#xff1a;https://www.xiaolincoding.com/network/1_base/how_os_deal_network_package.html 1. OSI七層模型 應用層&#xff0c;負責給應用程序提供統一的接口&#xff1b;表示層&#xff0c;負責把數據轉換成兼容另一個系統能識別的格式&#xff1b;會話…

深度剖析云邊對接技術:探索開放API接口的價值與意義

在當今數字化時代的浪潮中&#xff0c;云邊對接與開放API接口成為了塑造行業生態的重要驅動力。隨著云計算、物聯網和邊緣計算等技術的快速發展&#xff0c;傳統產業正在邁向數字化轉型的關鍵時刻。而在這個過程中&#xff0c;云邊對接技術以及開放的應用程序接口(API)扮演著舉…

處理STM32 DMA方式下的HAL_UART_ERROR_ORE錯誤

1. 檢查并調整DMA和UART配置 確保初始化順序&#xff1a;需要確保USART的CR寄存器UE位開關留到最后打開&#xff0c;即完成USART和DMA的所有配置初始化后再使能USART。這樣可以避免初始化順序不當導致的通信問題。配置合適的DMA緩沖區&#xff1a;確保DMA緩沖區足夠大&#xf…

Facebook海外三不限 | 如何降低Facebook頻繁被封的風險

本文將討論Facebook賬戶被封的原因及降低封禁風險的方法&#xff0c;以維護用戶的賬戶安全和社交樂趣。 1. 常見原因&#xff1a;賬戶被封通常與發布違反社區標準的內容有關&#xff0c;如仇恨言論、暴力內容、欺詐虛假信息、非法活動、騷擾、版權侵權等。此外&#xff0c;未授…

el-date-picker選擇開始日期的近半年

<el-date-pickerv-model"form[val.key]":type"val.datePickerType || daterange":clearable"val.clearable && true"range-separator"~"start-placeholder"開始日期"end-placeholder"結束日期"style&q…

玩轉Linux進度條

準備工作&#xff1a; 一.關于緩沖區 首先&#xff0c;咱們先來一段有意思的代碼&#xff1a; #include<stdio.h> #include<unistd.h> int main() {printf("you can see me");sleep(5);} 你可以在你的本地運行一下&#xff0c;這里我告訴大家運行結果…

【SAP HANA 33】前端參數多選情況下HANA如何使用in來匹配?

場面描述: 在操作界面經常會出現某個文本框需要多選的情況,然后后臺需要根據多選的值進行匹配搜索。 一般處理的情況是: 1、在Java后端動態生成SQL 2、不改變動態SQL的情況,直接當做一個正常的參數進行傳遞 本次方案是第二個,直接當做一個正常的字符串參數進行傳遞即…

【面試題-014】Mysql數據庫有哪些索引類型?

文章目錄 B 樹和 B 樹區別B 樹B 樹 mysql聚簇索引和非聚簇索引聚簇索引&#xff08;Clustered Index&#xff09;非聚簇索引&#xff08;Non-Clustered Index&#xff09;總結 MyISAM和InnoDB兩種常見的存儲引擎區別MySQL的主從同步原理如何確保主從同步的數據一致性&#xff1…

使用C++實現高效的套接字連接池

在現代網絡應用中&#xff0c;高效管理網絡連接是實現高并發和低延遲的重要因素。下面將詳細介紹如何使用C實現一個高效的套接字連接池&#xff0c;以便在需要時快速復用連接&#xff0c;從而提高系統性能和資源利用率。 一、什么是連接池&#xff1f; 連接池是一種管理網絡連…

RFID防盜門:守護您的商品資產安全!

在新零售運營管理中&#xff0c;防盜是至關重要的一環。根據美國零售聯合會發布的年度零售安全調查&#xff0c;2022年美國零售商損失了創紀錄的1121億美元。其中年度損失最大因素是由外部盜竊導致庫存損失和員工內部盜竊造成的。 然而傳統零售業商品資產盤點往往依賴人工排查&…

ConcurrentHashMap詳解 什么時候CAS什么時候synchronized

jdk:1.7 segment數組hashEntry數組鏈表實現 jdk版本&#xff1a;1.8&#xff1a;hashEntry數組紅黑樹實現 1、基本參數 //**1、最大容量** hashmap的最大容量也是這個&#xff0c;菜鳥一面被問到了 private static final int MAXIMUM_CAPACITY 1 << 30;//數組默認為…

《科技與健康》是什么級別的期刊?是正規期刊嗎?能評職稱嗎?

問題解答 問&#xff1a;《科技與健康》期刊萬方網可查嗎 答&#xff1a;萬方、維普可查 問&#xff1a;《科技與健康》是正規期刊嗎&#xff1f; 答&#xff1a;萬方維普收錄的正規期刊。主管單位&#xff1a;長江出版傳媒股份有限公司 主辦單位&#xff1a;湖北科學技術…

孩子出生后為什么要做聽力篩查?

孩子出生后為什么要做聽力篩查&#xff1f; 新生兒聽力篩查&#xff0c;就是對所有新生兒在盡早的時間&#xff08;出生48小時后&#xff09;進行系統的聽力篩查測試。據相關文獻報道&#xff0c;在我國&#xff0c;正常分娩的新生兒聽力障礙的發生率約為0.1&#xff5e;0.3%&a…

機場專用手持激光驅鳥器原理及優勢

在機場的驅鳥工作中&#xff0c;各類驅鳥設備共同構建起一道堅不可摧的防線&#xff0c;以保障航班的安全起降。其中激光驅鳥器以其卓越的性能和顯著效果&#xff0c;在機場鳥擊防治中發揮著至關重要的作用。 激光驅鳥器&#xff0c;分為大型自動式和小型手持式&#xff0c;其有…