前驱知识准备

模块机制

CMJ 规范的可使用场景:

  • 服务端JS应用
  • 命令行工具
  • 图形化用户界面(GUI)桌面应用
  • 混合应用(如Titanium,Adobe AIR等)

模块上下文

module.exports | exports

模块寻径算法

  • .js => .json => .node
  • 递归父目录的node_modules

    模块缓存机制

    通常一个包一旦被加载了,那么在第二次执行require()的时候就会在缓存中获取暴露的API,而不会重新加载一遍该模块里面的代码再次返回。

包机制

包描述文件

包目录

  • package.json 在根目录下
  • 二进制文件应当在bin目录下
  • JS源码应当在lib目录下
  • 文档应当在doc目录下
  • 单元测试文件应当在test目录下

Node依赖简介(deps)

高效的V8

  • JIT(Just in time):即时编译,编译出的结果直接是机器语言,而非字节码
  • 垃圾回收:借鉴了 JAVA VM 的精确垃圾回收管理,别家是保守垃圾管理
  • 内联缓存(InLine Cache):提高属性的访问效率,无须每次访问属性都对哈希表进行一次寻址,有了缓存,V8能立马知道这个属性的偏移量,不用再次计算寻址的偏移量了
  • 隐藏类:所有如属性一样的对象会被归为同一个隐藏类

    驱动事件循环的 libuv

  • 基于epoll/kqueue/IOCP/event ports实现的全能事件循环

  • 异步TCP和UDP套接字
  • 异步DNS解析
  • 异步文件、文件系统操作
  • 文件系统事件
  • ANSI转义码控制的TTY
  • 使用UNIX domain 套接字或者命名管道实现的套接字共享IPC
  • 子进程
  • 线程池
  • 信号(Signal)处理
  • 高精度时钟
  • 线程和同步元

    http-parser

    一个C实现的HTTP消息解析器

  • 无依赖

  • 持久化连接的流式处理(keep-alive)
  • 分段信息(chunk)的解码
  • 缓冲区溢出攻击的防御
  • 解析HTTP消息
    • 消息头键值对(Header)
    • 内容长度(Content-Length)
    • 请求方法(GET\POST\PUT\DELETE)
    • 返回状态码(Status Code)
    • 传输编码(Transfer-Encoding)
    • HTTP版本
    • 请求地址(URL)
    • 消息体

      OpenSSL

      一套安全套接字层协议库。实现了基本的加密功能:SSL与TLS协议。

zlib

提供数据压缩功能的库。
facebook开发的另一套zstd据说压缩比zlib优秀。

环境

node-gyp

  1. npm install -g node-gyp # 全局安装
  2. node-gyp -h # 查看帮助
  3. NODEJS_ORG_MIRROR=https://npm.taobao.org/mirrors/node node-gyp install # 把当前执行的node.js相应版本的源码头文件、lib文件等下载解压到当前用户目录的.node-gyp目录下
  4. node-gyp build # 调用make以构建模块
  5. node-gyp clean # 清理生成的构建文件以及out目录
  6. node-gyp configure # 为当前模块生成Makefile项目配置文件
  7. node-gyp rebuild # 一次性依次执行clean/configure和build
  8. node-gyp install # 为指定版本的node.js安装开发环境的文件
  9. node-gyp list # 输出当前安装的node.js开发环境文件
  10. node-gyp remove # 移除指定版本的node.js开发环境文件

:::info 扩展:install命令有一个环境变量NODEJS_ORG_MIRROR,这个变量指向安装包的registry,因此将其设置为阿里巴巴的镜像地址,就能够顺利下载node文件 :::

