當前位置:
首頁 > 科技 > 認識 V8 引擎

認識 V8 引擎

前言

大周末推薦這麼一篇可能會讓你看起來很懵的文章,更多讓我想起在騰訊TFC大會上迷度分享v8優化的時候一片藍屏的內容。今日早讀文章由騰訊前端工程師@默語授權分享。

正文從這開始~

JavaScript絕對是最火的編程語言之一,一直具有很大的用戶群,隨著在服務端的使用(NodeJs),更是爆發了極強的生命力。編程語言分為編譯型語言和解釋型語言兩類,編譯型語言在執行之前要先進行完全編譯,而解釋型語言一邊編譯一邊執行,很明顯解釋型語言的執行速度是慢於編譯型語言的,而JavaScript就是一種解釋型腳本語言,支持動態類型、弱類型、基於原型的語言,內置支持類型。鑒於JavaScript都是在前端執行,而且需要及時響應用戶,這就要求JavaScript可以快速的解析及執行。

隨著Web相關技術的發展,JavaScript所要承擔的工作也越來越多,早就超越了「表單驗證」的範疇,這就更需要快速的解析和執行JavaScript腳本。V8引擎就是為解決這一問題而生,在node中也是採用該引擎來解析JavaScript。

V8是如何使得JavaScript性能有大幅提升的呢?通過對一些書籍和文章的學習,梳理了V8的相關內容,本文將帶你認識 V8。

1.渲染引擎及網頁渲染

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

1.1.渲染引擎

渲染引擎:能夠將HTML/CSS/JavaScript文本及相應的資源文件轉換成圖像結果。渲染引擎的主要作用是將資源文件轉化為用戶可見的結果。在瀏覽器的發展過程中,不同的廠商開發了不同的渲染引擎,如Tridend(IE)、Gecko(FF)、WebKit(Safari,Chrome,Andriod瀏覽器)等。WebKit是由蘋果2005年發起的一個開源項目,引起了眾多公司的重視,幾年間被很多公司所採用,在移動端更佔據了壟斷地位。更有甚者,開發出了基於WebKit的支持HTML5的web操作系統(如:Chrome OS、Web OS)。

下面是WebKit的大致結構:

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

操作系統:是管理和控制計算機硬體與軟體資源的計算機程序,是直接運行在「裸機」上的最基本的系統軟體,任何其他軟體都必須在操作系統的支持下才能運行。WebKit也是在操作系統上工作的。

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

WebCore 是各個瀏覽器使用的共享部分,包括HTML解析器、CSS解析器、DOM和SVG等。JavaScriptCore是WebKit的默認引擎,在谷歌系列產品中被替換為V8引擎。WebKit Ports是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引擎沒有中間位元組碼)。一段代碼的抽象語法樹示例如下:

functiondemo(name){

console.log(name);

}

抽象語法樹如下:

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

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

2.V8引擎

V8引擎是一個JavaScript引擎實現,最初由一些語言方面專家設計,後被谷歌收購,隨後谷歌對其進行了開源。V8使用C++開發,,在運行JavaScript之前,相比其它的JavaScript的引擎轉換成位元組碼或解釋執行,V8將其編譯成原生機器碼(IA-32, x86-64, ARM, or MIPS CPUs),並且使用了如內聯緩存(inline caching)等方法來提高性能。有了這些功能,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=;

functiontest(x,y){

counter++;

if(counter

// do something

return jeri ;

}

varunknown=newDate();

console.log(unknown);

}

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

在最近發布的 V8 5.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.4 GB,32位系統下約為0.7 GB),其深層原因是 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這樣的編譯器優化,可能會存在性能問題。

JavaScriptCore引擎是WebKit中默認的JavaScript引擎,也是蘋果開源的一個項目,應用較為廣泛。最初,性能不是很好,從2008年開始了一系列的優化,重新實現了編譯器和位元組碼解釋器,使得引擎的性能有較大的提升。隨後內嵌緩存、基於正則表達式的JIT、簡單的JIT及位元組碼解釋器等技術引入進來,JavaScriptCore引擎也在不斷的迭代和發展。

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

