模块化开发:CommonJS & ES Module

720 阅读4分钟

学习模块化开发的过程中,相信大家对 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 规范中,我们的导出功能是 exportsmodule.exports。如下代码,我们分别用两种方法导出了一个对象,对象的属性有nameage

// 我们先来看一段代码
const name = 'lizhyu';
const age = '25';

exports.name = name;
exports.age = age;
// or
module.exports = {
  name: 'zhl',
  age: 25
}

我们每个文件都有两个默认的对象 exportsmodule。我们使用 exports 导出数据,require 接收,这里的原理是 require 就是一个 node 提供的找到 exports 对象的函数,也就是exportsrequire 指向的都是同一个内存地址。这里大家就可以想到了,如果不是使用 const而是let,那么我们修改name之后,再在导出文件中调用字段的话也会动态改变。

graph TD
exports.js导出obj --> X0111内存
require.js导入obj --> X0111内存

exportsmodule.exports的不同(面试)

exportsmodule.exports 的功能是相同的,但是在 node 源码中是这么定义 exports 的,module.exports = exports,本质上就是导出 module.exports,也就是把内存地址复制给了module.exports,所以exportsmodule.exportsrequire 指向的都是同一个内存地址。 当定义 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提供的模块化规则,主要有importexportsexport default,这三个方法的具体功能和用法可以查看es6官方文档【module的语法】,我这里主要说明一些注意的知识点。

1. import 异步导入模块,在js解析阶段进行
2. import() 按需导入模块,可在函数中进行,缓解加载缓慢的问题
3. export {} 导出数据,此处的 {} 不是对象,而是一种约定的符号,用于接收数据,所以不能用es的语法糖
4. 在js引擎中,这里有一个模块环境记录的处理,bind导出的字段,类似于: const name = name(name为导出的字段);
   如果在导出文件中,后续异步修改了name,在导入文件中拿到的name是最新的name值,就是因为export的内部结构;