1.瀏覽器渲染過程
1.1 下載文件【掌握】
當(dāng)我們輸入一個網(wǎng)址之后,dns服務(wù)器將網(wǎng)址中的域名解析為ip地址(服務(wù)器地址)之后,(這個過程就是通過網(wǎng)址找服務(wù)器地址)。
找到服務(wù)器地址后,通常會返回一個index.html網(wǎng)頁,然后瀏覽器開始解析html網(wǎng)頁,解析的過程會遇到加載css的標簽-》從服務(wù)器下載css文件,遇到script標簽-》下載對應(yīng)的js文件,js相關(guān)的代碼也會執(zhí)行,比如寫了一個函數(shù),執(zhí)行完后,就能在瀏覽器里運行這個函數(shù)。
1.2 執(zhí)行文件
瀏覽器內(nèi)核(排版引擎)幫助我們解析代碼。
1.2.1.html css解析【掌握】
來看看瀏覽器的渲染過程

1.瀏覽器內(nèi)核幫助解析html文件,通過html parser將html文件轉(zhuǎn)化成dom樹,
在這個過程中可以編寫了js代碼來對dom進行操作,代碼由js引擎執(zhí)行。
2.樣式文件由css parser進行解析生成樣式規(guī)則style rules,然后和dom樹合在一起形成渲染樹
3.通過布局引擎對渲染樹在進行一步操作形成最終的渲染樹
4.最后進行繪制 -> 瀏覽器展示
1.2.2.js代碼執(zhí)行
1.2.2.1.為什么需要js引擎
- 高級的編程語言都是需要轉(zhuǎn)成最終的機器指令來執(zhí)行的;
- 事實上我們編寫的JavaScript無論你交給瀏覽器或者Node執(zhí)行,最后都是需要被CPU執(zhí)行的;
- 但是CPU只認識自己的指令集,實際上是機器語言,才能被CPU所執(zhí)行;
- 所以我們需要JavaScript引擎幫助我們將JavaScript代碼翻譯成CPU指令來執(zhí)行;
1.2.2.2.瀏覽器內(nèi)核和JS引擎的關(guān)系
瀏覽器內(nèi)核分為兩種:渲染引擎和js引擎。
最開始渲染引擎和js引擎并沒有很嚴格的區(qū)分,后來js引擎越來越獨立,內(nèi)核則傾向于只指渲染引擎,內(nèi)核的種類很多,常見的內(nèi)核有四種:Trident / Gecko / Blink / Webkit
這里我們先以WebKit為例,WebKit事實上由兩部分組成的:
- WebCore:負責(zé)HTML解析、布局、渲染等等相關(guān)的工作;
- JavaScriptCore:解析、執(zhí)行JavaScript代碼;
另外一個強大的JavaScript引擎就是V8引擎。
1.1.2.3.v8引擎的原理【理解】
1.定義
V8是用C ++編寫的Google開源高性能JavaScript和WebAssembly引擎,它用于Chrome和Node.js等。
它實現(xiàn)ECMAScript和WebAssembly,可以在很多環(huán)境下運行,比如說:Windows 7或更高版本,macOS 10.12+和使用x64,IA-32, ARM或MIPS處理器的Linux系統(tǒng)上運行。
-
它也可以獨立運行,也可以嵌入到任何C ++應(yīng)用程序中。(很少)
2.底層架構(gòu)
圖片.png 1.編寫了js代碼想要交給cpu去執(zhí)行,但是js代碼直接放在cpu里面,cpu無法識別,cpu只能識別0101這樣的機器語言,所以js代碼需要借助v8引擎。
2.parse對代碼進行語法分析和詞法分析->生成抽象語法樹
https://astexplorer.net/3.拿到抽象語法樹,我們可以把它轉(zhuǎn)化成es5的代碼也可以轉(zhuǎn)化成字節(jié)碼,轉(zhuǎn)化成字節(jié)碼需要用的一個庫ignition(轉(zhuǎn)化器)。
為什么不直接轉(zhuǎn)成0101機器語言?
因為無法確定這個代碼會運行在怎樣的環(huán)境上(windows,mac,linux),不同環(huán)境的cpu架構(gòu)不同,不同cpu架構(gòu)能執(zhí)行的機器指令不同,所以無法確定機器指令,所以才轉(zhuǎn)化為字節(jié)碼。
字節(jié)碼可以跨平臺,轉(zhuǎn)化為機器指令后就可以運行了。4.字節(jié)碼需要轉(zhuǎn)化成匯編指令,再轉(zhuǎn)化為機器指令才可以運行
這個過程有點耗時,如果代碼(函數(shù))執(zhí)行多次,把這個函數(shù)保存下來,之后就能直接從字節(jié)碼生成機器指令了。
v8引擎有一個庫,TurboFan 。ignition收集函數(shù)執(zhí)行信息,標注執(zhí)行頻率高的函數(shù),通過TurboFan將這些函數(shù)轉(zhuǎn)化為優(yōu)化的機器碼,之后再執(zhí)行的時候就不需要經(jīng)過復(fù)雜的轉(zhuǎn)化過程了,直接執(zhí)行機器指令,得到運行結(jié)果,可以提高性能。
有一個問題,比如說保存了高頻率函數(shù)
function sum(num1,num2){
return num1+num2
}
sum(20,20)
sum(30,30)
sum('aa','bb').
當(dāng)優(yōu)化的機器碼發(fā)現(xiàn)執(zhí)行指令不同時(數(shù)值相加變成了字符串拼接),會進行一個操作deoptimzation,反優(yōu)化到字節(jié)碼后再轉(zhuǎn)化成運行結(jié)果。
這樣會消耗性能,所以在寫代碼的時候使用typescript,有一個類型的限制,效率會更高一些。
ps:預(yù)解析:解析會執(zhí)行的函數(shù)和變量
2.js代碼執(zhí)行過程【掌握】
var name = "jim"
var num1 = 20
var num2 = 30
var result = num1 + num2
2.0.選擇代碼運行環(huán)境
瀏覽器(包含v8引擎):在html內(nèi)加載js文件,
node環(huán)境終端(包含v8引擎/其他js引擎):命令行-》node js文件
2.1.初始化全局對象
代碼在運行之前需要被解析。
解析時(js源代碼到ast抽象語法樹的解析過程)
v8引擎會幫我們初始化全局對象GlobalObject-GO,包含環(huán)境(瀏覽器或者node)內(nèi)的全局變量(settimeou函數(shù)t,String類...),Window屬性(指向globalObject)和代碼里面定義的變量(尚未賦值的name,num1...,因為還沒有執(zhí)行所以未賦值)
var globalObject = {
String: "類",
Date: "類",
setTimeount: "函數(shù)",
window: globalObject,
name: undefined,
num1: undefined,
num2: undefined,
result: undefined
}
2.2.執(zhí)行上下文棧中運行代碼
為了執(zhí)行代碼, v8引擎內(nèi)部會有一個執(zhí)行上下文棧(Execution Context Stack, ECStack)(函數(shù)調(diào)用棧),(其實就是內(nèi)存,代碼要執(zhí)行的話需要先從磁盤加載到內(nèi)存里面去,在內(nèi)存中將代碼轉(zhuǎn)化成對應(yīng)的機器指令,最后在cpu中進行執(zhí)行。)
ps:對內(nèi)存會劃分結(jié)構(gòu),分別是棧結(jié)構(gòu)和堆結(jié)構(gòu)。
代碼要執(zhí)行都需要放到執(zhí)行上下文棧中,一般是放函數(shù)。
2.3.在執(zhí)行上下文棧中執(zhí)行全局代碼
因為我們執(zhí)行的是全局代碼, 為了全局代碼能夠正常的執(zhí)行, 需要創(chuàng)建 全局執(zhí)行上下文(Global Execution Context)(全局代碼需要被執(zhí)行時才會被創(chuàng)建),然后放進ECStack里。全局執(zhí)行上下文中有一個VO變量對象,指向GO,然后開始執(zhí)行代碼。
2.4.執(zhí)行代碼,根據(jù)代碼從上往下依次執(zhí)行。
var name = "why"
//通過vo-》go找到name,值改成“why”
console.log(num1)
//作用域提升,打印undefined
var num1 = 20
//同上
var num2 = 30
//同上
var result = num1 + num2
//同上
console.log(result)
//在vo-》go查找result,然后打印出來
