V8嵌入指南

中英文原文鏈接:https://github.com/Chunlin-Li/Chunlin-Li.github.io/blob/master/blogs/javascript/V8_Embedder's_Guide_CHS.md

簡介

如果你已經(jīng)閱讀了入門指南, 一定知道 V8 是一個獨立運行的虛擬機, 其中有些關(guān)鍵性概念比如: handles, scopes 和 contexts. 本文將深入討論這些概念, 并且引入一些其他對于將 c++ 程序嵌入到 V8 中非常重要的信息.V8 API 為編譯和執(zhí)行JS腳本, 訪問 C++ 方法和數(shù)據(jù)結(jié)構(gòu), 錯誤處理, 開啟安全檢查等提供了函數(shù)接口. 你的 C++ 應(yīng)用可以像使用其他 C++ 庫一樣使用 V8, 只需要在你的代碼中 include V8 頭文件(include/v8.h).如果你需要優(yōu)化你的V8應(yīng)用, "V8 設(shè)計元素"一文將會為你提供一些有用的背景知識.
  本文是為那些想要將 V8 JavaScript 引擎嵌入到 C++ 程序中的工程師而撰寫. 它將幫你搭建 C++ 程序的對象和方法與 JavaScript 對象和函數(shù)之間的橋梁.

1、Handles and Garbage Collection (Handle 與 GC 垃圾)

Handle 提供了一個JS 對象在堆內(nèi)存中的地址的引用. V8 垃圾回收器將回收一個已無法被訪問到的對象占用的堆內(nèi)存空間. 垃圾回收過程中, 回收器通常會將對象在堆內(nèi)存中進行移動. 當(dāng)回收器移動對象的同時, 也會將所有相應(yīng)的 Handle 更新為新的地址.當(dāng)一個對象在 JavaScript 中無法被訪問到, 并且也沒有任何 Handle 引用它, 則這個對象將被當(dāng)作 "垃圾" 對待. 回收器將不斷將所有判定為 "垃圾" 的對象從堆內(nèi)存中移除. V8 的垃圾回收機制是其性能的關(guān)鍵所在. 更多相關(guān)信息見 "V8 設(shè)計元素" 一文.
  Local Handles 保存在一個棧結(jié)構(gòu)中, 當(dāng)棧的析構(gòu)函數(shù)(destructor)被調(diào)用時將同時被銷毀. 這些 handle 的生命周期取決于 handle scope (當(dāng)一個函數(shù)被調(diào)用的時候, 對應(yīng)的 handle scope 將被創(chuàng)建). 當(dāng)一個 handle scope 被銷毀時, 如果在它當(dāng)中的 handle 所引用的對象已無法再被 JavaScript 訪問, 或者沒有其他的 handle 指向它, 那么這些對象都將在 scope 的銷毀過程中被垃圾回收器回收. 入門指南中的例子使用的就是這種 Handle.
  Local handle 對應(yīng)的類是 Local<SomeType>
  注意 : Handle 棧并不是 C++ 調(diào)用棧的一部分, 不過 handle scope 是被嵌入到C++棧中的. Handle scope只支持棧分配, 而不能使用 new 進行堆分配.
  Persistent handle 是一個堆內(nèi)存上分配的 JavaScript 對象的引用, 這點和 local handle 一樣. 但它有兩個自己的特點, 是對于它們所關(guān)聯(lián)的引用的生命周期管理方面. 當(dāng)你 希望 持有一個對象的引用, 并且超出該函數(shù)調(diào)用的時期或范圍時, 或者是該引用的生命周期與 C++ 的作用域不一致時, 就需要使用 persistent handle 了. 例如 Google Chrome 就是使用 persistent handle 引用 DOM 節(jié)點. Persistent handle 支持弱引用, 即 PersistentBase::SetWeak, 它可以在其引用的對象只剩下弱引用的時候, 由垃圾回收器出發(fā)一個回調(diào).

  • 一個 UniquePersistent<SomeType> 依賴 C++ 的構(gòu)造函數(shù)和析構(gòu)函數(shù)來管理其引用的對象的生命周期.
  • 當(dāng)使用構(gòu)造函數(shù)創(chuàng)建一個 Persistent<SomeType> 后, 必須在使用完后顯式調(diào)用 Persistent::Reset.
  • Eternal 是一個用于預(yù)期永遠不會被釋放的 JavaScript 對象的 persistent handle, 使用它的代價更小, 因為它減輕了垃圾回收器判定對象是否存活的負擔(dān).
  • Persistent 和 UniquePersistent 都無法被拷貝, 使得它無法成為 C++11 之前的標準庫容器的值. PersistentValueMap 和 PersistentValueVector 為persistent 值提供了容器類, 并且?guī)в?Map 或類 Vector 的語義. C++11 的開發(fā)者不需要他們, 因 C++11 改變了語義, 解決了潛在的問題.
  • 當(dāng)然, 每次創(chuàng)建對象的時候, 都創(chuàng)建一個相應(yīng)的 local handle 會產(chǎn)生大量的 handle. 此時, handle scope 就派上用處了. 你可以將 handle scope 看作是存有許多 handle 的容器. 當(dāng) handle scope 銷毀時, 其中的所有 handle 也隨即銷毀, 這樣, 這些 handle 所引用的對象就能夠在下一次垃圾回收的時候被恰當(dāng)?shù)奶幚砹?
      回到我們在入門指南中的簡單示例上 , 在下面這張圖表中你可以看到 handle-stack 和在堆內(nèi)存上分配的對象. 注意, Context::New() 將返回一個 Local handle, 基于它, 我們創(chuàng)建了一個新的 Persistent handle 來演示 Persistent handle 的用法.
  • 當(dāng)析構(gòu)函數(shù) HandleScope::~HandleScope 被調(diào)用時, handle scope 被刪除, 其中的 handle 所引用的對象將在下次 GC 的時候被適當(dāng)?shù)奶幚? 垃圾回收器會移除 source_obj 和 script_obj 對象, 因為他們已經(jīng)不再被任何 handle 引用, 并且在 JS 代碼中也無法訪問到他們. 而 context handle 即使在離開 handle scope 后也并不會被移除, 因為它是 persistent handle, 只能通過對它顯式調(diào)用 Reset 才能將其移除.
    注意: 整篇文檔中的名詞 handle 都表示 local handle, 如果要表示 persistent handle 會使用全稱.
