A闪的 BLOG 技术与人文
存取器是一个当对象属性被 JS 代码访问的时候计算并返回一个值的 C++ 回调. 存取器是通过 Object Template 的 SetAccessor 方法进行配置的. 该方法接收属性的名称和与其相关联的回调函数, 分别在 JS 读取和写入该属性时触发.
通过为Object开放一个属性,来访问C++中的变量。下面实例中,在全局对象中设置名称为x的属性,并绑定一个C++变量。
int x = 0;
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();
}
Local<ObjectTemplate> global_templ = ObjectTemplate::New(isolate);
global_templ->SetAccessor(String::NewFromUtf8(isolate, "x"), XGetter, XSetter);
Persistent<Context> context = Context::New(isolate, NULL, global_templ);
如果把ObjectTemplate看作一个类型的实例的话,那么Accessors相当于定义属性。
假设有如下C++类
class Point {
public: Point(int x, int y) : x_(x), y_(y) { }
int x_, y_;
}
为了让任意多个 C++ Point 实例在 JS 中可用, 需要为每一个 C++ Point 创建一个 JS 对象, 并将它们联系起来. 这可以通过外部值和内部成员实现.
首先为 point 创建一个 Object template 封装对象:
Local<ObjectTemplate> point_templ = ObjectTemplate::New(isolate);
每个 JS point 对象持有一个 C++ 封装对象的引用, 封装对象中有一个 Internal Field, 之所以这么叫是因为它们无法在 JS 中访问, 而只能通过 C++ 代码访问. 一个对象可以有任意多个 Internal Field, 其数量可以按以下方式在 Object Template 上设置.
point_templ->SetInternalFieldCount(1);
此处的 internal field count 设置为了 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);
接下来通过创建一个新的 template 实例来封装一个 C++ point, 将封装对象的 interanl field 设置为 0.
Point* p = ...;
Local<Object> obj = point_templ->NewInstance();
obj->SetInternalField(0, External::New(isolate, p));
以上代码中, 外部对象就是一个 void* 的封装体. 外部对象只能用来在 internal field 上存储引用值. JS 对象无法直接引用 C++ 对象, 因此可以将外部值当作是一个从 JS 到 C++ 的桥梁.
以下是 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();
}