JavaScriptCore 的大致流程為:源代碼-抽象語法樹-位元組碼-JIT-本地代碼。JavaScriptCore與V8有一些不同之處,其中最大的不同就是新增了位元組碼的中間表示,並加入了多層JIT編譯器(如:簡單JIT編譯器、DFG JIT編譯器、LLVM等)優化性能,不停的對本地代碼進行優化。(在 V8 的 5.9 版本中,新增了一個 Ignition 位元組碼解釋器,TurboFan 和 Ignition 結合起來共同完成JavaScript的編譯,此後 V8 將與 JavaScriptCore 有大致相同的流程,Node 8.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;

module mymodule{

interface[

InterfaceName=MyObject

]MyObj{

readonly attribute long myAttr;

DOMString myMethod(DOMString myArg);

};

}

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

4.2. Extension機制

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

Extension機制的大致思路就是,V8提供一個基類Extension和一個全局註冊函數,要想擴展JavaScript能力,需要經過以下步驟:

classMYExtension:publicv8::Extension{

public:

MYExtension():v8::Extension("v8/My","native function my();"){}

virtual v8::HandleGetNativeFunction(

v8::Handlename){

// 可以根據name來返回不同的函數

returnv8::FunctionTemplate::New(MYExtention::MY);

}

staticv8::HandleMY(constv8::Arguments&args){

// Do sth here

returnv8::Undefined();

}

};

MYExtension extension;

RegisterExtension(&extension);

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

2.創建一個該子類的對象,並通過註冊函數將該對象註冊到V8引擎,當JavaScript調用』my』函數時就可被調用到。

Extension機制是調用V8的介面注入新函數,動態擴展非常方便,但沒有綁定機制高效,適用於對性能要求不高的場景。

總結

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

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

類型。對於函數,JavaScript是一種動態類型語言,JavaScriptCore和V8都使用隱藏類和內嵌緩存來提高性能,為了保證緩存命中率,一個函數應該使用較少的數據類型;對於數組,應盡量存放相同類型的數據,這樣就可以通過偏移位置來訪問。

數據表示。簡單類型數據(如整型)直接保存在句柄中,可以減少定址時間和內存佔用,如果可以使用整數表示的,盡量不要用浮點類型。

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

優化回滾。在執行多次之後,不要出現修改對象類型的語句,盡量不要觸發優化回滾,否則會大幅度降低代碼的性能。

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

參考資料

《WebKit技術內幕》

《JavaScript高級程序設計》

《深入淺出Node.js》

為什麼V8引擎這麼快

V8 Ignition:JS 引擎與位元組碼的不解之緣 - CNode 技術社區、

關於本文

作者:@默語

點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 前端早讀課 的精彩文章:

我接觸過的前端數據結構與演算法
阿里巴巴大優酷事業部前端專家
團隊技術信息流建設
什麼樣的工程師才能算老司機
阿里雲IoT事業部招前端、Node.js工程師

TAG:前端早讀課 |

您可能感興趣

認識宮頸癌 認識HPV 從我做起
認識你的「認識」
帶你走近VR,認識與了解VR
k9帶你認識茶杯犬
迄今最早的國號錢幣,一起認識認識
認識自己,認識人性
你想認識的「PS」
從認識自我到認識世界
時尚達人小知識,認識全球70大帽子
SNH48中4大美女,我猜你只認識鞠婧禕,全認識的絕對是鐵粉!
認識自然,認識自己
認識自己,是認識世界的槓桿
認識世界&我對區塊鏈的淺認識
重新認識ERP
這10樣20世紀老物件,認識2樣正常認識6樣厲害,全認識我叫你師傅
認識到自己,才能認識世界
5個中國女團,我賭10包辣條你只認識S.H.E,全認識的真厲害!
老物件,老輩人認識,10張圖能認出5個都少見,全認識的基本沒有
重新認識下,我叫VIVE htc
認識情緒,就是認識你的人生