// This function returns a new array with three elements, x, y, and z.  
// 該函數(shù)返回一個新數(shù)組, 其中包含 x, y, z 三個元素  
Local<Array> NewPointArray(int x, int y, int z) {  
  v8::Isolate* isolate = v8::Isolate::GetCurrent();  
  
  // We will be creating temporary handles so we use a handle scope.  
  // 我們稍后需要創(chuàng)建一個臨時 handle, 因此我們需要使用 handle scope.  
  EscapableHandleScope handle_scope(isolate);  
  
  // Create a new empty array.  
  // 創(chuàng)建一個新的空數(shù)組.  
  Local<Array> array = Array::New(isolate, 3);  
  
  // Return an empty result if there was an error creating the array.  
  // 如果創(chuàng)建數(shù)組失敗, 返回空.  
  if (array.IsEmpty())  
    return Local<Array>();  
  
  // Fill out the values  
  // 填充值  
  array->Set(0, Integer::New(isolate, x));  
  array->Set(1, Integer::New(isolate, y));  
  array->Set(2, Integer::New(isolate, z));  
  
  // Return the value through Escape.  
  // 通過 Escape 返回數(shù)組  
  return handle_scope.Escape(array);  
}  

在這里要注意這個模型的一個陷阱: 你無法從一個在 handle scope 中聲明的函數(shù)中返回一個 local hanle. 如果你這么做了, 那么這個 local handle 將在返回前, 首先在 handle scope 的析構(gòu)函數(shù)被調(diào)用時被刪除. 返回一個 local handle 的正確方法應(yīng)該是構(gòu)建一個 EscapableHandleScope 而不是 HandleScope, 并調(diào)用其 Escape()方法, 將你想要返回的 handle 傳遞給它. 以下是一個實踐中的例子:

