经常在面试或者其他文章看到关于模块化的问题,之前也只是寥寥看了几次,对于 CommonJS,AMD,ES6也说不出个所以然,于是今天抽空好好看了 红宝书第4版关于模块化的介绍,这里记录一下。
理解模块模式
初衷
在开发中肯定有设计大量三方库或者业务逻辑代码,较好的方式是将其分割为多个小模块,最后以一定的方式连接起来使用,这就是模块化兴起的初衷。
模块化一般包含什么?
模块化通常需要含一下几个点。
模块标识符
模块系统本质上是键/值实体,每个模块都有引用它的标志符,一般是路径或者文件名,部分模块系统也支持自定义标识符,这取决于哪种模块系统。
模块依赖
模块系统的核心就是是管理依赖了,脑海中大概有一张 A→B→C,这样的图就可以明白了。
模块加载
根据依赖,加载模块,在浏览器中只有整个依赖图都加载完成,才可以执行入口模块。
入口
这个就比较好理解了,相互依赖的模块必须指定一个模块作为入口(entry point),这也是代码执行的起点。
异步依赖
因为 JavaScript 可以异步执行,所以如果能按需加载就好了。换句话说,可以让 JavaScript 通知模块系统在必要时加载新模块,并在模块加载完成后提供回调。
// 在模块 A 里面
load('moduleB').then(function(moduleB) {
moduleB.doStuff();
});
动态依赖
可以在程序结构中动态引入模块
if (loadCondition) {
require('./moduleA');
}
静态分析
模块中包含的发送到浏览器的 JavaScript 代码经常会被静态分析,分析工具会检查代码结构并在不实际执行代码的情况下推断其行为。
循环依赖
CommonJS、AMD 和ES6 在内的所有模块系统都支持循环依赖。
上面几点就是模块化的一些基本知识,接下来就看下我们比较关心的也面试偶尔问道的 CommonJS,AMD,ES6
CommonJS
CommonJS 规范主要概述了同步声明依赖的模块定义,一般用于服务端规范,比如node.js(Node.js使用了轻微修改版本的 CommonJS,因为 Node.js 主要在服务器环境下使用,所以不需要考虑网络延迟问题。),CommonJS模块语法是不能直接在浏览器中使用的
使用方法
var moduleB = require('./moduleB');
module.exports = {
stuff: moduleB.doStuff();
};
/**
一般使用 require 和 module.exports的方式
可以赋值给变量,也可直接 require 进入,同时不管请求多少次,也只会加载一次
第一次加载后,模块就会缓存起来,供后续使用
**/
AMD
CommonJS 以服务器端为目标环境,能够一次性把所有模块都加载到内存,而 AMD 异步模块定义,是以浏览器为目标。 AMD模块是以函数包装的方式来实现的,就避免暴露全局变量的方式,但是需要AMD加载库来实现。
// ID 为'moduleA'的模块定义。moduleA 依赖 moduleB,
// moduleB 会异步加载
define('moduleA', ['moduleB'], function(moduleB) {
return {
stuff: moduleB.doStuff();
};
});
ES6 模块
以上说的两种都需要模块加载器,ES6引入了模块规范,同时原生浏览器也开始支持,融入了 AMD 和 CommonJS的优点
//像我们平常写的
import { foo } from './fooModule.js'
export default 123
//或者
<script type="module">
import './moduleA.js'
<script>
<script type="module" src="./moduleA.js"></script>
/**
与传统脚本不同,所有模块都会像<script defer>加载的脚本一样按顺序执行。解析到<script
type="module">标签后会立即下载模块文件,但执行会延迟到文档解析完成。
**/
ES6模块的加载
既可以通过浏览器加载,也可以通过打包工具加载
ES6模块部分优点
1、模块只能加载一次。
2、模块可以请求加载其他模块。
3、模块顶级 this 的值是 undefined(常规脚本中是 window)。
4、模块中的 var 声明不会添加到 window 对象。
5、ES6 模块是异步加载和执行的,输出值的引用。
几个不同之处
1、CommonJS模块输出是值的拷贝,ES6 Module模块输出的值是引用
2、CommonJS是运行时加载,ES6 Module是编译是输出
3、AMD规范是采用异步方式,依赖前置必须一开始就写好,所有的依赖加载完成后才会执行回调函数里的内容
剩下关于 ES6 模块导入导出的使用方法和规则就不逐一介绍了。