什么是C++拓展

  • 一个编译好的C++模块除了后缀名是.node之外,它其实就是一个系统的动态链接库。相当于Windows下的.dll、Linux下的.so、macOS 下的.dylib
  • 在 node.js 中引入一个C++模块的过程实际上就是Node.js在运行时引入了一个动态链接库的过程。

    模块加载原理

    nodev6.9.4
    node入口:node_main.cc
    1. int main(int argc, char *argv[]) {
    2. // Disable stdio buffering, it interacts poorly with printf()
    3. // calls elsewhere in the program (e.g., any logging from V8.)
    4. setvbuf(stdout, nullptr, _IONBF, 0);
    5. setvbuf(stderr, nullptr, _IONBF, 0);
    6. return node::Start(argc, argv);
    7. }
    Start函数是 node.cc 中的,进一步深入到LoadEnvironment会发现如下代码:
    1. // Execute the lib/internal/bootstrap_node.js file which was included as a
    2. // static C string in node_natives.h by node_js2c.
    3. // 'internal_bootstrap_node_native' is the string containing that source code.
    4. Local<String> script_name = FIXED_ONE_BYTE_STRING(env->isolate(), "bootstrap_node.js");
    5. Local<Value> f_value = ExecuteString(env, MainSource(env), script_name);
    6. // ...
    7. Local<Function> f = Local<Function>::Cast(f_value);
    8. // ...
    9. // We start the process this way in order to be more modular. Developers
    10. // who do not like how bootstrap_node.js sets up the module system but do
    11. // like Node's I/O bindings may want to replace 'f' with their own function.
    12. Local<Value> arg = env->process_object();
    13. f->Call(Null(env->isolate()), 1, &arg);
    这段代码的意思是node.js执行lib/internal/bootstrap_node.js文件以进行初始化启动。执行该文件后得到的f_value就是bootstrap_node.js文件中所实现的那个函数对象。

SetupProcessObject就是用来实现全局对象process的,可以看到其设置API的部分代码如下:

  1. env->SetMethod(process, "hrtime", Hrtime);
  2. env->SetMethod(process, "cpuUsage", CPUUsage);
  3. env->SetMethod(process, "dlopen", DLOpen);
  4. env->SetMethod(process, "uptime", Uptime);
  5. env->SetMethod(process, "memoryUsage", MemoryUsage);
  6. env->SetMethod(process, "binding", Binding);

image.png

node.js 入口启动的流程图

几种模块的加载过程

  • C++核心模块:采用纯C++编写,存在于Node.js源码中且编译进Node.js的可执行二进制文件中。
  • Node.js内置模块:即lib下以同名JS代码形式实现的模块,也被编译进Node.js的可执行二进制文件中了。
  • 用户源码模块:非Node.js内置模块的JS源码模块。如入口模块
  • C++拓展:采用纯C++编写,以动态链接库的形式存在

    C++核心模块

    process.binding

    1. static void Binding(const FunctionCallbackInfo<Value>& args) {
    2. Environment* env = Environment::GetCurrent(args);
    3. // 从参数中获得文件标识的字符串(文件名)并赋值给 module_v
    4. Local<String> module = args[0]->ToString(env->isolate());
    5. node::Utf8Value module_v(env->isolate(), module);
    6. Local<Object> cache = env->binding_cache_object();
    7. Local<Object> exports;
    8. if (cache->Has(env->context(), module).FromJust()) {
    9. exports = cache->Get(module)->ToObject(env->isolate());
    10. args.GetReturnValue().Set(exports);
    11. return;
    12. }
    13. // 将一个字符串附加在 process.moduleLoadList 后面
    14. char buf[1024];
    15. snprintf(buf, sizeof(buf), "Binding %s", *module_v);
    16. Local<Array> modules = env->module_load_list_array();
    17. uint32_t l = modules->Length();
    18. modules->Set(l, OneByteString(env->isolate(), buf));
    19. // 在得到标识字符串后,获取 C++ 核心模块
    20. // get_builtin_module 内部就是在 modlist_builtin 的 C++ 核心模块链表上对比文件标识,从而返回相应的模块
    21. node_module* mod = get_builtin_module(*module_v);
    22. if (mod != nullptr) {
    23. exports = Object::New(env->isolate());
    24. // 内置的模块对象并没有 "module" 对象,只有 exports.
    25. CHECK_EQ(mod->nm_register_func, nullptr);
    26. CHECK_NE(mod->nm_context_register_func, nullptr);
    27. Local<Value> unused = Undefined(env->isolate());
    28. mod->nm_context_register_func(exports, unused,
    29. env->context(), mod->nm_priv);
    30. cache->Set(module, exports);
    31. } else if (!strcmp(*module_v, "constants")) {
    32. exports = Object::New(env->isolate());
    33. DefineConstants(env->isolate(), exports);
    34. cache->Set(module, exports);
    35. } else if (!strcmp(*module_v, "natives")) {
    36. exports = Object::New(env->isolate());
    37. DefineJavaScript(env, exports);
    38. cache->Set(module, exports);
    39. } else {
    40. char errmsg[1024];
    41. snprintf(errmsg,
    42. sizeof(errmsg),
    43. "No such module: %s",
    44. *module_v);
    45. return env->ThrowError(errmsg);
    46. }
    47. args.GetReturnValue().Set(exports);
    48. }

    C++ 核心模块是在 node_module_register中被逐一注册进链表中的。 ```cpp extern “C” void node_module_register(void m) { struct node_module mp = reinterpret_cast(m); // 如果传入待注册模块的标识位是内置模块,就将其加入C++核心模块的链表中;否则将认为是其他模块 if (mp->nm_flags & NM_F_BUILTIN) { mp->nm_link = modlist_builtin; modlist_builtin = mp; } else if (!node_is_initialized) { // “Linked” modules are included as part of the node project. // Like builtins they are registered before node::Init runs. mp->nm_flags = NM_F_LINKED; mp->nm_link = modlist_linked; modlist_linked = mp; } else { modpending = mp; } }

struct node_module get_builtin_module(const char name) { struct node_module* mp;

for (mp = modlist_builtin; mp != nullptr; mp = mp->nm_link) { if (strcmp(mp->nm_modname, name) == 0) break; }

CHECK(mp == nullptr || (mp->nm_flags & NM_F_BUILTIN) != 0); return (mp); }

  1. node.h 中有一个[`NODE_MODULE_CONTEXT_AWARE_X`](https://github.com/nodejs/node/blob/v6.9.4/src/node.h#L458) 宏是用于注册C++核心模块的。即推测只要在C++核心模块中调用该宏就会被注册进Node.js核心模块链中。<br />我们看看file模块的源码[node_file.cc](https://github.com/nodejs/node/blob/v6.9.4/src/node_file.cc#L1511),其最后一行代码
  2. ```cpp
  3. NODE_MODULE_CONTEXT_AWARE_BUILTIN(fs, node::InitFs)