Escape 方法將其參數(shù)的值拷貝到一個封閉作用域中, 然后照常刪除所有 Local handle, 然后將一個含有指定值的新的 handle 送回給調(diào)用方.

2、Contexts (上下文)##

  • 在 V8 中, 一個 context 就是一個執(zhí)行環(huán)境, 它使得可以在一個 V8 實例中運行相互隔離且無關(guān)的 JavaScript 代碼. 你必須為你將要執(zhí)行的 JavaScript 代碼顯式的指定一個 context.
    之所以這樣是因為 JavaScript 提供了一些內(nèi)建的工具函數(shù)和對象, 他們可以被 JS 代碼所修改. 比如, 如果兩個完全無關(guān)的 JS 函數(shù)都在用同樣的方式修改一個 global 對象, 很可能就會出現(xiàn)一個意外的結(jié)果.
  • 如果要為所有必須的內(nèi)建對象創(chuàng)建一個新的執(zhí)行上下文(context), 在 CPU 時間和內(nèi)存方面的開銷可能會比較大. 然而, V8 的大量緩存可以對其優(yōu)化, 你創(chuàng)建的第一個 context 可能相對比較耗時, 而接下來的 context 就快捷很多. 這是因為第一個 context 需要創(chuàng)建內(nèi)建對象并解析內(nèi)建的 JavaScript 代碼. 而后續(xù)的 context 只需要為它自己創(chuàng)建內(nèi)建對象即可, 而不用再解析 JS 代碼了. 伴隨 V8 的快照 (snapshot) 特性 (通過 build 選項 snapshot=yes 開啟, 默認打開), 首次創(chuàng)建 context 的時間將會得到大量優(yōu)化, 因為快照包含了一個序列化的堆, 其中包含了已解析編譯過的內(nèi)建 JavaScript 代碼. 隨著垃圾回收, V8 大量的緩存也是其高性能的關(guān)鍵因素, 更多信息請參閱 "V8 設(shè)計元素"一文.
  • 當(dāng)你創(chuàng)建一個 context 后, 你可以進出此上下文任意多的次數(shù). 當(dāng)你在 context A 中時, 還可以再進入 context B. 此時你將進入 B 的上下文中. 當(dāng)退出 B 時, A 又將成為你的當(dāng)前 context. 正如下圖所展示的那樣.
    注意, 每個 context 中的內(nèi)建函數(shù)和對象是相互隔離的. 你也可以在創(chuàng)建一個 context 的時候設(shè)置一個安全令牌. 更多信息請參閱安全模型一節(jié).
    在 V8 中使用 context 的動機是, 瀏覽器中的每個 window 和 iframe 可以擁有一個屬于自己的干凈的執(zhí)行環(huán)境.

3、Templates (模板)##

