我相信這是你須要知道的JS運行機制和JS引擎(V8)內部

前言

前幾天,我開始想寫前端生態周邊之瀏覽器幕後的文章。其實也是是對於零碎知識進行整合。但願能給你們帶來從0-1,而非模塊 的認識。另一個初衷也是做爲前端的視角到底應該瞭解瀏覽器的什麼內容。(不少人想深刻要看下WebKit源碼,V8源碼我的感受稍微有點離譜 並不適合每一個前端同窗去實踐 會讓你感受到絕望)前端

但願你們在認真閱讀的過程當中可以糾正個人問題。快來打我臉!

今天來到了你們平常接觸最多的模塊 JS引擎相關的內容;本篇文章的內容包含:node

  1. JavaScript運行機制
  2. 簡單瞭解一下引擎內部

術語概念 (熟悉的跳過,眼熟的回憶,不懂的認真看完後續有用到)

  1. 進程 cpu資源分配的最小單位 (進程可包含多個進程 這個能夠暫時不須要理解)
  2. 線程 線程是cpu調度的最小單位
  3. ECS:執行環境棧,Execution Context Stack

JavaScript運行機制

概述

  • JS是單線程的腳本語言
  • JS爲何設計爲單線程語言 主要的緣由是由於與它的用途有關係,做爲瀏覽器腳本語言,JS的主要用途是操做DOM。若是設計多線程,勢必會帶來操做衝突引起復雜的同步問題;
  • 爲了解決單線程排隊問題;出現了讓你頭疼的運行機制 包含主線程,任務隊列,event-loop。
爲了更好地理解Event Loop,請看下圖

上圖中,主線程運行的時候,產生堆(heap)和棧(stack),棧中的代碼調用各類外部API,它們在"任務隊列"中加入各類事件(onClick,onLoad)。只要棧中的代碼執行完畢,主線程就會去讀取"任務隊列",依次執行那些事件所對應的回調函數。
執行棧中的代碼,老是在讀取"任務隊列"以前執行。git

主線程 (執行棧)

執行棧(execution context stack)是JavaScript執行事件任務的線程。

請看下面這個gif圖:github

  1. 當在全局代碼中調用一個函數,程序將進入被調用的函數內部,並建立一個新的執行上下文,並將新建立的上下文壓入執行棧的頂部。
  2. 調用函數內部調用了其餘函數 會重複第一個步驟(程序進入調用函數內部,建立一個新的執行上下文並把它壓入執行棧的頂部)
  3. 依次執行從棧頂開始: 執行位於棧頂的執行上下文,一旦當前上下文函數執行結束,它將被從棧頂彈出,並將上下文控制權交給當前的棧;繼續執行直到棧空。

任務隊列

任務隊列(task queue)是進行存放異步任務運行結果事件 一種是同步任務(synchronous),另外一種是異步任務(asynchronous):
  1. 同步任務指的是在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務。
  2. 異步任務指的是直接進入任務隊列,而後等待知足條件(發出通知)而後最終進入主線程執行的任務。
請看下面這個gif圖:


(學過C++語言都知道main做爲主入口。不瞭解的不要緊此處main函數可作忽略)~~正則表達式

  1. 進入全局執行上下文
  2. console.log("start");進入主線程 (壓入棧的頂部)
  3. Timer1 執行中進入主線程發現是WEBapi 待timeout時間完成進入隊列
  4. Timer2 執行中進入主線程發現是WEBapi 待timeout時間完成進入隊列
  5. console.log("end);進入主線程執行算法

    執行順序爲 2-5-4-3

Event Loop

event-loop能夠理解爲一個處理機制。
主線程任務執行==主線程從"任務隊列"中讀取事件==執行...,這是個循環的過程。這種運行機制稱爲Event Loop(事件循環)

對照上面的動畫 簡單理解爲主線程爲空時就從任務隊列讀取待執行的事件(timer1,timer2)進入主線程進行執行。express

JS引擎(V8)概述

JavaScript 引擎 是一個程序或者執行 JavaScript 代碼的解釋器。一個JavaScript 引擎能夠做爲一個單獨的解釋器實現或者經過某種方式將 JavaScript 編譯爲字節碼的即時編譯器。json

常說的JavaScript 引擎:後端

  1. JavaScriptCore 表明瀏覽器Safari
  2. Rhino 表明瀏覽器Mozilla Firefox
  3. Chakra 表明瀏覽器Internet Explorer(IE)
  4. V8 表明瀏覽器 Chrome 開源,用 C++ 實現的
下面介紹一下V8的一些內部實現,優點。

JS執行內部過程

  1. JS代碼轉換爲AST語法樹表示。
// 函數
 function greet() {
   console.log("wlove");
 }
 // AST樹 json
 {"type":"Program","start":0,"end":47,"body":[{"type":"FunctionDeclaration","start":0,"end":46,"id":{"type":"Identifier","start":9,"end":14,"name":"greet"},"expression":false,"generator":false,"async":false,"params":[],"body":{"type":"BlockStatement","start":17,"end":46,"body":[{"type":"ExpressionStatement","start":23,"end":44,"expression":{"type":"CallExpression","start":23,"end":43,"callee":{"type":"MemberExpression","start":23,"end":34,"object":{"type":"Identifier","start":23,"end":30,"name":"console"},"property":{"type":"Identifier","start":31,"end":34,"name":"log"},"computed":false,"optional":false},"arguments":[{"type":"Literal","start":35,"end":42,"value":"wlove","raw":"\"wlove\""}],"optional":false}}]}}],"sourceType":"module"}

  1. AST轉換爲字節碼
    有了解的同窗應該知道以前的V8直接是轉換機器碼。可是機器碼佔空間很大,若是v8 緩存機制將 全部 js 代碼編譯成機器碼緩存下來,這樣會致使緩存佔用的內存、磁盤空間很大。並且退出 Chrome 再打開時序列化、反序列化緩存時間成本也很高。
    在時間,空間成本都很高的狀況下 引入了字節碼。
  2. 字節碼解釋器TurboFan 內部也存在不少工做內容簡單列取幾點:api

    1. 字節碼處理程序生成
    2. 字節碼生成
    3. 解釋器寄存器分配
    4. Context鏈
    5. 異常處理
    6. JS代碼解釋執行
    7. ....
  3. JIT (Just In Time) 混合使用編譯器和解釋器的技術。編譯器啓動速度慢,執行速度快。解釋器的啓動速度快,執行速度慢。而JIT技術就是取倆者之長 (Ignition(字節碼解釋器) + TurboFan (JIT編譯器) 的組合;後面的應用也是愈來愈廣 )
  4. 虛擬機(垃圾回收,內存管理等)

    V8 使用了分代和大數據的內存分配,在回收內存時使用精簡整理的算法標記未引用的對象,而後消除沒有標記的對象,最後整理和壓縮那些還未保存的對象,便可完成垃圾回收。
    內存分配:

    • 年輕分代:爲新建立的對象分配內存空間,常常須要進行垃圾回收。爲方便年輕分代中的內容回收,可再將年輕分代分爲兩半,一半用來分配,另外一半在回收時負責將以前還須要保留的對象複製過來。
  • 年老分代:根據須要將年老的對象、指針、代碼等數據保存起來,較少地進行垃圾回收。
  • 大對象:爲那些須要使用較多內存對象分配內存,固然一樣可能包含數據和代碼等分配的內存,一個頁面只分配一個對象。

    內存(垃圾)回收:

    1. 年輕分代中的對象垃圾回收主要經過Scavenge算法進行垃圾回收。
    2. 因考慮在年老分代中存活對象居多,因此主要採用了Mark-Sweep(標記清除)標記清除和Mark-Compact(標記整理)相結合的方式進行垃圾回收。
  1. 代碼執行

工做過程編譯和運行

V8引擎編譯階段:

主要類以下所示:

  1. Script:Script類 包含JS代碼,和編譯後的本地代碼。(此處爲編譯入口)
  2. Compiler:編譯器類:Script類調用編譯生成代碼 包括生成AST,本地代碼等。
  3. AstNode:抽象語法樹node類(做爲節點基類,包含不少子類爲後續生成代碼作輔助)
  4. AstVisitor:抽象語法樹訪問類,主要用來遍歷異構的抽象語法樹;
  5. FullCodeGenerator:訪問類可調用來遍歷AST來爲JS生成本地可執行代碼。

編譯過程大概爲:

  1. Script類調用Compiler類爲其生成AST和本地代碼。
  2. Compile函數先使用Parser類生成AST,再使用FullCodeGenerator類來生成本地代碼。
  3. FullCodeGenerator使用多個後端來生成與平臺相匹配的本地彙編代碼。

V8引擎運行階段:

主要類以下所示:

  1. Script此處爲運行入口 編譯以後生成的本地代碼;
  2. Execution:JS運行過程當中的輔組類;包含一些函數 如call函數;
  3. JSFunction:須要執行的JavaScript函數表示類;
  4. Runtime:運行這些本地代碼的輔組類,主要提供運行時所需的輔組函數,如:屬性訪問、類型轉換、編譯、算術、位操做、比較、正則表達式等;
  5. Heap:運行本地代碼須要使用的內存堆類;
  6. MarkCompactCollector:垃圾回收機制的主要實現類,用來標記、清除和整理等基本的垃圾回收過程;
  7. SweeperThread:負責垃圾回收的線程。


執行過程大概爲:

  1. V8 當函數被調用會檢測是否有本地代碼若是有進行調用。
  2. V8 當函數被調用會檢測是否又本地代碼若是沒有將會生成本地代碼。
  3. 執行編譯後代碼構建JS對象須要Runtime類來輔組建立對象,並須要從Heap類分配內存。
  4. 最後將不用的空間進行標記清除和垃圾回收。
固然V8內部還有不少例如隱藏類,擴展機制等等。有興趣能夠看一下 《WebKit技術內幕》 若是想要讀源碼推薦從 代碼量還少的V8 開始閱讀。加油少年。

最後

分享一下最近: 工做很忙很忙(對接外部資源,整合內部,作技術探索研發...);生活很忙很忙(有一點就是運動必定要堅持下來..) 而後呢寫做也會堅持下來(每週1-3篇)。你們有任何想了解的內容隨時留言 只要我知道的就會馬上分享出來幫助你們。若是不知道我能夠去學。嘿嘿

下一篇待定吧 加油 前程似錦 靚仔靚女!!!

上文圖片部分來源網絡 侵權請聯繫刪除 謝謝。
相關文章
相關標籤/搜索