本篇文章通過圖文為你介紹了V8引擎大概的執行程序,你可以了解到代碼是從掃描器Scaner變成tokens,從決議器Parser變成AST,從解釋器變成位元組碼等等,以及JavaScript代碼在執行的程序中,它在記憶體的情況是如何變化的,讓你從更加底層的角度去理解你的js代碼是如何運行的,了解這些后你就能從更加底層的角度去理解var的變數提升,閉包的形成等了,
瀏覽器原理
瀏覽器內核與js引擎
瀏覽器內核又稱“排版引擎”,“渲染引擎”,“瀏覽器引擎”,叫法很多,簡單來說干的活就是將代碼(HTML,XML,CSS,圖片等)決議排版布局后輸出到顯示幕讓你看到,
JavaScript引擎是一個專門處理JavaScript腳本的虛擬機,一般會附帶在網頁瀏覽器之中,
主流瀏覽器內核與js引擎:
| 瀏覽器 | 內核 | js引擎 |
|---|---|---|
| Safari | WebKit | javaScriptCore |
| Chrome | Blink | V8 |
| firefox | Gecko | SpiderMonkey... |
瀏覽器渲染程序概述
輸入網址,服務器回傳html,瀏覽器內核開始決議html,遇到link 等之類則會暫停,去下載對應的css或者js,
- 首先hmtl會被決議為dom樹;
- 然后css會被決議為cssom規則樹;
- 根據dom樹和cssom規則樹構建渲染樹,
- 瀏覽器根據渲染資料進行布局(回流),此階段瀏覽器計算各節點在頁面中確切位置和大小,也稱自動重排,
- 布局后進行繪制,將內容顯示在螢屏上,
渲染引擎不會等所有html決議完成后再去,構建render tree,而是決議完一部分就顯示一部分,以提高用戶體驗,
V8引擎的執行
V8引擎決議程序概述
BLinK內核遇到js代碼后,會以流的形式傳遞給v8,然其開始作業:
- 首先接收到流后,會有掃描器Scanner對其進行詞法分析將代碼轉化為
tokens; - 然后決議器parser將其轉換為AST抽象語法樹,
- 再由解釋器ignition(圖中閃電部分)生成位元組碼再進行執行,
Parser再探:
Parser決議的時并不會進行全量決議(全部決議1.耗時間;2.決議后的位元組碼需放入記憶體耗記憶體),而是有延遲決議的策略,也就是一種按需決議給方案,( 理解:首先會Perpaser會決議出所需的最少限度的內容,比如內部有未呼叫的函式,則決議出函式宣告,當呼叫時則paser對該函式進行完整的決議 ),
Ignition再探:
Ignition關注的是減少 V8 的記憶體開銷,會進行執行前的優化作業,它會將AST進行分析將多次呼叫的函式標記為熱點函式 交由TurboFan進行編譯生成優化后的機器碼(優化,方便快速呼叫)執行,而單次呼叫的函式則會被生成位元組碼再做執行,所以它也會有編譯程序的,所以也有人對JS是否是解釋型語言有爭議,而正如最新的MDN上的檔案說的JavaScript是一種具有函式優先的輕量級,解釋型或即時編譯型的編程語言,應該是最準確的吧,
V8記憶體模型
V8的記憶體主要分為堆和堆疊兩部分,用以執行代碼,和JVM有點類似??,
堆: 這是最大的記憶體塊,也是垃圾回收(GC)發生的地方,
堆疊: 每個V8行程有一個堆記憶體,這是存盤靜態資料的地方,包括方法/函式框架、原始值和指向物件的指標,

當然這只是簡化版,實際的情況也會比這復雜得多(如下):

GC垃圾回收
- 參考計數:物件有參考指向它,參考就+1,參考為0就進行回收,但其會產生回圈參考,
- 標記清除:早期V8中堆記憶體采用的一種清除演算法,此會有一個根物件,如V8中全域物件,垃圾回收器會定時從根開始去找參考的物件,沒有參考的物件就會回收,可以很好解決回圈參考的問題,
JavaScript在記憶體中的執行程序
執行前準備:
-> 首先,js引擎在執行代碼之前會在在堆記憶體中創建一個全域物件GO(Global Object):
- 該物件在所有作用域可訪問
- 會有
Date,Math,SetTimeOut,SetInterval,String,Array,Number等 - 內置window屬性指向它本身
-> 然后,JavaScript引擎會在內部創建執行背景關系堆疊ECS(Execution Context Stack),用于執行代碼呼叫,
開始執行:
-> 首先會創建一個全域執行環境GEC(Global Execution Context),它包含:在paser轉成AST的程序中,將全域定義的變數,函式加入到GO中,初始為undefined,(變數作用域提升:全域定義的變數,函式會先入GO再執行),
并將其入堆疊到ECS中,然后逐行執行,進行變數賦值,函式執行操作,
-> 在執行到一個函式時會創建函式執行背景關系FEC(Fuction Execution Context),并壓入執行背景關系堆疊ECS,它包含三部分:
- 在決議函式成為AST樹結構時,會創建AO(Activation Object)包含:形參,arguments,函式定義(函式代碼),函式指向物件,定義邊量;
- 作用域鏈:VO(在函式中就是AO物件) + 父級作用域
- this系結的值,
準備執行【創建GO 創建ECS 決議全域變數,函式(若變數初始為undefined,若函式則創建函式物件進行存盤)】-> 執行代碼【遇到函式呼叫 -> 創建其函式的AO物件 -> 創建其函式執行背景關系 -> 執行函式內部代碼】
注:在最新的ECMA標準中,變數物件VO,該為了變數環境VE,其可以不為物件,只要其能存盤環境記錄,其包含的內容也有些差異,
結合代碼示例進行分析
案例一
var name = "shinna_mashiro";
foo(666);
function foo(num){
console.log(m);
var m = 10;
var n = 20;
function bar(){
console.log(name)
}
bar()
}
這是通過var宣告的變數,而通過let,const宣告的變數ECMA262對它們是這么描述的:The variables(let 或 const)are created when their containing Lexical Environment is instantiate but may not be accessed in any way until the variable's LexicalBinding is evaluated. 這些變數會被創建在包含他們的詞法環境(VE -> VO)被實體化時,但是是不可以訪問的,直到詞法系結被執行,也就是在FEC創建的時候,VE被實體化時就會創建它,但是不能被訪問,所以提升不了,(暫時性死區)



案例二閉包
function makeAdder(count){
return function(num){
retrun count + num;
}
}
var add10 = makeAdder(10);
console.log(add10(5));
可以看到在代碼執行完后,閉包結構中,會一直還有參考在GO中,所以此時不會對其記憶體進行回收,

部分參考及補充:
1.Visualizing memory management in V8 Engine (JavaScript, NodeJS, Deno, WebAssembly):https://deepu.tech/memory-management-in-v8/
2.全面分析總結JS記憶體模型:https://segmentfault.com/a/1190000021996331
3.V8引擎詳解:https://juejin.cn/post/6844904146798116871
4.JavaScript到底是解釋型語言還是編譯型語言?:https://segmentfault.com/a/1190000013126460
5.Blazingly fast parsing, part 2: lazy parsing: https://v8.dev/blog/preparser
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/498875.html
標籤:JavaScript