在一個 context 中, template 是 JavaScript 函數(shù)和對象的一個模型. 你可以使用 template 來將 C++ 函數(shù)和數(shù)據(jù)結(jié)構(gòu)封裝在一個 JavaScript 對象中, 這樣它就可以被 JS 代碼操作. 例如, Chrome 使用 template 將 C++ DOM 節(jié)點封裝成 JS 對象, 并且將函數(shù)安裝在 global 命名空間中. 你可以創(chuàng)建一個 template 集合, 在每個創(chuàng)建的 context 中你都可以重復(fù)使用它們. 你可以按照你的需求, 創(chuàng)建任意多的 template. 然而在任意一個 context 中, 任意 template 都只能擁有一個實例.
  在 JS 中, 函數(shù)和對象之間有很強的二元性. 在 C++ 或 Java 中創(chuàng)建一種新的對象類型通常要定義一個類. 而在 JS 中你卻要創(chuàng)建一個函數(shù), 并以函數(shù)為構(gòu)造器生成對象實例. JS 對象的內(nèi)部結(jié)構(gòu)和功能很大程度上是由構(gòu)造它的函數(shù)決定的. 這些也反映在 V8 的 template 的設(shè)計中, 因此 V8 有兩種類型的 template:

  • FunctionTemplate
      一個 Function Template 就是一個 JS 函數(shù)的模型. 我們可以在我們指定的 context 下通過調(diào)用 template 的 GetFunction 方法來創(chuàng)建一個 JS 函數(shù)的實例. 你也可以將一個 C++ 回調(diào)與一個當(dāng) JS 函數(shù)實例執(zhí)行時被調(diào)用的 function template 關(guān)聯(lián)起來.
  • ObjectTemplate
      每一個 Function Template 都與一個 Object Template 相關(guān)聯(lián). 它用來配置以該函數(shù)作為構(gòu)造器而創(chuàng)建的對象. 你也可以給這個 Object Template 關(guān)聯(lián)兩類 C++ 回調(diào):
  • 存取器回調(diào). 當(dāng)指定的對象屬性被 JS 訪問時調(diào)用.
  • 攔截器回調(diào). 當(dāng)任意對象屬性被訪問時調(diào)用.
    存取器和攔截器將在后面的部分講到.
// Create a template for the global object and set the  
// built-in global functions.  
// 為 global 對象創(chuàng)建一個 template 并設(shè)置內(nèi)建全局函數(shù)  
Local<ObjectTemplate> global = ObjectTemplate::New(isolate);  
global->Set(String::NewFromUtf8(isolate, "log"), FunctionTemplate::New(isolate, LogCallback));  
  
// Each processor gets its own context so different processors  
// do not affect each other.  
// 每個任務(wù)都有屬于自己的 context, 所以不同的任務(wù)相互之間不影響.  
Persistent<Context> context = Context::New(isolate, NULL, global);  
This example code is taken from JsHttpProcessor::Initializer in the process.cc sample.  
  
以上代碼提供了一個 為 global 對象創(chuàng)建 tamplate 并設(shè)置內(nèi)建全局函數(shù)的例子.  
  
示例代碼是 process.cc 中 JsHttpProcessor::Initializer 的片段.  

4、Accessors (存取器)##

存取器是一個當(dāng)對象屬性被 JS 代碼訪問的時候計算并返回一個值的 C++ 回調(diào). 存取器是通過 Object Template 的 SetAccessor 方法進行配置的. 該方法接收屬性的名稱和與其相關(guān)聯(lián)的回調(diào)函數(shù), 分別在 JS 讀取和寫入該屬性時觸發(fā).
  存取器的復(fù)雜性源于你所操作的數(shù)據(jù)的訪問方式:

  • 訪問靜態(tài)全局變量
  • 訪問動態(tài)變量
  • 假設(shè)有兩個 C++ 整數(shù)變量 x 和 y, 要讓他它們可以在 JS 中通過 global 對象進行訪問. 我們需要在 JS 代碼讀寫這些變量的時候調(diào)用相應(yīng)的 C++ 存取器函數(shù). 這些存取函數(shù)將一個 C++ 整數(shù)通過 Integer::New 轉(zhuǎn)換成 JS 整數(shù), 并將 JS 整數(shù)轉(zhuǎn)換成32位 C++ 整數(shù). 來看下面的例子:
void XGetter(Local<String> property,  
                const PropertyCallbackInfo<Value>& info) {  
    info.GetReturnValue().Set(x);  
  }  
  
  void XSetter(Local<String> property, Local<Value> value,  
               const PropertyCallbackInfo<Value>& info) {  
    x = value->Int32Value();  
  }  
  
  // YGetter/YSetter are so similar they are omitted for brevity  
  
  Local<ObjectTemplate> 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 = Context::New(isolate, NULL, global_templ);  

