AMD, CMD, CommonJs, ES6 模块… 这是些什么鬼?
为什么引入一个模块, 有时用 require(“express”) , 而有时却要用 import Vue from ‘vue’ ?
让我们一起来梳理一下 JavaScript 的模块化吧 ~
1. 引例
什么是模块化? 为什么需要模块化? 让我们来看一个简单的例子…
假设有 2 个 js 文件: m1.js
和 m2.js
, 代码分别如下:
/m1.js
1 | const x = 'M1' |
/m2.js
1 | const x = 'M2' |
然后, 我们再写一个 index.html
, 代码如下:
/index.html
1 |
|
最后, 用浏览器打开index.html
, 你看到了什么?
是不是前后弹出了消息框 “M1” 和 “Hello, I’m M1” ? 如果打开Chrome的”开发者工具”, 你还将在控制台看到报错信息:
“Uncaught SyntaxError: Identifier ‘x’ has already been declared” (m2.js : 1)
回看一下上面的代码, 你会发现在 m1.js 和 m2.js 中都定义了常量 x
和 函数 sayHello
, 在 index.html 中, 我们先后将 m1.js 和 m2.js 引入, 将它们”整合”到了一起. 可以想像, 常量 x 和 函数 sayHello 势必会冲突, 冲突的结果就是 m2.js 完败, 因为代码按顺序执行到 m2.js 中的第1行就出错了, 导致整个 m2.js 中的代码都没有成功执行. 最终的结果就是你看到的那样…
现在, 把 m2.js 中的第1行注释掉, 刷新网页, 你会发现这次弹出的消息框分别是 “M1” 和 “Hello, I’m M2”, 有意思吧, m2.js 中 sayHello 函数的定义”覆盖”了 m1.js 中的定义.
经过上面一番折腾, 你悟到了什么?
是的, JavaScript 的”散装”特性导致我们将多个.js 文件”整合”到一起时, 极易发生过高的”耦合”, 导致各种冲突和错误.
在当初 JavaScript 还是一个小角色的时候, 这样的冲突基本可以通过程序员们自己”注意点”就可以避免. 但如今, 无论在前端还是在后端(如: Node.js) JavaScript 俨然成了重要角色, 项目自身的JavaScript代码量暴增, 第3方代码大量整合, 这一切都令上述”冲突”频发, 亟需实现”模块化”, 简单说就是让不同 .js 文件中的代码处于一个独立的”模块”中, 模块内怎么乱管不着, 但各个模块间应相互独立, 互不干扰.
这就是引入”模块化”思想的初衷.
2. “模块化”实践
知道了为什么需要”模块化”, 那么如何实现呢?
一个最朴素的想法可以这样…
/m1.js
1 | const M1 = { |
m2.js 也作类似修改. 然后以M1.x
, M1.sayHello()
的方式使用.
嘿嘿, 有点简陋啊~
不过, 我们确实通过对象的”封装”实现了”模块化”, 哈哈~
实践中通常会使用”立即执行的函数”进行封装, 只是那样写的话, 小白可能不容易看明白, 暂不去扯了… 下面有更高级的方式, 继续往下看…
2.1 百花齐放, 还是群魔乱舞
我们俗称的 JavaScript, 其”学名”应叫 ECMAScript, 简称 ES.
在2015年6月以前, 我们使用的是 ES5 版本(2009年发布). 在那个年代, JavaScript 只被当作浏览器中运行的”小脚本”, 跑龙套的角色, 呵呵~ 在 ES5 标准中并没有模块的概念.
在那个时代, JavaScript 代码通过类似的方式引入后, 全部混杂在一起, 基本上相当于把各个 .js 文件中的代码都复制 + 粘贴在一起, 这就很凶险了… 就像一群疯子在公海搞军事演习, 一不小心就擦枪走火… 命名冲突, 代码相互耦合等问题层出不穷.
当然, 一些聪明的娃娃会使用函数/对象进行封装, 以隔离代码/变量. 但谁又能保证这世界上是没有几个二货…
随着时代的发展, JavaScript 逐渐从浏览器里的”小脚本”成长为使用广泛的编程语言, 模块化成为了大家梦寐以求的东西. 所谓模块化, 可简单理解为将代码封装成相对独立的单元(模块), 在模块内部你想翻天都行, 但模块之间的相互协作则需要遵循一套规范的语法. 这其实是软件工程”高内聚, 低耦合”思想的体现.
在 JavaScript 模块化探索的过程中, 出现过很多规范(标准/建议), 比如: AMD, CMD, CommonJs… 它们都算是民间标准, 因为…. ECMA 官方似乎睡着了, 标准严重滞后…
- AMD (Asynchronous Module Definition) 从名字就可以看出, 它使用异步的模块加载策略, 因而更适合用于浏览器端, 以免前端页面”假死”.
AMD是一个规范, 它是 RequireJS
推广过程中对模块定义的规范化产出,而 RequireJS 是对这个概念的实现.
CMD(Common Module Definition) 是阿里的大神搞出来的东东(是的, 就是你的那个阿里爸爸), CMD同样是一个规范, 在阿里的
Sea.js
中遵循了这个规范. 更适合于浏览器端CommonJS 使用的是同步加载策略, 更多适用于服务器端. 在
Node.js
出生的年代, ECMA 还在睡… 所以, 它在万千佳丽中选择了 CommonJS 作为其模块化方案.
直到 2015年6月, ECMA 终于醒了, “正规军”终于来了, 发布了 ECMAScript 6, ES6 顺应时代潮流, 引入了模块化规范. 但不幸的是, 它走出了一条自己的路… 虽然, 所体现的”思想”与世上已经存在的模块化方案一致, 但在具体的语法上多少有些细微的差异. 所以, 目前在模块化标准这事上, 似乎又开始群魔乱舞了…
随着 Node.js 版本的更替, 它也想着向正规军靠拢, 但时至今日, 在 Node.js v14.7.0 版本中, 对标准的 ECMAScript 模块规范的支持仍处于实验阶段.
2.2 怎么选?
BB了这么半天, 那有什么结论吗?
展望未来, 随着ECMAScript 标准逐步完善, 应该可以看到天下大统, 近年来, ECMAScript 标准高频更新, 应该在不久的将来就会天下归一, 到时也自然不用纠结该用什么模块化规范了, 选正规军就好. 只是, 目前的阶段还有点点乱…
个人拙见
服务器端 ( Node.js 的世界 ), 先遵循CommonJS, 等Node.js完全兼容ECMAScript再说.
浏览器端可使用ECMAScript标准 (ES Module). 目前各大主流浏览器基本上已经支持ES Module, 参见https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/export. 更何况我们不是还有webpack, babel 吗?
3. 实操
写点代码实操一下吧~
3.1 浏览器端 ( ES Module )
引例中 m1.js 和 m2.js 的代码分别这样改一下…
/m1.js
1 | // 命名导出 |
/m2.js
1 | const x = 'M2' |
以上代码分别简单演示了ECMAScript标准中, 模块向外界导出 feature 的两种方式: 命名导出 和 默认导出
来看看怎么使用… 修改 index.html
/index.html
1 |
|
注意第5行中的
type="module"
, 只有在一个模块(module)中才可以使用import
去引入别的模块默认导出的东西, 可以一坨的导入, 如第7行, 也可以使用
import { x, sayHello } from "./m2.js"
的方式”解构”导入第6行不能写作
import m1 from "./m1.js"
, 因为m1.js中没有默认导出.但可写作
import * as m1 from "./m1.js"
, 将命名导出的所有 feature “整合”为 m1, 然后使用m1.x
和m1.sayHello()
的方式引用.
如果你用 VUE CLI 搭建过VUE项目, 你应该见过上面代码中的写法.
上面的例子不能直接双击在浏览器里打开执行, 即不能通过 file:// URL 方式运行 JS 模块 — 这将导致 CORS 错误.
你需要通过 HTTP 服务器运行, 也就是例如 http://localhost:8080/index.html 的方式打开.
你电脑上应该装了 Node.js 环境吧 ~ 那你可以使用
anywhere
之类的静态服务器启动项目试试 ~
1
2
3
4
5 # 全局安装 anywhere
npm i anywhere -g
# 进入index.html所在的目录, 启动anywhere
anywhere
3.2 服务器端(Node.js, CommonJS)
如前所述, 在 Node.js 的世界里使用的是 CommonJS 规范, 虽然它已经在向 ECMAScript 规范靠拢了, 但目前还处于试验阶段.
下面举个小例子, 展示一下CommonJS模块规范.
在Node.js的世界里, 每个 .js 文件都自成一个模块, 需要主动使用module.exports
向外界导出需要”暴露”的内部 feature.
/module-example.js
1 | const PI = 3.14 |
index.js
1 | const m = require('./module-example.js') |
其实单独看某种模块化规范, 无论是 ES Module 还是 CommonJS 都不难懂.
只是实践中常会一个会同时出现两种规范, 例如前端VUE, 后端Node.js的项目中两种规范的写法都会出现, 保存头脑清醒, 不要搞混了就行.
Revised on 2021/10/30 23:45:12 by Bailey
-
Next PostWeb应用程序的鉴权机制与JWT
-
Previous PostFlex -- 网页布局好帮手