至此,真相大白,基本上每个C++核心模块的源码末尾都有一个宏调用将该模块注册进C++核心模块的链表中,以供process.binding时进行获取。

Node.js内置模块

node的启动脚本lib/internal/bootstrap_node.js中有一个NativeModule的声明,其负责处理Node.js内置模块。

  1. function NativeModule(id) {
  2. this.filename = `${id}.js`;
  3. this.id = id;
  4. this.exports = {};
  5. this.loaded = false;
  6. this.loading = false;
  7. }
  8. // NativeModule._source 是 process.binding的返回结果,即DefineJavaScript的处理结果
  9. NativeModule._source = process.binding('natives');
  10. NativeModule._cache = {};
  11. // 当id值为'native_module'时返回它自身,否则就进入到nativeModule.compile进行编译
  12. NativeModule.require = function(id) {
  13. if (id === 'native_module') {
  14. return NativeModule;
  15. }
  16. //...
  17. process.moduleLoadList.push(`NativeModule ${id}`);
  18. const nativeModule = new NativeModule(id);
  19. nativeModule.cache();
  20. nativeModule.compile();
  21. return nativeModule.exports;
  22. };
  23. // ...
  24. NativeModule.getSource = function(id) {
  25. return NativeModule._source[id];
  26. };
  27. NativeModule.wrap = function(script) {
  28. return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
  29. };
  30. NativeModule.wrapper = [
  31. '(function (exports, require, module, __filename, __dirname) { ',
  32. '\n});'
  33. ];
  34. NativeModule.prototype.compile = function() {
  35. // 通过 NativeModule.getSource 获取源码,其返回的是 NativeModule._source 数组中的相应内容
  36. var source = NativeModule.getSource(this.id);
  37. source = NativeModule.wrap(source);
  38. this.loading = true;
  39. try {
  40. const fn = runInThisContext(source, {
  41. filename: this.filename,
  42. lineOffset: 0,
  43. displayErrors: true
  44. });
  45. // 进行包裹,形成一段闭包代码,之后将其放入到 vm 中运行,并传入事先准备好的 module 和 exports 对象供其导出
  46. fn(this.exports, NativeModule.require, this, this.filename);
  47. this.loaded = true;
  48. } finally {
  49. this.loading = false;
  50. }
  51. };