注意上述代碼中的 Object Template 是和 context 同時創(chuàng)建的. 事實上 Template 可以提前創(chuàng)建好, 并可以在任意 context 中使用.

5、Accessing Dynamic Variables (訪問動態(tài)變量)##

在前一個例子中, 變量是靜態(tài)全局的, 那么如果是一個動態(tài)操縱的呢? 比如用于標記一個 DOM 樹是否在瀏覽器中的變量? 我們假設(shè) x 和 y 是 C++ 類 Point 上的成員:

class Point {  
   public:  
    Point(int x, int y) : x_(x), y_(y) { }  
    int x_, y_;  
  }  

為了讓任意多個 C++ Point 實例在 JS 中可用, 我們需要為每一個 C++ Point 創(chuàng)建一個 JS 對象, 并將它們聯(lián)系起來. 這可以通過外部值和內(nèi)部成員實現(xiàn).
首先為 point 創(chuàng)建一個 Object template 封裝對象:
Local<ObjectTemplate> point_templ = ObjectTemplate::New(isolate);
每個 JS point 對象持有一個 C++ 封裝對象的引用, 封裝對象中有一個 Internal Field, 之所以這么叫是因為它們無法在 JS 中訪問, 而只能通過 C++ 代碼訪問. 一個對象可以有任意多個 Internal Field, 其數(shù)量可以按以下方式在 Object Template 上設(shè)置.
point_templ->SetInternalFieldCount(1);
此處的 internal field count 設(shè)置為了 1, 這表示該對象有一個 internal field, 其 index 是 0, 指向一個 C++ 對象.
將 x 和 y 存取器添加到 template 上:

point_templ.SetAccessor(String::NewFromUtf8(isolate, "x"), GetPointX, SetPointX);
point_templ.SetAccessor(String::NewFromUtf8(isolate, "y"), GetPointY, SetPointY);

接下來通過創(chuàng)建一個新的 template 實例來封裝一個 C++ point, 將封裝對象的 interanl field 設(shè)置為 0.

Point* p = ...;
  Local<Object> obj = point_templ->NewInstance();
  obj->SetInternalField(0, External::New(isolate, p));

以上代碼中, 外部對象就是一個 void* 的封裝體. 外部對象只能用來在 internal field 上存儲引用值. JS 對象無法直接引用 C++ 對象, 因此可以將外部值當(dāng)作是一個從 JS 到 C++ 的橋梁. 從這種意義上來說, 外部值是和 handle 相對的概念( handle 是 C++ 到 JS 對象的引用 ).
  以下是 x 的存取器的定義, y 的和 x 一樣.

void GetPointX(Local<String> property,  
                 const PropertyCallbackInfo<Value>& info) {  
    Local<Object> self = info.Holder();  
    Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));  
    void* ptr = wrap->Value();  
    int value = static_cast<Point*>(ptr)->x_;  
    info.GetReturnValue().Set(value);  
  }  
  
  void SetPointX(Local<String> property, Local<Value> value,  
                 const PropertyCallbackInfo<Value>& info) {  
    Local<Object> self = info.Holder();  
    Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));  
    void* ptr = wrap->Value();  
    static_cast<Point*>(ptr)->x_ = value->Int32Value();  
  }  

6、Interceptors (攔截器)##

我們可以設(shè)置一個回調(diào), 讓它在對應(yīng)對象的任意屬性被訪問時都會被調(diào)用. 這就是 Interceptor. 考慮到效率, 分為兩種不同的 interceptor:

  • 屬性名攔截器: 當(dāng)通過字符串形式的屬性名訪問時調(diào)用. 比如在瀏覽器中使用 document.theFormName.elementName 進行訪問.
  • 屬性索引攔截器: 當(dāng)通過屬性的下標/索引訪問時調(diào)用. 比如在瀏覽器中使用 document.forms.elements[0] 進行訪問.
    V8 源碼 process.cc 的代碼中, 包含了一個使用 interceptor 的例子. 在下面的代碼片段中, SetNamedPropertyHandler 指定了 MapGet 和 MapSet 兩個 interceptor:
