前言 JavaScript 语言诞生至今,模块规范化之路曲曲折折。
前言
JavaScript 语言诞生至今,模块规范化之路曲曲折折。社区先后出现了各种解决方案,包括 AMD、CMD、CommonJS 等,而后 ECMA 组织在 JavaScript 语言标准层面,增加了模块功能(因为该功能是在 ES2015 版本引入的,所以在下文中将之称为 ES6 module)。
今天我们就来聊聊,为什么会出现这些不同的模块规范,它们在所处的历史节点解决了哪些问题?
何谓模块化?
或根据功能、或根据数据、或根据业务,将一个大程序拆分成互相依赖的小文件,再用简单的方式拼装起来。
全局变量
演示项目
为了更好的理解各个模块规范,先增加一个简单的项目用于演示。
Window
在刀耕火种的前端原始社会,JS 文件之间的通信基本完全依靠window
对象(借助 HTML、CSS 或后端等情况除外)。
-
config.js
var api = 'https://github.com/ronffy'; var config = { api: api, }
-
utils.js
var utils = { request() { console.log(window.config.api); } }
-
main.js
window.utils.request();
-
HTML
<!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta > <title>【深度全面】JS模块规范进化论</title> </head> <body> <!-- 所有 script 标签必须保证顺序正确,否则会依赖报错 --> <script src="./js/config.js"></script> <script src="./js/utils.js"></script> <script src="./js/main.js"></script> </body> </html>
IIFE
浏览器环境下,在全局作用域声明的变量都是全局变量。全局变量存在命名冲突、占用内存无法被回收、代码可读性低等诸多问题。
这时,IIFE(匿名立即执行函数)出现了:
用 IIFE 重构 config.js:
(function (root) {
var api = 'https://github.com/ronffy';
var config = {
api: api,
};
root.config = config;
}(window));
IIFE 的出现,使全局变量的声明数量得到了有效的控制。
命名空间
依靠window
对象承载数据的方式是 “不可靠” 的,如window.config.api
,如果window.config
不存在,则window.config.api
就会报错,所以为了避免这样的错误,代码里会大量的充斥var api = window.config && window.config.api;
这样的代码。
这时,namespace
登场了,简约版本的namespace
函数的实现(只为演示,不要用于生产):
function namespace(tpl, value) {
return tpl.split('.').reduce((pre, curr, i) => {
return (pre[curr] = i === tpl.split('.').length - 1
? (value || pre[curr])
: (pre[curr] || {}))
}, window);
}
用namespace
设置window.app.a.b
的值:
namespace('app.a.b', 3); // window.app.a.b 值为 3
用namespace
获取window.app.a.b
的值:
var b = namespace('app.a.b'); // b 的值为 3
var d = namespace('app.a.c.d'); // d 的值为 undefined
app.a.c
值为undefined
,但因为使用了namespace
, 所以app.a.c.d
不会报错,变量d
的值为undefined
。