[TOC]
# 常見的 JS解析器
JavaScript 虛擬機是一種進程虛擬機,專門設計來解釋和執行的 JavaScript代碼。

# JavaScript vs C++
c++是編譯型語言,在程序執行之前必須進行專門的編譯過程,在生成本地代碼的過程中,變量的地址和類型已經確定,運行本地代碼時利用數組和位移就可以存取變量和方法的地址,不需要再進行額外的查找,幾個機器指令即可完成,節省了確定類型和地址的時間,執行效率高
JavaScript是解釋行語言,支持動態類型,弱類型,在程序運行的時候才進行編譯,而編譯前需要確定變量的類型,效率比較低,對不同系統平臺有較大的兼容性(跨平臺性好),需要快速地執行和解析JavaScript腳本來提高性能,V8因此而誕生
# V8引擎誕生
**v8是谷歌開源的一個基于C++語言開發的JavaScript引擎**,可以實現ECMA-262中規定的ECMAScript,其被用于谷歌瀏覽器chrome,安卓瀏覽器,node.js等大型項目當中,V8引擎可以獨立運行不依賴于其他環境,也可以嵌入任何的C++應用當中使用。
# V8工作流程
V8將JavaScript代碼進行編譯,生成抽象語法樹(AST),對作用域進行分析,分辨出局部變量或全局變量,再通過JIT技術將語法樹直接轉換成原生代碼,沒有經過字節碼的編譯,節省了編譯時間,得到了不是最優的代碼,其后通過數據分析器挑選使用頻率高的代碼進行優化,若優化后的效果不如之前的話就進行優化回滾。