在 src/node_javascript.cc 中有一个DefineJavaScript,它做的事情就是遍历一遍natives数组里面的内容,并将其一一加入要返回的对象中,其中对象名的键名为源码文件名标识,键值是源码本体的字符串。
那这个natives 又是怎么来的呢——是 node.gyp 编译而来。
node.gyp 其中有一步是 node_js2c,这一步就是用 Python 去调用 tools/js2c.py 的文件,其主要作用就是将lib下的JS文件转换成 src/node_natives.h 文件。这个src/node_natives.h 会在Node.js编译前完成,这样在编译到src/node_javascript.cc 时它所需要的 src/node_natives.h 头文件就存在了。

也就是说,在Node.js中调用 NativeModule.require 时,会根据传入的文件标识来返回相应的 JS 源文件内容。

用户源码模块

与内置模块类似,会被加上一个闭包的头尾,然后Node.js执行这个闭包产生结果。
在源码中执行的require()就是lib/module.js中的Module类实例对象的require()函数

  1. Module.prototype.require = function(path) {
  2. assert(path, 'missing path');
  3. assert(typeof path === 'string', 'path must be a string');
  4. return Module._load(path, this, /* isMain */ false);
  5. };

即直接调用了静态函数Module._load

  1. Module._load = function(request, parent, isMain) {
  2. // ...
  3. var filename = Module._resolveFilename(request, parent, isMain);
  4. var cachedModule = Module._cache[filename];
  5. if (cachedModule) {
  6. return cachedModule.exports;
  7. }
  8. if (NativeModule.nonInternalExists(filename)) {
  9. debug('load native module %s', request);
  10. return NativeModule.require(filename);
  11. }
  12. var module = new Module(filename, parent);
  13. if (isMain) {
  14. process.mainModule = module;
  15. module.id = '.';
  16. }
  17. Module._cache[filename] = module;
  18. tryModuleLoad(module, filename); // 尝试载入模块
  19. return module.exports;
  20. };
  1. function tryModuleLoad(module, filename) {
  2. var threw = true;
  3. try {
  4. module.load(filename);
  5. threw = false;
  6. } finally {
  7. if (threw) {
  8. delete Module._cache[filename];
  9. }
  10. }
  11. }

load()相当于一个适配器,根据传进来的文件名的后缀名不同,会使用不同的载入规则:

  1. Module.prototype.load = function(filename) {
  2. debug('load %j for module %j', filename, this.id);
  3. assert(!this.loaded);
  4. this.filename = filename;
  5. this.paths = Module._nodeModulePaths(path.dirname(filename));
  6. var extension = path.extname(filename) || '.js';
  7. if (!Module._extensions[extension]) extension = '.js';
  8. Module._extensions[extension](this, filename);
  9. this.loaded = true;
  10. };

