查看原文
其他

五分钟带你回顾前端模块化发展史

霍语佳 前端食堂 2021-01-15


这是前端食堂的第20篇原创 



「观感度:🌟🌟🌟🌟🌟」

「口味:麻酱面藕」

「烹饪时间: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中逐渐产生了一些分支,也就是业内熟知的AMDCMD等。

AMD规范

「James Burke」提出了 AMD 规范,RequireJS 也是他的代表作。他同时开发了 amdefine(在node中可以使用AMD规范的库)。

AMD 主要是为了解决 CommonJS 规范在浏览器端的不足:

  • 缺少模块封装的能力
  • 使用同步的方式加载依赖
  • export 只能导出变量,导出函数需要用 module.export (这通常不符合直觉)

AMD 规范定义了一个 define 全局方法用来定义和加载模块,RequireJS 后期也扩展了 require 全局方法用来加载模块 。「其核心实现是内部的模块加载器。」

CMD规范

「@玉伯」提出了 sea.js (CMD规范的实现)。

准确的说 CMDSeaJS 在推广过程中对模块定义的规范化产物。

相比于AMD的异步加载,CMD更倾向于懒加载,规范本身也与CommonJS更贴近。

因为是懒加载机制,所以 sea.js 提供了 seajs.use 方法,来运行已经定义的模块。所有 define 的回调函数都不会立即执行,而是将所有的回调函数进行缓存,只有 use 之后,以及被 require的模块回调才会执行。

RequireJS 和 sea.js 的区别

  • sea.js 只有在 require 的地方,才会真正执行模块。
  • RequireJS 会先运行所有的依赖,得到所有的结果后再执行回调。

AMD 和 CMD 区别

  • 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。
  • CMD 推崇依赖就近,AMD 推崇依赖前置。
  • AMDAPI 一个当多个用,职责单一。CMD 中,每个API都简单纯粹。

来自@玉伯的回答

 https://www.zhihu.com/question/20351507/answer/14859415

UMD

UMDAMDCommonJS 的综合产物。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 defaultexport 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 模块化之路贡献的开发者们。


公众号:前端食堂

知乎:童欧巴

掘金:童欧巴

这是一个终身学习的男人,他在坚持自己热爱的事情,欢迎你加入前端食堂,和这个男人一起开心的变胖~


推荐阅读:

2020年前端工程师提升路线图

Vue.js 纪录片来了

前端食堂读者福利



在看和转发是莫大鼓励


Modified on

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存