1. 程式人生 > 其它 >V8 引擎是什麼?全面瞭解JavaScript引擎執行機制!

V8 引擎是什麼?全面瞭解JavaScript引擎執行機制!

 

    由谷歌構建的V8引擎是用C++編寫的開源專案,用於谷歌Chrome內部。然而不像其他引擎,V8也被用於流行的Node.js執行時。

 

  V8最開始是為了提高執行在瀏覽器內部的javascript執行效能而設計的。為了提高速度,V8將Javascript程式碼轉換成更有效率的機器碼,而不是使用一個直譯器。就像其他一些Javascript引擎比如SpiderMonkey或Rhino(Mozilla)所做的一樣,V8實現了一個即時(JIT)編譯器在程式碼執行時將Javascript程式碼編譯成機器碼。這裡最主要的區別是V8不生成位元組碼或其他中間程式碼。

 

      

  1.渲染引擎及網頁渲染

 

  瀏覽器自從上世紀80年代後期90年代初期誕生以來,已經得到了長足的發展,其功能也越來越豐富,包括網路、資源管理、網頁瀏覽、多頁面管理、外掛和擴充套件、書籤管理、歷史記錄管理、設定管理、下載管理、賬戶和同步、安全機制、隱私管理、外觀主題、開發者工具等。在這些功能中,為使用者提供網頁瀏覽服務無疑是最重要的功能,下面將對相關內容進行介紹。

 

  1.1.渲染引擎

 

  渲染引擎:能夠將HTML/CSS/Javascript文字及相應的資原始檔轉換成影象結果。渲染引擎的主要作用是將資原始檔轉化為使用者可見的結果。在瀏覽器的發展過程中,不同的廠商開發了不同的渲染引擎,如Tridend(IE)、Gecko(FF)、WebKit(Safari,Chrome,Andriod瀏覽器)等。WebKit是由蘋果2005年發起的一個開源專案,引起了眾多公司的重視,幾年間被很多公司所採用,在移動端更佔據了壟斷地位。更有甚者,開發出了基於WebKit的支援HTML5的web作業系統(如:ChromeOS、WebOS)。

 

  下面是WebKit的大致結構: 

 

 

  上圖中實線框內模組是所有移植的共有部分,虛線框內不同的廠商可以自己實現。下面進行介紹:

 

  作業系統:是管理和控制計算機硬體與軟體資源的計算機程式,是直接執行在“裸機”上的最基本的系統軟體,任何其他軟體都必須在作業系統的支援下才能執行。WebKit也是在作業系統上工作的。

 

  第三方庫,為了WebKit提供支援,如圖形庫、網路庫、視訊庫等。

 

  WebCore是各個瀏覽器使用的共享部分,包括HTML解析器、CSS解析器、DOM和SVG等。JavascriptCore是WebKit的預設引擎,在谷歌系列產品中被替換為V8引擎。WebKitPorts是WebKit中的非共享部分,由於平臺差異、第三方庫和需求的不同等原因,不同的移植導致了WebKit不同版本行為不一致,它是不同瀏覽器效能和功能差異的關鍵部分。

 

  WebKit嵌入式程式設計介面,供瀏覽器呼叫,與移植密切相關,不同的移植有不同的介面規範。

 

  測試用例,包括佈局測試用例和效能測試用例,用來驗證渲染結果的正確性。

 

  1.2.網頁渲染流程

 

  上面介紹了渲染引擎的各個模組,那麼一張網頁,要經歷怎樣的過程,才能抵達使用者面前?

 

 

  首先是網頁內容,輸入到HTML解析器,HTML解析器解析,然後構建DOM樹,在這期間如果遇到Javascript程式碼則交給Javascript引擎處理;如果來自CSS解析器的樣式資訊,構建一個內部繪圖模型。該模型由佈局模組計算模型內部各個元素的位置和大小資訊,最後由繪圖模組完成從該模型到影象的繪製。在網頁渲染的過程中,大致可分為下面3個階段。

 

  1.2.1.從輸入URL到生成DOM樹

 

  位址列輸入URL,WebKit呼叫資源載入器載入相應資源;

 

  載入器依賴網路模組建立連線,傳送請求並接收答覆;

 

  WebKit接收各種網頁或者資源資料,其中某些資源可能同步或非同步獲取;

 

  網頁交給HTML解析器轉變為詞語;

 

  直譯器根據詞語構建節點,形成DOM樹;

 

  如果節點是Javascript程式碼,呼叫Javascript引擎解釋並執行;

 

  Javascript程式碼可能會修改DOM樹結構;

 

  如果節點依賴其他資源,如圖片\css、視訊等,呼叫資源載入器載入它們,但這些是非同步載入的,不會阻礙當前DOM樹繼續建立;如果是Javascript資源URL(沒有標記非同步方式),則需要停止當前DOM樹建立,直到Javascript載入並被Javascript引擎執行後才繼續DOM樹的建立。

 

  1.2.2.從DOM樹到構建WebKit繪圖上下文

 

  CSS檔案被CSS直譯器解釋成內部表示;

 

  CSS直譯器完成工作後,在DOM樹上附加樣式資訊,生成RenderObject樹;

 

  RenderObject節點在建立的同時,WebKit會根據網頁層次結構構建RenderLayer樹,同時構建一個虛擬繪圖上下文。

 

  1.2.3.繪圖上下文到最終影象呈現

 

  繪圖上下文是一個與平臺無關的抽象類,它將每個繪圖操作橋接到不同的具體實現類,也就是繪圖具體實現類;

 

  繪圖實現類也可能有簡單的實現,也可能有複雜的實現,軟體渲染、硬體渲染、合成渲染等;

 

  繪圖實現類將2D圖形庫或者3D圖形庫繪製結果儲存,交給瀏覽器介面進行展示。

 

  上述是一個完整的渲染過程,現代網頁很多都是動態的,隨著網頁與使用者的互動,瀏覽器需要不斷的重複渲染過程。

 

  1.3.Javascript引擎

 

  Javascript本質上是一種解釋型語言,與編譯型語言不同的是它需要一遍執行一邊解析,而編譯型語言在執行時已經完成編譯,可直接執行,有更快的執行速度(如上圖所示)。Javascript程式碼是在瀏覽器端解析和執行的,如果需要時間太長,會影響使用者體驗。那麼提高Javascript的解析速度就是當務之急。Javascript引擎和渲染引擎的關係如下圖所示:

 

 

  Javascript語言是解釋型語言,為了提高效能,引入了Java虛擬機器和C++編譯器中的眾多技術。現在Javascript引擎的執行過程大致是:

 

  原始碼-→抽象語法樹-→位元組碼-→JIT-→原生代碼(V8引擎沒有中間位元組碼)。一段程式碼的抽象語法樹示例如下:

function demo(name) {
    console.log(name);
}

 

  抽象語法樹如下:

 

 

  V8更加直接的將抽象語法樹通過JIT技術轉換成原生代碼,放棄了在位元組碼階段可以進行的一些效能優化,但保證了執行速度。在V8生成原生代碼後,也會通過Profiler採集一些資訊,來優化原生代碼。雖然,少了生成位元組碼這一階段的效能優化,但極大減少了轉換時間。

 

  但是在2017年4月底,v8的5.9版本釋出了,新增了一個Ignition位元組碼直譯器,將預設啟動,從此之後將與JSCore有大致相同的流程。做出這一改變的原因為:(主要動機)減輕機器碼佔用的記憶體空間,即犧牲時間換空間;提高程式碼的啟動速度;對v8的程式碼進行重構,降低v8的程式碼複雜度。

 

  Javascript的效能和C相比還有不小的距離,可預見的未來估計也只能接近它,而不是與它相比,這從語言型別上已經決定。下面將對V8引擎進行更為細緻的介紹。

 

  2.V8引擎

 

  V8引擎是一個Javascript引擎實現,最初由一些語言方面專家設計,後被谷歌收購,隨後谷歌對其進行了開源。V8使用C++開發,,在執行Javascript之前,相比其它的Javascript的引擎轉換成位元組碼或解釋執行,V8將其編譯成原生機器碼(IA-32,x86-64,ARM,orMIPSCPUs),並且使用瞭如內聯快取(inlinecaching)等方法來提高效能。有了這些功能,Javascript程式在V8引擎下的執行速度媲美二進位制程式。V8支援眾多作業系統,如windows、linux、android等,也支援其他硬體架構,如IA32,X64,ARM等,具有很好的可移植和跨平臺特性。

 

  V8專案程式碼結構如下:

 

  2.1.資料表示

 

  Javascript是一種動態型別語言,在編譯時並不能準確知道變數的型別,只可以在執行時確定,這就不像c++或者java等靜態型別語言,在編譯時候就可以確切知道變數的型別。然而,在執行時計算和決定型別,會嚴重影響語言效能,這也就是Javascript執行效率比C++或者JAVA低很多的原因之一。

 

  在C++中,原始碼需要經過編譯才能執行,在生成原生代碼的過程中,變數的地址和型別已經確定,執行原生代碼時利用陣列和位移就可以存取變數和方法的地址,不需要再進行額外的查詢,幾個機器指令即可完成,節省了確定型別和地址的時間。由於Javascript是無型別語言,那就不能像c++那樣在執行時已經知道變數的型別和地址,需要臨時確定。Javascript和C++有以下幾個區別:

 

  編譯確定位置,C++編譯階段確定位置偏移資訊,在執行時直接存取,Javascript在執行階段確定,而且執行期間可以修改物件屬性;

 

  偏移資訊共享,C++有型別定義,執行時不能動態改變,可共享偏移資訊,Javascript每個物件都是自描述,屬性和位置偏移資訊都包含在自身的結構中;

 

  偏移資訊查詢,C++查詢偏移地址很簡單,在編譯程式碼階段,對使用的某型別成員變數直接設定偏移位置,Javascript中使用一個物件,需要通過屬性名匹配才能找到相應的值,需要更多的操作。

 

  在程式碼執行過程中,變數的存取是非常普遍和頻繁的,通過偏移量來存取,使用少數兩個彙編指令就能完成,如果通過屬性名匹配則需要更多的彙編指令,也需要更多的記憶體空間。示例如下:

 

 

  在Javascript中,除boolean,number,string,null,undefined這個五個簡單變數外,其他的資料都是物件,V8使用一種特殊的方式來表示它們,進而優化Javascript的內部表示問題。

 

  在V8中,資料的內部表示由資料的實際內容和資料的控制代碼構成。資料的實際內容是變長的,型別也是不同的;控制代碼固定大小,包含指向資料的指標。這種設計可以方便V8進行垃圾回收和移動資料內容,如果直接使用指標的話就會出問題或者需要更大的開銷,使用控制代碼的話,只需修改控制代碼中的指標即可,使用者使用的還是控制代碼,指標改動是對使用者透明的。

 

  除少數資料(如整型資料)由handle本身儲存外,其他內容限於控制代碼大小和變長等原因,都儲存在堆中。整數直接從value中取值,然後使用一個指標指向它,可以減少記憶體的佔用並提高訪問速度。一個控制代碼物件的大小是4位元組(32位裝置)或者8位元組(64位裝置),而在JavascriptCore中,使用的8個位元組表示控制代碼。在堆中存放的物件都是4位元組對齊的,所以它們指標的後兩位是不需要的,V8用這兩位表示資料的型別,00為整數,01為其他。

 

  Javascript物件在V8中的實現包含三個部分:隱藏類指標,這是v8為Javascript物件建立的隱藏類;屬性值表指標,指向該物件包含的屬性值;元素表指標,指向該物件包含的屬性。

 

  2.2.工作過程

 

  前面有過介紹,V8引擎在執行Javascript的過程中,主要有兩個階段:編譯和執行,與C++的執行前完全編譯不同的是,Javascript需要在使用者使用時完成編譯和執行。在V8中,Javascript相關程式碼並非一下完成編譯的,而是在某些程式碼需要執行時,才會進行編譯,這就提高了響應時間,減少了時間開銷。在V8引擎中,原始碼先被解析器轉變為抽象語法樹(AST),然後使用JIT編譯器的全程式碼生成器從AST直接生成本地可執行程式碼。這個過程不同於JAVA先生成位元組碼或中間表示,減少了AST到位元組碼的轉換時間,提高了程式碼的執行速度。但由於缺少了轉換為位元組碼這一中間過程,也就減少了優化程式碼的機會。

 

  V8引擎編譯原生代碼時使用的主要類如下所示:

 

  script:表示Javascript程式碼,即包含原始碼,又包含編譯之後生成的原生代碼,即是編譯入口,又是執行入口;

 

  Compiler:編譯器類,輔組script類來編譯生成程式碼,呼叫直譯器(Parser)來生成AST和全程式碼生成器,將AST轉變為原生代碼;

 

  AstNode:抽象語法樹節點類,是其他所有節點的基類,包含非常多的子類,後面會針對不同的子類生成不同的原生代碼;

 

  AstVisitor:抽象語法樹的訪問者類,主要用來遍歷異構的抽象語法樹;

 

  FullCodeGenerator:AstVisitor類的子類,通過遍歷AST來為Javascript生成本地可執行程式碼。

 

 

 

  Javascript程式碼編譯的過程大致為:script類呼叫Compiler類的Compile函式為其生成原生代碼。Compile函式先使用Parser類生成AST,再使用FullCodeGenerator類來生成原生代碼。原生代碼與具體的硬體平臺密切相關,FullCodeGenerator使用多個後端來生成與平臺相匹配的本地彙編程式碼。由於FullCodeGenerator通過遍歷AST來為每個節點生成相應的彙編程式碼,缺失了全域性檢視,節點之間的優化也就無從談起。

 

  在執行編譯之前,V8會構建眾多全域性物件並載入一些內建的庫(如math庫),來構建一個執行環境。而且在Javascript原始碼中,並非所有的函式都被編譯生成原生代碼,而是延遲編譯,在呼叫時才會編譯。

 

  由於V8缺少了生成中間程式碼這一環節,缺少了必要的優化,為了提升效能,V8會在生成原生代碼後,使用資料分析器(profiler)採集一些資訊,然後根據這些資料將原生代碼進行優化,生成更高效的原生代碼,這是一個逐步改進的過程。同時,當發現優化後代碼的效能還不如未優化的程式碼,V8將退回原來的程式碼,也就是優化回滾。下面介紹一下執行階段,該階段使用的主要類如下所示:

 

  script:表示Javascript程式碼,即包含原始碼,又包含編譯之後生成的原生代碼,即是編譯入口,又是執行入口;

 

  Execution:執行程式碼的輔組類,包含一些重要函式,如Call函式,它輔組進入和執行script程式碼;

 

  JSFunction:需要執行的Javascript函式表示類;

 

  Runtime:執行這些原生代碼的輔組類,主要提供執行時所需的輔組函式,如:屬性訪問、型別轉換、編譯、算術、位操作、比較、正則表示式等;

 

  Heap:執行原生代碼需要使用的記憶體堆類;

 

  MarkCompactCollector:垃圾回收機制的主要實現類,用來標記、清除和整理等基本的垃圾回收過程;

 

  SweeperThread:負責垃圾回收的執行緒。

 

 

  先根據需要編譯和生成這些原生代碼,也就是使用編譯階段那些類和操作。在V8中,函式是一個基本單位,當某個Javascript函式被呼叫時,V8會查詢該函式是否已經生成原生代碼,如果已經生成,則直接呼叫該函式。否則,V8引擎會生成屬於該函式的原生代碼。這就節約了時間,減少了處理那些使用不到的程式碼的時間。其次,執行編譯後的程式碼為Javascript構建JS物件,這需要Runtime類來輔組建立物件,並需要從Heap類分配記憶體。再次,藉助Runtime類中的輔組函式來完成一些功能,如屬性訪問等。最後,將不用的空間進行標記清除和垃圾回收。

 

  2.3.優化回滾

 

  因為V8是基於AST直接生成原生代碼,沒有經過中間表示層的優化,所以原生代碼尚未經過很好的優化。於是,在2010年,V8引入了新的編譯器-Crankshaft,它主要針對熱點函式進行優化,基於Javascript原始碼開始分析而非原生代碼,同時構建Hydroger圖並基於此來進行優化分析。

 

  Crankshaft編譯器為了效能考慮,通常會做出比較樂觀和大膽的預測—程式碼穩定且變數型別不變,所以可以生成高效的原生代碼。但是,鑑於Javascript的一個弱型別的語言,變數型別也可能在執行的過程中進行改變,鑑於這種情況,V8會將該編譯器做的想當然的優化進行回滾,稱為優化回滾。

 

  示例如下:

  varcounter=0;

  functiontest(x,y){

  counter++;

  if(counter<1000000){

  return'jeri';

  }

  varunknown=newDate();

  console.log(unknown);

  }

 

  該函式被呼叫多次之後,V8引擎可能會觸發Crankshaft編譯器對其進行優化,而優化程式碼認為示例程式碼的型別資訊都已經被確定。但,由於尚未真正執行到newDate()這個地方,並未獲取unknown這個變數的型別,V8只得將該部分程式碼進行回滾。優化回滾是一個很耗時的操作,在寫程式碼過程中,儘量不要觸發優化該操作。

 

  在最近釋出的V85.9版本中,新增了一個Ignition位元組碼直譯器,TurboFan和Ignition結合起來共同完成Javascript的編譯。這個版本中消除Cranshaft這個舊的編譯器,並讓新的Turbofan直接從位元組碼來優化程式碼,並當需要進行反優化的時候直接反優化到位元組碼,而不需要再考慮JS原始碼。

 

  2.4.隱藏類與內嵌快取

 

  2.4.1.隱藏類

 

  在執行C++程式碼時,僅憑几個指令即可根據偏移資訊獲取變數資訊,而Javascript裡需要通過字串匹配來查詢屬性值的,這就需要更多的操作才能訪問到變數資訊,而程式碼量變數存取是十分頻繁的,這也就制約了Javascript的效能。V8借用了類和偏移位置的思想,將本來通過屬性名匹配來訪問屬性值的方法進行了改進,使用類似C++編譯器的偏移位置機制來實現,這就是隱藏類。

 

  隱藏類將物件劃分成不同的組,對於組內物件擁有相同的屬性名和屬性值的情況,將這些組的屬性名和對應的偏移位置儲存在一個隱藏類中,組內所有物件共享該資訊。同時,也可以識別屬性不同的物件。示例如下:

 

 

  使用Point構造了兩個物件p和q,這兩個物件具有相同的屬性名,V8將它們歸為同一個組,也就是隱藏類,這些屬性在隱藏類中有相同的偏移值,p和q共享這一資訊,進行屬性訪問時,只需根據隱藏類的偏移值即可。由於Javascript是動態型別語言,在執行時可以更改變數的型別,如果上述程式碼執行之後,執行q.z=2,那麼p和q將不再被認為是一個組,q將是一個新的隱藏類。

 

  2.4.2.內嵌快取

 

  正常訪問物件屬性的過程是:首先獲取隱藏類的地址,然後根據屬性名查詢偏移值,然後計算該屬性的地址。雖然相比以往在整個執行環境中查詢減小了很大的工作量,但依然比較耗時。能不能將之前查詢的結果快取起來,供再次訪問呢?當然是可行的,這就是內嵌快取。

 

  內嵌快取的大致思路就是將初次查詢的隱藏類和偏移值儲存起來,當下次查詢的時候,先比較當前物件是否是之前的隱藏類,如果是的話,直接使用之前的快取結果,減少再次查詢表的時間。當然,如果一個物件有多個屬性,那麼快取失誤的概率就會提高,因為某個屬性的型別變化之後,物件的隱藏類也會變化,就與之前的快取不一致,需要重新使用以前的方式查詢雜湊表。

 

  2.5.記憶體管理

 

  Node中通過Javascript使用記憶體時就會發現只能使用部分記憶體(64位系統下約為1.4GB,32位系統下約為0.7GB),其深層原因是V8垃圾回收機制的限制所致(如果可使用記憶體太大,V8在進行垃圾回收時需耗費更多的資源和時間,嚴重影響JS的執行效率)。下面對記憶體管理進行介紹。

 

  記憶體的管理組要由分配和回收兩個部分構成。V8的記憶體劃分如下:

 

  Zone:管理小塊記憶體。其先自己申請一塊記憶體,然後管理和分配一些小記憶體,當一塊小記憶體被分配之後,不能被Zone回收,只能一次性回收Zone分配的所有小記憶體。當一個過程需要很多記憶體,Zone將需要分配大量的記憶體,卻又不能及時回收,會導致記憶體不足情況。

 

  堆:管理Javascript使用的資料、生成的程式碼、雜湊表等。為方便實現垃圾回收,堆被分為三個部分:

 

  年輕分代:為新建立的物件分配記憶體空間,經常需要進行垃圾回收。為方便年輕分代中的內容回收,可再將年輕分代分為兩半,一半用來分配,另一半在回收時負責將之前還需要保留的物件複製過來。

 

  年老分代:根據需要將年老的物件、指標、程式碼等資料儲存起來,較少地進行垃圾回收。

 

  大物件:為那些需要使用較多記憶體物件分配記憶體,當然同樣可能包含資料和程式碼等分配的記憶體,一個頁面只分配一個物件。

 

 

  垃圾回收

 

  V8使用了分代和大資料的記憶體分配,在回收記憶體時使用精簡整理的演算法標記未引用的物件,然後消除沒有標記的物件,最後整理和壓縮那些還未儲存的物件,即可完成垃圾回收。

 

  在V8中,使用較多的是年輕分代和年老分代。年輕分代中的物件垃圾回收主要通過Scavenge演算法進行垃圾回收。在Scavenge的具體實現中,主要採用了Cheney演算法:通過複製的方式實現的垃圾回收演算法。它將堆記憶體分為兩個semispace,一個處於使用中(From空間),另一個處於閒置狀態(To空間)。當分配物件時,先是在From空間中進行分配。當開始進行垃圾回收時,會檢查From空間中的存活物件,這些存活物件將被複制到To空間中,而非存活物件佔用的空間將會被釋放。完成複製後,From空間和To空間的角色發生對換。在垃圾回收的過程中,就是通過將存活物件在兩個semispace空間之間進行復制。年輕分代中的物件有機會晉升為年老分代,條件主要有兩個:一個是物件是否經歷過Scavenge回收,一個是To空間的記憶體佔用比超過限制。

 

  對於年老分代中的物件,由於存活物件佔較大比重,再採用上面的方式會有兩個問題:一個是存活物件較多,複製存活物件的效率將會很低;另一個問題依然是浪費一半空間的問題。為此,V8在年老分代中主要採用了Mark-Sweep(標記清除)標記清除和Mark-Compact(標記整理)相結合的方式進行垃圾回收。

 

  2.6.快照

 

  在V8引擎啟動時,需要構建Javascript執行環境,需要載入很多內建物件,同時也需要建立內建的函式,如Array,String,Math等。為了使V8更加整潔,載入物件和建立函式等任務都是使用Javascript檔案來實現的,V8引擎負責提供機制來支援,就是在編譯和執行Javascript前先載入這些檔案。

 

  V8引擎需要編譯和執行這些內建的Javascript程式碼,同時使用堆等來儲存執行過程中建立的物件、程式碼等,這些都需要時間。為此,V8引入了快照機制。將這些內建的物件和函式載入之後的記憶體儲存並序列化。序列化之後的結果很容易反序列化,經過快照機制的啟動時間可以縮減幾毫秒。快照機制也可以將一些開發者認為需要的Javascript檔案序列化,以減少處理時間。不過快照機制的載入的程式碼不能被CrankShaft這樣的編譯器優化,可能會存在效能問題。

 

  3.V8VSJavascriptCore

 

  JavascriptCore引擎是WebKit中預設的Javascript引擎,也是蘋果開源的一個專案,應用較為廣泛。最初,效能不是很好,從2008年開始了一系列的優化,重新實現了編譯器和位元組碼直譯器,使得引擎的效能有較大的提升。隨後內嵌快取、基於正則表示式的JIT、簡單的JIT及位元組碼直譯器等技術引入進來,JavascriptCore引擎也在不斷的迭代和發展。

 

  V8引擎自誕生之日起就以效能優化作為目標,引入了眾多新技術,極大了帶動了整個業界Javascript引擎效能的快速發展。總的來說,V8引擎較為激進,青睞可以提高效能的新技術,而JavascriptCore引擎較為穩健,漸進式的改變著自己的效能。總的來說Javascript引擎工作流程(包含v8和JavascriptCore)如下所示:

 

 

  JavascriptCore的大致流程為:原始碼-→抽象語法樹-→位元組碼-→JIT-→原生代碼。JavascriptCore與V8有一些不同之處,其中最大的不同就是新增了位元組碼的中間表示,並加入了多層JIT編譯器(如:簡單JIT編譯器、DFGJIT編譯器、LLVM等)優化效能,不停的對原生代碼進行優化。(在V8的5.9版本中,新增了一個Ignition位元組碼直譯器,TurboFan和Ignition結合起來共同完成Javascript的編譯,此後V8將與JavascriptCore有大致相同的流程,Node8.0中V8版本為5.8)

 

  還有就是在資料表示方面,V8在不同的機器上使用與機器位數相匹配的資料表示,而在JavascriptCore中控制代碼都是使用64位表示,其可以表示更大範圍的數字,所以即使在32位機器上,浮點型別同樣可以儲存在控制代碼中,不再需要訪問堆中的資料,當也會佔用更多的空間。

 

  4.功能擴充套件

 

  Javascript引擎的主要功能是解析和執行Javascript程式碼,往往不能滿足使用者多樣化的需要,那麼就可以增加擴充套件以提升它的能力。V8引擎有兩種擴充套件機制:繫結和擴充套件。

 

  4.1.繫結機制

 

  使用IDL檔案或介面檔案生成繫結檔案,將這些檔案同V8引擎一起編譯。WebKit中使用IDL來定義Javascript,但又與IDL有所不同,有一些改變。定義一個新的介面的步驟大致如下:

 

  1.定義新的介面檔案,可以在Javascript程式碼進行呼叫,如mymodule.MyObj.myAttr;

 

  

  modulemymodule{

  interface[

  InterfaceName=MyObject

  ]MyObj{

  readonlyattributelongmyAttr;

  DOMStringmyMethod(DOMStringmyArg);

  };

  }

 

  2.按照引擎定義的標準介面為基礎實現介面類,生成Javascript引擎所需的繫結檔案。WebKit提供了工具幫助生成所需的繫結類,根據引擎不同和引擎開發語言的不同而有所差異。V8引擎會為上述示例程式碼生成v8MyObj.h(MyObj類具體的實現程式碼)和V8MyObj.cpp(橋接程式碼,輔組註冊橋接的函式到V8引擎)兩個繫結檔案。

 

  Javascript引擎繫結機制需要將擴充套件程式碼和Javascript引擎一塊編譯和打包,不能根據需要在引擎啟動後再動態注入這些原生代碼。在實際WEB開發中,開發者都是基於現有瀏覽器的,根本不可能介入到Javascript引擎的編譯中,繫結機制有很大的侷限性,但其非常高效,適用於對效能要求較高的場景。

 

  4.2.Extension機制

 

  通過V8的基類Extension進行能力擴充套件,無需和V8引擎一起編譯,可以動態為引擎增加功能特性,具有很大的靈活性。

 

  Extension機制的大致思路就是,V8提供一個基類Extension和一個全域性註冊函式,要想擴充套件Javascript能力,需要經過以下步驟:

class MYExtension : public v8::Extension {
    public:
        MYExtension() : v8::Extension("v8/My", "native function my();") {}
        virtual v8::Handle GetNativeFunction (
        v8::Handle name) {
            // 可以根據name來返回不同的函式
            return v8::FunctionTemplate::New(MYExtention::MY);
        }
        static v8::Handle MY(const v8::Arguments& args) {
            // Do sth here
            return v8::Undefined();
        }
};
MYExtension extension;
RegisterExtension(&extension);

 

 

  1.基於Extension基類構建一個它的子類,並實現它的虛擬函式—GetNativeFunction,根據引數name來決定返回實函式;

 

  2.建立一個該子類的物件,並通過註冊函式將該物件註冊到V8引擎,當Javascript呼叫’my’函式時就可被呼叫到。

 

  Extension機制是呼叫V8的介面注入新函式,動態擴充套件非常方便,但沒有繫結機制高效,適用於對效能要求不高的場景。

 

  總結

 

  在過去幾年,Javascript在很多領域得到了廣泛的應用,然而限於Javascript語言本身的不足,執行效率不高。Google也推出了一些Javascript網路應用,如Gmail、GoogleMaps及GoogleDocsoffice等。這些應用的效能不僅受到伺服器、網路、渲染引擎以及其他諸多因素的影響,同時也受到Javascript本身執行速度的影響。然而既有的Javascript引擎無法滿足新的需求,而效能不佳一直是網路應用開發者最關心的。Google就開始了V8引擎的研究,將一系列新技術引入Javascript引擎中,大大提高了Javascript的執行效率。相信隨著V8引擎的不斷髮展,Javascript也會有更廣泛的應用場景,前端工程師也會有更好的未來!

 

  那麼結合上面對於V8引擎的介紹,我們在程式設計中應注意:

 

  型別。對於函式,Javascript是一種動態型別語言,JavascriptCore和V8都使用隱藏類和內嵌快取來提高效能,為了保證快取命中率,一個函式應該使用較少的資料型別;對於陣列,應儘量存放相同型別的資料,這樣就可以通過偏移位置來訪問。

 

  資料表示。簡單型別資料(如整型)直接儲存在控制代碼中,可以減少定址時間和記憶體佔用,如果可以使用整數表示的,儘量不要用浮點型別。

 

  記憶體。雖然Javascript語言會自己進行垃圾回收,但我們也應儘量做到及時回收不用的記憶體,對不再使用的物件設定為null或使用delete方法來刪除(使用delete方法刪除會觸發隱藏類新建,需要更多的額外操作)。

 

  優化回滾。在執行多次之後,不要出現修改物件型別的語句,儘量不要觸發優化回滾,否則會大幅度降低程式碼的效能。

 

  新機制。使用Javascript引擎或者渲染引擎提供的新機制和新介面提高效能。

 

文章轉自:V8 引擎是什麼?全面瞭解JavaScript引擎執行機制!_JavaScript-考高分網 (kaotop.com)