默认有3种规则:

  • Module._extensions[“.js”]
  • Module._extensions[“.json”]
  • Module._extensions[“.node”]

    1. // Native extension for .js
    2. Module._extensions['.js'] = function(module, filename) {
    3. var content = fs.readFileSync(filename, 'utf8');
    4. module._compile(internalModule.stripBOM(content), filename);
    5. };

    Module._extensions['.js'] 这种规则做了两件事情:

  • 同步读取源码的内容,使用fs.readFileSync(filename, 'utf8');

  • 调用module._compile()编译源码并执行 ```cpp // 生成闭包源码,然后传入相应的函数执行 Module.prototype._compile = function(content, filename) { // Remove shebang var contLen = content.length; if (contLen >= 2) { if (content.charCodeAt(0) === 35/#/ &&

    1. content.charCodeAt(1) === 33/*!*/) {
    2. if (contLen === 2) {
    3. // Exact match
    4. content = '';
    5. } else {
    6. // Find end of shebang line and slice it off
    7. var i = 2;
    8. for (; i < contLen; ++i) {
    9. var code = content.charCodeAt(i);
    10. if (code === 10/*\n*/ || code === 13/*\r*/)
    11. break;
    12. }
    13. if (i === contLen)
    14. content = '';
    15. else {
    16. // Note that this actually includes the newline character(s) in the
    17. // new output. This duplicates the behavior of the regular expression
    18. // that was previously used to replace the shebang line
    19. content = content.slice(i);
    20. }
    21. }

    } }

    // 生成闭包源码 var wrapper = Module.wrap(content); // 用 vm 编译 wrapper var compiledWrapper = vm.runInThisContext(wrapper, { filename: filename, lineOffset: 0, displayErrors: true });

    if (process._debugWaitConnect && process._eval == null) { if (!resolvedArgv) {

    1. // we enter the repl if we're not given a filename argument.
    2. if (process.argv[1]) {
    3. resolvedArgv = Module._resolveFilename(process.argv[1], null);
    4. } else {
    5. resolvedArgv = 'repl';
    6. }

    }

    // Set breakpoint on module start if (filename === resolvedArgv) {

    1. delete process._debugWaitConnect;
    2. const Debug = vm.runInDebugContext('Debug');
    3. Debug.setBreakPoint(compiledWrapper, 0, 0);

    } } var dirname = path.dirname(filename); var require = internalModule.makeRequireFunction.call(this); var args = [this.exports, require, this, filename, dirname]; var depth = internalModule.requireDepth; if (depth === 0) stat.cache = new Map(); var result = compiledWrapper.apply(this.exports, args); if (depth === 0) stat.cache = null; return result; };

  1. - 开发者调用`require(filename)`(等同于调用`module.load(filename);`)
  2. - 源码加闭包马甲:一个模块的源码经过闭包化后,就形成了一个接收exportsrequiremodule__filename__dirname的闭包函数。这就是我们平时编写代码的时候能直接使用exportsrequiremodule等内容的原因了。在我们编写的源码模块被载入的时候,这些变量会随着闭包传进来而被使用。这个闭包会在第一次加载该模块的时候执行一次,之后就一直存在于模块缓存中(除非手动清除缓存)。也即一个模块逻辑代码只会被执行一次。
  3. - `vm` 编译 `wrapper`
  4. - 传入参数执行`vm`
  5. <a name="JcP0J"></a>
  6. #### 入口模块
  7. [src/node_main.cc](https://github.com/nodejs/node/blob/master/src/node_main.cc#L127)
  8. ```cpp
  9. return node::Start(argc, argv);

C++拓展模块

用户源码模块和C++扩展模块加载的区别是通后后缀名来区分的。Module._extensions[".node"]则是C++拓展模块。

  1. Module._extensions['.node'] = function(module, filename) {
  2. return process.dlopen(module, path._makeLong(filename));
  3. };

