本文翻译自 http://write.blog.csdn.net/postedit
Handle和垃圾收集
handle在v8中,用于记录js对象在堆中的位置。v8的垃圾收集器在收集那些不可到达的内存时,会对堆进行整理。它会移动对象的位置,以达到优化内存的目的。
当v8的垃圾收集器移动js对象的位置时,它会同时更新handle的值,让handle能够指向对象新的位置。
v 8中有两种handle
本地handle (LocalHandle) 。 这种handle存在于堆栈之上,并且在析构函数被调用时释放js对象。它的生命周期取决于handle scope, handle scope通常在函数调用的开始位置声明。当handle scope 被删除,垃圾收集器将回收被这些handle指向的对象。
Local Handle通过类 Handle<SomeType>来管理。
注意:handle stack不同于c++的call stack。但是handle scope可以嵌入到c++的stack中。Handle scope只能是作为栈变量,不同通过new方法分配。
比如,google chrome就会使用持久handle保持一个DOM节点。创建一个支持hnandle,需要使用Persisten类的构造,删除则通过Persistent::Dispose。
通过Persistent::MakeWeak,持久handle可以被标记为 weak。
持久Handle使用 Persistent<SomeType>。
当你需要创建很多handle的时候,就需要用到handle scope。handle scope是一个handle容器。当handle scope被删除时,handle scope管理的所有handle都会被删除。
请看例子 使用C++调用V8 。下图介绍了例子中代码生成的handle对象。注意 Context::New()返回的是一个本地handle,我们为这个本地handle创建了一个持久handle来演示持久handle的用法。
当HandleScope::~HandleScope调用时,如果没有其他的引用,handle scope内的handle将在下次垃圾收集时被移除。
对象source_obj和script_obj将在他们不再被其他任何handle引用,也不会被javascript中的代码引用时被垃圾收集器回收。
但是context是一个持久handle,它将不会被回收,除非主动调用它的Dispose函数。
这里需要注意一个陷阱:你不能从一个声明了handle scope的函数中直接返回一个本地handle。这种情况下,在你返回local handle之前,handle scope就会将该handle删除。
正确的方式,是使用HandleScope::Close方法来返回。如下例:
// This function returns a new array with three elements, x, y, and z.
Handle<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.
HandleScope handle_scope(isolate); //声明了HandleScope了
// Create a new empty array.
Handle<Array> array = Array::New(3);
// Return an empty result if there was an error creating the array.
if (array.IsEmpty())
return Handle<Array>();
// Fill out the values
array->Set(0, Integer::New(x));
array->Set(1, Integer::New(y));
array->Set(2, Integer::New(z));
// Return the value through Close.
return handle_scope.Close(array); //这样才能正确返回一个本地handle
}
Contexts
在v8中, context是一个分离的、独立的javascript执行环境。当你运行一段js代码时,你必须明确给出一个context。
为什么这是必须的?因为javascript提供了一组内建的函数和对象,这些都可以被javascript代码改变。如果两个完全不相关的javascript代码更改了全局的对象,就会造成不可预知的结果。
在CPU时间和内存角度看,创建一个执行环节似乎是一件很耗费时间和资源的事情,但是,v8通过广泛的缓存策略,仅让你第一次创建context时花销会比较大,后面再创建任意多的context的花销就很小了。
当第一次创建时,v8必须创建内建对象,并解析内建js代码。而后续context只需要创建他们自己的内建对象。
如果v8设置上了snapshot特性(build选项 snapshot=yes,这是默认值),第一次所花费的开销将大幅被优化。
当你创建了一个context后,你可以进入退出它任意多次。当你在context A的时候,你可以进入一个不同的context,B。这时,context B将取代A成为你的当前Context。 当你退出context B的时候,A就自动成为你的当前context:
注意:不同context内的内建函数和对象是相互分离的。
templates
一个template是javascript函数的蓝图。你可以使用一个template来将c++函数和结构体包装到javascript对象中,让javascirpt脚本来使用它。例如,google chrome使用template来包装c++ DOM节点为js对象,并提供全局访问函数。
你也可以创建一组template让所有的context来共享使用。你可以创建任意多的template。但是每个template在context中只有一个实例。
在javascript中,function和object有很大的重叠。在java和c++中,我们通过定义一个类来创建一个新的类型,而在javascirpt中,我们通过创建一个新的function,然后用这个function作为构造函数来创建一个实例。javascript对象的内存组织方式和功能都非常接近于创建它的函数。这影响到了v8 template的工作方法。
有两种template:
function template。这是创建一个独立函数的模板。创建一个javascript的template实例,需要通过template的GetFunction方法。你也可以给这个function template关联一个回调函数。当javascript函数实例调用时,这个回调会被访问。
object template。每个function template都会有一个关联的object template。这是为关联创建该对象的函数。你可以为object template关联两个类型的回调:
- accessor callbacks : 当脚本访问特定对象的属性时调用;
- interceptor callbacks 当脚本访问任意对象属性时调用;
下面的代码战士了如何使用template为全局对象增加内建函数的方法
// Create a template for the global object and set the
// built-in global functions.
Handle<ObjectTemplate> global = ObjectTemplate::New();
global->Set(String::New("log"), FunctionTemplate::New(LogCallback));
// Each processor gets its own context so different processors
// do not affect each other.
Persistent<Context> context = Context::New(NULL, global);
Accessors
一个accessor是一个c++的回调函数。当javascript脚本方位一个对象属性时,它被调用并返回一个值。
accessor通过object template来设置。使用SetAccessor方法。该方法需要提供一个属性名和关联的两个回调(读写)作为参数。
访问全局静态变量
假设有两个C++整型变量,x和y,被javascript当做全局变量来使用。要实现它,javascript需要调用C++的accessor函数。这些accessor函数通过Integer::New将C++整型转换为javascript整数;通过Int32Value将javascript整数转为c++整数。例子如下:
Handle<Value> XGetter(Local<String> property,
const AccessorInfo& info) {
return Integer::New(x);
}
void XSetter(Local<String> property, Local<Value> value,
const AccessorInfo& info) {
x = value->Int32Value();
}
// YGetter/YSetter are so similar they are omitted for brevity
Handle<ObjectTemplate> global_templ = ObjectTemplate::New();
global_templ->SetAccessor(String::New("x"), XGetter, XSetter);
global_templ->SetAccessor(String::New("y"), YGetter, YSetter);
Persistent<Context> context = Context::New(NULL, global_templ);
注意:object template和context是同时创建的。object template可以被一次创建并被多个context使用。
访问动态变量
假设x和y是C++类Point的成员变量:
class Point {
public:
Point(int x, int y) : x_(x), y_(y) { }
int x_, y_;
}
如果让C++的point实例可以被javascript使用,我们需要为每个C++ point创建一个javascript对象,并且在javascript对象和c++对象之间建立关联。
第一步,创建针对Point对象的object template
Handle<ObjectTemplate> point_templ = ObjectTemplate::New();
point_templ->SetInternalFieldCount(1);
这里,我们设置了一个长度为1的内部field,它的索引从0开始。
然后我们可以添加x和y的访问子:
point_templ.SetAccessor(String::New("x"), GetPointX, SetPointX);
point_templ.SetAccessor(String::New("y"), GetPointY, SetPointY);
下面,要将一个C++的point实例和javascript的代码关联。我们通过External对象来作为中介。因为javascript不知道如何处理一个c++对象。
Point* p = ...;
Local<Object> obj = point_templ->NewInstance();
obj->SetInternalField(0, External::New(p));
External实际上是对一个void* 只针的封装。
下面,我们就可以实现对x和y的get和set访问了:
Handle<Value> GetPointX(Local<String> property,
const AccessorInfo &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_;
return Integer::New(value);
}
void SetPointX(Local<String> property, Local<Value> value,
const AccessorInfo& 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();
}
Interceptors
有两种Interceptor,- 命名属性interceptor - 通过属性的字符串名来访问,如: document.theFormName.elementName
- 索引属性interceptor - 通过索引访问的属性,如 document.forms.elements[0];
V8源代码中的示例process.cc展示了一个使用interceptors的例子。下面是SetNamedPropertyHandler中关于MapGet和MapSet的interceptor部分代码
Handle<ObjectTemplate> result = ObjectTemplate::New();
result->SetNamedPropertyHandler(MapGet, MapSet);
Handle<Value> JsHttpRequestProcessor::MapGet(Local<String> name,
const AccessorInfo &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 Handle<Value>();
// Otherwise fetch the value and wrap it in a JavaScript string.
const string &value = (*iter).second;
return String::New(value.c_str(), value.length());
}
安全模型
”同源“策略阻止不同源内的数据相互访问。
v8通过一个context定义一个”源“。默认情况下,跨context访问是不允许的。为了访问其他context,你需要一个安全token或者安全回调。
安全token可以是任意值,但是通常会使一个符号,一个全局唯一的字符串。你也可以通过SetSecurityToken来得到一个特殊的安全token。如果你不指定的话,v8会在你创建context时自动生成一个。
当一个对全局变量访问产生时,v8的安全系统首先检测全局对象的安全token和试图访问它的代码的安全token是否一致,如果token一致,那么访问将被授权。
如果token不一致,那么v8将通过一个回调来判断是否允许访问。
你可以通过SetAccessCheckCallbacks方法来设置这样一个回调。
异常
当有错误发生时,v8将抛出异常。如,当脚本或者函数试图访问一个不存在的属性时,或者对一个非函数进行调用时。
当操作不成功时,v8会返回一个空的handle。你的代码需要检查v8的返回值是否为空。这一点很重要。 通过函数IsEmpty()
你可以通过TryCatch来捕捉异常,如
TryCatch trycatch;
Handle<Value> v = script->Run();
if (v.IsEmpty()) {
Handle<Value> exception = trycatch.Exception();
String::AsciiValue exception_str(exception);
printf("Exception: %s\n", *exception_str);
// ...
}
如果value返回为空,而且你没有一个TryCatch对象,那么你的代码会跳出。如果有TryCatch,异常就会被捕捉,你的代码会继续进行。
继承
javascript是一种无类别的面向对象语言,它使用原型继承来代替类继承。这让习惯传统面向对象语言的程序员困惑。
基于类的面对对象语言,如java, C++,是建筑在对类和对象严格区分的基础上的。javascript是一种基于原型的语言,因而没有这么严格的区分,它只有简单的对象。
javascript不能直接支持类继承,但是javacript的原型机制可以很简单的的实现自定义属性和方法的添加。在javascript中,你可以自定义对象的属性,如
// 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
这个方法添加到了已经存在的对象实例上。
加入我创建另外一个bycycle的实例,如mountainbike,那么mountainbike.wheels将返回undefined,除非你用同样的方法添加它。
有时,很需要这种需求。希望为每个bycycle对象的实例头添加相同的书写。这是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中通过template实现。每个FunctionTemplate都有一个PrototypeTemplate方法,可以设置function的原型。你可以设置属性、绑定c++函数等,这些都通过PrototypeTemplate作用到所有的实例上。如
Handle<FunctionTemplate> biketemplate = FunctionTemplate::New();
biketemplate->PrototypeTemplate().Set(
String::New("wheels"),
FunctionTemplate::New(MyWheelsMethodCallback)->GetFunction();
)
这将使所有的
biketemplate
实例都有wheels方法。当实例的wheels被调用时,C++函数MyWheelsMethodCallback就会被调用。
V8的FunctionTemplate类还提供了一个公共方法Inhert(),你可以通过它来继承任意的FunctionTemplate,如
void Inherit(Handle<FunctionTemplate> parent);