徹底理解js是單線程

JS執行是單線程

單線程是指Js引擎執行Js時只分了一個線程給他執行,也就是執行js時是單線程的。

那麼問題來了,什麼是線程?進程又是什麼?

在分析瀏覽器的渲染過程之前,我們先了解一下什麼是進程和線程:

(1)什麼是進程?

進程是CPU進行資源分配的基本單位

(2)什麼是線程?

線程是CPU調度的最小單位,是建立在進程的基礎上運行的單位,共享進程的內存空間。

多進程

1、瀏覽器是多進程

2、不同類型的標籤頁都會開啓一個新的進程

3、相同類型的標籤頁是會合併到一個進程

1、瀏覽器進程

(1)負責管理各個標籤頁的創建和銷燬

(2)負責瀏覽器的頁面顯示和功能(前進,後退,收藏等)

(3)負責資源的管理與下載

2、第三方插件進程

(1)負責每個第三方插件的使用,每個第三方插件使用時候都會創建一個對應的進程

3、GPU進程

(1)負責3D繪製和硬件加速

4、瀏覽器渲染進程(咱們這回主要分析的)

1、瀏覽器內核,主要負責HTML,CSS,JS等文件的解析和執行

什麼是瀏覽器內核?

瀏覽器內核就是瀏覽器渲染進程,從接收下載文件後再到呈現整個頁面的過程,由瀏覽器渲染進程負責,主要流程如下:

1、解析HTML文件和CSS文件,加載圖片等資源文件,渲染成用戶看到的頁面 2、執行解析js文件腳本代碼

這裏主要講瀏覽器頁面渲染過程,在該過程中瀏覽器渲染進程會開啓多個線程協作完成,主要的線程以及作用如下:

1、GUI渲染線程 

2、JS引擎線程 

3、事件觸發線程 

4、定時器觸發線程 

5、異步HTTP請求線程

什麼是JS引擎?

1、JS內核,也稱JS引擎(例如V8引擎),負責處理執行javascript腳本程序, 

2、由於js是單線程(一個Tab頁內中無論什麼時候都只有一個JS線程在運行JS程序),依靠任務隊列來進行js代碼的執行,所以js引擎會一直等待着任務隊列中任務的到來,然後加以處理。

注意,JavaScript 只在一個線程上運行,不代表 JavaScript 引擎只有一個線程。事實上,JavaScript 引擎有多個線程,單個腳本只能在一個線程上運行(稱爲主線程),其他線程都是在後臺配合

爲了利用多核 CPU 的計算能力,HTML5 提出 Web Worker 標準,允許 JavaScript 腳本創建多個線程,但是子線程完全受主線程控制,且不得操作 DOM。所以,這個新標準並沒有改變 JavaScript 單線程的本質。

V8引擎的內部結構

1、V8是一個非常複雜的項目,使用cloc統計可知,它竟然有超過100萬行C++代碼。

2、V8由許多子模塊構成,其中這4個模塊是最重要的:

1.Parser:負責將JavaScript源碼轉換爲Abstract Syntax Tree (AST)2.Ignition:interpreter,即解釋器,負責將AST轉換爲Bytecode,解釋執行Bytecode;3.TurboFan:compiler,即編譯器,利用Ignitio所收集的類型信息,將Bytecode轉換爲優化的彙編代碼;4.Orinoco:garbage collector,垃圾回收模塊,負責將程序不再需要的內存空間回收;

再次強調:單線程是指Js引擎執行Js時只分了一個線程給他執行,也就是執行js時是單線程的。

既然JS是單線程的,那怎麼實現異步的呢?

單線程意味着什麼:JavaScript 同時只能執行一個任務,其他任務都必須在後面排隊等待。也就是說代碼只能同步執行,必須執行上一行才能執行下一行。

console.log('1')
setTimeout(() => {
    console.log('2')
}, 0);
console.log('3')

然而並不是,還有異步!!!

程序裏面所有的任務,可以分成兩類:同步任務(synchronous)和異步任務(asynchronous)。

同步任務是那些沒有被引擎掛起、在主線程上排隊執行的任務。只有前一個任務執行完畢,才能執行後一個任務。

異步任務是那些被引擎放在一邊,不進入主線程、而進入任務隊列的任務。只有引擎認爲某個異步任務可以執行了(比如 Ajax 操作從服務器得到了結果),該任務(採用回調函數的形式)纔會進入主線程執行。排在異步任務後面的代碼,不用等待異步任務結束會馬上運行,也就是說,異步任務不具有“堵塞”效應。

舉例來說,Ajax 操作可以當作同步任務處理,也可以當作異步任務處理,由開發者決定。如果是同步任務,主線程就等着 Ajax 操作返回結果,再往下執行;如果是異步任務,主線程在發出 Ajax 請求以後,就直接往下執行,等到 Ajax 操作有了結果,主線程再執行對應的回調函數。

那怎麼實現異步的呢?

任務隊列和事件循環

JavaScript 運行時,除了一個正在運行的主線程,引擎還提供一個任務隊列(task queue),裏面是各種需要當前程序處理的異步任務。(實際上,根據異步任務的類型,存在多個任務隊列。爲了方便理解,這裏假設只存在一個隊列。)

首先,主線程會去執行所有的同步任務。等到同步任務全部執行完,就會去看任務隊列裏面的異步任務。如果滿足條件,那麼異步任務就重新進入主線程開始執行,這時它就變成同步任務了。等到執行完,下一個異步任務再進入主線程開始執行。一旦任務隊列清空,程序就結束執行。

異步任務的寫法通常是回調函數。一旦異步任務重新進入主線程,就會執行對應的回調函數。如果一個異步任務沒有回調函數,就不會進入任務隊列,也就是說,不會重新進入主線程,因爲沒有用回調函數指定下一步的操作。

JavaScript 引擎怎麼知道異步任務有沒有結果,能不能進入主線程呢?答案就是引擎在不停地檢查,一遍又一遍,只要同步任務執行完了,引擎就會去檢查那些掛起來的異步任務,是不是可以進入主線程了。這種循環檢查的機制,就叫做事件循環(Event Loop)。維基百科的定義是:“事件循環是一個程序結構,用於等待和發送消息和事件(a programming construct that waits for and dispatches events or messages in a program)”。

代碼是如何執行的?

宏任務(macro-task)、微任務(micro-task)

除了廣義的同步任務和異步任務,JavaScript 單線程中的任務可以細分爲宏任務和微任務。

1.macro-task包括:script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。

2.micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver。

有了宏任務和微任務的概念後,那 JS 的執行順序是怎樣的?是宏任務先還是微任務先?

第一次事件循環中,JavaScript 引擎會把整個 script 代碼當成一個宏任務執行,執行完成之後,再檢測本次循環中是否尋在微任務,存在的話就依次從微任務的任務隊列中讀取執行完所有的微任務,再讀取宏任務的任務隊列中的任務執行,再執行所有的微任務,如此循環。JS 的執行順序就是每次事件循環中的宏任務-微任務。

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