实际调用的就是在src/node.cc中SetupProcessObject里挂载上去的DLOpen

  1. env->SetMethod(process, "dlopen", DLOpen);
  1. // DLOpen is process.dlopen(module, filename).
  2. // Used to load 'module.node' dynamically shared objects.
  3. // FIXME(bnoordhuis) Not multi-context ready. TBD how to resolve the conflict
  4. // when two contexts try to load the same shared object. Maybe have a shadow
  5. // cache that's a plain C list or hash table that's shared across contexts?
  6. void DLOpen(const FunctionCallbackInfo<Value>& args) {
  7. Environment* env = Environment::GetCurrent(args);
  8. uv_lib_t lib;
  9. CHECK_EQ(modpending, nullptr);
  10. if (args.Length() != 2) {
  11. env->ThrowError("process.dlopen takes exactly 2 arguments.");
  12. return;
  13. }
  14. Local<Object> module = args[0]->ToObject(env->isolate()); // Cast
  15. node::Utf8Value filename(env->isolate(), args[1]); // Cast
  16. // 使用uv_dlopen加载链接库
  17. const bool is_dlopen_error = uv_dlopen(*filename, &lib);
  18. // Objects containing v14 or later modules will have registered themselves
  19. // on the pending list. Activate all of them now. At present, only one
  20. // module per object is supported.
  21. node_module* const mp = modpending;
  22. modpending = nullptr;
  23. if (is_dlopen_error) {
  24. Local<String> errmsg = OneByteString(env->isolate(), uv_dlerror(&lib));
  25. uv_dlclose(&lib);
  26. #ifdef _WIN32
  27. // Windows needs to add the filename into the error message
  28. errmsg = String::Concat(errmsg, args[1]->ToString(env->isolate()));
  29. #endif // _WIN32
  30. env->isolate()->ThrowException(Exception::Error(errmsg));
  31. return;
  32. }
  33. if (mp == nullptr) {
  34. uv_dlclose(&lib);
  35. env->ThrowError("Module did not self-register.");
  36. return;
  37. }
  38. if (mp->nm_version != NODE_MODULE_VERSION) {
  39. char errmsg[1024];
  40. snprintf(errmsg,
  41. sizeof(errmsg),
  42. "Module version mismatch. Expected %d, got %d.",
  43. NODE_MODULE_VERSION, mp->nm_version);
  44. // NOTE: `mp` is allocated inside of the shared library's memory, calling
  45. // `uv_dlclose` will deallocate it
  46. uv_dlclose(&lib);
  47. env->ThrowError(errmsg);
  48. return;
  49. }
  50. if (mp->nm_flags & NM_F_BUILTIN) {
  51. uv_dlclose(&lib);
  52. env->ThrowError("Built-in module self-registered.");
  53. return;
  54. }
  55. // 将加载的链接库句柄转移到 mp 上
  56. mp->nm_dso_handle = lib.handle;
  57. mp->nm_link = modlist_addon;
  58. modlist_addon = mp;
  59. Local<String> exports_string = env->exports_string();
  60. // exports_string其实就是 exports
  61. // 这句的意思是`exports = module.exports`
  62. Local<Object> exports = module->Get(exports_string)->ToObject(env->isolate());
  63. if (mp->nm_context_register_func != nullptr) {
  64. mp->nm_context_register_func(exports, module, env->context(), mp->nm_priv);
  65. } else if (mp->nm_register_func != nullptr) {
  66. mp->nm_register_func(exports, module, mp->nm_priv);
  67. } else {
  68. uv_dlclose(&lib);
  69. env->ThrowError("Module has no declared entry point.");
  70. return;
  71. }
  72. // Tell coverity that 'handle' should not be freed when we return.
  73. // coverity[leaked_storage]
  74. }

DLOpen先使用uv_dlopen(*filename, &lib)打开了*.node扩展(动态链接库),将其载入到内存uv_lib_t中,然后通过

V8 基础

V8 宿主:

  • chrome宿主:HTML DOM、window对象等内容
  • node 宿主:沙盒机制(VM)、文件系统、网络操作等

Node:

  • V8: JS 运行时,用于解释执行JS
  • libuv: 实现了“事件循环”

内存机制

JS的数据类型都是由V8内部的内存机制进行管理的。即V8创建的数据存储单元只能被它的内存回收机制所回收,而不能被我们自己进行管理。
V8 堆内存

  • 新生代内存区:基本的数据对象都会被分配到这里。特点是小而频。
  • 老生代指针区:这是一堆指向老生代内存区具体数据内容的指针。基本上从新生代蜕变过来的对象都会被移动至此。
  • 老生代数据区:存放数据对象,而不是指向其他对象的指针。老生代指针区的指针就往这边指
  • 大对象区:这里存放体积超越其他区大小的对象,每个对象有自己的内存,垃圾回收并不会移动大对象。
  • 代码区:代码对象(就是包含JIT之后指令的对象)会被分配到这里。这是唯一拥有执行权限的内存区
  • Cell区、属性Cell区、Map区:存放cell、属性cell 和 Map,每个区域都是存放相同大小的元素,结构简单。

新生代内存区

Scavenge 算法:将内存一分为二,有较大的空间浪费

  • From空间:处于使用状态
  • To空间:处于闲置状态

在分配对象时,总使用 From空间进行分配;
在 GC 时,V8 检查 From 空间中的存活对象,将这些对象复制到 To 空间中,剩下的对象就会被释放,完成复制后From 和 To 角色对调。
经过多次新生代 GC 后如果某个对象依旧坚挺,则会被晋升到老生代内存。

  • 至少经历过一次 GC 的过程
  • 如果 To 的使用空间已经超过了25%

