Chrome瀏覽器引擎 Blink & V8

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"背景"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這篇文章是我的前端技術系列文章中瀏覽器工作原理欄目中的第二篇。瀏覽器引擎(也被稱作佈局引擎或渲染引擎)是瀏覽器的重要組成部分。瀏覽器引擎最重要的工作就是將HTML文本和其他頁面中的資源轉換成可以與用戶產生交互的頁面。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了瀏覽器引擎外,佈局引擎和渲染引擎是另外兩個相關的概念,理論上,兩個引擎可以獨立實現,但在實際情況中,往往很少將二者分開實現。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了包含佈局和渲染引擎外,瀏覽器引擎還遵循"},{"type":"link","attrs":{"href":"https://en.wikipedia.org/wiki/Content_Security_Policy","title":null},"content":[{"type":"text","text":"文檔安全策略(Content Security Policy)"}]},{"type":"text","text":"以保證站點間相互獨立。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在運行JavaScript代碼的功能上,基本上主流的瀏覽器都使用獨立的引擎,起初JavaScript語言只被用於在瀏覽器中使用,但現在JavaScript幾乎可以在任何地方使用,這需要JavaScript引擎可以獨立於瀏覽器單獨使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而像"},{"type":"link","attrs":{"href":"https://en.wikipedia.org/wiki/Electron_(software_framework)","title":null},"content":[{"type":"text","text":"Electron framework"}]},{"type":"text","text":"這樣的技術就是整合Chromium的渲染引擎和Nodejs而實現的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我想通過這篇文章把V8中的技術儘可能的詳述,涵蓋的內容會比較多,可反覆閱讀 :)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"常見的瀏覽器引擎"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"瀏覽器引擎是Web平臺技術中一系列標準(HTML、CSS、ECMAScript、WebGL、Web Storage等等)的具體實現,不同的瀏覽器引擎在遵循同樣的標準下,還實現了額外的功能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Gecko是Mozilla的瀏覽器引擎,在Firefox中使用,"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"SpiderMonkey是Firefox的JavaScript引擎。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Apple爲Safari瀏覽器創造了Webkit引擎,Webkit引擎內置了"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"JavaScriptCore引擎。雖然Apple允許在IOS設備上可以使用其他的瀏覽器代替Safari,但所用通過App Store分發的瀏覽器必須使用Webkit引擎。例如,Opera Mini瀏覽器在IOS設備上使用Webkit引擎,而在其他設備上使用Blink引擎。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Google起初使用Webkit作爲Chrome瀏覽器的引擎,後來以Webkit引擎爲基礎創造了Blink引擎,所有基於Chromium開源瀏覽器衍生的產品都使用blink引擎。而大名鼎鼎的V8引擎就是Chromium-based瀏覽器的JavaScript引擎。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Microsoft維護着自己的EdgeHTML引擎,作爲老的Trident引擎的替代方案。新的Edge的瀏覽器已經開始使用Chromium的Blink引擎了,而EdgeHTML引擎只在window 10上的"},{"type":"link","attrs":{"href":"https://en.wikipedia.org/wiki/Universal_Windows_Platform","title":null},"content":[{"type":"text","text":"Universal Windows Platform"}]},{"type":"text","text":"中被使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c2/c2d5a491c0b230edf39337130cd12f67.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"天下合久必分,分久必合,隨着Edge也加入了Blink的陣營,基本上Webkit內核及Webkit內核的衍生Blink已經統治了瀏覽器市場。到目前,單單Chrome的市場佔有率已有六成。接下來,就讓我們來聊聊Blink和V8引擎。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Chromium & Blink"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"寬泛的說,Blink實現了在瀏覽器頁籤中所有的渲染工作,其中包括:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實現了Web平臺中的標準,例如HTML標準,包括DOM、CSS等。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"內置了V8引擎用於運行JavaScript。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從網絡堆棧中獲取資源"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"構建DOM樹"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"計算樣式和佈局"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"內置了"},{"type":"link","attrs":{"href":"https://chromium.googlesource.com/chromium/src/+/HEAD/cc/README.md","title":null},"content":[{"type":"text","text":"Chrome Compositor"}],"marks":[{"type":"underline"}]},{"type":"text","text":"和繪製圖形的能力"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"藉助"},{"type":"link","attrs":{"href":"https://chromium.googlesource.com/chromium/src/+/HEAD/content/public/README.md","title":null},"content":[{"type":"text","text":"Content public APIs"}],"marks":[{"type":"underline"}]},{"type":"text","text":",Blink可以被內置在很多諸如Chromium,Android WebView和Opera這樣的應用中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"進程/線程架構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"進程"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Chromium擁有一套多進程架構。Chromium有一個瀏覽器進程和多個帶有沙盒能力的渲染進程。Blink則運行在渲染進程中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從安全的角度考慮,讓不同的站點保持相互隔離是非常重要的,這被稱作"},{"type":"text","marks":[{"type":"strong"}],"text":"站點隔離(Site Isolation)"},{"type":"text","text":"。理論上講,一個渲染進程應該最多隻能負責一個站點的渲染工作。但實際上,當用戶打開很多頁籤時,渲染進程與站點1對1的關係會佔用大量的內存。所以一個渲染進程可能會被多個iframe或頁籤所共享,也就是說一個頁面中的多個iframe可能被多個渲染進程渲染,而在不同頁面中的多個iframe也可能被同一個渲染進程渲染。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"所以,在iframe,頁籤和渲染進程間並不存在一對一的關係。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於Blink運行在渲染進程中的沙盒中,當Blink需要訪問文件或播放視頻或者訪問用戶信息(cookie、password等)時必須與瀏覽器進程通信。這種不同進程間的通信方式被"},{"type":"link","attrs":{"href":"https://chromium.googlesource.com/chromium/src/+/master/mojo/README.md","title":null},"content":[{"type":"text","marks":[{"type":"underline"}],"text":"Mojo"}]},{"type":"text","text":"實現。隨着Chromium不斷向服務化架構演進,Blink可以通過Mojo來降低消息傳遞過程中對發送方和接收方對於具體實現的依賴(服務可能在多個進程中,也可能在同一個進程中,消息傳遞方式不同)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/24/240541c94b8227856e0c4062a8f9db7e.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Mojo"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Mojo是一系列庫的集合,用於提供一種進程內或跨進程的通信方案,其中包含了與平臺無關的通用的IPC方案、消息IDL格式化和可以與不同語言集成的綁定庫。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d9/d990029007862acd8e71392f766b48ee.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Message pipe"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個"},{"type":"text","marks":[{"type":"strong"}],"text":"消息通道(Message pipe)"},{"type":"text","text":"建立其兩個"},{"type":"text","marks":[{"type":"strong"}],"text":"端點(endpoint)"},{"type":"text","text":"之間的通道。每一個端點都有一個用於收消息的隊列,同時還可以向另一個端點發送消息,而消息通道是雙向的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Mojom"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Mojom文件描述了消息的類型。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了消息通道和消息類型,通道中的一個端點可以被指定成"},{"type":"codeinline","content":[{"type":"text","text":"Remote"}],"marks":[{"type":"strong"}]},{"type":"text","text":",它可以發送Mojom文件中定義好類型的消息。另一個端點則可以被指定成"},{"type":"codeinline","content":[{"type":"text","text":"Receiver"}],"marks":[{"type":"strong"}]},{"type":"text","text":",用於接收消息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"線程"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Blink包含一個主線程,多個Worker線程,還有一些其他的線程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"幾乎所有重要的工作都運行在主線程上。包括運行JavaScript(除了Workers),DOM生成,CSS樣式和佈局計算等,所以交互性能的優化關鍵主要圍繞主線程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Blink會爲Web workers,Service workers創建出獨立的線程。雖然運行的都是JavaScript,但主線程與worker線程的運行環境是不共享的,需要通過消息來傳遞數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Blink和V8也可能會創建出其他的用於音視頻,數據庫和垃圾回收(GC)等功能的線程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於線程間通信,會使用PostTask提供的api。除了真的因爲性能的原因,使用共享內存的方式實現通信並不被推薦,這也是Blink不使用線程鎖(MutexLocks)的原因。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/89/89ab6c79bbfa58c540f5ebc72be974b5.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"Page, Frame, Document, DOMWindow"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"概念"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個"},{"type":"text","marks":[{"type":"strong"}],"text":"頁面(Page)"},{"type":"text","text":"代表一個瀏覽器頁籤,一個渲染進程可能負責渲染多個頁面。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個"},{"type":"text","marks":[{"type":"strong"}],"text":"框(Frame)"},{"type":"text","text":"代表主框或者一個iframe,一個"},{"type":"text","marks":[{"type":"strong"}],"text":"頁面至少包含一個框。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個"},{"type":"text","marks":[{"type":"strong"}],"text":"DOMWindow"},{"type":"text","text":"代表JavaScript中的window對象,每個框只有一個"},{"type":"text","marks":[{"type":"strong"}],"text":"DOMWindow"},{"type":"text","text":"。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個"},{"type":"text","marks":[{"type":"strong"}],"text":"Document"},{"type":"text","text":"代表JavaScript中的window.document對象,每個框只有一個"},{"type":"text","marks":[{"type":"strong"}],"text":"Document。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個"},{"type":"text","marks":[{"type":"strong"}],"text":"ExecutionContext"},{"type":"text","text":"在主線程中抽象一個Document,在worker線程中抽象WorkerGlobalScope。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"渲染進程 :頁面 = 1 :N"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"頁面 :框 = 1 :M"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Frame : DOMWindow : Document (或ExecutionContext) 在任何情況下都是 1 : 1 : 1 ,但有時引用關係會變化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"跨進程iframes(OOPIF)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雖然站點隔離機制讓頁面變的更安全,但卻增加了複雜度。站點隔離致力於爲每一個站點創建一個渲染進程,例"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"如,"},{"type":"link","attrs":{"href":"https://mail.example.com","title":null},"content":[{"type":"text","marks":[{"type":"underline"}],"text":"https://mail.example.com"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":" 和 "},{"type":"link","attrs":{"href":"https://chat.example.com","title":null},"content":[{"type":"text","marks":[{"type":"underline"}],"text":"https://chat.example.com"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":" 屬於同一個站點,而 "},{"type":"link","attrs":{"href":"https://noodles.com","title":null},"content":[{"type":"text","marks":[{"type":"underline"}],"text":"https://noodles.com"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":" 和 "},{"type":"link","attrs":{"href":"https://pumpkins.com","title":null},"content":[{"type":"text","text":"https://pumpkins.com"}],"marks":[{"type":"underline"}]},{"type":"text","text":"則不屬於同一個站點。如果一個頁面中存在跨站點的iframe則可能被多個渲染進程承載。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從主框的角度看,主框是"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"LocalFrame,iframe則是RemoteFrame。從iframe的角度看,主框則是RemoteFrame,而iframe則是LocalFrame。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"LocalFrame和RemoteFrame間的通信被瀏覽器進程管理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Web IDL綁定"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Web IDL (Web Interface definition language)是用於描述Web平臺中定義的標準如何被瀏覽器實現的接口定義語言,通過瀏覽器對這些標準中定義的接口的實現,Web開發者可以使用JavaScript對象來調用這些標準功能。Blink在實現這些標準的同時,還需要爲V8中的JavaScript提供調用Blink的途徑,這就是Web IDL Bindings。通過對Web IDL的實現和Bindings的存在,就實現了類似在JavaScript中訪問某個節點的第一個子節點"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"的功能(node."},{"type":"text","text":"firstChild)"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"。在實現了通用標準的同時,瀏覽器還實現了自己特有的功能定義,通用的標準被定義在"},{"type":"link","attrs":{"href":"https://heycam.github.io/webidl/","title":null},"content":[{"type":"text","text":"the Web IDL spec"}],"marks":[{"type":"underline"}]},{"type":"text","text":",而Blink自己的定義則被定義在"},{"type":"link","attrs":{"href":"https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/IDLExtendedAttributes.md","title":null},"content":[{"type":"text","text":"Blink-specific IDL extended attributes"}],"marks":[{"type":"underline"}]},{"type":"text","text":"中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通常在idl文件被構建時,"},{"type":"link","attrs":{"href":"https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/IDLCompiler.md","title":null},"content":[{"type":"text","marks":[{"type":"underline"}],"text":"the IDL compiler"}]},{"type":"text","text":" 會自動爲具體的實現類生成Blink-V8的綁定。當在JavaScript中調用node.firstChild時,V8會調用V8Node::firstChildAttributeGetterCallback() ,然後進一步調用Node::firstChild() 。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"渲染流水線"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Rendering pipeline定義了從HTML字符到在屏幕上顯示像素的過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/59/59f93c4bc67cf902dcaad2524f5d709f.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於這部分的內容可以閱讀前一篇文章。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"V8"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8是Google打造的開源的,高性能的JavaScript和WebAssembly引擎,使用C++語言實現。V8引擎被應用在Chrome、Nodejs和其他應用中。V8引擎可以獨立運行,也可以運行在任何的C++程序中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個V8的實例被稱作Isolate,每一個isolate都有獨立GC的堆棧空間。這就意味着一個Isolate中的JavaScript對象不能直接訪問另一個Isolate中的對象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Chrome中,每個渲染進程都有一個V8 Isolate,所有被同一個渲染進程處理的站點的JavaScript代碼在同一個Isolate中運行。但對於Web worker,每一個worker則擁有自己的Isolate。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Isolate中,存在一個或多個JavaScript上下文環境(JavaScript content)。Chrome爲每個iframe創建一個JavaScript環境。此外,每個Chrome extension對於一個iframe都有自己的JavaScript環境。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Blink通常使用ScriptState對象作爲JavaScript環境的引用,blink::ScriptState與v8::Context有着1 : 1的關係。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"執行流水線"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript腳本的運行需要經歷一系列的過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a5/a5595e9e68069bbe5d22c63c9ab8b324.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要運行的JavaScript腳本會從網絡或緩存中被"},{"type":"text","marks":[{"type":"strong"}],"text":"加載。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過對JavaScript腳本文本的分析可以生成用於描述源代碼結構化的數據,"},{"type":"text","marks":[{"type":"strong"}],"text":"抽象語法樹(AST)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"Ignition解釋器"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"會將AST轉化成生成體積更小的"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"字節碼"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":",字節碼中的每行指令代表着對寄存器的操作,當字節碼生後以後AST將會被廢棄以節省空間,後續的執行和優化都基於字節碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"在解釋器執行字節碼時,"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"Object Shapes"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"會試圖將代碼中對象的類型緩存下來生成"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"Type Feedback"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":",當訪問這些對象時會嘗試從緩存中獲取,如果找不到再動態查找並更新緩存。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"TurboFan"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"是V8中的代碼優化編譯器,它會評估函數是否需要被進一步優化成機器碼以提高性能,需要被優化的函數被編譯成"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"Optimized Code"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"但當編譯後的函數被發現函數中變量的數據類型與之前緩存的類型不同時,則需要放棄優化的代碼回到字節碼重新解釋執行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來讓我們逐一瞭解每個過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"加載(Loading)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"加載是V8獲取JavaScript腳本文本的過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/33/33980d9f58ed08dcbca52989379c4b24.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8並不負責資源的下載,所以這些資源可能來自網絡、緩存,也可能來自Service worker。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8可以在文件下載的同時進行接下來的分析工作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8擁有腳本熱加載的能力:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Warm Load"},{"type":"text","text":":當V8再次運行同樣的腳本時,會將腳本編譯後的結果緩存在硬盤中。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Hot Load:"},{"type":"text","text":"當第三次訪問時,V8可以跳過分析和編譯過程直接從硬盤中讀取之前被編譯的結果。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"分析(Parsing)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4f/4f9f1c010a79037cd568d8145b75bfd9.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"分析是將JavaScript腳本文本轉化成"},{"type":"text","marks":[{"type":"strong"}],"text":"抽象語法樹(Abstract Syntax Tree)的過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"詞法分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"詞法分析(lexical analysis)"},{"type":"text","text":"是將一系列字符轉換成"},{"type":"text","marks":[{"type":"strong"}],"text":"標記(token)"},{"type":"text","text":"的過程。這裏的"},{"type":"text","marks":[{"type":"strong"}],"text":"標記"},{"type":"text","text":"是表示源代碼的最小單位,將輸入的字符流轉換成標記的過程被稱爲"},{"type":"text","marks":[{"type":"strong"}],"text":"標記化(tokenization)"},{"type":"text","text":",在這個過程中,詞法分析器還會對這些標記進行分類。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"常見的標記分類有:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"標識符(identifier):x,color,UP"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關鍵字(keyword):if,var,return"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"分隔符(separator):(,}, ;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"操作符(operator):*,=,>"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"字面量(literal):\"Hello world\", true, 666"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"註釋(comment):// 單行, /* 多行 */"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"Scanner"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"掃描器處在詞法分析的第一個階段,通常基於狀態機實現,可以在能識別的標記間不斷切換。每種標記可以代表一個字符或由多個字符組成的序列。很多時候,根據第一個非空字符就可以推斷出標記的類型,然後逐個處理後面的字符,直到出現不屬於該類型標記字符集中的字符時結束(最長一致性原則)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"掃描器只處理utf-16的字符集,所在在掃描器拿到字符之前會有字符集轉換的過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"Evaluator"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"評估器處在詞法分析的第二個階段,用於將某些帶有語義的詞定義成"},{"type":"text","marks":[{"type":"strong"}],"text":"值(value)"},{"type":"text","text":",一個"},{"type":"text","marks":[{"type":"strong"}],"text":"語義("},{"type":"text","marks":[{"type":"color","attrs":{"color":"#202122","name":"user"}},{"type":"strong"}],"text":"lexeme"},{"type":"text","marks":[{"type":"strong"}],"text":")"},{"type":"text","text":"是標記類型和值的組合。有的標記有值,比如標識符類型的標記,而有的標記沒有值,比如分隔符類型的標記。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如,在源代碼中是這樣的:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"net_worth_future = (assets – liabilities);"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"生成的語義標記可能是這樣的:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"IDENTIFIER net_worth_future\nEQUALS\nOPEN_PARENTHESIS\nIDENTIFIER assets\nMINUS\nIDENTIFIER liabilities\nCLOSE_PARENTHESIS\nSEMICOLON"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"語法分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"語法分析("},{"type":"text","marks":[{"type":"color","attrs":{"color":"#202122","name":"user"}},{"type":"strong"}],"text":"syntactic analysis,也叫 parsing"},{"type":"text","marks":[{"type":"strong"}],"text":")"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#202122","name":"user"}}],"text":"是根據某種給定的"},{"type":"text","text":"形式文法"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#202122","name":"user"}}],"text":"對由單詞序列(如英語單詞序列)構成的輸入文本進行分析並確定其語法結構的一種過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"語法分析器"},{"type":"text","text":"(parser)的作用是進行語法檢查、並根據輸入的單詞序列生成帶有層次的數據結構(通常是語法分析樹、抽象語法樹等)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"抽象語法樹與作用域"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"抽象語法樹(Abstract Syntax Tree)"},{"type":"text","text":"是源代碼結構的一種抽象表示。它以樹狀的形式表現編程語言的語法結構,樹上的每個節點都表示源代碼中的一種結構。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏我們以一段代碼作爲例子"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"function sayHi () {\n var str = \"hello world\";\n return str;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"轉化成AST後的結構是這樣的"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7e/7e62d86a0d4c4277bc91ad76a679f88d.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"FunctionLiteral"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"代表sayHi函數,Block代表函數體。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"左邊的"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"VariableDeclaration"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"代表變量str的聲明,"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#323232","name":"user"}},{"type":"strong"}],"text":"Assignment"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#323232","name":"user"}}],"text":"代表賦值,"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#323232","name":"user"}},{"type":"strong"}],"text":"ReturnStatement"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#323232","name":"user"}}],"text":"代表return語句。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏的"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#323232","name":"user"}},{"type":"strong"}],"text":"VariableProxy"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#323232","name":"user"}}],"text":"代表對變量的引用,在目前這個階段還不知道是引用哪個變量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在分析這個階段除了要生成AST,還要分析作用域。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1a/1ac03cdfbd40f995afbd50da9ee3b5a8.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過分析,全局作用域中包含函數sayHi的聲明,而sayHi的函數作用域中包含變量str的聲明。而兩個"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#323232","name":"user"}},{"type":"strong"}],"text":"VariableProxy"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#323232","name":"user"}}],"text":"所引用的變量也可以確定下來。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#202122","name":"user"}}],"text":"V8有兩種分析器:Preparser和Full parser。Preparser分析器可以推遲那些不是立即需要分析的函數以減少代碼啓動需要的時間。Preparser只會處理語法分析和一些錯誤的檢查而不會生成抽象語法樹。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8在這個階段對各種類型的標記的掃描有着各種的優化手段,感興趣的同學可以繼續閱讀"},{"type":"link","attrs":{"href":"https://v8.dev/blog/scanner","title":null},"content":[{"type":"text","text":"Blazingly fast parsing, part 1: optimizing the scanner"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這裏推薦一個用於分析抽象語法樹的網站:"},{"type":"link","attrs":{"href":"https://astexplorer.net/","title":null},"content":[{"type":"text","text":"AST Explorer"}]},{"type":"text","text":"。在各種lint自定義規則,babel、webpack插件等代碼分析、生成的場景裏都有幫助。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"解釋(I"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"nterpreting"},{"type":"text","text":")"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"解釋階段會將AST轉換成字節碼(bytecode)。得益於"},{"type":"link","attrs":{"href":"https://en.wikipedia.org/wiki/Just-in-time_compilation","title":null},"content":[{"type":"text","text":"即時編譯(just-in-time (JIT) compilation)"}]},{"type":"text","text":"技術,包括V8在內的現代瀏覽器JavaScript引擎結合了"},{"type":"text","marks":[{"type":"strong"}],"text":"提前編譯(AOT)"},{"type":"text","text":"的高性能和解釋的靈活性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"起初,代碼首先被編譯器快速的編譯成沒有被優化過的機器碼,在運行的同時再有選擇的將需要優化的代碼通過更高級的編譯器進行優化再編譯。這樣做雖然提高了運行速度,但也浪費了資源。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中比較顯著的問題是被編譯過的機器碼會佔用大量的內存,即使有的代碼可能只會被執行一次。爲了解決這些問題,V8團隊提出了新的JavaScript解釋器,"},{"type":"link","attrs":{"href":"https://v8.dev/docs/ignition","title":null},"content":[{"type":"text","text":"Ignition"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"藉助"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"Ignition"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":",V8可以將AST先轉化成更簡潔的字節碼,其大小與以往的機器碼相比縮小到50%至25%的空間。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/35/355cb34cfd38811ba0c6e19ab7e7025c.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"Ignition"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"生成的"},{"type":"text","text":"字節碼會被用於優化的編譯器和調試工具當作數據源,當字節碼生成以後,抽象語法樹就可以被廢棄掉以節省內存。在生成字節碼的同時,還會在字節碼上增加一些元數據,比如源代碼的位置和用於執行字節碼的處理函數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來我們利用d8近距離觀察一下bytecode,這裏我們將下面的JavaScript放在名爲bytecode.js的文件中,並運行d8的調試工具。"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"function sayHi () {\n var str = 'hello world'\n return str\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以得到下面的結果"}]},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"# d8 --print-bytecode bytecode.js\n[generated bytecode for function: (0x300d082d25e5 )]\nParameter count 1\nRegister count 2\nFrame size 16\n 0x300d082d26ae @ 0 : 12 00 LdaConstant [0]\n 0x300d082d26b0 @ 2 : 26 fa Star r0\n 0x300d082d26b2 @ 4 : 27 fe f9 Mov , r1\n 0x300d082d26b5 @ 7 : 62 3e 01 fa 02 CallRuntime [DeclareGlobals], r0-r1\n 0x300d082d26ba @ 12 : 0d LdaUndefined \n 0x300d082d26bb @ 13 : ab Return \nConstant pool (size = 1)\n0x300d082d2681: [FixedArray] in OldSpace\n - map: 0x300d08042201 \n - length: 1\n 0: 0x300d082d2631 \nHandler Table (size = 0)\nSource Position Table (size = 0)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第6行到第11行的輸出就是轉化以後的字節碼。其中的LdaConstant、Star、Mov等等的就是指令。字節碼對應的指令比較多,這裏截取一部分,更多的指令及解釋可以在v8的源代碼文件src/interpreter/interpreter-generator.cc中查看。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3e/3e63d210cef55982c21198790116dca3.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在開始分析代碼之前需要先介紹"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong"}],"text":"累加器(Accumulator)"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":",累加器是V8中的一個特殊的寄存器,用於存放中間結果"},{"type":"text","text":"。"}]},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"LdaConstant [0]\nStar r0\nMov , r1\nCallRuntime [DeclareGlobals], r0-r1\nLdaUndefined \nReturn "}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"LdaConstant [0]"},{"type":"text","text":"代表從常量池中取出0號下標的常量並放入累加器中。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Star r0"},{"type":"text","text":"表示將累加器中的內容存放到r0寄存器中。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Mov , r1"},{"type":"text","text":"表示將寄存器的內容放到r1寄存器中。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"CallRuntime [DeclareGlobals], r0-r1"},{"type":"text","text":"表示用r0和r1寄存器中的內容作爲參數調用DeclareGlobals函數。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"LdaUndefined"},{"type":"text","text":"代表將Undefined加載到累加器中。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Return"},{"type":"text","text":"代表返回累加器中的內容。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript對象被分配在堆中,並不能直接被嵌入到字節碼中。所以在字節碼中,用數組下標代表JavaScript對象的引用的位置。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們將源代碼稍作修改,然後分析更復雜的字節碼"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"function sayHi () {\n console.log(str)\n var str = 'hello world'\n}\n\nsayHi()"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"[generated bytecode for function: (0x3da0082d25e5 )]\nParameter count 1\nRegister count 3\nFrame size 24\n 0x3da0082d26b2 @ 0 : 12 00 LdaConstant [0]\n 0x3da0082d26b4 @ 2 : 26 f9 Star r1\n 0x3da0082d26b6 @ 4 : 27 fe f8 Mov , r2\n 0x3da0082d26b9 @ 7 : 62 3e 01 f9 02 CallRuntime [DeclareGlobals], r1-r2\n 0x3da0082d26be @ 12 : 13 01 00 LdaGlobal [1], [0]\n 0x3da0082d26c1 @ 15 : 26 f9 Star r1\n 0x3da0082d26c3 @ 17 : 5d f9 02 CallUndefinedReceiver0 r1, [2]\n 0x3da0082d26c6 @ 20 : 26 fa Star r0\n 0x3da0082d26c8 @ 22 : ab Return \nConstant pool (size = 2)\n0x3da0082d2681: [FixedArray] in OldSpace\n - map: 0x3da008042201 \n - length: 2\n 0: 0x3da0082d2631 \n 1: 0x3da0082d25b1 \nHandler Table (size = 0)\nSource Position Table (size = 0)\n[generated bytecode for function: sayHi (0x3da0082d2641 )]\nParameter count 1\nRegister count 3\nFrame size 24\n 0x3da0082d280a @ 0 : 13 00 00 LdaGlobal [0], [0]\n 0x3da0082d280d @ 3 : 26 f8 Star r2\n 0x3da0082d280f @ 5 : 28 f8 01 02 LdaNamedProperty r2, [1], [2]\n 0x3da0082d2813 @ 9 : 26 f9 Star r1\n 0x3da0082d2815 @ 11 : 5a f9 f8 fa 04 CallProperty1 r1, r2, r0, [4]\n 0x3da0082d281a @ 16 : 12 02 LdaConstant [2]\n 0x3da0082d281c @ 18 : 26 fa Star r0\n 0x3da0082d281e @ 20 : 0d LdaUndefined \n 0x3da0082d281f @ 21 : ab Return \nConstant pool (size = 3)\n0x3da0082d27d5: [FixedArray] in OldSpace\n - map: 0x3da008042201 \n - length: 3\n 0: 0x3da00824a15d \n 1: 0x3da00824a1d1 \n 2: 0x3da0082d278d \nHandler Table (size = 0)\nSource Position Table (size = 0)\nundefined"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏我們主要看第二段字節碼"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"LdaGlobal [0], [0]"},{"type":"text","text":"代表從常量池下標爲0的位置讀取內容並存儲在累加器中,也就是console。而第二個[0]代表"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong"}],"text":"反饋向量槽(feedback vector slot)"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"中相應的位置,目的爲了幫助後面的優化編譯器提供優化信息。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Star r2"},{"type":"text","text":"代表將累加器中的內容存儲到r2寄存器中,此時r2中存放的就是console。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"LdaNamedProperty r2, [1], [2]"},{"type":"text","text":"代表獲取r2寄存器中的對象的在常量池中1號位的屬性。常量池1號位存放的是log,LdaNamedProperty r2, [1]也就是console.log,並存放在累加器中。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Star r1"},{"type":"text","text":"將累加器中的console.log存放在r1寄存器中。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"CallProperty1 r1, r2, r0, [4]"},{"type":"text","text":"表示調用r1寄存器中的console.log,以r2、r0寄存器中的內容爲參數。r2中是console,當我們發現r0直到第32行纔會存放常量hello world,所以調用console.log會打印出undefined。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"執行(Execution)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/92/924f8c01fa3254e222075be8ed2d223f.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript是一種動態語言,一行簡單的屬性訪問可以包含複雜的語義。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Object.foo可能是簡單的屬性訪問,也可能會調用Getter,甚至可能需要遍歷原型鏈查找。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種動態性需要消耗更多的時間查找屬性,會降低運行的速度。爲了提高性能,V8將第一次分析的結果緩存起來,當再次訪問屬性時直接從緩存中讀取。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8會調用GetProperty(object, \"foo\", feedback_cache),如果feedback中緩存了對於object.foo的操作則跳過任何查找步驟。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Object Shapes"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Object Shapes也被叫做Hidden Classes或Maps,代表着JavaScript對象的結構,屬性和元素如何被存儲。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript作爲一種動態語言在實例化對象之後可以輕易的添加或刪除對象中的屬性。"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"class Point {\n constructor(x, y) {\n this.x = x\n this.y = y\n }\n}\n\nconst point = new Point(1, 1)\npoint.version = '1.0.0'"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"像Java這樣的靜態語言,一個對象中的屬性結構可以在編譯前就確定下來,所以這些屬性的值可以存儲在一段連續的內存空間中,屬性間的偏移量可以通過屬性的類型計算出來。但由於JavaScript的動態性,屬性的查找會慢於那些靜態語言。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了解決這個問題,V8使用"},{"type":"text","marks":[{"type":"strong"}],"text":"Hidden Classes"},{"type":"text","text":"來描述對象的結構。"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"let point = { x:1, y:1, '2': '1', in: {} };\npoint.out = {};\npoint[1] = 1;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"利用d8調試工具運行上面的代碼可以得到下面的結果。"}]},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"DebugPrint: 0xe4a08148b69: [JS_OBJECT_TYPE]\n - map: 0x0e4a083074d5 [FastProperties]\n - prototype: 0x0e4a082c3c69 \n - elements: 0x0e4a08148bc5 [HOLEY_ELEMENTS]\n - properties: 0x0e4a08148cc9 \n - All own properties (excluding elements): {\n 0xe4a082d25c5: [String] in OldSpace: #x: 1 (const data field 0), location: in-object\n 0xe4a082d25d5: [String] in OldSpace: #y: 1 (const data field 1), location: in-object\n 0xe4a082d25f5: [String] in OldSpace: #in: 0x0e4a08148c1d (const data field 2), location: in-object\n 0xe4a082d2605: [String] in OldSpace: #out: 0x0e4a08148c6d (const data field 3), location: properties[0]\n }\n - elements: 0x0e4a08148bc5 {\n 0: 0x0e4a08042429 \n 1: 1\n 2: 0x0e4a08044939 \n 3-19: 0x0e4a08042429 \n }\n0xe4a083074d5: [Map]\n - type: JS_OBJECT_TYPE\n - instance size: 24\n - inobject properties: 3\n - elements kind: HOLEY_ELEMENTS\n - unused property fields: 2\n - enum length: invalid\n - stable_map\n - back pointer: 0x0e4a083074ad \n - prototype_validity cell: 0x0e4a082d277d \n - instance descriptors (own) #4: 0x0e4a08148c89 \n - prototype: 0x0e4a082c3c69 \n - constructor: 0x0e4a082c38a1 \n - dependent code: 0x0e4a080421b5 \n - construction counter: 0"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"map "},{"type":"text","text":"描述了point對象的類型的詳細信息"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"prototype "},{"type":"text","text":"描述了point對象的原型引用"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"elements "},{"type":"text","text":"用於存放元素類型的屬性"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"properties "},{"type":"text","text":"用於存放命名類型的屬性"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"in-object "},{"type":"text","text":"代表屬性的值直接存放在對象中"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在JavaScript對象類型中,屬性可以分成"},{"type":"text","marks":[{"type":"strong"}],"text":"命名屬性(Named properties)和元素(Elements)。"},{"type":"text","text":"命名屬性是那些屬性名是非數字的屬性,而元素屬性是那些類似數組下標的屬性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b5/b5251530f458f909f9338984faa9e210.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Hidden Class中命名屬性和元素分別存儲在不同的空間中,它們的存儲方式既可能是數組也可能是字典。在JavaScript中的每個對象都有一個Hidden Class用於描述對象的類型信息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"而有個這些對象的類型信息後,多個擁有相同屬性名稱和屬性數量的對象可以共享一個類型信息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在對象被初始化之後,對象中屬性的修改會形成新的類型信息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4c/4c5ef65684116513b5ebe57d7f306ddd.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當對象的屬性新增出現不同的順序時,v8創建新的分支。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/65/658cec5e92b474411d3c0ac047760a0a.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同時屬性的存儲位置也會發生改變。"}]},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"0xe4a082d25f5: [String] in OldSpace: #in: 0x0e4a08148c1d (const data field 2), location: in-object\n0xe4a082d2605: [String] in OldSpace: #out: 0x0e4a08148c6d (const data field 3), location: properties[0]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏我們重點分析point對象中的in和out屬性,我們發現在對象初始化定義時的in屬性被存儲在對象內部(in-object),而在對象初始化之後被添加的out屬性則被添加到properties中,而訪問in-object的屬性速度要快於properties中的屬性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"所以屬性的動態修改會產生新的Hidden Class及屬性存儲位置的變化。在實際開發中儘量不要在對象初始化好以後再動態的增加或刪除對象中的屬性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了類型信息,訪問JavaScript對象的屬性時就可以像靜態語言那樣通過座標偏移量來快速的定位。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Inline Cache"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8通過"},{"type":"text","marks":[{"type":"strong"}],"text":"內聯緩存(Inline Cache)"},{"type":"text","text":"策略優化訪問對象的性能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"IC爲函數創建名叫"},{"type":"text","marks":[{"type":"strong"}],"text":"反饋向量("},{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong"}],"text":"FeedBack Vector"},{"type":"text","marks":[{"type":"strong"}],"text":")"},{"type":"text","text":"的用於存放對象及對象屬性的信息。"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"function load(o) {\n return o.x\n}\n\nload({ x: 10 })"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時IC會在反饋向量中存儲 "},{"type":"codeinline","content":[{"type":"text","text":"{ x: 10 }"}]},{"type":"text","text":"對象的類型信息和x屬性的信息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/35/35c891a284d1b7b32508a7dc56cf26f4.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"反饋向量與數據庫表結構相似,0代表在表中的位置"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"LOAD_IC代表操作的類型"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"Monomorphic是當前這條反饋向量的狀態是"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"單態"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":",狀態是IC中非常重要的概念,除了單態還有"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"多態(Polymorphic)"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"和"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D5156","name":"user"}},{"type":"strong"}],"text":"復態(M"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"egamorphic"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D5156","name":"user"}},{"type":"strong"}],"text":")"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D5156","name":"user"}}],"text":"。在函數運行過程中隨着參數類型的變化,狀態可能會發生變化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D5156","name":"user"}}],"text":"Map中存放對象的類型信息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Bitfield中放置對象屬性的信息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了反饋向量,load函數被反覆調用時如果參數類型沒有發生變化,就可以通過bitfield中存儲的屬性信息快速的找到屬性中的值,避免反覆的動態查詢。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當參數的類型發生變化時:"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"load({ x: 100 })\nload({ y: 1, z: 2 })"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要把兩種類型都緩存起來。此時,反饋向量中的狀態就會從"},{"type":"text","marks":[{"type":"strong"}],"text":"單態"},{"type":"text","text":"變成"},{"type":"text","marks":[{"type":"strong"}],"text":"多態,"},{"type":"text","text":"反饋向量的結構也會發生變化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1c/1cd9e8b08fbf8612f26188df973d65e5.png","alt":null,"title":"","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"反饋向量從原本存儲一個類型信息變化成數組結構存儲多個類型信息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來我們通過d8工具和Indicium工具分析IC的變化過程。"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"class Test {\n constructor(a, b, c, d, e) {\n if (a) {\n this.a = a;\n }\n\n if (b) {\n this.b = b;\n }\n\n if (c) {\n this.c = c;\n }\n\n if (d) {\n this.d = d;\n }\n\n if (e) {\n this.e = e;\n }\n }\n}\n\nconst util = {\n merge(test) {\n return test.a + test.b + test.c + test.d + test.e;\n }\n};\n\nlet rst;\n\nconsole.time('Test');\nconst test1 = new Test(1);\nfor (let i = 0; i < 10e6; i++) {\n rst = util.merge(test1);\n}\n\nconst test2 = new Test(null, 1);\nfor (let i = 0; i < 10e6; i++) {\n rst = util.merge(test2);\n}\n\nconst test3 = new Test(null, null, 1);\nfor (let i = 0; i < 10e6; i++) {\n rst = util.merge(test3);\n}\n\nconst test4 = new Test(null, null, null, 1);\nfor (let i = 0; i < 10e6; i++) {\n rst = util.merge(test4);\n}\n\nconst test5 = new Test(null, null, null, null, 1);\nfor (let i = 0; i < 10e6; i++) {\n rst = util.merge(test5);\n}\nconsole.timeEnd('Test');"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏我們在Test實例化的過程中動態的添加a, b, c, d, e屬性中的一個來創造出5中類型信息。並記錄5次循環的運行時間。"}]},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"d8 ic.js --trace-maps --trace_ic --log-source-code\nconsole.timeEnd: Test, 12668.593000"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/31/3156ab34f61eddeaf58446ce4c1ce7cd.png","alt":null,"title":"image.png","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過Indicium對日誌的分析,我們發現在調用merge函數時,反饋向量的狀態從0到"},{"type":"text","marks":[{"type":"strong"}],"text":"復態"},{"type":"text","text":"改變了3次。"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"merge函數第一次調用時原本爲空的反饋向量被放入了 { a: 1 } 的類型信息,此時反饋向量的狀態從0變成"},{"type":"text","marks":[{"type":"strong"}],"text":"單態"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二次調用時 { b: 1 } 被放入反饋向量中,參數類型發生了改變,反饋向量的狀態從"},{"type":"text","marks":[{"type":"strong"}],"text":"單態"},{"type":"text","text":"變成"},{"type":"text","marks":[{"type":"strong"}],"text":"多態"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三、四次是 { c: 1 } 和 { d: 1 } 的類型信息被放入反饋向量,狀態"},{"type":"text","marks":[{"type":"strong"}],"text":"多態"},{"type":"text","text":"變成"},{"type":"text","marks":[{"type":"strong"}],"text":"多態"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當第五種對象 { e: 1 } 被傳入函數merge時,反饋向量中存儲的類型信息已經達到5種,此時V8不再緩存類型信息來優化運行速度,狀態從 "},{"type":"text","marks":[{"type":"strong"}],"text":"多態"},{"type":"text","text":"變成"},{"type":"text","marks":[{"type":"strong"}],"text":"復態"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"當反饋向量中的對象只有1種類型信息時爲狀態爲單態,2-4種時爲多態,超過4種以後變成復態。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在我們將代碼中Test構造函數的中if去掉,讓由Test構造出的對象結構保持一致,重新運行調試命令可以得到下面的結果。"}]},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"console.timeEnd: Test, 560.487000"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/82/82e7ca527db7da311af1a8acdc993bc5.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們發現運行時間大幅降低的同時,反饋向量狀態由原本的4種變化變成1種。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"通過對IC的分析,在實際開發中,要儘量減少函數參數的類型種類的數量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"優化(Optimizing)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"優化過程是V8利用"},{"type":"text","marks":[{"type":"strong"}],"text":"TurboFan"},{"type":"text","text":"編譯器將字節碼編譯成機器碼的過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/69/694362e2487221a69a2ade93ef1a7aaf.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"TurboFan"},{"type":"text","text":"是一個\"Sea-of-nodes\"基於圖的編譯器,它將代碼中的"},{"type":"text","marks":[{"type":"strong"}],"text":"數據、流程控制和副作用依賴"},{"type":"text","text":"以節點的方式表達。通過不同階段的優化,將代碼編譯成機器碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2b/2b0f1150e44813a150c927d897c083b1.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在解釋運行的過程中,TurboFan會選擇個別的函數進行優化以保證優化是有意義的。相比以前的編譯器,"},{"type":"text","marks":[{"type":"strong"}],"text":"TurboFan"},{"type":"text","text":"以"},{"type":"text","marks":[{"type":"strong"}],"text":"Ignition"},{"type":"text","text":"生成的字節碼作爲數據源,而不再需要重新構建AST結構。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"TurboFan"},{"type":"text","text":"的優化過程可參見下圖"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/79/791b3b3527370d1fd892d38218f38ceb.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在"},{"type":"text","marks":[{"type":"strong"}],"text":"TurboFan"},{"type":"text","text":"將字節碼編譯成機器碼的過程中,還進行了簡化處理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/05/05df35d2e21b2a963b777c737c87f44d.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過"},{"type":"text","marks":[{"type":"strong"}],"text":"TurboFan"},{"type":"text","text":"對字節碼的進一步優化,讓JavaScript代碼中的函數可以擁有與編譯語言同樣的運行速度。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"之所以"},{"type":"text","marks":[{"type":"strong"}],"text":"TurboFan"},{"type":"text","text":"可以像編譯靜態語言那樣編譯JavaScript是需要建立在"},{"type":"text","marks":[{"type":"strong"}],"text":"Type Feedback"},{"type":"text","text":"上,也就是說在函數中的對象類型信息不變的情況下編譯纔有意義。所以當被優化的函數在運行過程中發現數據類型發生變化時就需要放棄優化的代碼,回到解釋執行的過程中執行並更新類型信息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"內存管理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8的垃圾回收器被稱作"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"Orinoco"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在一次垃圾回收過程中,"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"Orinoco"},{"type":"text","text":"通過對象的引用訪問程序中的所有對象,那些回收器無法訪問的對象所佔用的內存會被回收掉。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"window.obj = new Object()\nwindow.obj = new Object()"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8e/8ecc1d7957478e0cabeb0ff6c099aa95.png","alt":null,"title":"","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"由於obj指向了新的對象,所以圖中紅色虛線的部分就會被回收。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}},{"type":"strong"}],"text":"Orinoco"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"將存放對象的堆空間分成新、老兩個生代。新創建的對象被放在新生代中,存活時間較長的對象則被存放在老生代中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新生代的存儲空間最大可以達到32M,而老生代可以達到2G。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了對象的存儲空間,還需要爲用於執行的代碼分配獨立的內存空間。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript中的對象由"},{"type":"text","marks":[{"type":"strong"}],"text":"Orinoco"},{"type":"text","text":"回收,其他如DOM之類的對象則由Blink的垃圾回收器"},{"type":"text","marks":[{"type":"strong"}],"text":"Oilpan"},{"type":"text","text":"處理,所以V8通過"},{"type":"text","marks":[{"type":"strong"}],"text":"Orinoco"},{"type":"text","text":"解決了跨JS/C++的對象引用訪問的功能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8有着不同的垃圾回收策略:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Minor GC(Scavenge)用於新生代"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Major GC(Full Mark-Compact)用於整個堆空間"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Minor GC"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新生代中的內存空間被分成兩個同樣大的空間,一個用於放新創建的對象,另一個則是空的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c8/c8466ea9f03a74902e0b8e6b66ec5dab.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當Minor GC觸發時會將對象區中可以被訪問的對象複製到空閒區中,然後形成新的對象區和空閒區。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5e/5eb3591ac7c65430000ac5616ffe06e1.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果一個對象經歷了兩次Minor GC後依然存活則會被複制到老生代中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Major GC"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Major GC包括幾個階段"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"標記(Marking)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個階段V8會嘗試訪問所有的對象以標記那些"},{"type":"text","marks":[{"type":"strong"}],"text":"可訪問"},{"type":"text","text":"和"},{"type":"text","marks":[{"type":"strong"}],"text":"不可訪問"},{"type":"text","text":"的對象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"壓實(Compaction)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"壓實是碎片整理的過程,由於複製對象是一種高成本的操作,所以V8只會壓實那些高度碎片化的頁(Page) 。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/84/844508a47c1be703417bd1cde0368ad8.png","alt":null,"title":"","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在壓實過程中,會將原本已經碎片化的頁中的對象複製到新的頁中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"清除(Sweep)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"清除這個階段會將頁中無法訪問的對象內存清空,同時在Free List中更新這些空白的區域。這個過程與壓實過程基本上是同時開始的,對於那些不需要壓實的頁的不可訪問對象的內存空間會直接清除掉。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f9/f9bd42379623133fd77ea39427f248f5.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Major GC會運行Blink Oilpan GC過程,所以Oilpan中需要清除的對象也會在Major GC過程中被銷燬。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"State of GC in V8"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果GC工作完全在主線程上進行,則可能會對用戶體驗產生影響。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通常讓垃圾回收兼顧低延遲和高吞吐是困難的,爲了讓垃圾回收過程不會阻塞主線程上其他的工作,V8將整個垃圾回收過程以增量的方式拆分成多個階段,這些不同階段的回收過程可以穿插在主線程上其他的工作間隙處。與此同時,用多線程並行的方式讓垃圾回收工作在非主線程上進行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Minor GC"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在新生代GC過程中,V8將GC任務分配給幫助線程。每個線程會收到一些指針,並立即開始訪問這些對象並將他們複製到空閒區中。由於在多個線程執行任務的過程中可能會訪問同一個對象,所以這些任務必須以原子化並同步的方式運行。當一個線程移動了一個對象,則會將對象的指針更新成新的位置,當其他線程訪問這個對象時位置就已經發生了變化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ed/ed657eb15946a66327d3bdac5bb4426f.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Major GC"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當堆空間接近極限時,並行的標記工作在幫助線程中開始運行,與Minor GC不同的是這部分工作完全在幫助線程中,主線程只負責彙總。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fa/fab5a6b9a3ee08634d91a945f467a42a.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當幫助線程中標記工作完成後,主線程會暫停JavaScript的執行並快速的確認所有存活下來的對象已經被標記過,然後與一部分幫助線程同時完成壓實的工作,與此同時,另外一部分線程會開始清除那些需要銷燬的對象並在Free List中更新這部分空間。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過多年來的演進,現在的V8垃圾回收性能已經有了顯著的提升。在開發過程中,絕大多數情況我們並不需要考慮垃圾回收的問題,但通過對這部分內容的學習,可以讓我們更好的掌握垃圾回收的原理,而這些原理在不同的語言中是通用的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Blink作爲Chrome的內核運行在渲染進程中,負責幾乎所有發生在瀏覽器頁籤中的工作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Blink在實現Web規範中定義的標準的同時也實現了屬於Chrome特有的功能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8是Blink的JavaScript引擎,負責運行JavaScript腳本。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個JavaScript腳本運行要經歷加載、分析、解釋執行、優化和反優化幾個過程,其中"},{"type":"text","marks":[{"type":"strong"}],"text":"Ignition"},{"type":"text","text":"負責將抽象語法樹轉化成字節碼以節省存儲空間,"},{"type":"text","marks":[{"type":"strong"}],"text":"TurboFan"},{"type":"text","text":"則根據對象類型信息將部分函數的字節碼編譯成機器碼加速執行。編譯後的函數運行過程中發現對象類型變化了則回退到字節碼解釋階段執行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8將內存分成新生代、老生代和代碼空間分別存儲新創建的對象、長期存活的對象和可執行代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新生代中的垃圾回收使用Scavenge策略,快速的在兩個相同大小的空間交換的過程中將不再存活的對象釋放掉。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而另一種Full Mark-Compact策略則負責整個堆的垃圾回收,經過在不同的線程中完成標記-壓實-清除工作保證垃圾回收過程在低延遲的同時可以實現高吞吐量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到此,這篇文章終於要結束了,希望這篇文章能幫助你搭建起Blink和V8的概要視圖,爲你的前端知識體系點亮一顆新的技能樹。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在最後的相關資料中羅列了我在學習V8過程中參考的文檔和視頻,這裏一併奉上。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後的最後,謝謝你的閱讀。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"相關資料"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://docs.google.com/presentation/d/1sJj-JnSvM71zq-N_CT8qqsLngdW8fgOMiJdd2ylLojs/edit#slide=id.g26e94b78b8_0_9","title":null},"content":[{"type":"text","text":"Life of a script"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://v8.dev/blog/trash-talk","title":null},"content":[{"type":"text","text":"V8 Trash talk"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.youtube.com/watch?v=Scxz6jVS4Ls&feature=emb_logo","title":null},"content":[{"type":"text","text":"Orinoco: The new V8 Garbage Collector Peter Marshall"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.chromium.org/blink/blink-gc","title":null},"content":[{"type":"text","text":"Chromium blink-gc"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://darksi.de/d.sea-of-nodes/","title":null},"content":[{"type":"text","text":"Sea of Nodes"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://benediktmeurer.de/2017/03/01/v8-behind-the-scenes-february-edition","title":null},"content":[{"type":"text","text":"V8: Behind the Scenes (February Edition feat. A tale of TurboFan)"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.youtube.com/watch?v=u7zRSm8jzvA","title":null},"content":[{"type":"text","text":"V8 and How It Listens to You"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://v8.dev/blog/system-analyzer","title":null},"content":[{"type":"text","text":"Indicium: V8 runtime tracer tool"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://v8.dev/docs/d8","title":null},"content":[{"type":"text","text":"d8"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://erdem.pl/2019/08/v-8-function-optimization","title":null},"content":[{"type":"text","text":"V8 function optimization"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://v8.dev/blog/fast-properties","title":null},"content":[{"type":"text","text":"Fast properties in V8"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://medium.com/dailyjs/understanding-v8s-bytecode-317d46c94775","title":null},"content":[{"type":"text","text":"Understanding V8’s Bytecode"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/lazyparser/v8-internals","title":null},"content":[{"type":"text","text":"V8 Internals: Porting to RISC-V Build & Run, Igni-on Bytecodes"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.youtube.com/watch?v=J9HAvlW7BqA","title":null},"content":[{"type":"text","text":"Understanding Why The New V8 Is So Fast, One Demo At A Time"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.youtube.com/watch?v=r5OWCtuKiAk&feature=emb_logo","title":null},"content":[{"type":"text","text":"BlinkOn 6 Day 1 Talk 2: Ignition - an interpreter for V8"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://v8.dev/blog/ignition-interpreter","title":null},"content":[{"type":"text","text":"Firing up the Ignition interpreter"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://docs.google.com/presentation/d/1OqjVqRhtwlKeKfvMdX6HaCIu9wpZsrzqpIVIwQSuiXQ/edit#slide=id.g1357e6d1a4_0_58","title":null},"content":[{"type":"text","text":"Ignition: An Interpreter for V8 [BlinkOn]"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://docs.google.com/document/d/1aitSOucL0VHZa9Z2vbRJSyAIsAz24kX8LFByQ5xQnUg/edit#","title":null},"content":[{"type":"text","text":"How Blink works"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://chromium.googlesource.com/chromium/src/+/master/mojo/README.md","title":null},"content":[{"type":"text","text":"Mojo"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章