V8 の埋め込み入門
このドキュメントでは、V8 の主要な概念をいくつか紹介し、「hello world」の例を使って V8 コードを使い始める方法を説明します。
対象読者 #
このドキュメントは、C++ アプリケーションに V8 JavaScript エンジンを埋め込みたい C++ プログラマーを対象としています。独自のアプリケーションの C++ オブジェクトとメソッドを JavaScript から利用できるようにし、JavaScript のオブジェクトと関数を C++ アプリケーションから利用できるようにする方法を説明します。
Hello world #
JavaScript のステートメントを文字列引数として受け取り、JavaScript コードとして実行し、結果を標準出力に出力する Hello World の例 を見てみましょう。
まず、いくつかの重要な概念
- Isolate は、独自のヒープを持つ VM インスタンスです。
- ローカルハンドルは、オブジェクトへのポインタです。すべての V8 オブジェクトはハンドルを使用してアクセスされます。 V8 ガベージコレクターの動作方法により、ハンドルは必須です。
- ハンドルスコープは、任意の数のハンドルを格納するコンテナと考えることができます。ハンドルを使い終わったら、それぞれを個別に削除する代わりに、スコープを削除するだけで済みます。
- コンテキストは、V8 の単一インスタンス内で、分離され、無関係な JavaScript コードを実行できるようにする実行環境です。 JavaScript コードを実行するコンテキストを明示的に指定する必要があります。
これらの概念については、詳細ガイド で詳しく説明しています。
サンプルを実行する #
以下の手順に従って、サンプルを自分で実行してください。
Git の手順 に従って、V8 のソースコードをダウンロードします。
この hello world サンプルの手順は、V8 v11.9 で最後にテストされています。 このブランチは、
git checkout branch-heads/11.9 -b sample -t
でチェックアウトできます。ヘルパースクリプトを使用してビルド構成を作成します
tools/dev/v8gen.py x64.release.sample
ビルド構成を確認して手動で編集するには、以下を実行します
gn args out.gn/x64.release.sample
Linux 64 システムで静的ライブラリをビルドします
ninja -C out.gn/x64.release.sample v8_monolith
ビルドプロセスで作成された静的ライブラリにリンクして、
hello-world.cc
をコンパイルします。たとえば、GNU コンパイラを使用する 64 ビット Linux では、次のようになります。g++ -I. -Iinclude samples/hello-world.cc -o hello_world -fno-rtti -lv8_monolith -lv8_libbase -lv8_libplatform -ldl -Lout.gn/x64.release.sample/obj/ -pthread -std=c++17 -DV8_COMPRESS_POINTERS -DV8_ENABLE_SANDBOX
より複雑なコードの場合、V8 は ICU データファイルがないと失敗します。このファイルをバイナリが格納されている場所にコピーします
cp out.gn/x64.release.sample/icudtl.dat .
コマンドラインで
hello_world
実行可能ファイルを実行します。たとえば、Linux の V8 ディレクトリで、以下を実行します。./hello_world
Hello, World!
と出力されます。やった!
メインブランチと同期しているサンプルを探している場合は、hello-world.cc
ファイルを確認してください。これは非常に単純な例であり、文字列としてスクリプトを実行する以上のことをしたいと思うでしょう。 以下の詳細ガイド には、V8 埋め込み者向けの詳しい情報が記載されています。
その他のサンプルコード #
以下のサンプルは、ソースコードのダウンロードの一部として提供されています。
process.cc
#
このサンプルは、たとえば Web サーバーの一部である可能性のある、架空の HTTP リクエスト処理アプリケーションをスクリプト化できるように拡張するために必要なコードを提供します。 Process
という関数を指定する必要がある JavaScript スクリプトを引数として取ります。 JavaScript の Process
関数は、たとえば、架空の Web サーバーによって提供される各ページのヒット数を収集するなど、情報を収集するために使用できます。
shell.cc
#
このサンプルは、ファイル名を引数として受け取り、その内容を読み取って実行します。 JavaScript コードスニペットを入力して実行できるコマンドプロンプトが含まれています。このサンプルでは、オブジェクトテンプレートと関数テンプレートを使用して、print
などの追加関数が JavaScript に追加されています。
詳細ガイド #
スタンドアロンの仮想マシンとしての V8 の使用方法と、ハンドル、スコープ、コンテキストなどの V8 の主要な概念について理解したので、これらの概念についてさらに詳しく説明し、V8 を独自の C++ アプリケーションに埋め込むための鍵となる他のいくつかの概念を紹介します。
V8 API は、スクリプトのコンパイルと実行、C++ メソッドとデータ構造へのアクセス、エラー処理、セキュリティチェックの有効化のための関数を提供します。アプリケーションは、他の C++ ライブラリと同様に V8 を使用できます。 C++ コードは、ヘッダー include/v8.h
をインクルードすることにより、V8 API を介して V8 にアクセスします。
ハンドルとガベージコレクション #
ハンドルは、ヒープ内の JavaScript オブジェクトの場所への参照を提供します。 V8 ガベージコレクターは、アクセスできなくなったオブジェクトによって使用されているメモリを再利用します。ガベージコレクションプロセス中、ガベージコレクターはしばしばオブジェクトをヒープ内の異なる場所に移動します。ガベージコレクターがオブジェクトを移動すると、ガベージコレクターはオブジェクトを参照するすべてのハンドルをオブジェクトの新しい場所で更新します。
オブジェクトは、JavaScript からアクセスできず、それを参照するハンドルがない場合、ガベージと見なされます。ガベージコレクターは、ガベージと見なされるすべてのオブジェクトを定期的に削除します。 V8 のガベージコレクションメカニズムは、V8 のパフォーマンスの鍵となります。
ハンドルにはいくつかの種類があります
ローカルハンドルはスタック上に保持され、適切なデストラクタが呼び出されると削除されます。これらのハンドルの有効期間は、多くの場合関数呼び出しの開始時に作成されるハンドルスコープによって決まります。ハンドルスコープが削除されると、ガベージコレクターは、ハンドルスコープ内のハンドルによって以前に参照されていたオブジェクトを、JavaScript や他のハンドルからアクセスできなくなった場合に、自由に割り当て解除できます。このタイプのハンドルは、上記の hello world の例で使用されています。
ローカルハンドルは、
Local<SomeType>
クラスを持ちます。**注意:** ハンドルスタックは C++ コールスタックの一部ではありませんが、ハンドルスコープは C++ スタックに埋め込まれています。ハンドルスコープはスタック割り当てのみ可能で、
new
で割り当てることはできません。永続ハンドルは、ローカルハンドルと同様に、ヒープ割り当てされた JavaScript オブジェクトへの参照を提供します。処理する参照の有効期間管理が異なる 2 つの種類があります。複数の関数呼び出しでオブジェクトへの参照を保持する必要がある場合、またはハンドルの有効期間が C++ スコープに対応しない場合は、永続ハンドルを使用します。たとえば、Google Chrome は永続ハンドルを使用してドキュメントオブジェクトモデル (DOM) ノードを参照します。永続ハンドルは、
PersistentBase::SetWeak
を使用して弱くすることができ、オブジェクトへの唯一の参照が弱い永続ハンドルからのものである場合にガベージコレクターからのコールバックをトリガーします。UniquePersistent<SomeType>
ハンドルは、C++ のコンストラクタとデストラクタに依存して、基になるオブジェクトの有効期間を管理します。Persistent<SomeType>
はコンストラクタで構築できますが、Persistent::Reset
で明示的にクリアする必要があります。
めったに使用されない他のタイプのハンドルがあり、ここでは簡単に説明します
Eternal
は、削除されることがないと予想される JavaScript オブジェクトの永続ハンドルです。ガベージコレクターがそのオブジェクトの生存期間を判断する必要がないため、使用コストが安くなります。Persistent
とUniquePersistent
はどちらもコピーできないため、C++11 以前の標準ライブラリコンテナの値としては適していません。PersistentValueMap
とPersistentValueVector
は、マップやベクトルのようなセマンティクスを持つ、永続値のコンテナクラスを提供します。 C++11 の埋め込みでは、C++11 のムーブセマンティクスが根本的な問題を解決するため、これらは必要ありません。
もちろん、オブジェクトを作成するたびにローカルハンドルを作成すると、多くのハンドルが発生する可能性があります。これは、ハンドルスコープが非常に役立つところです。ハンドルスコープは、多くのハンドルを保持するコンテナと考えることができます。ハンドルスコープのデストラクタが呼び出されると、そのスコープ内で作成されたすべてのハンドルがスタックから削除されます。ご想像のとおり、これにより、ハンドルが指すオブジェクトは、ガベージコレクターによってヒープから削除される資格を得ます。
非常に単純な hello world の例 に戻ると、次の図にハンドルスタックとヒープ割り当てオブジェクトを示します。 Context::New()
は Local
ハンドルを返し、Persistent
ハンドルの使用方法を示すために、それに基づいて新しい Persistent
ハンドルを作成することに注意してください。

