JavaScript單線程和瀏覽器事件循環簡述

JavaScript單線程

在上篇博客《Promise的前世此生和妙用技巧》的開篇中,咱們曾簡述了JavaScript的單線程機制和瀏覽器的事件模型。應不少網友的回覆,在這篇文章中將繼續展開這一個話題。固然這裏是博主的一些理解,若是還存在什麼紕漏的話,請不吝指教。javascript

JavaScript這門語言運行在瀏覽器中,是以單線程的方式運行的。說到單線程,就得從操做系統進程開始提及。進程和線程都是操做系統的概念。進程是應用程序的執行實例,每個進程都是由私有的虛擬地址空間、代碼、數據和其它系統資源所組成;進程在運行過程當中可以申請建立和使用系統資源(如獨立的內存區域等),這些資源也會隨着進程的終止而被銷燬。而線程則是進程內的一個獨立執行單元,在不一樣的線程之間是能夠共享進程資源的,因此在多線程的狀況下,須要特別注意對臨界資源的訪問控制。在系統建立進程以後就開始啓動執行進程的主線程,而進程的生命週期和這個主線程的生命週期一致,主線程的退出也就意味着進程的終止和銷燬。主線程是由系統進程所建立的,同時用戶也能夠自主建立其它線程,這一系列的線程都會併發地運行於同一個進程中。前端

在多線程操做的狀況下能夠實現應用的並行處理,而提升整個應用程序的性能和吞吐量,更大粒度的榨取本機的CPU利用率,特別是現代不少語言都支持了多核並行處理技術。而後JavaScript竟然仍是單線程執行,爲何呢?java

這是由於JavaScript這門腳本語言誕生的使命所致:JavaScript爲處理頁面中用戶的交互,以及操做DOM樹、CSS樣式樹來給用戶呈現一份動態而豐富的交互體驗和服務器邏輯的交互處理。若是JavaScript是多線程的方式來操做這些UI DOM,則可能出現UI操做的衝突;在多線程的交互下,處於UI中的DOM節點就可能成爲一個臨界資源,假設存在兩個線程同時操做一個DOM,而線程1要求瀏覽器刪除DOM節點,線程2卻但願修改這個節點的某些樣式風格。這個時候瀏覽器就沒法裁決採用哪種策略了。固然咱們能夠爲瀏覽器引入「排它鎖」或者是「樂觀鎖」來解決這些衝突,但爲了不引入了更大的複雜性,因此JavaScript從誕生開始就選擇了單線程執行。node

由於單線程執行,因此對於JavaScript的任務而言,在同一時間內只能執行一個特定的任務,而且它會阻塞其餘的任務執行。那麼JavaScript的執行不會很慢嗎?特別是對於長時間任務執行的時候,那麼其餘的任務就得不到執行。然而在軟件開發中,特別是應用軟件開發中,對於I/O設備的訪問都是一些及其耗時的操做。在這些耗時任務執行的時候,其實並不必等待它的完成,在I/O任務完成以前JavaScript徹底能夠繼續執行其餘的任務,直到I/O任務完成後再繼續執行該任務的處理就行。JavaScript在設計之初,就意識這一點。因此在JavaScript中將這些耗時的I/O等操做封裝爲了異步的方法,等到這些任務完成後就將後續的處理操做封裝爲JavaScript任務放入執行任務隊列中,等待JavaScript線程空閒的時候被執行。所以這裏造成了另外一個話題「瀏覽器的事件循環」機制,將在後續中詳細闡述。promise

由於在JavaScript語言中,和其餘大多數語言不同之處:JavaScript中耗時的I/O操做都被處理爲異步操做,以及回調註冊機制。異步和回調彷彿和JavaScript就是「與生俱來」的同樣。如Nodejs創始人Ryan Dahl所言,JavaScript語言的非阻塞的異步I/O事件驅動模型,以及JavaScript在Chrome推動下的屢次性能優化、具備函數式等高級語言特性,所以最終Nodejs選擇JavaScript。因爲Nodejs最終選擇了JavaScript,今後也大大的推進了JavaScript在非瀏覽器領域的急速擴展。瀏覽器