Local<ObjectTemplate> result = ObjectTemplate::New(isolate);  
result->SetNamedPropertyHandler(MapGet, MapSet);  
The MapGet interceptor is provided below:  
  
void JsHttpRequestProcessor::MapGet(Local<String> name,  
                                    const PropertyCallbackInfo<Value>& info) {  
  // Fetch the map wrapped by this object.  
  map<string, string> *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<string, string>::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()));  
}  

和存取器一樣, 對應(yīng)的回調(diào)將在每次屬性被訪問的時候調(diào)用, 只不過攔截器會處理所有的屬性, 而存取器只針對相關(guān)聯(lián)的屬性.

7、Security Model (安全模型)##

同源策略用來防止從一個源載入的文檔或腳本存取另外一個源的文檔. 這里所謂的 "同源" 是指相同的 protocal + domain + port, 這三個都相同的兩個網(wǎng)頁才被認為是同源. 如果沒有它的保護, 惡意網(wǎng)頁將危害到其他網(wǎng)頁的完整性.
  同源策略在 Netscape Navigator 2.0 中首次引入
  在 V8 中, 同源被定義為相同的 context. 默認情況下, 是無法訪問別的 context 的. 如果一定要這樣做, 需要使用安全令牌或安全回調(diào). 安全令牌可以是任意值, 但通常來說是個唯一的規(guī)范字符串. 當(dāng)建立一個 context 時, 我們可以通過 SetSecurityToken
來指定一個安全令牌, 否則 V8 將自動為該 context 生成一個.當(dāng)試圖訪問一個全局變量時, V8 安全系統(tǒng)將先檢查該全局對象的安全令牌, 并將其和試圖訪問該對象的代碼的安全令牌比對. 如果匹配則放行, 否則 V8 將觸發(fā)一個回調(diào)來判斷是否應(yīng)該放行. 我們可以通過 object template 上的 SetAccessCheckCallbacks
方法來定義該回調(diào)來并決定是否放行. V8 安全系統(tǒng)可以用被訪問對象上的安全回調(diào)來判斷訪問者的 context 是否有權(quán)訪問. 該回調(diào)需要傳入被訪問的對象, 被訪問的屬性以及訪問的類型(例如讀, 寫, 或刪除), 返回結(jié)果為是或否.
  Chrome 實現(xiàn)了這套機制, 對于安全令牌不匹配的情況, 只有以下這些才可以通過安全回調(diào)的方式來判斷是否可以放行:
window.focus()
window.blur()
window.close()
window.location
window.open()
history.forward()
history.back()
和 history.Go()

8、Exceptions (異常)##

如果發(fā)生錯誤, V8 會拋出異常. 比如, 當(dāng)一個腳本或函數(shù)試圖讀取一個不存在的屬性時, 或者一個不是函數(shù)的值被當(dāng)作函數(shù)進行調(diào)用執(zhí)行時.
  如果一個操作不成功, V8 將返回一個空的 handle. 因此我們應(yīng)該在代碼中檢查返回值是否是一個空的 handle, 可以使用 Local 類的公共成員函數(shù) isEmpty() 來檢查 handle 是否為空.
  我們也可以像以下示例一樣 Try Catch 代碼中發(fā)生的異常:

TryCatch trycatch(isolate);  
 Local<Value> v = script->Run();  
 if (v.IsEmpty()) {  
   Local<Value> exception = trycatch.Exception();  
   String::Utf8Value exception_str(exception);  
   printf("Exception: %s\n", *exception_str);  
   // ...  
 }  

如果 value 以一個空 handle 返回, 而你沒有 TryCatch 它, 你的程序掛掉, 反之則可以繼續(xù)執(zhí)行.

