命名冲突和文件依赖复杂,是前端开发过程中的两个经典问题。解决方式就是模块化编程。
比如 Sea.js,一个文件就是一个模块,通过 exports 暴露模块,再通过 require 引入模块。这意味着不需要命名空间了,可以让模块依赖内置。开发者只需关心当前模块的依赖,其他事情 Sea.js 都会自动处理好。对模块开发者来说,这是一种很好的关注度分离。
模块化编程的优点是提高可维护性。
业界遵循的规范
- Node.js 遵循 CommonJs 规范。
- Sea.js 遵循 CMD 规范
- require.js 遵循 AMD 规范。
CommonJs、AMD、CMD、UMD 的区别
- AMD 全称 Asynchronous Module Definition,CMD 全称 Common Module Definition,UMD 全称 Universal Module Definition。
- CommonJs 是同步的,主要用在服务器端,因为模块都是放在服务器,读取速度不成问题。
- AMD、CMD 是异步的,主要用在浏览器端,因为从服务器加载模块需要时间,所以采用异步。
- AMD 输出模块兼容 CommonJs 。
- 在编写模块时,AMD 的形式是依赖前置(默认)和依赖就近,CMD 的形式是依赖就近。
- UMD 是 AMD 与 CommonJs 的结合。
四种规范的书写例子
CommonJs:
1var bye = require('./bye.js');2var a = 1;3function b() {4 console.log(a);5}6module.exports = {7 b: b;8};9exports.bye = bye;
AMD:
1define(['a', 'c'], function(a, c) {2 // ['a', 'c'] 是模块依赖前置3 a();4 b();5
6 var b = 1;7 // 输出模块有三种或者不输出8 // 1 直接 return9 return b;10 // 2 兼容 CommonJs11 module.exports = {12 a: a;13 };14 // 3 兼容 CommonJs15 exports.b = require('./hello.js').b;1 collapsed line
16});
CMD:
1define(function(require, exports, module) {2 var a = require('./hello.js'); // 模块依赖就近3 var b = require('./bye.js'); // 模块依赖就近4 a();5 b();6 // 输出模块有三种或者不输出7 // 1 直接 return8 return a;9 // 2 兼容 CommonJs10 module.exports = {11 a: a;12 };13 // 3 兼容 CommonJs14 exports.b = b;15})
UMD:
1(function(window, factory) {2 if (typeof exports === 'object') {3 // CommonJs4 module.exports = factory();5 } else if (typeof define === 'function' && defind.amd) {6 // AMD7 defind(factory);8 } else {9 // None10 window.eventUtil = factory();11 }12})(this, function() {13 // ...14})
ES6 Module
最后说一下 ES6 Module,它应该是未来模块化的趋势,现在笔者在写代码也都是用 ES6 模块化语法,再用构建工具编译。
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
上面提到运行时加载和编译时加载,ES6 Module 属于后者,即无需加载整个模块后再读取方法,而是在编译时就能指定方法。
ES6 Module 还有个特点是,不仅可以在 JavaScript 文件,也可以在浏览器里使用 Module。
在 JavaScript 使用:
1// 导出2export function doStuff() {}3// 引入4import "doStuff";
在浏览器里使用:
1<script type="module" src="./main.js"></script>2// 或3<script type="module">4 import './main.js';5</script>
目前支持 ES6 Module 的浏览器:
- Safari 10.1+
- Chrome 61+
- Firefox 54 需要设置 dom.moduleScripts.enabled
- Edge 16+
使用 ES6 Module 需要注意的规范:
- 注意引用的路径,不支持 main.js,支持 /main.js,./main.js,../main.js,**/main.js
- 使用 nomodule 属性向后兼容
- Modules 默认使用 Defer,即慢于同步的请求,但先于显式 Defer 的请求
- 引用或内联的 Modules 都支持 Async
- 多次引用同一个 Module,只执行一次
- 通常需要 CORS,即需要
Access-Control-Allow-Origin: *
- 发送请求默认不包括证书
- Modules 需要设置可用的 MIME 类型,否则不会执行
最后再贴上一个优秀的 slides,方便读者理解模块化的来龙去脉:JavaScript 模块化七日谈
参考:
Javascript模块化编程(一):模块的写法
Javascript模块化编程(二):AMD规范
Javascript模块化编程(三):require.js的用法
ECMAScript 6 入门
关于 CommonJS AMD CMD UMD
SeaJS 和 RequireJS 的异同
AMD 和 CMD 的区别有哪些?
ECMAScript modules in browsers