# V8 vs JavaScriptCore
JavaScriptCore是WebKit中默認的JavaScript引擎,它是蘋果開源的一個項目,是蘋果Safari瀏覽器的JavaScript引擎,應用較為廣泛。
* JavaScriptCore的大致流程:JavaScript源代碼 -> 抽象語法樹(AST)-> 字節碼-> 本地代碼
* V8的大致流程:JavaScript源代碼 -> 抽象語法樹(AST)-> 本地代碼 (2017年4月發布5.9版本后新增了Ignition字節碼解釋器,與JScore流程大致相同)
V8引擎并不將抽象語法樹轉變成字節碼或者其它中間表示,沒有像 Java 一樣的虛擬機或者字節碼解釋器。
> 這樣做的原因?
> **主要是為了減少這抽象語法樹到字節碼的轉換時間,這一切都在網頁加載時候完成,雖然可以提高優化的可能,但是這些分析可能帶來巨大的時間浪費。**
# V8特性
## 兩種編譯器:full compiler 和 crankshaft(后來在5.9版本后被消除)
* full compiler: full compiler是不含優化的編譯器,目標是快速地生成原生代碼,以保持頁面始終快速運轉,所以full compiler省去了將語法樹(AST)轉換為字節碼的過程,直接生成原生代碼。
* crankshaft: 由于full compiler沒有對代碼進行優化,所以V8引入了crankshaft編譯器,通過數據分析器來挑選使用頻率高的函數來進行優化,生成高效的原生代碼,但是鑒于JavaScript是一門動態類型語言,很有可能在程序運行過程中進行類型改變,所以V8會將crankshaft編譯過的代碼進行優化回滾,直至回滾到當前最優狀態。
## 內聯緩存(Inline caches, ICs)
由于JavaScript是一門動態類型語言,在很多操作上會相當復雜,可能一個簡單的操作符都會引發起上百條指令。而V8中使用了內聯緩存的機制,大致就是一個包含了對某個操作的多種實現方案的函數,在程序運行的時候動態生成并且緩存起來,方便重用,當再次訪問的時候進行判斷是否可以直接使用緩存結果,這樣減少了很大的工作量。
## 隱藏類
JavaScript訪問對象屬性的時候是通過匹配字符串的形式來查找的,而V8借鑒了C++語言中類和偏移位置的思想,實現了隱藏類,將對象按照屬性是否相同劃分到不同的組當中,將這些組的屬性名和對應的偏移位置保存在一個隱藏類中,組內所有對象共享該信息。假如對象中新增了新的屬性,那么這個對象就會被劃分到一個新的隱藏類當中。
## 垃圾回收
JavaScript使用了垃圾回收的機制,也意味著程序中是不能對內存進行管理的,這樣的好處是無需程序員來額外操作內存問題,防止內存泄漏,但是壞處是無法對內存進行控制也無法對垃圾回收器進行反饋。
垃圾回收器意味著識別到需要回收的內存,將其重新分配或返還給操作系統。先看看V8的內存管理,V8將數據分為新生代,老生代和大對象區。
* 新生代:為新創建的對象分配內存空間,經常需要進行垃圾回收。為方便年輕分代中的內容回收,可再將年輕分代分為兩半,一半用來分配,另一半在回收時負責將之前還需要保留的對象復制過來。我們只需保有一個指向內存區的指針,不斷根據新對象的大小對其進行遞增即可。當該指針達到了新生代區的末尾,就會有一次清理,清理掉新生代區中不活躍的死對象。
* 老生代:字符串、封箱的數字以及未封箱的雙精度數字數組,在新生區存活一段時間后會被移動到這里。根據需要將年老的對象、指針、代碼等數據保存起來,較少地進行垃圾回收。
* 大對象區:為那些需要使用較多內存對象分配內存,當然同樣可能包含數據和代碼等分配的內存,一個頁面只分配一個對象。垃圾回收器從不移動大對象。
在新生代中垃圾回收主要采用Scavenge算法,新生代區被劃分為兩個等大的子區:出區、入區。絕大多數內存的分配都會在出區發生,當出區耗盡時,我們交換出區和入區(這樣所有的對象都歸屬在入區當中),然后將入區中活躍的對象復制至出區或老生代區當中。
在老生區中,V8在標記-清除或標記-緊縮(大周期)的過程中進行回收。大周期進行的并不頻繁。一次大周期通常是在移動足夠多的對象至老生區后才會發生。至于足夠多到底是多少,則根據老生區自身的大小和程序的動向來定。
# V8后續發展
在2017年4月V8發布的5.9 版本中,V8團隊收集了JavaScript的實測性能并仔細分析了Full-codegen的缺點和Crankshaft,新增了一個 Ignition字節碼解釋器,TurboFan和Ignition結合起來共同完成JavaScript的編譯。關于更多TurboFan and Ignition的資料,可以參考:[https://cnodejs.org/topic/59084a9cbbaf2f3f569be482](https://cnodejs.org/topic/59084a9cbbaf2f3f569be482)和[http://benediktmeurer.de/2016/11/25/v8-behind-the-scenes-november-edition/](http://benediktmeurer.de/2016/11/25/v8-behind-the-scenes-november-edition/)
# 參考
[JavaScript引擎](https://blog.csdn.net/liwenfei123/article/details/80677670)
[淺讀V8——強大的JavaScript引擎](https://www.jianshu.com/p/332c15fd7c7d)
- 修仙之路
- 基礎原理篇
- JS和Node.js事件環機制剖析
- 一圖理解原型鏈
- 手寫篇
- 基礎手寫
- 手寫實現 Promise A+ 類庫
- 手寫 CommonJS
- 手寫 Express 框架
- 手寫 React Router 4.0
- 手寫虛擬 DOM 和 DOM-Diff
- 手寫 Webpack 實現
- 手寫一個 MVVM 類庫
- 手寫一個 Vue-cli 腳手架
- 手寫 JWT 類庫
- 手寫 Mobx 類庫
- 手寫前端性能和錯誤監控框架
- 手寫 Vue 路由
- 手寫 Vuex 實現
- 手寫 redux 狀態容器
- 手寫 throttle 和 debounce
- Node 高級
- Mongodb
- 安全測試篇
- CSRF原理實現
- XSS原理實現
- 九種跨域方法全解析
- 編寫單元測試
- 爬蟲篇
- 使用puppeteer破解滑動驗證碼
- 工程篇
- 使用AST語法樹手工轉譯ES6代碼
- 編寫自己的webpack插件
- 實戰篇
- webpack4.0 實戰
- Canvas+Websocket 實現彈幕
- canvas 動效
- SVG 動效
- CSS3 實現 Apple Watch 中的呼吸燈效果
- CSS3 實現動態氣泡屏保效果
- 算法篇
- 基礎知識
- 服務器端
- 分布式架構中的冪等性
- TCP/UDP
- Docker
- V8
- 動畫篇
- 貝塞爾曲線
- requestAnimationFrame
- 框架篇
- 隨記