CommonJS 规范
Node 的发展使得 npm 模块库的内容日益丰富,这些 npm 的生成也遵循着 CommonJs 的规范。如今对于前端开发者已经离不开 npm ,所以 CommonJs 的规范及 require()加载机制对于开发者来说也就极其重要。
CommonJs 对于模块的定义分为 模块引用、模块定义、模块标识 三个部分
模块引用
const sum = require('sum')
复制代码模块定义
在 CommonJs 规范中,使用 require方法来引入模块。在模块中的上下文提供了一个 exports 对象和 module.exports 对象用于导出当前模块的变量或方法。
module.exports 导出模块:
module.exports = {
   a: 2
}
复制代码exports 导出模块:
exports.a = 2
复制代码关于 exports、module.exports 的区别:require方法加载到的只有  module.exports 这个对象,而我们在编写模块时用到的 exports 对象实际上只是对module.exports 的引用
模块标识
模块标识就是 require() 的时候传入的参数。
标识符代表着包名,也可以是以 .、 ..开头的相对路径或者绝对路径。
模块实现
在 Node 中模块分为两类:
- 一类是 Node 内置模块,又称为核心模块(http、fs等)
- 一类是用户自定义模块,又称为文件模块
当我们在当前上下文中引入模块时,需要经历以下四个步骤:
- 缓存加载
- 路径分析
- 文件定位
- 文件编译执行
模块缓存加载
不论是核心模块还是文件模块,Node 对引入过的模块都会进行缓存,以减少二次引入时的开销。Node 缓存的是编译过的对象。缓存加载是第一优先级,核心模块的缓存加载会优先于文件模块的缓存。
路径分析
const sum = require('sum')
const sum = require('../sum')
const sum = require('/sum')
复制代码require方法接受一个模块标识符作为参数,sum、 ../sum、/sum 即为模块标识符。Node 正是基于这样的标识符进行模块查找。
标识符分类:
- 核心模块, http,fs,path等
- 以 .或..开头的相对路径模块
- 以 /开头的绝对路径模块
- 自定义模块,比如众多 npm 包
核心模块的加载:
核心模块的加载仅此于缓存加载,在 Node 进程启动时,核心模块就被编译为二进制代码加载进内存中。
PS: 如果想加载一个标识符与核心模块相同的模块,我们必须换一个标识符或者换用路径的方式加载。
这里测试一下, 新建 test.js 和 http.js 在同一目录下:
新建一个 test.js 文件:
const a = require('http')
console.log(a)
复制代码新建一个 http.js 文件
module.exports = {
    a : 1
}
复制代码打印如下:

由此可见,打印内容为 http 模块内容。如果我们试图加载一个与核心模块标识符相同的模块,读取的还是核心模块。那如何加载与核心模块相同标识符的模块,可以采用相对路径的方式或修改标识符。
修改一下以相对路径方式引入:
const a = require('./http')
console.log(a)
复制代码效果如下:

路径形式的模块
以 .和 ..开头的标识符,在引入时会将相对路径转换为绝对路径,并以绝对路径为索引将文件编译执行后的对象添加到缓存,在二级加载时更快。
文件模块的加载指明了文件的位置,所以在查找的过程中很快,加载速度慢于核心模块。
自定义模块
自定义模块通常是指开发者自行封装的模块,比如丰富的 npm 社区,Node 在加载自定义模块的时候会遵循一套路径规则,该规则表现为一套路径数组。
关于数组的详细规则,我们可以自行测试:
创建一个测试文件,find_path.js 
console.log(module.paths)
复制代码Mac 系统下,输出路径数组如下
[
  '/Users/zjg/study/demo/node_modules',
  '/Users/zjg/study/node_modules',
  '/Users/zjg/node_modules',
  '/Users/node_modules',
  '/node_modules'
]
复制代码可以看出,模块路径查找的规则是这样:
- 查找当前文件目录下的 node_modules目录
- 查找父目录下的 node_modules目录
- 父目录的父目录下的 node_modules目录
- …
- 根目录下的 node_modules目录
这种查询目录的方式和 JavaScript 的原型链或作用域的查询方式很类似。
文件定位
当我们使用 require() 的时候,如果标识符中不包含文件扩展名,比如
require('mytest')
复制代码这种情况下,Node 会按照 .js 、.json 、.node 的次序补充扩展名尝试加载模块。
如果 mytest 是一个 npm 包文件,Node 通过补充文件扩展名没有找到对应文件,找到的是一个目录,这时 Node 会把这个目录当作一个包来处理。
Node 对 CommpnJs 包规范做了一定支持,首先,Node 会在当前包目录下找到 packsge.json 文件,通过 JSON.parse() 解析出包描述对象,找到 main 属性指定的入口文件位置。如果没有文件扩展名,则先进行扩展名分析。如果 main 属性指定的文件不存在,或者当前包目录没有 package.josn 文件, Node 则会尝试查找 index 文件名,一次查找 index.js、 index.josn、 inndex.node。
如果当前目录分析没有找模块,Node 则按照模块路径数组下一个目录开始查找,依次查找完成,如果最终没有找到模块,则会抛出错误,以上便是 Node 加载一个模块时所做的全部处理。






















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)
