搜尋
首頁web前端js教程深入理解JavaScript的並發模型與事件循環機制

深入理解JavaScript的並發模型與事件循環機制

Nov 27, 2019 pm 04:08 PM
javascriptsettimeout協程執行緒

我們知道JS語言是串列執行、阻塞式、事件驅動的,那麼它又是怎麼支援並發處理資料的呢?

深入理解JavaScript的並發模型與事件循環機制

"單執行緒"語言

#在瀏覽器實作中,每個單頁都是一個獨立進程,其中包含了JS引擎、GUI介面渲染、事件觸發、定時觸發器、非同步HTTP請求等多個執行緒。

行程(Process)是作業系統CPU等資源分配的最小單位,是程式的執行實體,是執行緒的容器。
執行緒(Thread)是作業系統能夠進行運算調度的最小單位,一條執行緒指的是一個單一順序在進程中的控制流。

因此我們可以說JS是"單執行緒"式的語言,程式碼只能按照單一順序進行串行執行,並在執行完成前阻塞其他程式碼。

【相關課程推薦:JavaScript影片教學

#JS資料結構

深入理解JavaScript的並發模型與事件循環機制

#如上圖所示為JS的幾種重要資料結構:

 ● 堆疊(Stack):用於JS的函數巢狀調用,後進先出,直到堆疊被清空。

 ● 堆(Heap):用於儲存大塊資料的記憶體區域,如物件。

 ● 佇列(Queue):用於事件循環機制,先進先出,直到佇列為空。 事件循環

我們的經驗告訴我們JS是可以並發執行的,例如定時任務、並發AJAX請求,那這些是怎麼完成的呢?其實這些都是JS在用單線程模擬多線程完成的。 深入理解JavaScript的並發模型與事件循環機制

如上圖所示,JS串列執行主執行緒任務,當遇到非同步任務如定時器時,將其放入事件佇列中,在主執行緒任務執行完畢後,再去事件佇列中遍歷取出隊首任務執行,直到佇列為空。

全部執行完成後,會有主監控進程,持續偵測佇列是否為空,如果不為空,則繼續事件循環。 setTimeout定時任務

定時任務

setTimeout(fn, timeout)

會先交給瀏覽器的計時器模組,等延遲時間到了,再將事件放入到事件隊列裡,等主線程執行結束後,如果隊列中沒有其他任務,則會被立即處理,而如果還有沒有執行完成的任務,則需要等前面的任務都執行完成才會被執行。因此setTimeout的第2個參數是最少延遲時間,而非等待時間。

當我們預期到一個操作會很繁重耗時又不想阻塞主執行緒的執行時,會使用立即執行任務:
setTimeout(fn, 0);

特殊場景1:最小延遲為1ms

然而考慮這麼一段程式碼會怎麼執行:<pre class="brush:php;toolbar:false">setTimeout(()=&gt;{console.log(5)},5) setTimeout(()=&gt;{console.log(4)},4) setTimeout(()=&gt;{console.log(3)},3) setTimeout(()=&gt;{console.log(2)},2) setTimeout(()=&gt;{console.log(1)},1) setTimeout(()=&gt;{console.log(0)},0)</pre>了解完事件佇列機制,你的答案應該是0,1,2,3,4,5 ,然而答案卻是

1,0,2,3,4,5

,這個是因為瀏覽器的實作機制是最小間隔為1ms。 <pre class="brush:php;toolbar:false">// https://github.com/nodejs/node/blob/v8.9.4/lib/timers.js#L456 if (!(after &gt;= 1 &amp;&amp; after </pre>瀏覽器以32位元bit來儲存延時,若大於

2^32-1 ms(24.8天)

,導致溢位會立刻執行。

特殊場景2:最小延遲為4ms

計時器的巢狀呼叫超過4層時,會導致最小間隔為4ms:

var i=0;
function cb() {
    console.log(i, new Date().getMilliseconds());
    if (i <blockquote>可以看到前4層也不是標準的立刻執行,在第4層後間隔明顯變大到4ms以上:</blockquote><pre class="brush:php;toolbar:false">0 667
1 669
2 670
3 672
4 676
5 681
6 685

Timers can be nested; after five such nested timers, however, the interval is forced to be at least four milliseconds.

特殊場景3:瀏覽器節流


為了優化後台tab的載入佔用資源,瀏覽器對後台未啟動的頁面中定時器延遲限制為1s。

對追蹤型腳本,如Google分析等,在目前頁面,依然是4ms的延時限制,而後台tabs為10s。 setInterval定時任務

此時,我們會知道,setInterval會在每個定時器延時時間到了後,將會一個新的事件fn放入事件佇列,如果前面的任務執行太久,我們會看到連續的fn事件被執行而感覺不到時間預設間隔。

因此,我們要盡量避免使用setInterval,改用setTimeout來模擬迴圈計時任務。 睡眠函數

###JS一直缺少休眠的語法,借助ES6新的語法,我們可以模擬這個功能,但同樣的這個方法因為借助了setTimeout也無法保證準確的睡眠延遲:###
function sleep(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  })
}
// 使用
async function test() {
    await sleep(3000);
}

async await机制

async函数是Generator函数的语法糖,提供更方便的调用和语义,上面的使用可以替换为:

function* test() {
    yield sleep(3000);
}
// 使用
var g = test();
test.next();

但是调用使用更加复杂,因此一般我们使用async函数即可。但JS时如何实现睡眠函数的呢,其实就是提供一种执行时的中间状态暂停,然后将控制权移交出去,等控制权再次交回时,从上次的断点处继续执行。因此营造了一种睡眠的假象,其实JS主线程还可以在执行其他的任务。

