模块机制
引入模块需要三步:
- 路径分析
- 文件定位
- 编译执行
模块分为核心模块和文件模块
- 核心模块:Node 提供,与 Node 源代码一起编译进二进制文件,Node 进程启动时直接加载到内存中,require 时省略文件定位和编译执行,而且路径分析时会优先判断,所以加载速度最快
- 文件模块:用户编写的模块,运行时动态加载
模块引入时优先从缓存中引入,缓存中没有就用 fs 模块同步读取引入并加到缓存中
1// 每一个模块都是一个对象2function Module(id, parent) {3 this.id = id;4 this.exports = {};5 this.parent = parent;6 if (parent && parent.children) {7 parent.children.push(this);8 }9 this.filename = null;10 this.loaded = false;11 this.children = [];12}
用户模块
1// > console.log(module)2// Module {3// id: '<repl>',4// path: '.',5// exports: {},6// parent: undefined,7// filename: null,8// loaded: false,9// children: [],10// paths: [11// ...12// '/Users/ahabhgk/node_modules',13// '/Users/node_modules',14// '/node_modules',15// ...16// ]17// }1819// > console.log(require)20// [Function: require] {21// resolve: [Function: resolve] { paths: [Function: paths] },22// main: undefined,23// extensions: [Object: null prototype] {24// '.js': [Function],25// '.json': [Function],26// '.node': [Function],27// '.mjs': [Function]28// },29// cache: [Object: null prototype] {}30// }3132// Native extension for .json33Module._extensions['.json'] = function(module, filename) {34 var content = NativeModule.require('fs').readFileSync(filename, 'utf8');35 try {36 module.exports = JSON.parse(stripBOM(content));37 } catch (err) {38 err.message = filename + ': ' + err.message; throw err;39 }40};4142// Native extension for .js43Module._extensions['.js'] = function(module, filename) {44 var content = NativeModule.require('fs').readFileSync(filename, 'utf8');45 try {46 content = '(function (exports, require, module, __filename, __dirname) {' + content + '});'47 var fn = vm.runInThisContext(content) // 类似 eval,只是具有明确上下文,不会污染全局48 fn(exports, require, module, filename, dirname)49 } catch (err) {50 err.message = filename + ': ' + err.message; throw err;51 }52};5354//Native extension for .node (C/C++ 扩展模块)55Module._extensions['.node'] = process.dlopen;
C/C++ 扩展模块通过 node-gyp 编译成 .node 文件,.node 在 *nix 平台就是 .so 文件,Windows 平台就是 .dll 文件,通过 libuv 实现跨平台
process.dlopen('./hello.node', exports)
把扩展模块返回结果放到 exports 空对象中
核心模块
由 C/C++ 编写的称为 buildin module 内建模块(比如 node_os、node_fs、node_buffer、node_crypto、node_http_parser、node_zlib),会放在 node_module_list 数组中,加载内建模块时会先建一个 exports 空对象,从 node_module_list 取出内建模块填充 exports
核心模块 .js 文件编译时会先把 js 代码转换为 C/C++ 代码,引入时通过 process.binding()
取出 '(function (exports, require, module, __filename, __dirname) {' + content + '});'
包裹后的代码并进行缓存
核心模块 .js 文件和内建模块的关系是全部或部分是依赖于内建模块,.js 文件通过 node_module_list 取出内建模块,用户 require 时通过 process.binding 取出