9、Inheritance (繼承)##

JS 是一個無類的面向?qū)ο缶幊陶Z言, 因此, 它使用原型繼承而不是類繼承. 這會讓那些接受傳統(tǒng)面向?qū)ο笳Z言(比如 C++ 和 Java)訓(xùn)練的程序員感到迷惑.基于類的面向?qū)ο缶幊陶Z言, 比如 C++ 和 Java, 是建立在兩種完全不同實體的概念上的: 類和實例. 而 JS 是基于原型的語言, 因此沒有這些區(qū)別, 它只有對象. JS 本身并不原生支持 類這個層級的聲明; 然而, 它的原型機制簡化了給對象實例添加自定義屬性或方法的過程. 在 JS 中, 你可以像以下代碼這樣給對象添加屬性:

// Create an object "bicycle"   
function bicycle(){   
}   
// Create an instance of bicycle called roadbike  
var roadbike = new bicycle()  
// Define a custom property, wheels, on roadbike   
roadbike.wheels = 2  

這種方式定義的屬性只存在于該對象實例上. 如果創(chuàng)建另一個 bicycle() 實例則其并沒有 wheels 屬性, 進行訪問將返回 undefined. 除非顯式的將 wheels 屬性添加上去.
  有時這正是我們所需要的, 但有時我們希望將屬性添加到所有這些實例上去, 這是 JS 的 prototype 對象就派上用處了. 為了使用原型對象, 可以通過 prototype 關(guān)鍵詞訪問對象原型, 然后在它上面添加自定義的屬性:

function bicycle(){   
}  
// Assign the wheels property to the object's prototype  
bicycle.prototype.wheels = 2  

此后, 所有 bicycle() 的實例都將預(yù)置該屬性值了.
  V8 通過 template 可以使用同樣的方法. 每個 FunctionTemplate 都有一個 PrototypeTemplate 方法可以返回該函數(shù)的原型. 我們可以給它設(shè)置屬性, 也可以將 C++ 函數(shù)關(guān)聯(lián)到這些屬性, 然后所有該 FunctionTemplate 對應(yīng)的實例上都將有這些屬性和對應(yīng)的值或函數(shù):

Local<FunctionTemplate> biketemplate = FunctionTemplate::New(isolate);  
 biketemplate->PrototypeTemplate().Set(  
     String::NewFromUtf8(isolate, "wheels"),  
     FunctionTemplate::New(isolate, MyWheelsMethodCallback)->GetFunction();  
 )  

以上代碼將使所有 biketemplate 的原型鏈上都具有 wheels 方法, 當(dāng)在對應(yīng)實例上調(diào)用 wheels 方法時, MyWheelsMethodCallback 將被執(zhí)行.
  V8 的 FunctionTemplate 類提供了公共的成員函數(shù) Inherit(), 當(dāng)我們希望當(dāng)前 function template 繼承另外一個 function template 的時候可以調(diào)用該方法:

void Inherit(Local<FunctionTemplate> parent);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內(nèi)容

  • V8 是一個獨立運行的虛擬機, 其中有些關(guān)鍵性概念比如: handles, scopes 和 contexts. ...
    轉(zhuǎn)角遇見一直熊閱讀 8,978評論 1 8
  • 目錄 1.靜態(tài)作用域與動態(tài)作用域 2.變量的作用域 3.JavaScript 中變量的作用域 4.JavaScri...
    一縷殤流化隱半邊冰霜閱讀 7,114評論 37 113
  • JavaScript絕對是最火的編程語言之一,一直具有很大的用戶群,隨著在服務(wù)端的使用(NodeJs),更是爆發(fā)了...
    不去解釋閱讀 2,422評論 1 16
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,781評論 18 139
  • 很實用的編程英語詞庫,共收錄一千五百余條詞匯。 第一部分: application 應(yīng)用程式 應(yīng)用、應(yīng)用程序app...
    春天的蜜蜂閱讀 1,389評論 0 22