简述模块发展史
凑合的模块系统
利用立即调用函数表达式 (Immediately Invoked Function Expresssion 简称 IIFE)将代码块封装在匿名闭包中,代码块是立即执行的。
// iifeModule是为匿名函数代码块创建的命名空间
var iifeModule = (function () {
var count = 0
return {
increase: function(){
++count
},
reset: function(){
count = 0
}
}
})()
iifeModule.increase()
iifeModule.reset()
通过传参,使代码块可以有额外的依赖 。
var deps1 = function () {
console.log('deps1')
}
var deps2 = function () {
console.log('deps2')
}
var iifeModule = (function (deps1, deps2) {
var count = 0
deps1()
deps2()
return {
increase: function(){
++count
},
reset: function(){
count = 0
}
}
})(deps1, deps2)
iifeModule.increase()
iifeModule.reset()
传统框架还应用了揭示模块模式(revealing module pattern)。这种模式只返回一个对象,属性值是私有成员的数据和引用,使用者无需关注底层实现。
var revealingModule = (function (deps1, deps2) {
var count = 0
deps1()
deps2()
var publicIncrease = function(){ ++count }
var publicReset = function(){ count = 0 }
return { increase: publicIncrease, reset: publicReset }
})(deps1, deps2)
在模块内部也可以定义模块,这样可以实现命名空间嵌套:
var revealingModule = (function (deps1, deps2) {
var count = 0
deps1()
deps2()
var publicIncrease = function(){ ++count }
var publicReset = function(){ count = 0 }
// 这样可以实现命名空间嵌套
var publicCount = {
getCount: function (){
return count
}
}
return { increase: publicIncrease, reset: publicReset, count: publicCount }
})(deps1, deps2)
revealingModule.count.getCount()
但是如果模块间存在依赖,引入文件时会有严格的顺序,否则会产生运行时bug。
随着前端工程的日益庞大,前端模块化也经过了漫长的发展。模块化的基本思想:把逻辑分块,各自封装,相互独立,每个块自行决定对外暴露什么,同时自行决定引入执行哪些外部代码。不同的实现和特性,产生了不同的规范。
成熟的模块系统
目前主流的模块化规范是CJS、AMD、CMD、ESM。
CJS -- CommonJS node.js 指定的标准 服务器端模块规范
// easyExport.js 文件
// 导出
let easyExport = function easyExport(){}
module.exports = { easyExport }
// exports.easyExport = easyExport
/************************************/
// core.js 文件
// 导入
let easyExport = require('./easyExport.js')
通过 module + exports 去对外暴露接口。
通过 require 去调用其他模块。
优点:所有模块同步加载。
缺点:服务器端运行可以,浏览器端就会造成js解析阻塞,页面加载速度缓慢。
AMD -- Asynchronous Module Definition / 异步模块定义
允许定制回调函数 经典实现框架:require.js
通过define定义,使用require一次性引入所有需要的依赖,将依赖前置
// define(id?, [depends]?, callback)
// require([module], callback)
define('amdModule', ['deps1', 'deps2'], (deps1, deps2) => {
// 业务逻辑
let count = 0
const increase = function(){++count}
deps1()
deps2()
return { increase }
})
reuire(['amdModule'], amdModule => {
amdModule.increase()
})
优点:支持模块异步加载,经典实现框架 require.js
缺点:会有引入成本,没有考虑按需加载;因为是异步加载,加载完之后就执行,可能遇到某个依赖中用到的另一个依赖还没加载到
CMD -- Common Module Definition / 通用模块定义
主要应用框架 sea.js
通过define定义,在需要用到依赖的地方使用require引入,支持按需解析,执行
// math.js
define(function(require, exports, module) {
exports.add = function() {
var sum = 0, i = 0, args = arguments, l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};
});
// increment.js
define(function(require, exports, module) {
var add = require('math').add;
exports.increment = function(val) {
return add(val, 1);
};
});
// program.js
define(function(require, exports, module) {
var inc = require('increment').increment;
var a = 1;
inc(a); // 2
module.id == "program";
});
优点:异步加载,按需执行,依赖就近,用到的依赖都是确定加载并执行完成的
缺点:依赖于打包;扩大了模块内的体积;加载时需先解析模块路径
很多时候工程中会同时支持多种方式,那么需要如何兼容呢,就出现了UMD(Universal Module Definition)。
// UMD
(function(global, factory) {
if (typeof module !== 'undefined' && typeof exports === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(['moduleName'], factory)
} else if (typeof define === 'function' && define.cmd) {
define(function(require, exports, module) {
module.exports = factory()
})
} else {
global = typeof globalThis !== 'undefined' ? globalThis : global || self
global.module = factory();
}
}(this, function() {
return {}
}))
ESM -- ES Modules
普通函数定义,通过 export 导出,通过 import 引入
一个文件只能有一个export default ,export可以有多个
// increase.js
let count = 0
export increase = () => { ++count }
// cala.js
import { increase } from 'increase'
const cala = increase()
export default cala
ES11原生支持动态加载
import('deps1').then((deps2) => {
deps2()
})
优点:通过一种最统一的形态整合了所有JS的模块化