模块加载与运行原理


模块加载与运行原理_第1张图片

1.CommonJS规范


模块加载与运行原理_第2张图片
Node与浏览器以及W3C组织、CommonJS组织、ES之间的关系

(1)导出:exports和module.exports

    * exports:将要导出的对象作为其属性即可,只能导出实例化对象,若为特定的类型则会切断和module.exports的联系,因为exports是指向module.exports的一个引用

    * module.exportsrequire()返回的是module.exports导出的结果


模块加载与运行原理_第3张图片
若x是require该文件的结果变量,则x.a的结果是2

: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文件中;


模块加载与运行原理_第4张图片
以UNIX环境为例的main()函数

* 进入src/node.cc文件中:Start() —》StartNodeInstance() —》LoadEnvironment():将bootstrap_node.js文件封装在一个function中,通过f->call()进入bootstrap_node.js文件实现xx.js的加载、编译和执行;

模块加载与运行原理_第5张图片
模块加载与运行原理_第6张图片
模块加载与运行原理_第7张图片
引入并调用bootStrap_node.js文件
模块加载与运行原理_第8张图片
process.arg[1]取出文件名xx.js->path.resolve()解析文件路径->run(Module.runMain)编译执行xx.js

综上:node xx.js启动一个文件就是require(xx.js)

3.模块加载原理

(1)模块实例:文本模块核心模块的模块实例区别:

模块加载与运行原理_第9张图片
文本模块实例的构造函数
模块加载与运行原理_第10张图片
js核心模块实例的构造函数

(1)require()源码在lib/module.js中:

模块加载与运行原理_第11张图片
Module的原型链上,每个模块实例都有一个require()方法,其内部调用Module._load()方法

(2)Module._load():require()实现的原理:确定绝对路径(Module._resolveFilename())+加载(根据返回的模块标识符:优先缓存->NativeModule.require()/tryModuleLoad())

模块加载与运行原理_第12张图片
Module._cache()将new的模块实例缓存

* Module._resolveFilename():确定模块的绝对路径,以它作为模块的标识符;

模块加载与运行原理_第13张图片
举例说明Module._findPath()过程如下:
模块加载与运行原理_第14张图片

 * 模块加载:分核心模块和文本模块:

1)核心模块:NativeModule.require()加载;

    js核心模块process.binding('natives')从内存中读取,再编译执行;

模块加载与运行原理_第15张图片

    c/c++核心模块:process.binding(内建模块名)通过get_builtin_module()从内存中读取直接执行;

模块加载与运行原理_第16张图片

 2)文件模块:module.load():node会新建一个模块对象,然后根据绝对路径载入并编译执行,将结果缓存;

模块加载与运行原理_第17张图片
针对不同文件模块有不同的载入方法


模块加载与运行原理_第18张图片
js文件模块:通过fs.readFileSync()载入


模块加载与运行原理_第19张图片
JSON文件模块:通过JSON.parse()载入


模块加载与运行原理_第20张图片
.node文件模块:通过process.dlopen()得到编译后的可执行文件

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模块文件代码以字符串形式直接加载进内存中

模块加载与运行原理_第21张图片

2)NativeModule.require():通过模块id做索引从natives数组中取出js核心模块的源码,再通过wrapper()头尾包装(),执行和导出exports对象,将其编译结果缓存到NativeModule._cache对象上;

模块加载与运行原理_第22张图片

* 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()前都已经编译好了,加载之后不需要编译了,直接执行之后就可以被外部调用了;

你可能感兴趣的:(模块加载与运行原理)