下面的文字是來自Nodejs官網:安全

nodejs-javascript-簡介

固然對於非I/O的操做耗時操做如上篇博文《Promise的前世此生和妙用技巧》所說,在HTML5中也提升了新的解決方案,它就是Web Worker。Web Worker就是在當前JavaScript的執行主線程中利用Worker類新開闢一個額外的線程來加載和運行特定的JavaScript文件,這個新的線程和JavaScript的主線程之間並不會互相影響和阻塞執行的;而且在Web Worker中提供這個新線程和JavaScript主線程之間數據交換的接口:postMessage和onMessage事件。但在HTML5 Web Worker中是不能操做DOM的,任何須要操做DOM的任務都須要委託給JavaScript主線程來執行,因此雖然引入HTML5 WebWorker但仍然沒有改線JavaScript單線程的本質。對於HTML5的Web Worker和在C# WinForm設計中的BackgroundWorker很相似,對於這類GUI(圖形化界面)操做的應用程序中,對於UI界面的操做都須要委託給UI主線程來執行,避免多線程狀況下UI操做的安全性和避免沒必要要的多線程訪問控制的複雜度。性能優化

瀏覽器事件循環

在上面已經提到JavaScript中爲了避免阻塞UI的渲染,不少JavaScript任務都是異步的,它們包括鍵盤、鼠標I/O輸入輸出事件、窗口大小的resize事件、定時器(setTimeout、setInterval)事件、Ajax請求網絡I/O回調等。當這些異步任務發生的時候,它們將會被放入瀏覽器的事件任務隊列中去。在瀏覽器內部中存在一個消息循環池,也叫Event Loop(事件循環),JavaScript引擎在運行時後單線程的處理這些事件任務。例如用戶在網頁中點擊了button事件,則它們會被放入在這個事件循環池中,須要等到JavaScript運行時執行線程空閒時候纔會按照隊列先進先出的原則被一一執行。對於setTimeout這類定時任務也是同樣的,只有當定時時刻達到的時候,它們纔會被放入瀏覽器的事件隊列中等待被執行;因爲此時的JavaScript主線程也許並不空閒,因此它將並不會被JavaScript引擎所當即執行,由於在JavaScript語言設計中setTimeout這類定時任務的執行時間並非精確的。在前端開發中常常會發現setTimeout(func, 0)頗有用,由於這並非理解執行,而是將當前執行回調函數放入瀏覽器的事件隊列中,等待當前其餘任務的完成,而後在執行它;因此setTimeout(func, 0)具備改變當前代碼執行順序的做用,讓瀏覽器有機會完成UI界面渲染等任務後在執行這段回調函數。固然對於老式瀏覽器這裏具備16ms的差距,HTML5規定爲4ms,以及關於動畫操做中的requestAnimationFrame,請讀者參見MDN資料https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame服務器

瀏覽器事件循環以下圖所示:微信

瀏覽器事件模型

雖然JavaScript是單線程執行的,可是瀏覽器並非單線程執行的,它們有JavaScript的執行線程、UI節點的渲染線程,圖片等資源的加載線程,以及Ajax請求線程等。在Chrome設計中,爲了防止因一個Tab window的奔潰而影響整個瀏覽器,它的每個Tab被設計爲一個進程;在Chrome設計中存在不少的進程,並利用進程間通信來完成它們之間的同步,所以這也是Chrome快速的法寶之一。對於Ajax的請求也須要特殊線程來執行,當須要發送一個Ajax請求的時候,瀏覽器會開闢一個新的線程來執行HTTP的請求,它並不會阻塞JavaScript線程的執行,HTTP請求狀態變動事件會被做爲回調放入到瀏覽器的事件隊列中等待被執行。

總結

寫到這裏,本文也進入了尾聲。但願這篇文章能給閱讀本文的讀者一些啓發,同時若是本文中存在不足的地方,也但願你能不吝指教。另外,同時也歡迎關注博主的微信公衆號[破狼](微信二維碼位於博客右側),這裏將會爲你們第一時間推送博主的最新博文,謝謝你們的支持和鼓勵。

相關文章
相關標籤/搜索