NodeJS 集成了 V8 作为 JS 引擎。利用 V8 暴露出来的 API 可以高效地和 NodeJS 进行通信,从而编写自己需要的 native 模块。实际上,NodeJS 底层很大程度上也是直接使用了 Chrome V8 所暴露的API。
一、基本概念
- 内存机制
1、V8 中 JS 数据类型都是由 V8 内部的内存机制进行管理的
2、V8是由C++编写的,所以在JS中声明一个变量,本质上对应一个C++变量,C++这边也可以操作这个变量
3、V8垃圾回收的前提是变量既没有被JS引用,也没有被对应的C++引用
4、新生代内存区分为两个 Semispace: From 和 To,内存分配从 From 进行,回收时,检查 From 区存活的对象并复制到To空间中,然后 From 和 To 角色互换
5、当新生代中的对象经过多次垃圾回收后仍然存活,则其可以进行对象晋升到老生代内存区
6、老生代内存区使用标记清除(容易产生内存碎片)或者标记整理(效率稍低,但不会产生碎片)管理内存
- 基本数据类型
1、隔离实例 Isolate,即一个 V8 实例,本身不执行 JS,也没有 JS 环境的执行上下文
// 实例参数
v8::Isolate::CreateParams create_params;
// 创建 v8 实例
v8::Isolate *isolate = v8::Isolate::New(create_params)
不过,在进行 NodeJS 的 C++ 扩展时不需要手动创建 v8 实例,因为以及处于 v8 环境中了,直接获取 NodeJS 环境所使用的实例即可:
void Method(const v8::FunctionCallbackInfo<v8::Value>& args)
Isolate * isolate = args.GetIsolate();
2、上下文 Context,用来定义 JS 执行环境的一个对象,在创建时需要和 v8 实例 isolate 绑定
v8::Isolate * isolate = ...
v8::Local<v8::Context> context = v8::Context::New(isolate);
开发 NodeJS 的 C++ 扩展时通常不需要这个对象
3、脚本 Script,包含一段已经编译好的 JS 脚本对象,编译时需要和一个上下文 context 绑定
v8::Local<v8::Context> context = ...
v8::Local<v8::String> source = 一段 JS 代码
// 绑定上下文并编译
v8::Local<v8::Value> script = v8::Script::Compile(context,source).ToLocalChecked();
// 执行脚本
v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
二、句柄和句柄作用域
1、句柄和引用的区别:v8 在进行垃圾回收的时候,会对对象进行多次移动,一旦移动了对象,该对象的引用就变成了游离的,即失去了与该对象的联系。但是句柄仍然可以正确找到该对象
2、常用句柄:本地句柄(v8::Local),持久句柄(v8::Persistent),待实本地句柄(v8::MaybeLocal)
3、本地句柄存在于栈内存,其生命周期由其所在的句柄作用域决定。当句柄作用域对象被删除的时候,在该作用域内创建的句柄所指向的对象会被垃圾回收(没有被其它地方引用)
4、本地句柄基本操作:
a、创建 New
Local<Number> Number::New(Isolate* isolate,double value)
b、清除 Clear,即设置为空句柄
Local<Number> number = Number::New(isolate,123);
number.Clear()
c、是否为空 IsEmpty
Local<Number> number = Number::New(isolate,123);
if(number.IsEmpty()){
}
d、类型转换 As/Cast,As 是实例函数,Cast 是静态函数
Local<Number> number = Local<Number>::Cast(handle); // 假设 handle 是 Local<Value>句柄
Local<Number> number = handle.As<Number>();
5、持久句柄,对应于JS中堆内存中的引用,可通过 SetWeak 使持久句柄变为弱持久句柄,当一个JS对象的引用只剩下弱持久句柄时,v8 垃圾回收就会触发一个回调
6、持久句柄还可分为:唯一持久句柄(v8::UniquePersistent)和一般持久句柄(v8::Persistent),一般持久句柄需要显示调用 Reset 方法才能将其清除
7、持久句柄基本操作
持久句柄通常是通过本地句柄升格而成。
a、创建 Persistent
Local<Number> local = Number::New(isolate,123);
Persistent<Number> persistent = Persitent(local)
Persistent<Number> persistent = Persitent(isolate,local)
b、清除 Clear 和判空 IsEmpty
c、置为弱持久句柄实例函数 SetWeak 和 取消弱持久句柄实例函数 ClearWeak
d、标记为独立持久句柄 MarkIndependent,独立持久句柄何以在新生代回收时被回收,而非独立句柄则不行,在开发C++扩展时,一般和 SetWeak 成对出现,因为通常是期望弱持久句柄尽早被回收
persistent_handle.SetWeak(test,Callback,WeakCallbackType::kParameter);
persistent_handle.MarkIndependent();
e、是否为弱句柄 IsWeak,是否为独立 IsIndependent
8、代实本地句柄 MaybeLocal。当一个函数返回这种句柄,说明返回值可能为空,需要做判空处理,如果要拿到真实本地句柄,需要调用 ToLocalChecked 方法
Local<Value> x = some_value;
MaybeLocal<String> s = some_value.ToString();
// 判空
if(!s.IsEmpty()){
Local<String> s_local = s.ToLocalChecked();
}
9、句柄作用域是一个维护一堆句柄的容器,当一个句柄作用域对象的构析函数被调用时,这个作用域内创建的句柄都会被垃圾回收,EscapableHandleScope 除外
10、句柄作用域以 HandleScope 或者 EscapableHandleScope 存在于栈内存中
11、句柄作用域常见操作
Isolate::Scope isolate_scope(isolate);
// 在栈中创建一个句柄作用域
HandleScope handle_scope(isolate);
// 创建可逃句柄作用域
EscapableHandleScope scope(isolate);
scope.Escape(..)
三、模版
v8 中的模版是JS函数或者对象的模具,可以通过模具将 C++ 函数或者数据结构包成JS的函数或对象
- 函数模版 FunctionTemplate
```cpp
void Method(const FunctionCallbackInfo
& args) { Isolate* isolate = args.GetIsolate(); args.GetReturnValue().Set(String::NewFromUtf8(isolate, “this is a function”)); }
void init(Local