1.CommonJS规范
(1)导出:exports和module.exports
* exports:将要导出的对象作为其属性即可,只能导出实例化对象,若为特定的类型则会切断和module.exports的联系,因为exports是指向module.exports的一个引用;
* module.exports:require()返回的是module.exports导出的结果;
注:exports -> {} <- module.exports,初始值是一个空对象;
(2)导入:require()
* 优先级:缓存加载(node缓存的是编译和执行后的对象,浏览器缓存的是文件) —》核心模块加载—》文件模块加载;
* 导入步骤:路径分析—》文件定位—》编译执行
2.Node运行原理
(1)当输入node xx.js执行.js文件时干了什么?
* 调用node项目的src/node_main.cc入口文件:区分运行环境是windows环境和*nix环境,调用node::start()方法进入src/node.cc文件中;
* 进入src/node.cc文件中:Start() —》StartNodeInstance() —》LoadEnvironment():将bootstrap_node.js文件封装在一个function中,通过f->call()进入bootstrap_node.js文件实现xx.js的加载、编译和执行;
综上:node xx.js启动一个文件就是require(xx.js);
3.模块加载原理
(1)模块实例:文本模块与核心模块的模块实例区别:
(1)require()源码在lib/module.js中:
(2)Module._load():require()实现的原理:确定绝对路径(Module._resolveFilename())+加载(根据返回的模块标识符:优先缓存->NativeModule.require()/tryModuleLoad());
* Module._resolveFilename():确定模块的绝对路径,以它作为模块的标识符;
* 模块加载:分核心模块和文本模块:
1)核心模块:NativeModule.require()加载;
js核心模块:process.binding('natives')从内存中读取,再编译执行;
c/c++核心模块:process.binding(内建模块名)通过get_builtin_module()从内存中读取直接执行;
2)文件模块:module.load():node会新建一个模块对象,然后根据绝对路径载入并编译执行,将结果缓存;
4.模块的编译执行:调用具体的编译方式将文件执行后返回给调用者
(1)核心模块:在node项目创建时通过node.gyp将所有的c/c++文件编译为二进制文件存储在node内存中(这不属于require()时的操作了);js核心模块在require()时还需要编译一次,而c/c++核心模块就不需要了;
* js核心模块:
1)node项目创建时的编译—转为c/c++代码:采用V8的js2c.py库将所有的js模块文件代码以字符串形式存储在c++的数组中,生成node.natives.h头文件;启动node进程时,js模块文件代码以字符串形式直接加载进内存中;
2)NativeModule.require():通过模块id做索引从natives数组中取出js核心模块的源码,再通过wrapper()头尾包装(),执行和导出exports对象,将其编译结果缓存到NativeModule._cache对象上;
* c/c++核心模块:编译过程在node项目创建时(生成node.exe),通过node_module_struct结构体定义到node命名空间(就知道了有哪些内建模块了),在.h头文件里将内建模块统一放进node_module_list的数组中,然后被编译进二进制文件加载进node内存中;
(2)文本模块:只有.js文件需要编译;
.js文件:其过程和js核心模块第二次编译几乎一样;
.node文件:是c/c++扩展文件编译后的结果:与c/c++核心模块编译类似(通过node-gyp),只是不需要写入node命名空间,在require()前都已经编译好了,加载之后不需要编译了,直接执行之后就可以被外部调用了;