什么是瀏覽器引擎?什么是JavaScript引擎?


一、瀏覽器內核?

不同的瀏覽器有不同的內核:

1、Gecko,早期被Netscape和Mozilla Firefox瀏覽器瀏覽器使用;

2、Trident,微軟開發,被IE4~IE11瀏覽器使用,但是Edge瀏覽器已經轉向Blink;

3、Webkit,蘋果基於KHTML開發、開源的,用於Safari,Google Chrome之前也在使用;

4、Blink:是Webkit的一個分支,Google開發,目前應用於Google Chrome、Edge、Opera等;

......

事實上,瀏覽器內核指的是瀏覽器的排版引擎,也稱為瀏覽器引擎、頁面渲染引擎或樣板引擎

二、JavaScript引擎

在瀏覽器內核渲染DOM樹時,我們還需要JavaScript引擎來執行JS代碼,那么什么是JavaScript引擎,為什么需要JavaScript引擎?

1、為什么需要JavaScript引擎?

  • 高級編程語言都是需要轉成最終的機器指令來執行  
  • 編寫的JS代碼不管是交給瀏覽器還是Node.js來執行,最終都要被【CPU】執行
  • 只有CPU自己的指令集(機器語言),才能被CPU執行
  • JavaScript引擎的目的就是為了將JS代碼翻譯成CPU指令去執行

2、JavaScript引擎常見的有哪些?

  • SpiderMonkey:第一款JavaScript引擎,由Brendan Eich開發(也就是JavaScript作者)
  • Chakra:微軟開發,用於IT瀏覽器
  • JavaScriptCore:WebKit中的JavaScript引擎,Apple公司開發
  • V8:Google開發的強大JavaScript引擎,也幫助Chrome從眾多瀏覽器中脫穎而出

3、JavaScript是一門高級編程語言

  機器語言(CPU二進制指令集) -》 匯編語言(二進制轉換為能夠辨認、記憶的語言) -》高級語言(屏蔽了計算機架構的細節,兼容多種不同的 CPU 架構)

  高級語言的兩種執行方式:

  1、解釋執行,解釋執行會先將輸出的源碼通過解析器編譯成中間代碼,再間接應用解釋器解釋執行中間代碼,輸入后果。

  2、編譯執行,會將源碼轉換為中間代碼,而后編譯器會將中間代碼編譯成機器碼,通常編譯成的機器碼以二進制文件模式存儲,執行二進制文件輸入后果。編譯后的機器碼還能夠保留在內存中,能夠間接執行內存中的二進制代碼。

瀏覽器內核負責HTML解析、布局、渲染等相關工作,JavaScript引擎負責解析、執行JavaScript代碼。

三、V8引擎原理

【V8引擎】

  1、V8 是由谷歌收購並使用 C++開發並開源的 JavaScript & WebAssembly引擎,運用於 Chrome 瀏覽器 & Node.js,所以我們寫的 JavaScript 應用,大多數都跑在 V8 上;

  2、它實現ECMAScript和WebAssembly,並在Windows 7或更高版本,MacOS 10.12+和使用x64,IA-32, ARM或MIPS處理器的Linux系統上運行;

  3、V8可以獨立運行,也可以嵌入到任何C ++應用程序中;

【運行環境】

  宿主和 V8 共用同一套內存空間,在執行時,它要依賴於由宿主提供的基礎環境(類似於寄生關系),大致包含了我們熟悉的全局執行上下文、事件循環系統、堆棧空間、宿主環境特殊定制的 API等。除了需要宿主提供的一些基礎環境之外,V8 自身會使用創建好的堆和棧並提供 JavaScript 的核心功能(Object、Function、String)以及垃圾回收(GC)。

【引擎架構】

  V8 采用解釋執行和編譯執行這兩種形式,這種混合應用的形式稱為 JIT (即時編譯),V8 在執行 JavaScript 源碼時,首先解析器會將源碼解析為 AST 抽象語法樹,解釋器 (Ignition) 會將 AST 轉換為字節碼,一邊解釋一邊執行。解釋器同時會記錄某一代碼片段的執行次數,如果執行次數超過了某個閾值,這段代碼便會被標記為熱代碼(Hot Code),同時將運行信息反饋給優化編譯器 TurboFan,TurboFan 依據反饋信息,會優化並編譯字節碼,最初生成優化的機器碼。

【外圍模塊】

 V8 外圍模塊:

  • 解析器 Parser:解析器負責將 JavaScript 代碼轉換成 AST 形象語法樹
  • 解釋器 Ignition:解釋器負責將 AST 轉換為字節碼,並收集 TurboFan 須要的優化編譯信息
  • 編譯器 TurboFan:利用解釋器收集到的信息,將字節碼轉換為優化的機器碼

 V8 須要等編譯實現后才能夠運行代碼,所以解析和編譯過程中的性能非常重要。

【解析器 Parser】

 Parse模塊會將JavaScript代碼轉換成AST(抽象語法樹),這是因為解釋器並不直接認識JavaScript代碼,如果函數沒有被調用,那么是不會被轉換成AST的。PreParse(預解析),並不是一開始所有代碼都需要執行,所以V8引擎就實現了Lazy Parsing(延遲解析)的方案(惰性解析),它的作用是將不必要的函數進行預解析,也就是只解析暫 時需要的內容,而對函數的全量解析是在函數被調用時才會進行。

