Node.js的C++扩展教程(三)
今天我们来讲一下V8中句柄的概念。
句柄的定义
我们知道Windows下的应用程序,在打开时是一个窗口,这个窗口是有一个句柄的,称作窗口句柄。在Windows API中有 Handle handle = ::FindWindow(NULL, ClassName);
用于查找窗口句柄。但是这个句柄跟我们今天讲的V8中的句柄是两回事。
句柄在V8中是一个很重要的概念,它提供了对于堆内存中JavaScript数据对象的一个引用。在C++中传递参数用于同步数据有两种方式:一个是指针,一个是引用。那么同样是为了找到数据,为什么不用指针,而是使用引用呢?
我们都知道V8中存在垃圾回收机制一说,在垃圾回收的时候会不断的搬运堆数据,从一个地方移动到另一个地方。如果使用指针的话,一旦一个对象被移走,这个指针就变成了野指针。但垃圾回收器更新引用了这些数据库的那些句柄,让其断不了联系。
当一个对象不再被句柄引用时,那么它将很不幸地被认为是垃圾。Chrome V8的垃圾回收机制会不时地对其进行回收。 所以,句柄的引用对于Chrome V8的回收机制是很关键的。
句柄分类
句柄分为以下几类:
- 本地句柄(v8::Local)
- 持久句柄(v8::Persistent)
- 永生句柄(v8::Eternal)
- 待实本地句柄(MaybeLocal)
- 其他句柄
其中本地句柄(相当于一个局部变量)和持久句柄(相当于一个New变量)是最常用到的句柄。句柄存在的形式是C++的一个模板类,其需要根据不同的Chrome V8的数据类型进行不同的声明。例如:
v8::Local<v8::Number>
:本地JavaScript数值类型句柄。v8::Persistent<v8::String>
:持久JavaScript字符串类型句柄。
这些句柄都能通过 .方法名
来访问句柄对象的一些方法。而且它还重载了 *
和 ->
两个操作符。通过 *
操作符能得到这个句柄所引用的JavaScript数据对象实体指针,->
也一样。举个例子:假设我们有一个字符串本地句柄Local<String> str
,那么下面两种调用方式的意义就不一样:
- str.IsEmpty():句柄对象本身的函数,来判断这个句柄是否是一个空句柄。
- str->Lenghth():通过
->
得到String*,而String有一个方法Length可获得字符串长度。所以str->Length()是用于判断这个字符串包含多少个字符。另一种写法是(*str).Length()。
本地句柄
本地句柄(Local)存在于栈内存中,并在对应的析构函数被调用时被删除。这里的析构函数是句柄本身的析构函数,一般在块作用域或者函数作用域的时候被析构。也就是说它的生命周期是由其所在的句柄作用域(Handle Scope)决定的。在C++中,句柄作用域等同于块级作用域、函数作用域或者全局作用域。
1.创建(New)
new 是句柄的一个静态方法(直接使用类名来使用)。有两种方式:
Local<T>::New(Isolate* isolate, Local<T> that)
,通过另一个本地句柄进行复制构造。Local<T>::New(Isolate* isolate, const PersistentBase<T> &that>
:传入Isolate和一个持久句柄。
大多数情况下,我们都是通过Chrome V8中的JavaScript数据类型的一些静态方法来获取一个本地句柄,例如:
Local<Number> Number::New(Isolate* isolate, double value);
2.清除(Clear)
将指定句柄设置为一个空句柄——也就是一个指向null的句柄。
Local<Number> handle = Number::New(isolate, 233);
handle.Clear();
3.是否为空(IsEmpty)
不能使用与null比较的方式来判断,只能通过IsEmpty()函数来判断。
Local<Number> handle = Number::New(isolate, 233);
if(handle.IsEmpty())
{
...
}
4.数据类型转换(As/Cast)
将某种数据类型的本地句柄转换成另一种类型的本地句柄,就可以使用As或者Cast函数。其中As是成员函数,Cast是静态函数。
比如我们有一个Local<Value>
的本地句柄handle,我们就能通过这两个函数进行转换:
Local<Number> ret1 = Local<Number>::Cast(handle);
Local<Number> ret2 = handle.As<Number>();
持久句柄
持久句柄提供了一个堆内存中声明的JavaScript对象的引用。持久句柄和本地句柄在生命周期上的管理是两种不同的方式。
持久句柄通过SetWeak()的方式让句柄变成一个弱持久句柄。当对一个JavaScript对象的引用只剩下一个弱持久句柄时,Chrome V8的垃圾回收器就会触发一个回调。
1.构造函数
持久句柄一般是通过本地句柄升格而成。所以它的获取方法通常是在构造函数中传入一个本地句柄。
Persistent()
:先构建一个空的持久句柄,后期通过设置的方法将一个本地句柄转成持久句柄。Persistent(Isolate* isolate,Local<T> that)
:传入Isolate实例以及一个本地句柄,能得到这个本地句柄所引用的V8数据对象的一个持久句柄。Local
handle = Number::New(isolate, 2333);
Persistentphandle(isolate, handle);
2.清除(Clear)
和本地句柄使用方法一样。
3.是否为空(IsEmpty)
和本地句柄使用方法一样。
4.设置为弱持久句柄(SetWeak)
将持久句柄降格为一个弱持久句柄。函数原型如下:
template <typename p>
void Persistent<T>::SetWeak(P *parameter, WeakCallbackInfo<P>::Callback callbak, WeakCallbackType type);
参数意思如下:
- parameter:可以是任意数据类型。
- callack:一个回调函数。前文说过对于V8数据类型只有一个弱持久句柄的引用时,会触发一个回调函数,这个回调函数就是这里的callback。触发回到函数时,第一个参数parameter会被传入回到函数供你使用。
- type:回调函数的类型,是一个枚举值。
WeakCallbackInfo<T>::Callback
回调函数的原型如下:
typedef void(*Callback)(const WeakCallbackInfo<T> &data);
讲的通俗一点,如果以有一个类Test,要将它作为回调参数的话,回调函数就应该这么写:
void callback(const WeakCallbackInfo<Test>& data)
{
// 做回调的内容,比如删除掉你的 `Test` 对象指针以免泄漏
// 因为这个回调函数一旦被触发,就代表再也没有其他本地句柄、持久引用等
// 任何句柄指向它了。
Test* test = data.GetParameter();
delete test;
}
有了这个回调函数后,就能将它传入SetWeak函数中。
// 没有任何其他地方删除它
Test* test = new Test();
//假设persistent_handle是任意一个持久句柄
persistent_handle.SetWeak(test, Callback, WeakCallbackType::kParameter);
这样,在persistent_handle快要被垃圾回收时,就会调用回调函数里的代码,将test对象delete掉。
5.取消弱持久句柄(ClearWeak)
其作用是取消它的弱持久句柄——又变成一般的持久句柄了。
该函数没有参数:
persistent_handle.ClearWeak();
6.标记独立句柄(MarkIndependent)
独立的持久句柄有两个特性:
- Chrome V8的垃圾回收器可以自由地忽略包含这个句柄的对象组。
- 独立的持久句柄可以在新生代回收的时候被回收,而非独立句柄则不行。
7.是否为弱的(IsWeak)与是否为独立的(IsIndependent)
bool weak = persistent_handle.IsWeak();
bool independent = persistent_handle.IsIndependent();
永生句柄(Eternal)
我们认为这种句柄在程序的整个声明周期内是不会被删除的。类似C++中的全局变量。
待实本地句柄(MaybeLocal)
待实本地句柄是为了保证程序的健壮性而设计的。在原先的设计中,获取本地句柄时是需要判断句柄是否为空(IsEmpty)后,才能使用。而MaybeLocal就替代了这种检测,所以现在的代码就写成了:
MaybeLocal<String> s = x.ToString();
Local<String> _s = s.ToLocalChecked();
或者
Local<String> _s = x.ToString().ToLocalChecked();
总之:有有效句柄连接的对象实体不会被垃圾回收器进行回收。而失去了所有句柄引用的对象实体会被认为是垃圾,从而在下次垃圾回收的时候被释放。
还没有评论,来说两句吧...