Generator函数调用后会返回一个内部指针,指向多个异步任务的暂停点,当调用next函数时,从上一个暂停点开始执行。

协程

协程(coroutine)是指多个线程互相协作,完成异步任务的一种多任务异步执行的解决方案。他的运行流程:

 ● 协程A开始执行

 ● 协程A执行到一半,进入暂停,执行权转移到协程B

 ● 协程B在执行一段时间后,将执行权交换给A

 ● 协程A恢复执行

可以看到这也就是Generator函数的实现方案。

宏任务和微任务

一个JS的任务可以定义为:在标准执行机制中,即将被调度执行的所有代码块。

我们上面介绍了JS如何使用单线程完成异步多任务调用,但我们知道JS的异步任务分很多种,如setTimeout定时器、Promise异步回调任务等,它们的执行优先级又一样吗?

答案是不。JS在异步任务上有更细致的划分,它分为两种:

宏任务(macrotask)包含:

 ● 执行的一段JS代码块,如控制台、script元素中包含的内容。

 ● 事件绑定的回调函数,如点击事件。

 ● 定时器创建的回调,如setTimeout和setInterval。

微任务(microtask)包含:

 ● Promise对象的thenable函数。

 ● Nodejs中的process.nextTick函数。

 ● JS专用的queueMicrotask()函数。

深入理解JavaScript的並發模型與事件循環機制

宏任务和微任务都有自身的事件循环机制,也拥有独立的事件队列(Event Queue),都会按照队列的顺序依次执行。但宏任务和微任务主要有两点区别:

1、宏任务执行完成,在控制权交还给主线程执行其他宏任务之前,会将微任务队列中的所有任务执行完成。

2、微任务创建的新的微任务,会在下一个宏任务执行之前被继续遍历执行,直到微任务队列为空。

浏览器的进程和线程

浏览器是多进程式的,每个页面和插件都是一个独立的进程,这样可以保证单页面崩溃或者插件崩溃不会影响到其他页面和浏览器整体的稳定运行。

它主要包括:

1、主进程:负责浏览器界面显示和管理,如前进、后退,新增、关闭,网络资源的下载和管理。

2、第三方插件进程:当启用插件时,每个插件独立一个进程。

3、GPU进程:全局唯一,用于3D图形绘制。

4、Renderer渲染进程:每个页面一个进程,互不影响,执行事件处理、脚本执行、页面渲染。

单页面线程

浏览器的单个页面就是一个进程,指的就是Renderer进程,而进程中又包含有多个线程用于处理不同的任务,主要包括:

1、GUI渲染线程:负责HTML和CSS的构建成DOM树,渲染页面,比如重绘。

2、JS引擎线程:JS内核,如Chrome的V8引擎,负责解析执行JS代码。

3、事件触发线程:如点击等事件存在绑定回调时,触发后会被放入宏任务事件队列。

4、定时触发器线程:setTimeout和setInterval的定时计数器,在时间到达后放入宏任务事件队列。

5、异步HTTP请求线程:XMLHTTPRequest请求后新开一个线程,等待状态改变后,如果存在回调函数,就将其放入宏任务队列。

需要注意的是,GUI渲染进程和JS引擎进程互斥,两者只会同时执行一个。主要的原因是为了节流,因为JS的执行会可能多次改变页面,页面的改变也会多次调用JS,如resize。因此浏览器采用的策略是交替执行,每个宏任务执行完成后,执行GUI渲染,然后执行下一个宏任务。

Webworker线程

因为JS只有一个引擎线程,同时和GUI渲染线程互斥,因此在繁重任务执行时会导致页面卡住,所以在HTML5中支持了Webworker,它用于向浏览器申请一个新的子线程执行任务,并通过postMessage API来和worker线程通信。所以我们在繁重任务执行时,可以选择新开一个Worker线程来执行,并在执行结束后通信给主线程,这样不会影响页面的正常渲染和使用。

总结

1、JS是单线程、阻塞式执行语言。

2、JS通过事件循环机制来完成异步任务并发执行。

3、JS将任务细分为宏任务和微任务来提供执行优先级。

4、浏览器单页面为一个进程,包含的JS引擎线程和GUI渲染线程互斥,可以通过新开Web Worker线程来完成繁重的计算任务。

深入理解JavaScript的並發模型與事件循環機制

最后给大家出一个考题,可以猜下执行的输出结果来验证学习成果:

function sleep(ms) {
    console.log('before first microtask init');
    new Promise(resolve => {
        console.log('first microtask');
        resolve()
    })
    .then(() => {console.log('finish first microtask')});
    console.log('after first microtask init');
    return new Promise(resolve => {
          console.log('second microtask');
        setTimeout(resolve, ms);
    });
}
setTimeout(async () => {
    console.log('start task');
    await sleep(3000);
    console.log('end task');
}, 0);
setTimeout(() => console.log('add event'), 0);
console.log('main thread');

输出为:

main thread
start task
before first microtask init
first microtask
after first microtask init
second microtask
finish first microtask
add event
end task

本文来自 js教程 栏目,欢迎学习!

以上是深入理解JavaScript的並發模型與事件循環機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:segmentfault。如有侵權,請聯絡admin@php.cn刪除
Python vs. JavaScript:學習曲線和易用性Python vs. JavaScript:學習曲線和易用性Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python vs. JavaScript:社區,圖書館和資源Python vs. JavaScript:社區,圖書館和資源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

從C/C到JavaScript:所有工作方式從C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript引擎:比較實施JavaScript引擎:比較實施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript:探索網絡語言的多功能性JavaScript:探索網絡語言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),