【解釋器 Ignition】

    會將AST轉換成ByteCode(字節碼,字節碼不僅占用內存少,而且生成字節碼的工夫很快,晉升了啟動速度)並執行,同時會收集TurboFan優化所需要的信息(比如函數參數的類型信息,有了類型才能進行真實的運算); 如果函數只調用一次,Ignition會執行解釋執行ByteCode。

   寄存器:

   解釋器通常有兩種類型,基於棧和基於寄存器的解釋器,Ignition 解釋器在執行字節碼時,應用了通用寄存器和累加寄存器,函數參數和局部變量會保留在通用寄存器中,累加寄存器會保留兩頭計算結果。在執行指令的過程中,CPU 須要對數據進行讀寫,如果間接在內存中讀寫的話,會重大影響程序的執行性能。所以 CPU 就引入了寄存器,將一些兩頭數據寄存到寄存器中,晉升 CPU 的執行速度。

【編譯器 TurboFan】

    可以將字節碼編譯為CPU可以直接執行的機器碼,如果一個函數被多次調用,那么就會被標記為熱點函數,那么就會經過TurboFan轉換成優化的機器碼,提高代碼的執行性能; 但是,機器碼實際上也會被還原為ByteCode,這是因為如果后續執行函數的過程中,類型發生了變化(比如sum函數原來執行的是 number類型,后來執行變成了string類型),之前優化的機器碼並不能正確的處理運算,就會逆向的轉換成字節碼。

四、垃圾變量回收機制GC
【JavaScript的垃圾回收機制】
    了解垃圾回收之前,我們有必要了解一下JS中的數據是如何存儲的。 JS數據分為基本類型和引用類型,其中前者采用棧這個數據結構來存儲,后者采用堆這個數據結構來存儲,所以存儲形式的不同導致了它們的回收機制也有所區別。
   1、棧回收,首先系統棧用來存儲變量,它還有一個特點就是通過移動棧頂指針可以切換執行上下文,棧的回收機制比較簡單,就是在切換執行上下文的時候,棧頂內容自動被回收,這就是棧的垃圾回收機制。
   2、堆回收,引用類型數據用堆來存儲,在堆中,我們把存儲空間分為兩個部分,分別是新生代和老生代,老生代就是一些常駐內存,存活時間長,新生代就是臨時分配的內存,存活時間短,且新生代內存比老生代小得多。

 V8的垃圾回收策略主要基於分代式垃圾回收機制,現代的垃圾回收算法中按對象的存活時間將內存的垃圾回收進行不同的分代,然后分別對不同分代的內存施以更高效的算法。

   【Scavenge算法】

  在分代的基礎上,新生代中的對象主要通過Scavenge算法進行垃圾回收,在Scavenge的具體 實現中,主要采用了Cheney算法

 

 

 Cheney 算法是一種采用復制的方式實現的垃圾回收算法。它將堆內存一分為二,每一部分空間稱為 semispace。在這兩個 semispace 空間中,只有一個處於使用中,另一個處於閑置狀態。

 From表示當前正在使用的內存,To表示空閑內存。

 在垃圾回收的時候,會遍歷From里的內存,將還存活的變量移動到To的內存中中,非存活的直接回收。簡而言之, 在垃圾回收的過程中, 就是通過將存活對象在兩個 semispace 空間之間進行復制。

   【Mark-Sweep & Mark-Compact】

    Scavenge算法通過犧牲空間換時間的算法非常適合生命周期短的新生代,但是,當一個對象經過多次復制,生命周期較長的時候或在To空間不足的時候,對象會被分配到進入到老生代中,需要采用新的算法進行垃圾回收。

  新生代內存晉升老生代的條件:

  • 經歷過一次Scavenge回收
  • To內存里空間占用超過25%

 V8采用的方法就是標記法,Mark-Sweep 在標記階段遍歷堆中的所有對象,並標記活着的對象,在隨后的清除階段中,只清除沒有被標記的對象。Scavenge 中只復制活着的對象,而 Mark-Sweep 只清理死亡對象。然而Mark-Sweep 在進行一次標記清除回收后,內存空間會出現不連續的狀態,不可避免的會出現內存碎片,V8的處理也很直接,直接統一移動到一頭,移動完成后,直接清理掉邊界外的內存,這些移動的工作量也是回收中最費時的一部分。

 小知識:

 為了避免出現 JavaScript 應用邏輯與垃圾回收器看到的不一致的情況,垃圾回收的 3 種基本算法都需要將應用邏輯暫停下來,待執行完垃圾回收后再恢復執行應用邏輯,這種行為被稱為“全停頓",長時間的"全停頓"垃圾回收會讓用戶感受到明顯的卡頓,帶來體驗的影響。以1.5 GB的垃圾回收堆內存為例,V8做一次小的垃圾回收需要50毫秒以上,做一次非增量式的垃圾回收甚至要1秒以上。這是垃圾回收中引起JavaScript線程暫停執行的時間,在 這樣的時間花銷下,應用的性能和響應能力都會直線下降。

 【增量標記】

 為了降低全堆垃圾回收帶來的停頓時間,V8對此也采取了一些措施,采用增量標記的方法,其實說白了就是在標記階段:拆分為許多小“步進”,每做完一“步進”,就讓JavaScript應用邏輯執行一會兒,垃圾回收和應用邏輯交替執行直到標記階段完成。

 

參考:

1、https://zhuanlan.zhihu.com/p/37996721

2、https://juejin.cn/post/6854573211716321287

3、https://lequ7.com/guan-yu-v8-jiao-nv-peng-you-xue-qian-duan-zhi-shen-ru-li-jie-js-yin-qing.html

4、https://blog.csdn.net/qq_36380426/article/details/114826470

5、https://cloud.tencent.com/developer/article/1710084


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM