認(rèn)識瀏覽器的內(nèi)核
- 不同的瀏覽器又不同的內(nèi)核組成
- Gecko:早期被Netscape和Mozilla Friefox瀏覽器使用狂窑;
- Trident:微軟開發(fā),被IE4~IE11瀏覽器使用泉哈,但是Edge瀏覽器已經(jīng)轉(zhuǎn)向Blink;
- Webkit:蘋果基于KHTML開發(fā)丛晦、開源的,用于Safari烫沙、Google Chrome之前也在使用;
- Blink:是Webkit的一個分支锌蓄,Google開發(fā),目前應(yīng)用于Google Chrome瘸爽、Edge您访、Opera等洋只;
- 事實上辆沦,我們經(jīng)常說的瀏覽器內(nèi)核指的是瀏覽器的排版引擎:
- 排版引擎(layout engine)污桦,也稱為瀏覽器引擎(browser engine)、頁面渲染引擎(rendering engine)或模板引擎迷殿。
瀏覽器的工作原理
- JavaScript代碼蔚晨,在瀏覽器中是如何被執(zhí)行的?
瀏覽器加載文件.png
瀏覽器渲染過程
-
但是在這個執(zhí)行過程中银择,HTML解析的時候遇到了JavaScript標(biāo)簽,應(yīng)該怎么辦累舷?
-
會停止解析HTML,而去加載和執(zhí)行JavaScript代碼被盈;
瀏覽器渲染過程.png
-
-
那么,JavaScript代碼由誰來執(zhí)行呢只怎?
- JavaScript引擎
認(rèn)識JavaScript引擎
-
為什么需要JavaScript引擎呢?
- 我們前面說過身堡,高級的編程語言都是需要轉(zhuǎn)成最終的機器指令來執(zhí)行的;
- 事實上我們編寫的JavaScript無論你交給瀏覽器或者Node執(zhí)行,最后都是需要被CPU執(zhí)行的殿漠;
- 但是CPU只認(rèn)識自己的指令集,實際上是機器語言佩捞,才能被CPU所執(zhí)行;
- 所以我們需要JavaScript引擎幫助我們將JavaScript代碼翻譯成CPU指令來執(zhí)行一忱;
-
比較常見的JavaScript引擎有哪些呢?
- SpiderMonkey:第一款JavaScript引擎帘营,由Brendan Eich開發(fā)(也就是JavaScript作者);
- Chakra:微軟開發(fā)芬迄,用于IT瀏覽器;
- JavaScriptCore:Webkit中的JavaScript引擎禀梳,Apple公司開發(fā);
- V8:Google開發(fā)的強大JavaScript引擎算途,也幫助Chrome從眾多瀏覽器中脫穎而出;
瀏覽器內(nèi)核和JS引擎的關(guān)系
- 這里我們先以Webkit為例嘴瓤,Webkit事實上由兩部分組成的:
- WebCore: 負(fù)責(zé)HTML解析扫外、布局筛谚、渲染等相關(guān)的工作;
- JavaScriptCore:解析停忿、執(zhí)行JavaScript代碼;
-
在小程序中編寫的JavaScript代碼就是被JSCore執(zhí)行的瞎嬉;
瀏覽器內(nèi)核和JS引擎的關(guān)系.png
V8引擎的原理
-
我們來看一些官方對V8引擎的定義:
- V8是用C++編寫的Google開源高性能JavaScript和WebAssembly引擎,它用于Chrome和Node.js等氧枣。
- 它實現(xiàn)ECMAScript和WebAssembly,并在Windows 7或更高版本便监,macOS 10.12+和使用x64碳想,IA-32,ARM或MIPS處理器的Linux系統(tǒng)上運行胧奔。
- V8可以獨立運行,也可以嵌入到任何C++應(yīng)用程序中龙填。
V8引擎的原理.png
Parse對JavaScript源代碼進行解析,包括詞法分析和語法分析
const name = "why"
// 詞法分析:
tokens: [{type: "keyword", value: "const"},
{type: 'identifier', value: 'name'}]
- 根據(jù)tokens進行語法分析岩遗,生成AST抽象語法樹
- Ignition將ast轉(zhuǎn)為字節(jié)碼(因為不同的環(huán)境<window,Linux,mac>能執(zhí)行的機器指令不同),字節(jié)碼再轉(zhuǎn)為匯編代碼再轉(zhuǎn)為機器指令
- TurboFan收集信息宿礁,比如類型信息(例如某個執(zhí)行頻率較高的函數(shù)),將這些字節(jié)碼直接生成機器碼。
- Deoptimization操作對機器指令進行反向操作蔬芥,把機器指令轉(zhuǎn)為字節(jié)碼
V8引擎的架構(gòu)
- V8引擎本身的源碼非常復(fù)雜,大概有超過100w行C++代碼笔诵,通過了解它的架構(gòu),我們可以知道它是如何對JavaScript執(zhí)行的:
- Parse模塊會將JavaScript代碼轉(zhuǎn)換成AST(抽象語法樹)嗤放,這是因為解析器并不直接認(rèn)識JavaScript代碼壁酬;
- 如果函數(shù)沒有被調(diào)用次酌,那么是不會被轉(zhuǎn)換成AST的舆乔;
- Parse的V8官方文檔:https://v8.dev/blog/scanner
- Ignition是一個解析器,會將AST轉(zhuǎn)換成ByteCode(字節(jié)碼)
- 同時會收集TurboFan優(yōu)化所需要的信息(比如函數(shù)參數(shù)的類型信息希俩,有了類型才能進行真實的運算);
- 如果函數(shù)只調(diào)用一次颜武,Ignition會解析執(zhí)行ByteCode;
- Ignition的V8官方文檔:https://v8.dev/blog/ignition-interpreter
- TurboFan是一個編譯器鳞上,可以將字節(jié)碼編譯為CPU可以直接執(zhí)行的機器碼这吻;
- 如果一個函數(shù)被多次調(diào)用唾糯,那么就會被標(biāo)記為熱點函數(shù)怠硼,那么就會經(jīng)過TurboFan轉(zhuǎn)換成優(yōu)化的機器碼移怯,提高代碼的執(zhí)行性能;
- 但是舟误,機器碼實際上也會被還原為ByteCode,這是因為如果后續(xù)執(zhí)行函數(shù)的過程中嵌溢,類型發(fā)生了變化(比如sum函數(shù)原來執(zhí)行的是number類型同云,后來執(zhí)行變成了string類型)堵腹,之前優(yōu)化的機器碼并不能正確的處理運算,就會逆向的轉(zhuǎn)換成字節(jié)碼疚顷;
- TurboFan的V8官方文檔:https://v8.dev/blog/turbofan-jit
V8執(zhí)行的細(xì)節(jié)
- 那么我們的JavaScript源碼是如何被解析(Parse過程)的呢?
- Blink將源碼交給V8引擎腿堤,Stream獲取到源碼并且進行編碼轉(zhuǎn)換;
- Scanner會進行詞法分析(lexicla annalysis)笆檀,詞法分析會將代碼轉(zhuǎn)換成tokens;
- 接下來tokens會被轉(zhuǎn)換成AST樹酗洒,經(jīng)過Parser和PreParser:
- Parser就是直接將tokens轉(zhuǎn)成AST樹架構(gòu)士修;
- PreParser稱之為預(yù)解析樱衷,為什么需要預(yù)解析呢?
- 這是因為并不是所有的JavaScript代碼矩桂,在一開始時就會被執(zhí)行。那么對所有的JavaScript代碼進行解析侄榴,必然會影響網(wǎng)頁的運行效率;
- 所以V8引擎實現(xiàn)了Lazy Parsing(延遲解析)的方案癞蚕,它的作用是將不必要的函數(shù)進行預(yù)解析蕊爵,也就是只解析暫時需要的內(nèi)容在辆,而對函數(shù)的全量解析是在函數(shù)被調(diào)用時才會進行证薇;
-
比如我們在一個函數(shù)outer內(nèi)部定義了另外一個函數(shù)inner匆篓,那么inner函數(shù)就會進行預(yù)解析;
V8引擎的解析圖.png