デストラクタ HandleScope::~HandleScope
が呼び出されると、ハンドルスコープが削除されます。削除されたハンドルスコープ内のハンドルによって参照されるオブジェクトは、それらへの他の参照がない場合、次のガベージコレクションで削除される資格があります。ガベージコレクターは、source_obj
と script_obj
オブジェクトもヒープから削除できます。これは、それらがハンドルによって参照されなくなり、JavaScript から到達できなくなったためです。コンテキストハンドルは永続ハンドルであるため、ハンドルスコープが終了しても削除されません。コンテキストハンドルを削除する唯一の方法は、明示的に Reset
を呼び出すことです。
**注意:** このドキュメント全体で、「ハンドル」という用語はローカルハンドルを指します。永続ハンドルの場合は、その用語を完全な形で使用します。
このモデルには、1 つのよくある落とし穴があることに注意することが重要です。*ハンドルスコープを宣言する関数からローカルハンドルを直接返すことはできません*。そうすると、返そうとしているローカルハンドルは、関数が戻る直前にハンドルスコープのデストラクタによって削除されてしまいます。ローカルハンドルを返す正しい方法は、HandleScope
の代わりに EscapableHandleScope
を構築し、ハンドルスコープの Escape
メソッドを呼び出して、返したい値を持つハンドルを渡すことです。実際にどのように動作するかの例を次に示します。
// This function returns a new array with three elements, x, y, and 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.
v8::EscapableHandleScope handle_scope(isolate);
// Create a new empty array.
v8::Local<v8::Array> array = v8::Array::New(isolate, 3);
// Return an empty result if there was an error creating the array.
if (array.IsEmpty())
return v8::Local<v8::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.
return handle_scope.Escape(array);
}
Escape
メソッドは、引数の値を囲んでいるスコープにコピーし、すべてのローカルハンドルを削除してから、安全に返すことができる新しいハンドルコピーを返します。
コンテキスト #
V8では、コンテキストとは、それぞれ独立した無関係のJavaScriptアプリケーションをV8の単一インスタンス内で実行できる実行環境です。JavaScriptコードを実行するコンテキストを明示的に指定する必要があります。
なぜこれが必要なのでしょうか?JavaScriptは、JavaScriptコードによって変更可能な組み込みユーティリティ関数とオブジェクトのセットを提供しているためです。たとえば、完全に無関係な2つのJavaScript関数が両方ともグローバルオブジェクトを同じ方法で変更した場合、予期しない結果が発生する可能性が高くなります。
CPU時間とメモリの観点から見ると、構築する必要がある組み込みオブジェクトの数を考えると、新しい実行コンテキストを作成するのはコストのかかる操作のように思えるかもしれません。しかし、V8の広範なキャッシングにより、最初に作成するコンテキストはある程度コストがかかりますが、後続のコンテキストははるかに安価になります。これは、最初のコンテキストは組み込みオブジェクトを作成し、組み込みJavaScriptコードを解析する必要があるのに対し、後続のコンテキストはそれぞれのコンテキストの組み込みオブジェクトを作成するだけで済むためです。V8のスナップショット機能(ビルドオプションsnapshot=yes
で有効化され、デフォルトです)を使用すると、スナップショットにはシリアライズされたヒープが含まれており、組み込みJavaScriptコードのコンパイル済みコードが含まれているため、最初のコンテキストの作成に費やされる時間が高度に最適化されます。ガベージコレクションに加えて、V8の広範なキャッシングもV8のパフォーマンスの鍵となります。
コンテキストを作成したら、何度でも出入りできます。コンテキストAにいる間に別のコンテキストBに入ることもできます。これは、現在のコンテキストAをBに置き換えることを意味します。Bを終了すると、Aが現在のコンテキストとして復元されます。これは以下に示されています。

