2.2.1 node 架构及模块

414 阅读4分钟

js与node

  • js的表现能力取决于宿主环境的api支持。所以同样是js语法,我们在chrome、node、hybrid app、electron桌面应用的场景不一样;
  • chrome:web1.0只有对dom、bom的基本支持;web2.0,html5将网页带入web应用的时代,浏览器中出现了更多更强大的api;
  • node:用commonjs规范对模块进行管理,让js具备跨宿主环境的能力; node调用vm虚拟机运行js代码
    v8 api介绍点击
const vm = require('node:vm');

const contextObject = { globalVar: 1 };
//创建虚拟机 的执行上下文
vm.createContext(contextObject);

for (let i = 0; i < 10; ++i) {
  // 在执行上下文中运行js
  vm.runInContext('globalVar *= 2;', contextObject);
}
console.log(contextObject);
// 打印: { globalVar: 1024 }

Nodejs结构大体分为三个部分:

image.png

1)Node.js标准库:
这部分由javascript编写。也就是平时我们经常require的各个模块,如:http,fs、[express],request…… 这部分在源码的lib目录下可以看到;

2)Node bindings:
nodejs程序的main函数入口,还有提供给lib模块的C++类接口,这一层是javascript与底层C/C++沟通的桥梁,由C++编写,这部分在[源码]的src目录下可以看到;

  • c++用Node.js提供的编译工具将其编译为.node文件
  • js代码可以直接通过require关键字引入这个.node文件;
    js通过函数入参,c++拿到js传过来的内容和设置返回值。
  • 这样js就能够调用C++库,这个中间的桥梁就是bindings,由于node提供了很多binding,所以就叫做node bindings

3)最底层,支持Nodejs运行的关键:

  • V8 引擎:用来解析、执行javascript代码的运行环境。
  • libuv: 提供最底层的IO操作接口,包括文件异步IO的线程池管理和网络的IO操作,是整个异步IO实现的核心! 这部分由C/C++编写,在源码的deps目录下可以看到。
  • node内部库:比如node::ObjectWarp用来包装自定义类,实现对象回收
  • 其他库:方便些扩展模块

模块

js用户层通过require方法调用

image.png

1. 核心模块

node源代码编译为了二进制执行文件,node启动直接加载到内存中;
核心模块在路径分析优先判断,无需路径分析和编译,加载速度快;
可通过process.binding('natives')调用;

  • c++编写:又叫内建模块。存放到node_module_list数组中,通过get_builtin_module()方法取出,一般通过核心模块js调用。
  • js编写:需要转换成c++里的数组,缓存到 NativeModule._cache对象上。

image.png

2. 文件模块:由第三方编写,可以编写c++扩展模块,由js模块调用

  • .js文件:通过fs模块同步读取编译;
  • .node文件:c++编写,通过dlopen()方法加载,win下为.dll文件,linux下为.so文件;
  • .json:fs模块加载,JSON.parse()解析;
  • 其他:视为.js文件; 引入经历了3个步骤:
  1. 路径分析:require以后会从默认的以来目录下查找:如node_modules。可以 查看module.paths;
  2. 文件定位:支持扩展名.js、.json、.node;首先会读取package.json,如果没有会默认读index文件;
  3. 编译执行: 生成一个modules对象,缓存到Module._cache对象上
  • js文件:js模块代码被包装下图,代码通过runInThisCcontent()方法(类似eval)执行

image.png

  • node文件:node调用process.dlopen()方法,由libuv封装,对linux和window做了兼容;

node 启动过程

(1)首先 Node.js 会调用 registerBuiltinModules 函数注册 C++ 模块;用户可以通过 process.binding 访问 C++ 模块;
(2)注册完 C++ 模块后就开始创建 Environment 对象,Environment 是 Node.js 执行时的环境对象,类似一个全局变量的作用,他记录了 Node.js 在运行时的一些公共数据;
(3)初始化模块加载器loader,转译ast处理require节点,加载了用户模块和原生 JS 模块;
(4)执行用户代码,进入Libuv 事件循环;

  • v8递归解释es5 ast,类型为callExpression,调用作用域上相应函数。external类型为c++的包装,执行原生动作(可修改js虚拟机环境 或 返回handle,完成js与原生的交互)

推荐文章

欢迎关注我的前端自检清单,我和你一起成长