五分钟带你回顾前端模块化发展史
「观感度:🌟🌟🌟🌟🌟」
「口味:麻酱面藕」
「烹饪时间:10min」
CSS
早在 2.1
的版本就提出了 @import
来实现模块化,但是 JavaScript
直到 ES6
才出现官方的模块化方案 ES Module
。尽管早期 JavaScript
语言规范上不支持模块化,但这并没有阻止 JavaScript
的发展。官方没有模块化标准,那么我们就自己动手创建标准。社区里的前辈们创建并实现了规范,这些规范便是前端模块化发展之路上智慧的结晶。
模块化的价值
在 2020
年的今天来看,模块化应该具有以下价值:
可维护性 减少全局污染 可复用性 方便管理依赖关系 分治思想的实践
十年之前,模块化还只是使用「闭包」简单的实现一个命名空间。使用这种解决方式可以简单粗暴的处理全局变量和依赖关系等问题。
CommonJS
让我们把时间追溯到 2009 年 1 月,在这个普通的冬天里,万物开始生长。中国南极科学考察站昆仑站落成,成为南极海拔最高的科学考察站。在 JavaScript
社区中, Mozilla
的工程师 Kevin Dangoor
发起了 CommonJS
的提案。(最初叫ServerJS
)
再到后来横空出世的 Node.js
采用 CommonJS
模块化规范,同时还带来了 npm
(全球最大的模块仓库) 。
「nodejs 中模块的实现并非完全按照 CommonJS 的规范来的,而是进行了取舍,并增加了少许特性。」
CommonJS
规范在服务端表现出色,使得 JavaScript
在服务器端大放异彩,与传统服务器语言(PHP、Python)等产生抗衡甚至压倒之势。程序员们便萌发出了将它移植到浏览器端的想法。
然而由于CommonJS
的模块加载是同步的。我们知道,服务器端加载的模块从内存或磁盘中加载,耗时基本可忽略。但是在浏览器端却会造成阻塞,白屏时间过长,用户体验不够友好。
因此,从CommonJS
中逐渐产生了一些分支,也就是业内熟知的AMD
、CMD
等。
AMD规范
「James Burke」提出了 AMD
规范,RequireJS
也是他的代表作。他同时开发了 amdefine
(在node中可以使用AMD规范的库)。
AMD
主要是为了解决 CommonJS
规范在浏览器端的不足:
缺少模块封装的能力 使用同步的方式加载依赖 export
只能导出变量,导出函数需要用module.export
(这通常不符合直觉)
AMD
规范定义了一个 define
全局方法用来定义和加载模块,RequireJS
后期也扩展了 require
全局方法用来加载模块 。「其核心实现是内部的模块加载器。」
CMD规范
「@玉伯」提出了 sea.js
(CMD规范的实现)。
准确的说 CMD
是 SeaJS
在推广过程中对模块定义的规范化产物。
相比于AMD
的异步加载,CMD
更倾向于懒加载,规范本身也与CommonJS
更贴近。
因为是懒加载机制,所以 sea.js
提供了 seajs.use
方法,来运行已经定义的模块。所有 define
的回调函数都不会立即执行,而是将所有的回调函数进行缓存,只有 use
之后,以及被 require
的模块回调才会执行。
RequireJS 和 sea.js 的区别
sea.js
只有在require
的地方,才会真正执行模块。RequireJS
会先运行所有的依赖,得到所有的结果后再执行回调。
AMD 和 CMD 区别
对于依赖的模块, AMD
是提前执行,CMD
是延迟执行。CMD
推崇依赖就近,AMD
推崇依赖前置。AMD
的API
一个当多个用,职责单一。CMD
中,每个API都简单纯粹。
来自@玉伯的回答
https://www.zhihu.com/question/20351507/answer/14859415
UMD
UMD
是 AMD
和 CommonJS
的综合产物。AMD
用于浏览器,CommonJS
用于服务器。UMD
则是两者的兼容模式,解决了跨平台问题。
实现原理:if-else
详情请移步github
https://github.com/umdjs/umd
ES Module
天下大势,分久必合。
十年之后,官方爸爸推出的 ES6
模块化方案,一统浏览器和服务器。采用了完全静态化的方式进行模块加载。
语法
模块导入
// 默认导入default模块
// main.js
import name from './module.js'
// module.js
const name = '前端食堂'
export default name
// 如果想导入其他模块
// main.js
import { name, getName } from './module.js'
// module.js
export const name = '前端食堂'
export const getName = () => name
// 同时导入
// main.js
import name, { getName } from './module.js'
// module.js
const name = '前端食堂'
export const getName = () => name
export default name
// 重命名
// main.js
import * as mod from './module.js'
let name = ''
name = mod.name
name = mod.getName()
// module.js
export const name = '前端食堂'
export const getName = () => name
// 对单独的变量进行重命名
import { name, getName as getModName }
模块导出
// 第一种写法
export const name = '前端食堂'
// 第二种写法
export function getName() {
return name
}
export class Logger {
log(...args) {
console.log(...args)
}
}
// 第三种写法
const name = '前端食堂'
function getName() {
return name
}
class Logger {
log(...args) {
console.log(...args)
}
}
export {name, getName, Logger}
// 第四种写法
const name = '前端食堂'
export default name
这里只提供了一些基本语法,更多语法请参考阮一峰老师的 《ECMAScript 6 入门》。
https://es6.ruanyifeng.com/#docs/module
当然,不同的规范之中,被规范的语法也有所不同,这里推荐业内公认的语法规范airbnb。
https://github.com/airbnb/javascript
这里列举出几个airbnb
中模块导出/引入的规范。
1.如果模块只有一个输出值,就使用 export default
,如果模块有多个输出值,就不使用 export default
, export default
与普通的 export
不要同时使用。
2.模块导入时不要使用通配符。因为这样可以确保你的模块之中,有一个默认输出 (export default)
。
// bad
import * as myObject from './importModule';
// good
import myObject from './importModule';
ES Module与CommonJS的差异
1.语法 import/export
require/module
2.CommonJS
模块输出的是一个值的拷贝(不会随原始值变化),ES6
模块输出的是值的引用(会随着原始值变化)。
3.CommonJS
模块是运行时加载,ES6
模块是编译时输出接口。
写在最后
❝成天讨论这门语言好,或者那门语言坏的人,甚至是可悲的。既悲其一叶障目,更悲其大愚若智的自得心态。 ——《大道至简》
❞
JavaScrit
至今仍有被人诟病的缺憾之处。但历史告诉我们,再多的阻力也阻挡不了真正热爱它的人们。
感谢为 JavaScript
模块化之路贡献的开发者们。
公众号:前端食堂
知乎:童欧巴
掘金:童欧巴
这是一个终身学习的男人,他在坚持自己热爱的事情,欢迎你加入前端食堂,和这个男人一起开心的变胖~
推荐阅读: