学习模块化开发的过程中,相信大家对 CommonJS、ES Module 都不陌生,在模块化开发出现之前,大部分人开发都还是把所有代码写在几个js文件中,导致我们的代码十分臃肿不易维护,每次修改代码或者重构都是一件十分头疼的事情。自从开始学习模块化开发之后,我们把臃肿的代码按代码功能分割开,代码维护、新增功能都比之前简单多了,节约了我们大量的阅读代码的时间。提高我们的开发效率。那这篇文章我就大概说一下我对模块化开发的一些知识和理解。
CommonJS
接触过 Webpack 之后相信大家对 CommonJS 规范中的 require、exports、module.exports 并不陌生,接下来我说下在
node中这几个功能的用处和联系。
1. require
require是一个函数,帮助我们引入一个文件(模块)中导入的对象。这个函数的功能很简单,所以基本使用就不说了,我这里有一些require的细节可以分享给大家:
CommonJS加载过程是同步的,所以导入代码是同步进行的,会阻塞后面的代码,但是如果加载过的模块会做缓存,并且Module对象中的字段修改:loaded: true,后续不会再重复加载。
require导入既然是对象,那么就可以使用es6的关于对象的一些写法,比如解构赋值等。
require导入的查找规则如下:
/**
* require(X)
* 1. X是一个核心模块,比如path、http,直接返回核心模块,并停止查找
* 2. X是以./或../或/开头的,如果有后缀名,则会按后缀的文件查找,例如.json,那只会找json文件
* 如果没有后缀名,会有一个默认的顺序:
* 1. 直接查找文件X
* 2. 查找X.js
* 3. 查找X.json
* 4. 查找X.node
* 如果没有找到对应的文件,将X作为一个目录查找:
* 1. 查找X/index.js
* 2. 查找X/index.json
* 3. 查找X/index.node
* 3. 直接是一个X(没有路劲),并且X不是一个核心模块
* 从当前目录下面的node_modules开始查找,没找到就一直往上级查找node_modules,直到最顶层
*
* 如果都没找到就会报错:not found;
*/
2. exports 与 module.exports
exports 在我们的代码中起到一个导出的作用,在 CommonJS 规范中,我们的导出功能是 exports 与 module.exports。如下代码,我们分别用两种方法导出了一个对象,对象的属性有name和age。
// 我们先来看一段代码
const name = 'lizhyu';
const age = '25';
exports.name = name;
exports.age = age;
// or
module.exports = {
name: 'zhl',
age: 25
}
我们每个文件都有两个默认的对象 exports和module。我们使用 exports 导出数据,require 接收,这里的原理是 require 就是一个 node 提供的找到 exports 对象的函数,也就是exports、require 指向的都是同一个内存地址。这里大家就可以想到了,如果不是使用 const而是let,那么我们修改name之后,再在导出文件中调用字段的话也会动态改变。
graph TD
exports.js导出obj --> X0111内存
require.js导入obj --> X0111内存
exports 与 module.exports的不同(面试)
exports 与 module.exports 的功能是相同的,但是在 node 源码中是这么定义 exports 的,module.exports = exports,本质上就是导出 module.exports,也就是把内存地址复制给了module.exports,所以exports、module.exports、require 指向的都是同一个内存地址。
当定义 module.exports 时,exports 就被抛弃了。这是因为一旦module.exports重新定义了对象,那么exports的内存地址与module.exports的内存地址就不同了,所以exports的功能也就无效了。
graph TD
module.exports.js导出obj --> X0112内存
require.js导入obj --> X0112内存
exports.js导出obj --> X0111内存
模块的加载顺序(面试)
CommonJS采用的是图结构中的深度优先算法,一层中的一项逐级加载,到底后返回上一级看是否还有需要加载的模块,依次往上返回。
ES Module
顾名思义这是ES提供的模块化规则,主要有
import、exports、export default,这三个方法的具体功能和用法可以查看es6官方文档【module的语法】,我这里主要说明一些注意的知识点。
1. import 异步导入模块,在js解析阶段进行
2. import() 按需导入模块,可在函数中进行,缓解加载缓慢的问题
3. export {} 导出数据,此处的 {} 不是对象,而是一种约定的符号,用于接收数据,所以不能用es的语法糖
4. 在js引擎中,这里有一个模块环境记录的处理,bind导出的字段,类似于: const name = name(name为导出的字段);
如果在导出文件中,后续异步修改了name,在导入文件中拿到的name是最新的name值,就是因为export的内部结构;