各コンテキストの組み込みユーティリティ関数とオブジェクトは個別に保持されていることに注意してください。コンテキストを作成するときに、オプションでセキュリティトークンを設定できます。詳細については、セキュリティモデルセクションを参照してください。
V8でコンテキストを使用する動機は、ブラウザの各ウィンドウとiframeが独自の新しいJavaScript環境を持つことができるようにするためでした。
テンプレート #
テンプレートは、コンテキスト内のJavaScript関数とオブジェクトの青写真です。テンプレートを使用して、C++関数とデータ構造をJavaScriptオブジェクト内にラップし、JavaScriptスクリプトで操作できるようにすることができます。たとえば、Google Chromeはテンプレートを使用して、C++ DOMノードをJavaScriptオブジェクトとしてラップし、グローバル名前空間に関数をインストールします。テンプレートのセットを作成し、作成するすべての新しいコンテキストで同じテンプレートを使用できます。必要な数のテンプレートを持つことができます。ただし、特定のコンテキストには、テンプレートのインスタンスを1つだけ持つことができます。
JavaScriptでは、関数とオブジェクトの間に強い二重性があります。JavaまたはC++で新しいタイプのオブジェクトを作成するには、通常、新しいクラスを定義します。JavaScriptでは、代わりに新しい関数を作成し、その関数をコンストラクタとして使用してインスタンスを作成します。JavaScriptオブジェクトのレイアウトと機能は、それを構築した関数と密接に関連しています。これは、V8テンプレートの動作方法に反映されています。テンプレートには2つのタイプがあります。
関数テンプレート
関数テンプレートは、単一の関数の青写真です。JavaScript関数をインスタンス化するコンテキスト内からテンプレートの
GetFunction
メソッドを呼び出すことによって、テンプレートのJavaScriptインスタンスを作成します。JavaScript関数のインスタンスが呼び出されたときに呼び出されるC++コールバックを関数テンプレートに関連付けることもできます。オブジェクトテンプレート
各関数テンプレートには、関連付けられたオブジェクトテンプレートがあります。これは、この関数を使用してコンストラクタとして作成されたオブジェクトを構成するために使用されます。オブジェクトテンプレートには、2種類のC++コールバックを関連付けることができます。
- アクセサーコールバックは、スクリプトによって特定のオブジェクトプロパティにアクセスされたときに呼び出されます。
- インターセプターコールバックは、スクリプトによってオブジェクトプロパティにアクセスされたときに呼び出されます。
次のコードは、グローバルオブジェクトのテンプレートを作成し、組み込みグローバル関数を設定する例を示しています。
// Create a template for the global object and set the
// built-in global functions.
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
global->Set(v8::String::NewFromUtf8(isolate, "log"),
v8::FunctionTemplate::New(isolate, LogCallback));
// Each processor gets its own context so different processors
// do not affect each other.
v8::Persistent<v8::Context> context =
v8::Context::New(isolate, nullptr, global);
このコード例は、process.cc
サンプルのJsHttpProcessor::Initializer
から抜粋したものです。
アクセサー #
アクセサーは、JavaScriptスクリプトによってオブジェクトプロパティにアクセスされたときに値を計算して返すC++コールバックです。アクセサーは、SetAccessor
メソッドを使用して、オブジェクトテンプレートを介して構成されます。このメソッドは、関連付けられているプロパティの名前と、スクリプトがプロパティの読み取りまたは書き込みを試みたときに実行する2つのコールバックを受け取ります。
アクセサーの複雑さは、操作するデータのタイプによって異なります。
静的グローバル変数へのアクセス #
コンテキスト内でグローバル変数としてJavaScriptで使用できるようにする2つのC++整数変数、x
とy
があるとします。これを行うには、スクリプトがこれらの変数を読み書きするたびに、C++アクセサー関数を呼び出す必要があります。これらのアクセサー関数は、Integer::New
を使用してC++整数をJavaScript整数に変換し、Int32Value
を使用してJavaScript整数をC++整数に変換します。次に例を示します。
void XGetter(v8::Local<v8::String> property,
const v8::PropertyCallbackInfo<Value>& info) {
info.GetReturnValue().Set(x);
}
void XSetter(v8::Local<v8::String> property, v8::Local<v8::Value> value,
const v8::PropertyCallbackInfo<void>& info) {
x = value->Int32Value();
}
// YGetter/YSetter are so similar they are omitted for brevity
v8::Local<v8::ObjectTemplate> global_templ = v8::ObjectTemplate::New(isolate);
global_templ->SetAccessor(v8::String::NewFromUtf8(isolate, "x"),
XGetter, XSetter);
global_templ->SetAccessor(v8::String::NewFromUtf8(isolate, "y"),
YGetter, YSetter);
v8::Persistent<v8::Context> context =
v8::Context::v8::New(isolate, nullptr, global_templ);
上記のコードのオブジェクトテンプレートは、コンテキストと同時に作成されることに注意してください.テンプレートは事前に作成し、任意の数のコンテキストに使用できます.
動的変数へのアクセス #
前の例では、変数は静的でグローバルでした。ブラウザのDOMツリーのように、操作されているデータが動的である場合はどうでしょうか。x
とy
がC++クラスPoint
のオブジェクトフィールドであるとします。
class Point {
public:
Point(int x, int y) : x_(x), y_(y) { }
int x_, y_;
}
任意の数のC++ point
インスタンスをJavaScriptで使用できるようにするには、各C++ point
に1つのJavaScriptオブジェクトを作成し、JavaScriptオブジェクトとC++インスタンスの間に接続を作成する必要があります。これは、外部値と内部オブジェクトフィールドを使用して行われます。
最初に、point
ラッパーオブジェクトのオブジェクトテンプレートを作成します。
v8::Local<v8::ObjectTemplate> point_templ = v8::ObjectTemplate::New(isolate);
各JavaScript point
オブジェクトは、内部フィールドを使用してラッパーとなるC++オブジェクトへの参照を保持します。これらのフィールドは、JavaScript内からはアクセスできず、C++コードからのみアクセスできるため、このように呼ばれています。オブジェクトは任意の数の内部フィールドを持つことができ、内部フィールドの数はオブジェクトテンプレートで次のように設定されます。
point_templ->SetInternalFieldCount(1);
ここでは、内部フィールドカウントが1
に設定されているため、オブジェクトにはインデックスが0
の内部フィールドが1つあり、C++オブジェクトを指しています。
テンプレートにx
とy
のアクセサーを追加します。
point_templ->SetAccessor(v8::String::NewFromUtf8(isolate, "x"),
GetPointX, SetPointX);
point_templ->SetAccessor(v8::String::NewFromUtf8(isolate, "y"),
GetPointY, SetPointY);
次に、テンプレートの新しいインスタンスを作成し、内部フィールド0
をポイントp
の外部ラッパーに設定することにより、C++ポイントをラップします。
Point* p = ...;
v8::Local<v8::Object> obj = point_templ->NewInstance();
obj->SetInternalField(0, v8::External::New(isolate, p));
外部オブジェクトは、単にvoid*
のラッパーです。外部オブジェクトは、内部フィールドに参照値を格納するためにのみ使用できます。JavaScriptオブジェクトはC++オブジェクトを直接参照できないため、外部値はJavaScriptからC++に移行するための「ブリッジ」として使用されます。その意味で、外部値は、ハンドルがC++にJavaScriptオブジェクトへの参照を作成させるため、ハンドルの反対です。
x
のget
およびset
アクセサーの定義を以下に示します。y
アクセサーの定義は、x
がy
に置き換えられる点を除いて同じです。
void GetPointX(Local<String> property,
const PropertyCallbackInfo<Value>& info) {
v8::Local<v8::Object> self = info.Holder();
v8::Local<v8::External> wrap =
v8::Local<v8::External>::Cast(self->GetInternalField(0));
void* ptr = wrap->Value();
int value = static_cast<Point*>(ptr)->x_;
info.GetReturnValue().Set(value);
}
void SetPointX(v8::Local<v8::String> property, v8::Local<v8::Value> value,
const v8::PropertyCallbackInfo<void>& info) {
v8::Local<v8::Object> self = info.Holder();
v8::Local<v8::External> wrap =
v8::Local<v8::External>::Cast(self->GetInternalField(0));
void* ptr = wrap->Value();
static_cast<Point*>(ptr)->x_ = value->Int32Value();
}
アクセサーは、JavaScriptオブジェクトによってラップされたpoint
オブジェクトへの参照を抽出し、関連付けられたフィールドを読み書きします。このようにして、これらの汎用アクセサーは、任意の数のラップされたポイントオブジェクトで使用できます。
インターセプター #
スクリプトがオブジェクトプロパティにアクセスするたびにコールバックを指定することもできます。これらはインターセプターと呼ばれます。効率化のために、インターセプターには2つのタイプがあります。
- 名前付きプロパティインターセプター - 文字列名でプロパティにアクセスするときに呼び出されます。
ブラウザ環境での例は、document.theFormName.elementName
です。 - インデックス付きプロパティインターセプター - インデックス付きプロパティにアクセスするときに呼び出されます。ブラウザ環境での例は、
document.forms.elements[0]
です。
V8ソースコードに付属のサンプルprocess.cc
には、インターセプターの使用例が含まれています。次のコードスニペットでは、SetNamedPropertyHandler
はMapGet
およびMapSet
インターセプターを指定します。
v8::Local<v8::ObjectTemplate> result = v8::ObjectTemplate::New(isolate);
result->SetNamedPropertyHandler(MapGet, MapSet);
MapGet
インターセプターを以下に示します。
void JsHttpRequestProcessor::MapGet(v8::Local<v8::String> name,
const v8::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(v8::String::NewFromUtf8(
value.c_str(), v8::String::kNormalString, value.length()));
}
アクセサーと同様に、指定されたコールバックは、プロパティにアクセスされるたびに呼び出されます。アクセサーとインターセプターの違いは、インターセプターはすべてのプロパティを処理するのに対し、アクセサーは1つの特定のプロパティに関連付けられていることです。
セキュリティモデル #
「同一オリジンポリシー」(Netscape Navigator 2.0で最初に導入された)は、ある「オリジン」からロードされたドキュメントまたはスクリプトが、別の「オリジン」のドキュメントのプロパティを取得または設定することを防ぎます。ここでオリジンという用語は、ドメイン名(例:www.example.com
)、プロトコル(例:https
)、およびポートの組み合わせとして定義されています。たとえば、www.example.com:81
はwww.example.com
と同じオリジンではありません。2つのWebページが同じオリジンを持つと見なされるためには、3つすべてが一致する必要があります。この保護がなければ、悪意のあるWebページが別のWebページの整合性を損なう可能性があります。
V8では、「オリジン」はコンテキストとして定義されています。呼び出し元のコンテキスト以外へのアクセスは、デフォルトでは許可されていません。呼び出し元のコンテキスト以外にアクセスするには、セキュリティトークンまたはセキュリティコールバックを使用する必要があります。セキュリティトークンは任意の値にすることができますが、通常はシンボル、つまり他の場所には存在しない正規の文字列です。コンテキストを設定するときに、SetSecurityToken
を使用してセキュリティトークンをオプションで指定できます。セキュリティトークンを指定しない場合、V8は作成しているコンテキストのトークンを自動的に生成します。
グローバル変数にアクセスしようとすると、V8セキュリティシステムは最初に、アクセスされているグローバルオブジェクトのセキュリティトークンと、グローバルオブジェクトにアクセスしようとしているコードのセキュリティトークンを照合します。トークンが一致すると、アクセスが許可されます。トークンが一致しない場合、V8はコールバックを実行して、アクセスを許可するかどうかを確認します。オブジェクトテンプレートのSetAccessCheckCallbacks
メソッドを使用して、オブジェクトのセキュリティコールバックを設定することにより、オブジェクトへのアクセスを許可するかどうかを指定できます。その後、V8セキュリティシステムは、アクセスされているオブジェクトのセキュリティコールバックを取得し、それを呼び出して、別のコンテキストがアクセスを許可されているかどうかを尋ねることができます。このコールバックには、アクセスされているオブジェクト、アクセスされているプロパティの名前、アクセスの種類(たとえば、読み取り、書き込み、または削除)が渡され、アクセスを許可するかどうかが返されます。
このメカニズムはGoogle Chromeに実装されているため、セキュリティトークンが一致しない場合、特別なコールバックを使用して、次のものへのアクセスのみが許可されます。window.focus()
、window.blur()
、window.close()
、window.location
、window.open()
、history.forward()
、history.back()
、およびhistory.go()
。.
例外 #
V8は、エラーが発生した場合に例外をスローします。たとえば、スクリプトまたは関数が存在しないプロパティを読み取ろうとした場合、または関数ではない関数が呼び出された場合などです。
操作が成功しなかった場合、V8は空のハンドルを返します。したがって、コードが実行を継続する前に、戻り値が空のハンドルではないことを確認することが重要です。Local
クラスのパブリックメンバー関数IsEmpty()
を使用して、空のハンドルを確認します。
TryCatch
を使用して例外をキャッチできます。次に例を示します。
v8::TryCatch trycatch(isolate);
v8::Local<v8::Value> v = script->Run();
if (v.IsEmpty()) {
v8::Local<v8::Value> exception = trycatch.Exception();
v8::String::Utf8Value exception_str(exception);
printf("Exception: %s\n", *exception_str);
// ...
}
返される値が空のハンドルであり、TryCatch
を使用していない場合、コードは処理を中断する必要があります。TryCatch
を使用している場合は、例外がキャッチされ、コードは処理を続行できます。
継承 #
JavaScript は *クラスのない* オブジェクト指向言語であり、そのため、古典的な継承ではなくプロトタイプ継承を使用します。これは、C++ や Java のような従来のオブジェクト指向言語で訓練されたプログラマーにとっては、戸惑うかもしれません。
Java や C++ のようなクラスベースのオブジェクト指向言語は、クラスとインスタンスという 2 つの異なるエンティティの概念に基づいています。JavaScript はプロトタイプベースの言語であるため、この区別を設けません。単にオブジェクトがあります。JavaScript は、クラス階層の宣言をネイティブにサポートしていません。しかし、JavaScript のプロトタイプメカニズムは、オブジェクトのすべてのインスタンスにカスタムプロパティとメソッドを追加するプロセスを簡素化します。JavaScript では、オブジェクトにカスタムプロパティを追加できます。例えば
// Create an object named `bicycle`.
function bicycle() {}
// Create an instance of `bicycle` called `roadbike`.
var roadbike = new bicycle();
// Define a custom property, `wheels`, on `roadbike`.
roadbike.wheels = 2;
このように追加されたカスタムプロパティは、そのオブジェクトのインスタンスに対してのみ存在します。bicycle()
の別のインスタンス、例えば mountainbike
を作成した場合、wheels
プロパティが明示的に追加されない限り、mountainbike.wheels
は undefined
を返します。
これがまさに必要な場合もありますが、オブジェクトのすべてのインスタンスにカスタムプロパティを追加すると便利な場合があります。結局のところ、すべての自転車には車輪があります。ここで、JavaScript のプロトタイプオブジェクトが非常に役立ちます。プロトタイプオブジェクトを使用するには、カスタムプロパティを追加する前に、オブジェクトのキーワード prototype
を参照します。次のようにします。
// First, create the “bicycle” object
function bicycle() {}
// Assign the wheels property to the object’s prototype
bicycle.prototype.wheels = 2;
これで、bicycle()
のすべてのインスタンスに、wheels
プロパティが組み込まれるようになりました。
V8 では、テンプレートで同じアプローチが使用されます。各 FunctionTemplate
には、関数のプロトタイプのテンプレートを提供する PrototypeTemplate
メソッドがあります。PrototypeTemplate
にプロパティを設定し、C++ 関数をそれらのプロパティに関連付けることができます。これらは、対応する FunctionTemplate
のすべてのインスタンスに存在します。例えば
v8::Local<v8::FunctionTemplate> biketemplate = v8::FunctionTemplate::New(isolate);
biketemplate->PrototypeTemplate().Set(
v8::String::NewFromUtf8(isolate, "wheels"),
v8::FunctionTemplate::New(isolate, MyWheelsMethodCallback)->GetFunction()
);
これにより、biketemplate
のすべてのインスタンスのプロトタイプチェーンに wheels
メソッドが組み込まれるようになります。このメソッドが呼び出されると、C++ 関数 MyWheelsMethodCallback
が呼び出されます。
V8 の FunctionTemplate
クラスは、関数テンプレートを別の関数テンプレートから継承させたい場合に呼び出すことができるパブリックメンバー関数 Inherit()
を提供します。次のようにします。
void Inherit(v8::Local<v8::FunctionTemplate> parent);