淺談js運行機制(線程)

淺談js運行機制(線程)
1.前言
從開始接觸js時,我們便知道js是單線程的。單線程,異步,同步,互調,阻塞等。在實際寫js的時候,我們都會用到ajax,不管是原生的實現,還是藉助jQuery等工具庫實現,我們都知道,ajax可以實現局部刷新,並且在請求處理時,任然可以響應用戶的操作,比如點擊事件。不是說js是單線程嗎?這些都是怎麼實現的?
在閱讀《深入理解Bootrap的源碼》一書,在分析輪播組件(carousel.js)的源碼時,作者對一句代碼操作的註釋引起了我的興趣。

setTimeout(function(){
that.$element.trigger('slid');
},0);//觸發slid事件,這裏使用setTimeout是爲了確保UI刷新線程不被阻塞。
1
2
3
4
後面我會一一解答這些疑惑。

2.瀏覽器線程
js運作在瀏覽器中,是單線程的,即js代碼始終在一個線程上執行,這個線程稱爲js引擎線程。
瀏覽器是多線程的,除了js引擎線程,它還有:
UI渲染線程
瀏覽器事件觸發線程
http請求線程
EventLoop輪詢的處理線程
……..
這些線程的作用:

UI線程用於渲染頁面
js線程用於執行js任務
瀏覽器事件觸發線程用於控制交互,響應用戶
http線程用於處理請求,ajax是委託給瀏覽器新開一個http線程
EventLoop處理線程用於輪詢消息隊列
瀏覽器中的js任務:

執行JavaScript代碼
對用戶的輸入(包含鼠標點擊、鍵盤輸入等等)做出反應
處理異步的網絡請求

3.js單線程
單線程的含義是js只能在一個線程上運行,也就說,js同時只能執行一個js任務,其它的任務則會排隊等待執行。
js是單線程的,並不代表js引擎線程只有一個。js引擎有多個線程,一個主線程,其它的後臺配合主線程。
多線程之間會共享運行資源,瀏覽器端的js會操作dom,多個線程必然會帶來同步的問題,所有js核心選擇了單線程來避免處理這個麻煩。js可以操作dom,影響渲染,所以js引擎線程和UI線程是互斥的。這也就解釋了js執行時會阻塞頁面的渲染。
4.消息隊列(任務隊列)
JavaScript運行時,除了一個運行線程,引擎還提供一個消息隊列,裏面是各種需要當前程序處理的消息。新的消息進入隊列的時候,會自動排在隊列的尾端。

消息和回調函數相互聯繫;
  單線程意味着js任務需要排隊,如果前一個任務出現大量的耗時操作,後面的任務得不到執行,任務的積累會導致頁面的“假死”。這也是js編程一直在強調需要回避的“坑”。

5.js任務
任務分爲2種:

同步任務
異步任務
它們的區別是: 本段中的線程指的是js引擎主線程

同步任務:在主線程排隊支持的任務,前一個任務執行完畢後,執行後一個任務,形成一個執行棧,線程執行時在內存形成的空間爲棧,進程形成堆結構,這是內存的結構。執行棧可以實現函數的層層調用。注意不要理解成同步代碼進入棧中,按棧的出棧順序來執行。
異步任務會被主線程掛起,不會進入主線程,而是進入消息隊列,而且必須指定回調函數,只有消息隊列通知主線程,並且執行棧爲空時,該消息對應的任務纔會進入執行棧獲得執行的機會。
主線程執行的說明: 【js的運行機制】

(1)所有同步任務都在主線程上執行,形成一個執行棧。
(2)主線程之外,還存在一個”任務隊列”。只要異步任務有了運行結果,就在”任務隊列”之中放置一個事件。
(3)一旦”執行棧”中的所有同步任務執行完畢,系統就會讀取”任務隊列”,看看裏面有哪些事件。那些對應的異步任務,於是結束等待狀態,進入執行棧,開始執行。
(4)主線程不斷重複上面的第三步。

執行棧中的代碼(同步任務),總是在讀取”任務隊列”(異步任務)之前執行。

6.事件和回調函數
消息隊列隊列(或者叫任務隊列)是一個事件的隊列,IO響應時,會往隊列中添加一個消息,此時說明相關的異步代碼到了執行的時機,可以進入主線程的執行棧了。
主線程讀取消息隊列,可以讀取到對應的事件。
消息隊列可以響應IO事件,還有用戶產生的事件(比如點擊鼠標,頁面滾動),只要指定了回調函數,就會進入消息隊列,等待EventLoop輪詢線程處理,是否可以進入主線程的執行棧。
消息和回調函數相互聯繫的含義:主線程讀到消息,就會執行相應的回調函數;進入消息隊列的消息,必須對應相應的回調函數,否則這個消息會被丟棄不會進入消息隊列。
消息隊列是一個先進先出的隊列結構,這就決定了它的執行順序,先產生的消息會被主線程先讀取,會不會執行則會先檢查一下執行時間,因爲存在setTimeout等定時函數,這類事件產生的消息進入到消息隊列,被執行的時機取決與它在隊列中的位置和執行時間有關。【上文中使用setTimeout能夠避免阻塞UI線程就是這個原因】。

8.7.EventLoop
主線程從”任務隊列”中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱爲Event Loop(事件循環)。

簡單說,瀏覽器的兩個線程:一個負責程序本身的運行,稱爲”主線程”;另一個負責主線程與其他進程(主要是各種I/O操作)的通信,被稱爲”Event Loop線程”(可以譯爲”消息線程”)。

由於js是運行在單線程上的,所有瀏覽器單獨開啓一個線程來處理事件消息的輪詢,避免阻塞js的執行。

異步代碼的執行邏輯:
  每當遇到I/O的時候,主線程就讓EventLoop線程去通知相應的I/O程序,然後接着往後運行,所以不存在等待時間。等到I/O程序完成操作,EventLoop線程把消息添加到消息隊列,主線程就調用事先設定的回調函數,完成整個任務。

  • JavaIO中包括了網絡IO,我們通常把http請求歸類爲網絡IO.
  • js的ajax是new XMLHttpRequest()對象實現的,瀏覽器會新開一個線程來處理http請求,這就是ajax能夠實現局部刷新的同時,還能響應用戶交互的原因。
    這也說明了在處理IO時,瀏覽器通常會新開啓IO線程,這個屬於我的推測,並沒有查到對應的資料,因爲IO涉及的廣泛,這話也沒錯。

8.定時器
  前面也提到了定時器,定時器是會在進入消息隊列,這也就和異步代碼的執行邏輯一樣了。它在”消息隊列”的尾部添加一個消息,因此要等到同步任務和”消息隊列”現有的任務都處理完,纔會得到執行的機會,還要看定時器設置的時間是否到了纔會執行。

9.關於異步代碼的說明.
  從上文我們可以得知:使用定時器和事件可以實現異步,這是2種最爲明顯的實現異步的原理。對於異步的實現,阮一峯的博客說的非常清晰。

原文鏈接:https://blog.csdn.net/w2765006513/article/details/53743051

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章