摘要:注意句柄棧并不是調用棧中的一部分,但句柄域卻在棧中。一個依賴于構造函數和析構函數來管理下層對象的生命周期。對象模板用來配置將這個函數作為構造函數而創建的對象。
如果你已經閱讀過了上手指南,那么你已經知道了如何作為一個多帶帶的虛擬機使用 V8 ,并且熟悉了一些 V8 中的關鍵概念,如句柄,域 和上下文。在本文檔中,還將繼續深入討論這些概念并且介紹其他一些在你的 C++ 應用中使用 V8 的關鍵點。
V8 的 API 提供了編譯和執行腳本,訪問 C++ 方法和數據結構,處理錯誤和啟用安全檢查的函數。你的應用可以像使用其他的 C++ 庫一樣使用 V8 。你的 C++ 應用可以通過引入頭文件 include/v8.h 來訪問 V8 API 。
當你想要優化你的應用時,V8 設計概要文檔可以提供很多有用的背景知識。
前言這篇文檔的受眾是那些想要在自己的 C++ 程序中使用 V8 JavaScript 引擎的人。它將能幫助你在 JavaScript 中使用你應用中的 C++ 對象和方法,亦能幫助你在 C++ 應用中使用 JavaScript 對象和方法。
句柄和垃圾回收一個句柄提供了對在堆中的一個 JavaScript 對象地址的引用。V8 的垃圾回收器會在該對象不能再次被訪問到時,將其回收。在垃圾回收的過程中,垃圾回收器可能會改變對象在堆中的位置。當垃圾回收器移動對象時,所有引用到該對象的句柄也會被一同更新。
當一個對象在 JavaScript 中已經不可被訪問并且沒有任何指向它的句柄時,它就會被垃圾回收。V8 的垃圾回收機制是 V8 性能表現的關鍵。更多信息可參閱 V8 設計概要文檔。
句柄分為許多種:
本地句柄會被分配在棧中,并且當對應的析構函數被調用時,它也會被刪除。這些本地句柄的生命周期取決于它對應的句柄域(handle scope),句柄域通常在一個函數調用的開始被創建。當句柄域被刪除時,垃圾回收器就可以清除之前分配在該句柄域中的所有句柄了,因為它們不能再被 JavaScript 或其他句柄所訪問到。這也正是在上手指南中使用的句柄。
本地句柄可通過類 Local
注意:句柄棧并不是 C++ 調用棧中的一部分,但句柄域卻在 C++ 棧中。故句柄域不可通過 new 關鍵字來分配。
持久句柄提供了對分配在堆中的 JavaScript 對象的引用。當你需要在超過一次函數中保持對一個對象的引用時,或者句柄的生命周期與 C++ 的塊級域不相符時,你應使用持久句柄。例如,在 Google Chrome 中,持久句柄被用來引用 DOM 節點。一個持久句柄可以通過 PersistentBase::SetWeak ,變為弱(weak)持久句柄,當一個對象所剩的唯一引用是來自于一個弱持久句柄時,便會觸發垃圾回收。
一個 UniquePersistent
一個 Persistent
還有兩種很少會被使用到的句柄,在這里我們僅對它們做一個簡單的介紹:
永久(Eternal)句柄是一種為被認為永遠不會被刪除的 JavaScript 對象所設計的持久句柄。它的創建開銷更低,因為它不需要被垃圾回收。
Persistent
當然,每當你為了創建一個對象而創建一個本地句柄時,往往可能會導致創建了許多句柄。這就是句柄域所存在的意義。你可以將句柄域視作一個保存了許多句柄的容器。當句柄域的析構函數被調用時,所有它里面的句柄都會被從棧中移除。正如你所期望的,這些句柄做指向的對象隨后就可以被垃圾回收。
回到我們在上手指南中的例子,在下面的圖示中,你可以看到句柄棧和堆中的對象。值得注意的是,Context::New() 的返回值是一個本地句柄,然后我們基于它創建了一個新的持久句柄,用以闡述持久句柄的用途。
當 HandleScope::~HandleScope 析構函數被調用時,該句柄域便會被刪除。所有被句柄域中的句柄所引用的對象,都將可以在下次垃圾回收時被刪除,如果沒有其他對于它們的引用存在。垃圾回收器同樣還會刪除堆中的 source_obj 和 script_obj 對象,因為它們不被任何句柄所引用,并且也不可被 JavaScript 訪問。由于 context 對象是一個持久句柄,所以當句柄域退出時,它并不會被移除,唯一可以刪除它的辦法就是調用它的 Reset 方法。
注意:后文中的句柄如果不加注明,都指的是本地句柄。
在這個模型下,有一個非常常見的陷阱需要注意:你不可以直接地在一個聲明了句柄域的函數中返回一個本地句柄。如果你這么做了,那么你試圖返回的本地句柄,將會在函數返回之前,在句柄域的析構函數中被刪除。正確的做法是使用 EscapableHandleScope 來代替 HandleScope 創建句柄域,然后調用 Escape 方法,并且傳入你想要返回的句柄。例子:
// 這個函數會返回一個帶有 x,y 和 z 三個元素的新數組 LocalNewPointArray(int x, int y, int z) { v8::Isolate* isolate = v8::Isolate::GetCurrent(); // 我們將會創建一些臨時的句柄,所以我們先創建一個句柄域 EscapableHandleScope handle_scope(isolate); // 創建一個空數組 Local array = Array::New(isolate, 3); // 如果在創建數組時產生異常,則返回一個空數組 if (array.IsEmpty()) return Local (); // 填充數組 array->Set(0, Integer::New(isolate, x)); array->Set(1, Integer::New(isolate, y)); array->Set(2, Integer::New(isolate, z)); // 通過 Escape 返回該數組 return handle_scope.Escape(array); }
Escape 方法復制參數中的值至一個封閉的域中,然后刪除其他本地句柄,最后返回這個可以被安全返回的新句柄副本。
上下文在 V8 中,上下文是一個允許多個分別獨立的,不相關的 JavaScript 應用在一個多帶帶的 V8 實例中運行的執行環境。你必須為每一個你想要執行的 JavaScript 代碼指定一個上下文。
這樣做是必要的么?這么做的原因是,JavaScript 本身提供了一組內建的工具函數和對象,但它們又可以被 JavaScript 代碼所修改。例如,兩個完全沒有關聯的 JavaScript 函數同時修改了一個全局對象,那么可能就會造成不可預期的后果。
從 CPU 時間和內存的角度來看,創建一個擁有指定數量的內建對象的執行上下文似乎開銷很大。但是,V8 的緩存機制可以確保,雖然創建的第一個上下文開銷非常大,但后續的上下文創建的開銷都會小很多。這是因為第一次創建上下文時,需要創建內建對象和解析內建的 JavaScript 代碼,而后續的上下文創建則只需為它們的上下文創建內建對象即可。如果開啟了 V8 的快照特性(可通過選項 snapshot=yes 開啟,默認值即為開啟),第一次創建上下文的時間花銷也會被極大的優化,因為快照中包含了這些所需的內建 JavaScript 代碼已然被編譯后的版本。除了垃圾回收外,V8 的緩存也是 V8 性能表現的關鍵,更多詳情可參閱V8 設計概要文檔。
當你創建了一個上下文后,你可以隨意地進入和離開它,沒有次數的限制。當你已經在上下文 A 中時,你還可以再次進入另一個上下文 B ,這以為著當前的上下文環境變成了 B 。當你離開了 B 后,A 就再次成為了當前上下文。如圖示:
需要注意的是,JavaScript 內建工具函數和對象是相互獨立的。當你創建一個上下文時,你可以同時設置可選的安全標識(security token)。更多詳情請參閱下文的安全模型章節。
在 V8 中使用上下文的最初動機是,在一個瀏覽器中,每一個窗口和 iframe 都需要有各自獨立的 JavaScript 環境。
模板一個模板即為一個上下文中 JavaScript 函數和對象的藍圖。你可以在 JavaScript 對象內使用一個模板來包裹 C++ 函數和數據結構,致使它們可以被 JavaScript 腳本所操縱。例如,Google Chrome 使用模板來將 C++ DOM 節點包裹為 JavaScript 對象,然后在全局命名空間下注冊函數。你可以創建一個模板集合,然后在不同的上下文中使用它。模板的數量并沒有限制,但是在一個指定的上下文中,每一個模板都只允許有一個它的實例。
在 JavaScript 中,函數和對象間有強烈的二元性。在 Java 或 C++ 中,如果要創建一個新類型的對象,你需要首先定義一個新的類。而在 JavaScript 中,你需要定義一個新的函數,然后把這個函數視作一個構造函數。一個 JavaScript 對象的外形和功能都與它的構造函數關系密切。這些也都反應在了 V8 模板的工作方式中。模板分為兩種類型:
函數模板
一個函數模板就是一個獨立函數的藍圖。在一個你想要實例化 JavaScript 函數的上下文中,你可以通過調用模板的 GetFunction 方法來創建一個模板的 JavaScript 實例。當 JavaScript 函數實例被調用時,你還可以為模板關聯一個 C++ 回調函數一同被調用。
對象模板
每一個函數模板都有一個與之關聯的對象模板。對象模板用來配置將這個函數作為構造函數而創建的對象。你可以為對象模板關聯兩種類型的 C++ 回調:
訪問器回調會在指定對象原型被腳本訪問時被調用。
攔截器回調會在任何對象原型被腳本訪問時被調用。
訪問器和攔截器的詳情會在后文中繼續討論。
下面的例子中,我們將創建一個關聯全局對象的模板,然后設置一些內建的全局函數。
// 創建一個關聯全局對象的模板,然后設置一些內建的全局函數。 Localglobal = ObjectTemplate::New(isolate); global->Set(String::NewFromUtf8(isolate, "log"), FunctionTemplate::New(isolate, LogCallback)); Persistent context = Context::New(isolate, NULL, global);
該例子取自于 process.cc 中的 JsHttpProcessor::Initialiser。
訪問器訪問器為當一個 JavaScript 對象原型被腳本訪問時,執行的一個 C++ 回調函數,它計算并返回一個值。訪問器需要通過一個對象模板來配置,通過它的 SetAccessor 方法。這個方法的第一個參數為關聯的屬性,最后一個參數為當腳本試圖讀寫這個屬性時執行的回調。
訪問的復雜度取決于你想要其控制的數據類型:
訪問靜態全局變量
訪問動態變量
訪問靜態全局變量假設有兩個名為 x 和 y 的 C++ 整形變量,它們需要成為一個上下文的 JavaScript 中的全局變量。為了達成這個目的,當腳本讀或寫這些變量時,你需要調用 C++ 訪問器函數。這些訪問器函數使用 Integer::New 來把 C++ 整形數轉換為 JavaScript 整形數,并且使用 Int32Value 來把 JavaScript 整形數轉換為 C++ 整形數。例子:
void XGetter(Localproperty, const PropertyCallbackInfo & info) { info.GetReturnValue().Set(x); } void XSetter(Local property, Local value, const PropertyCallbackInfo & info) { x = value->Int32Value(); } // YGetter/YSetter 十分類似,這里就省略了 Local global_templ = ObjectTemplate::New(isolate); global_templ->SetAccessor(String::NewFromUtf8(isolate, "x"), XGetter, XSetter); global_templ->SetAccessor(String::NewFromUtf8(isolate, "y"), YGetter, YSetter); Persistent context = Context::New(isolate, NULL, global_templ);
注意代碼中的對象模板和上下文幾乎在同時創建。模板可以提前創建好,然后在任意數量的上下文中使用它。
訪問動態變量在上面的例子中,變量是靜態和全局的。那么,如果數據是動態的,像瀏覽器中的 DOM 樹這樣呢?假設我們有一個 C++ 類 Point,它有兩個屬性 x 和 y:
class Point { public: Point(int x, int y) : x_(x), y_(y) { } int x_, y_; }
為了讓任意數量的 C++ point 實例可以通過 JavaScript 訪問,我們需要為每一個 C++ point 實例創建一個 JavaScript 對象。這可以通過外部(external)值和內部(internal)屬性共同辦到。
首先創建一個對象模板,用以包裹 point 實例:
Localpoint_templ = ObjectTemplate::New(isolate);
每一個 JavaScript 中的 point 對象都保持了對 C++ 對象的引用,因為它以內部屬性的方式被包裹。這些屬性不可通過 JavaScript 訪問,只能通過 C++ 代碼訪問到。一個對象可以有任意數量的內部屬性,這個數量需通過以下方法來設置:
point_templ->SetInternalFieldCount(1);
上面的例子中,內部屬性的數量被設置為了 1,表明這對象有一個內部屬性,索引值為 0。
向模板添加 x 和 y 訪問器:
point_templ.SetAccessor(String::NewFromUtf8(isolate, "x"), GetPointX, SetPointX); point_templ.SetAccessor(String::NewFromUtf8(isolate, "y"), GetPointY, SetPointY);
接下來,我們通過創建一個新的模板實例來包裹 C++ point 實例,然后將內部屬性 0 設置為 p 的外部包裹。
Point* p = ...; Local
一個外部對象僅被用來在內部屬性中存儲引用。JavaScript 對象不能直接地引用 C++ 對象,所以外部值就像從 JavaScript 到 C++ 的“一座橋梁”。所以外部值是句柄的相反面,因為句柄的作用是讓我們在 C++ 中可以獲取 JavaScript 對象的引用。
以下便是 x 的讀和寫訪問器的定義,y 的定義和 x 的十分類似,只需將 x 替換為 y 即可:
void GetPointX(Localproperty, const PropertyCallbackInfo & info) { Local
訪問器抽象了對于 C++ point 對象的引用和對其的讀寫操作。這樣這些訪問器就可以被用于任意數量的被包裹后的 point 對象中了。
攔截器你還可以在一個腳本訪問任意對象屬性時,設置一個回調函數。這些回調函數稱為攔截器。攔截器分為兩種類型:
具名屬性攔截器,它會在訪問名稱為字符串的屬性時被調用,如瀏覽器環境中的 document.theFormName.elementName 。
索引屬性攔截器,它會在訪問索引屬性時被調用,如瀏覽器環境中的 document.forms.elements[0] 。
V8 源碼中的 process.cc 文件中,包含了一個攔截器的使用實例。下面例子中的 SetNamedPropertyHandler 設置了 MapGet 和 MapSet 這兩個攔截器:
Localresult = ObjectTemplate::New(isolate); result->SetNamedPropertyHandler(MapGet, MapSet);
MapGet 攔截器源碼如下:
void JsHttpRequestProcessor::MapGet(Localname, const PropertyCallbackInfo & info) { // Fetch the map wrapped by this object. map *obj = UnwrapMap(info.Holder()); // Convert the JavaScript string to a std::string. string key = ObjectToString(name); // Look up the value if it exists using the standard STL idiom. map ::iterator iter = obj->find(key); // If the key is not present return an empty handle as signal. if (iter == obj->end()) return; // Otherwise fetch the value and wrap it in a JavaScript string. const string &value = (*iter).second; info.GetReturnValue().Set(String::NewFromUtf8(value.c_str(), String::kNormalString, value.length())); }
和訪問器一樣,特定的回調函數會在一個屬性被訪問后觸發。它和訪問器的區別就是,訪問器會回調僅會在一個特定的屬性被訪問時觸發,而攔截器回調則會在任意屬性被訪問時觸發。
安全模型“同源策略”(首次出現于網景瀏覽器 2.0 中),用于阻止從另一個“源”中加載腳本或文檔到本地“源”里。這個源的概念中包含了域名(www.example.com),協議(http 或 https)和端口(如 www.example.com:81 和 www.example.com 不同源)。以上部分全部一樣,才能被視為同源。如果沒了這層保護,許多網頁就可以會遭到其他惡意網頁的攻擊。
在 V8 中,“源”即為上下文。在一個上下文中訪問另一個上下文默認是不被允許的。如果一定訪問,那么必須使用安全標識(security tokens)或安全回調(security callbacks)。一個安全標識可以是任意類型的值,但通常是一個 symbol 或一個唯一字符串。當你設置一個上下文時,可以通過 SetSecurityToken 可選地設置一個安全標識。如果你沒有明確地指明一個安全標識,那么 V8 將會為該上下文自動生成一個。
當試圖去訪問一個全局變量時,V8 的安全系統首先會去檢查被訪問的全局變量的上下文的安全標識與訪問代碼的上下文的安全標識是否一致,若一致,則允許訪問。如果安全標識不一致,那么 V8 將會觸發一個回調函數來判斷這個訪問是否該被允許。你可以通過在對象模板的方法 SetAccessCheckCallbacks ,來設置這個安全回調。這個回調的參數為,將會被訪問的對象,將會被訪問的屬性名,和訪問的類型(如讀,寫或刪除)并且返回值即表示是否允許這次訪問。
在 Google Chrome 中,這套安全機制運用在以下幾處:window.focus(),window.blur(),window.close(),window.location,window.open(),history.forward(),history.back() 和 history.go() 。
異常當一個錯誤發生時,V8 將會拋出一個異常。例如,當一個腳本或函數試圖去讀取一個不存在的屬性時,或一個非函數對象被調用時。
如果一次操作失敗了,V8 將會返回空句柄。因為在進一步操作前,檢查返回值是否是空句柄就變得尤為重要。我們可以通過本地句柄類(Local)的成員函數 IsEmpty() 來進行檢查。
你也可以通過 TryCatch 類捕獲異常,例子:
TryCatch trycatch(isolate); Localv = script->Run(); if (v.IsEmpty()) { Local exception = trycatch.Exception(); String::Utf8Value exception_str(exception); printf("Exception: %s ", *exception_str); // ... }
如果返回值是一個空句柄,并且你沒有使用 TryCatch ,那么你的代碼必須要終止。如果你使用了 TryCatch ,那么你的代碼則可以繼續執行。
繼承JavaScript 是第一個不基于類的面向對象編程語言。它使用了基于原型的繼承。這對于一直使用傳統面向對象編程語言(如 C++ 和 Java)的程序員來說,可能會有些困惑。
傳統的面向對象編程語言(如 C++ 和 Java)通常基于兩個概念:類和繼承。JavaScript 是一個基于原型的編程語言,所以它和傳統的面向對象編程語言不同,它只有對象。JavaScript 并不原生支持基于類聲明的繼承。但是 JavaScript 的原型機制簡化了為實例添加自定義屬性和方法的過程。在 JavaScript 中,你可以為單個實例添加自定義的屬性。例子:
// 創建一個對象 bicycle function bicycle(){ } // 創建一個名為 roadbike 的實例 var roadbike = new bicycle() // 為 roadbike 定義一個自定義屬性 wheels roadbike.wheels = 2
自定義屬性僅僅存在于當前這個實例中。如果我們創建了另一個 bicycle 實例,如 mountainbike ,mountainbike.wheels 將會是 undefined 。
某些時候,這就是我們想要的。而又有些時候,我們想要為所有的實例都添加上這個屬性。因為畢竟所有的自行車都有輪子。這是我們就會使用到原型機制。我們只需為對象的 prototype 屬性上添加我們想要的自定義屬性即可:
// 創建一個對象 bicycle function bicycle(){ } // 將 wheels 屬性添加到對象的原型上 bicycle.prototype.wheels = 2
這樣,所有的 bicycle 實例都將會擁有 wheels 屬性。
在 V8 的模板中,做法也是一樣的。每一個 FunctionTemplate 類實例都有一個 PrototypeTemplate 方法來給出函數的原型。你可以在其上添加屬性,為這些屬性關聯 C++ 函數。都會影響到該模板關聯所有的實例中。例子:
Localbiketemplate = FunctionTemplate::New(isolate); biketemplate->PrototypeTemplate().Set( String::NewFromUtf8(isolate, "wheels"), FunctionTemplate::New(isolate, MyWheelsMethodCallback)->GetFunction(); )
上面的代碼將會使所有的 biketemplate 實例擁有一個 wheels 方法。當該方法被調用時,C++ 函數 MyWheelsMethodCallback 就會執行。
V8 的 FunctionTemplate 類提供了一個公開成員函數 Inherit() ,當你想要一個函數模板繼承于另一個函數模板時,你可以使用它,例子:
void Inherit(Local最后parent);
原文鏈接:https://developers.google.com/v8/embed
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/86366.html
摘要:文章的第二部分涵蓋了內存管理的概念,不久后將發布。的標準化工作是由國際組織負責的,相關規范被稱為或者。隨著分析器和編譯器不斷地更改字節碼,的執行性能逐漸提高。 原文地址:How Does JavaScript Really Work? (Part 1) 原文作者:Priyesh Patel 譯者:Chor showImg(https://segmentfault.com/img...
摘要:本章將會深入谷歌引擎的內部結構。一個引擎可以用標準解釋程序或者即時編譯器來實現,即時編譯器即以某種形式把解釋為字節碼。引擎的由來引擎是由谷歌開源并以語言編寫。注意到沒有使用中間字節碼來表示,這樣就不需要解釋器了。 原文請查閱這里,略有刪減。 本系列持續更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第二章。 本章將會深入谷歌 V8 引擎的內部結構。我們也會...
摘要:問題什么是調用棧并且它是的一部分么調用棧當然是的一部分。為什么理解是重要的因為你在每個進程中只能獲取一個調用棧。它是一個從事件隊列中跳去事件的循環并且將它們的回調壓入到調用棧中。當調用棧為空的時候,事件循環可以決定下一步執行哪一個。 你并不知道Node 原文:You don’t know Node 譯者:neal1991 welcome to star my articles-tra...
摘要:文件系統請求和相關請求都會放進這個線程池處理其他的請求,如網絡平臺特性相關的請求會分發給相應的系統處理單元參見設計概覽。 譯者按:在 Medium 上看到這篇文章,行文脈絡清晰,闡述簡明利落,果斷點下翻譯按鈕。第一小節背景鋪陳略啰嗦,可以略過。剛開始我給這部分留了個 blah blah blah 直接翻后面的,翻完之后回頭看,考慮完整性才把第一節給補上。接下來的內容干貨滿滿,相信對 N...
摘要:原文引言這篇文檔包含了如何避免使代碼性能遠低于預期的建議尤其是一些會導致牽涉到等無法優化相關函數的問題一些背景在中并沒有解釋器但卻有兩個不同的編譯器通用編譯器和優化編譯器這意味著你的代碼總是會被編譯為機器碼后直接運行這樣一定很快咯并不是 原文:http://dev.zm1v1.com/2015/08/19/javascript-optimization-killers/引言 這篇文檔包...
閱讀 3699·2021-11-11 16:55
閱讀 1646·2021-10-08 10:04
閱讀 3581·2021-09-27 13:36
閱讀 2761·2019-08-30 15:53
閱讀 1855·2019-08-30 11:17
閱讀 1259·2019-08-29 16:55
閱讀 2098·2019-08-29 13:57
閱讀 2513·2019-08-29 13:13