老生代内存

这里保存的对象都是些生命周期很长甚至是常驻内存的对象,且老生代占用的内存较多。

  • Mark-Sweep(标记清除)
    • 标记:遍历,并标记活着的对象
    • 清除:只清除没有被标记的对象
  • Mark-Compact(标记整理)
    • 在清除的时候,让活着的剩余对象尽可能往内存区域前面靠
    • 即内存区域的紧缩
  • 惰性清理:因为标记了,所以直到哪些是活得,哪些是死的,因此不会实时清理,会根据自身需要延迟清理。

    隔离实例(Isolate)

    Isolate 就是 V8 引擎的实例,也可以理解为引擎本体。Isolate 内部拥有完全独立的各种状态、包括堆管理、垃圾回收等,但是不执行JS。可被创建多个。

    上下文(Context)

    Context 用来定义 JS 的执行环境。可以理解为一个沙箱化的执行上下文环境,内部预置了一系列的对象和函数。

    脚本(Script)

    Script是一个包含一段已经编译好的JS脚本的对象,数据类型就是Script。它在编译时就与一个处于活动状态的上下文进行绑定。

句柄(Handle)

句柄是用于获取JS对象实体的一种事物,有有效句柄连接的对象实体不会被垃圾回收器进行回收,而失去了所有句柄引用的对象实体会被认为是垃圾,从而在下次垃圾回收的时候被释放。
句柄类型:

  • 本地句柄(v8::Local): 存在于栈内存中,并在对应的析构函数被调用时被删除。生命周期由句柄作用域决定。
    • 创建:new()
    • 清除:clear()
    • 是否为空:isEmpty()
    • 转换数据类型:As/Cast
  • 持久句柄(v8::Persistent): 存在于堆内存中,
    • 清除:clear()
    • 是否为空:isEmpty
    • 置为弱持久句柄:SetWeak()
    • 取消弱持久句柄:ClearWeak()
    • 标记独立句柄:MarkIndependent()
    • 是否为弱的:isWeak()
    • 是否为独立的:isIndependent()
  • 永生句柄(v8::Eternal)
  • 待实本地句柄
  • 其他句柄

句柄的存在形式是C++的一个模板类,其需要根据不同的V8数据类型进行不同的声明。

句柄作用域

句柄作用域实际上是维护一堆句柄的类。以栈的形式一层一层套着,存在于Isolate实例中,栈顶的作用域是当前活动作用域,每次新建对象时得到的句柄都会与当前活动作用域绑定,当一个句柄作用域对象的析构函数被调用时,在这个作用域中创建的所有句柄都会被从栈中抹去。于是,通常情况下这些句柄所指的对象将会失去所有引用,然后会被垃圾回收器统一处理。这里有一种例外,就是可逃句柄作用域所设置的已逃脱句柄可以逃过一劫。

上下文(Context)

Context是独立且允许存在多个的,是JS代码的执行环境。一个Context为JS的执行提供了内置的对象和方法。

node 的内置模块 vm 的本质就是利用 Context 可以多个的特性。
lib/vm.js

模板(Template)

V8中的模板指的是在Context中JS对象及函数的一个模具。
Template的派生类

  • 函数模板(Function Template)
  • 对象模板(Object Template)

    常用数据类型

  • Value

  • String
  • Number
  • Integer
  • Int32
  • Uint32
  • Boolean
  • Object
  • Function
  • Array
  • FunctionCallbackInfo
  • ReturnValue

    异常

  • TryCatch对象:对应JS中的 try-catch 代码块

  • Isolate::Throw-Exception:对应 JS 的 throw 操作
  • Exception::Error: 对应 new Error 操作,并且这个静态类能生成的不只是普通的 Error 对象,还能生成诸如SyntaxError 等错误对象。

    资源工具

  • NodeJS使用淘宝npm镜像站的各种姿势:https://www.jianshu.com/p/253cb9003411

  • node-scripts:https://docs.npmjs.com/misc/scripts
  • V8: http://developers.google.com/v8/