Node.js模块机制
# Node.js模块机制
[TOC]
https://juejin.cn/post/6844904030905303054
Nodejs遵循的是 CommonJS 规范,当我们 require('moduleA') 时,模块是怎么通过名字或者路径获取到模块的呢?首先要聊一下模块引用、模块定义、模块标识三个概念。
# CommonJS规范
# 模块引用
模块上下文提供require()
方法来引入外部模块,看似简单的require函数, 其实内部做了大量工作。
# 模块定义
模块上下文提供了exports
对象用于导入导出当前模块的方法或者变量,并且它是唯一的导出出口。模块中存在一个module
对象,它代表模块自身,exports
是module的属性。一个文件就是一个模块,将方法作为属性挂载在exports上就可以定义导出的方式。
# 模块标识
模块标识就是传递给require()
方法的参数,它必须是符合小驼峰命名的字符串,或者以.
、..
开头的相对路径或者绝对路径,可以没有文件后缀名.js
。
# Node的模块实现
路径分析 -> 文件定位 -> 编译执行 -> 加入内存
# 路径分析
Node.js中模块可以通过文件路径或名字获取模块的引用。模块的引用会映射到一个js文件路径。 在Node中模块分为两类:
- 一是Node提供的模块,称为核心模块(内置模块),内置模块公开了一些常用的API给开发者,并且它们在Node进程开始的时候就预加载了。
- 另一类是用户编写的模块,称为文件模块。如通过NPM安装的第三方模块(third-party modules)或本地模块(local modules),每个模块都会暴露一个公开的API。以便开发者可以导入。
# 文件定位
文件扩展名分析
调用require()
方法时若参数没有文件扩展名,Node会按.js
、.json
、.node
的顺寻补足扩展名,依次尝试。
目录分析和包
require()
分析文件扩展名后,可能没有查到对应文件,而是找到了一个目录,此时Node会将目录当作一个包来处理。
# 模块编译
对于不同的文件,用不同的不同的是放载入
.js
通过fs模块同步读取文件后编译执行。.node
这是C/C++编写的扩展文件,通过dlopen()
方法加载最后编译生成的文件.json
同过fs模块同步读取文件后,用JSON.pares()
解析返回结果
核心模块编译
核心模块分为C/C++
编写和JavaScript编写的两个部分,其中C/C++
文件放在Node项目的src目录下,JavaScript文件放在lib目录下。
# import和require
import
是ES6的模块规范,require
是commonjs的模块规范,详细的用法我不介绍,我只想说一下他们最基本的区别,import是静态加载模块,require是动态加载
那么静态加载和动态加载的区别是什么呢?
静态加载时代码在编译的时候已经执行了,动态加载是编译后在代码运行的时候再执行。
# require原理
http://www.ruanyifeng.com/blog/2015/05/require.html
当 Node 遇到 require(X) 时,按下面的顺序处理。
(1)如果 X 是内置模块(比如 require('http')) a. 返回该模块。 b. 不再继续执行。
(2)如果 X 以 "./" 或者 "/" 或者 "../" 开头 a. 根据 X 所在的父模块,确定 X 的绝对路径。 b. 将 X 当成文件,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。
X X.js X.json X.node
c. 将 X 当成目录,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。
X/package.json(main字段) X/index.js X/index.json X/index.node
(3)如果 X 不带路径 a. 根据 X 所在的父模块,确定 X 可能的安装目录。 b. 依次在每个目录中,将 X 当成文件名或目录名加载。
(4) 抛出 "not found"