发展流程
graph LR
a("无模块化时期") --> b("IIFE(立即执行函数表达式)")
a -.-> a1("<script />引入")
b --> c(CommonJS)
c --> d("AMD")
d --> e("CMD")
e --> f("ESModule")
f --> g("前端工程化")
a1 -.-> a2["缺陷:变量全局污染"]
c -.-> c1("require/exports.module")
c1 -.-> c2["缺陷:无法异步"]
d -.-> d1("使用require.js")
d1 -.-> d2["缺陷:无法按需加载"]
f -.-> f1("import/exports")
| 名称 | 基本内容 | 是否允许异步引入 | 缺点 | 加载模式 | |
|---|---|---|---|---|---|
| 无模块化时期 | script标签引入不同js | 同步 | 全局污染 | ||
| IIFE | 立即执行函数 | 同步 | 不运行异步引入 | ||
| CMJ(CommonJS) | exports.module和require | 可异步 | 运行时加载 | ||
| AMD | 可异步 | 运行时加载 | |||
| CMD | 可异步 | 运行时加载 | |||
| ESM(ESModule ) | export及import | 可异步 | 编译时加载 |
该异步指的是在js过程中再引入,同步值必须先加载完再执行
1、无模块化时期
无模块时,直接将所有的js逻辑放到一个js文件中
<script src="./main.js"></script>
初步模块化理念:不同的js文件做各自对应的事情
<script src="./a.js"></script>
<script defer src="./b.js"></script>
<script async src="./c.js"></script>
引发问题:全局的污染
script标签存在defer属性和async属性
红色代表js脚本执行时间,绿色代表html解析。
defer和async都是异步加载,同步解析,只是async解析会打断当前html,defer是等当前html执行完再执行。
2、IIFE(立即执行函数表达式)
立即执行函数表达式创建了一个私有的作用域,在这个作用域内他们享有自己的变量和方法,不会影响到他的外部作用域。
(() => {
let count = 1;
const increase = () => count++;
const getCount = () => count
})()
早期jqury这些框架也是利用这种revealing的写法去实现的,jqury其实就是IIFE挺好的一个实践
const currentModule = ((dependencyModule) => {
// dependencyModule 引用依赖的模块
let count = 1;
const increase = () => count++;
const getCount = () => count
return { increase, getCount }
})(dependencyModule)
currentModule.increase();
currentModule.getCount();
3、CommonJS(CMJ)
CommonJS是node.js为了解决模块化问题提出的解决方案,这是一个同步的模块化解决方案,常用于服务器,运行时加载
// module.js
const count = 1;
const increase = () => count++;
const getCount = () => count;
node对每个module内置了exports以及module两个变量
一般来说exports = module.exports
但是如果将exports指向一个固定的值而不是通过引用的方式,则会切断exports和module.exports的关系
exports.increase = increase;
exports.getCount = getCount;
or
exports.module = { increase, getCount }
// moduleB.js
const module = require(/module);
module.increase();
module.getCount();
fs加载实际上是生成一个fs对象,再从该对象上读取相应方法。所以只有在运行时才能得到对象。 CommonJS模块输出的是一个值的拷贝。
新问题:怎么去解决异步依赖的问题
4、AMD
通过异步加载,允许执行回调函数,常用于浏览器,运行时加载
define('currentModule', [dependency1, dependency2], (dependency1, dependency2) => {
// do something;
return { increase, getCount }
})
// moduleB
require(['currentModule'], (module) => {
module.increase();
})
问题:如果本身我们运用同步的方案那怎么在我们同步的方案中兼容AMD呢
AMD内部提供了这样的方式
define('currentModule', [], require => {
const dependency1 = require('dependency1')
const dependency2 = require('dependency2')
return {}
})
5、CMD
CMD解决按需加载的问题
define('currentModule',[],(require, exports, module) => {
const dependency1 = require(dependency1)
// do something
const dependency2 = require(dependency2)
// do something
})
6、ESModule
特性:能够兼容浏览器端和服务器端,并且统一了使用的语法。ESModule是编译时加载
// module
const count = 1;
const increase = () => count++;
const getCount = () => count;
exports default { increase, getCount }
// moduleB
import module from 'module';
module.increase();
<script type="module" src="module.js"></script>
除此之外。ESModule还提供了动态加载模块的解决方案,结合了promise的思想
import('module').then((module) => { xxx })
ESModule输出的是值的引用。遇到import不会取执行模块,只是生成一个动态引用,等到真正用的时候再从模块里面取值。 不同的脚本加载一个相同的模块,得到的都是同样的实例。