背景说明
网络和时代在不断的发展,JavaScript不再仅仅是实现简单的页面交互逻辑,前端JS代码日益增多,如何管理好这些JS代码成为了一个难题。这时候就有了前端代码模块化的概念,在前端代码模块化的过程中,出现了很多模块化规范。例如CommonJS,AMD,CMD,ES6 Module规范,它们有着各自的优缺点。
1.为什么要模块化?
很久很久以前,前端是这样引入各个JS文件的
<script src="jquery.js"></script>
<script src="jquery_scroller.js"></script>
<script src="main.js"></script>
<script src="other1.js"></script>
<script src="other2.js"></script>
<script src="utils.js"></script>
就是这么简单的放在一起 有什么缺点?(难以维护,依赖模糊,请求过多)
- 使用这些文件的顺序不能出错。比如jquery需要先引入,才能引入jquery插件,才能在其他的文件中使用jquery。
- 污染全局作用域 例如utils.js文件中有each方法,log方法,那么再在别的文件不能再定义each方法,log方法(Yahoo有试过一个解决方案,如下,但仍不理想,其本质上仍然是一个对象)
var org = {};
org.CoolSite = {};
org.CoolSite.Utils = {};
org.CoolSite.Utils.each = function (arr) {
// 实现代码
};
org.CoolSite.Utils.log = function (str) {
// 实现代码
};
- 维护成本太高了,要找一个方法太困难。同时依赖关系也非常不明确。
2.模块化的好处有什么?
- 便于维护
- 便于复用
- 避免命名冲突
- 更好的分离,按需加载
3.模块化总共出现了几种规范?
CommonJS(Node.js) => AMD(require.js) => CMD(Sea.js) => ES Module(ES6模块化)
听说还有个UMD 它又是啥?
严格上说,umd不能算是一种模块规范,它没有模块定义和调用,这是AMD和CommonJS的结合体,保证模块可以被AMD和CommonJS调用
UMD其实就是AMD和CommonJS的组合,它的实现原理是啥?
- 先判断是否支持Node.js模块格式(判断exports是否存在),存在则使用Node.js模块格式
- 再判断是否支持AMD(判断define是否存在),存在则使用AMD方式加载模块
- 前两个都不存在,则将模块公开到全局(window或global)
4.为什么CommonJS还不错,却还需要AMD,CMD?
CommonJS是用同步的方式加载模块。 服务端,模块文件都存储在本地磁盘,读取很快,所以没问题 但是在浏览器端,限于网络原因,CommonJS并不适合浏览器端模块加载
于是就有了AMD,CMD这两个专门用于浏览器端的规范,他们都是异步加载模块
AMD规范是非同步加载模块,允许指定回调函数, 缺点是不能按需加载、开发成本大。 CMD规范是通过按需加载, 缺点是依赖SPM打包,模块的加载逻辑偏重。
5.同样适用于浏览器端,AMD和CMD有什么区别?
-
AMD预先加载所有的依赖,直到使用的时候才执行(依赖前置)
CMD推崇 依赖就近// CMD
define(function(require, exports, module) { var a = require('./a') a.doSomething() var b = require('./b') // 依赖可以就近书写 b.doSomething() })// AMD
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好 a.doSomething() b.doSomething() }) -
对于依赖的模块
AMD是提前执行(不过RequireJS从2.0开始,也改成可以延迟执行)
CMD是延迟执行(as lazy as possible 懒执行) -
AMD的API默认是一个当多个用,CMD推崇职责单一
例如:AMD里require分全局的和局部的。CMD里面没有全局的require,提供seajs.use()来实现模块系统的加载启动。CMD里每个API都简单纯粹。
6.CommonJS和ES6 Module有什么区别?
- CommonJS模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
- 值的拷贝: 一旦输出一个值,模块内部的变化就影响不到这个值
- 值的引用: 原始值变了的话,import加载的值也会跟着变
- CommonJS模块是运行时加载,ES6模块是编译时输出接口
- 运行时加载: CommonJS模块就是对象 输入时先加载整个模块,生成一个对象,再从这个对象上读取方法
- 编译时加载: ES6模块不是对象 通过export import可以指定加载某个输出值
7.目前开发主流的模块化规范是什么?
服务端,node 主要使用的就是CommonJS
而日常开发非服务端项目 主要使用ES6 Module,但是由于ES6目前无法在浏览器中执行,要通过babel将不被支持的import编译为当前受到广泛支持的require
8.CommonJS语法 和ES6 Module语法
- CommonJS
module.export = value
require()
- ES6 Module
export
import
9.AMD语法
/**
* 基本思想 通过define方法,将代码定义为模块
* 通过require方法,实现代码的模块加载
*/
define(['dataService','jquery'],function(dataService){
let name = 'Tom'
function showMsg(){
alert(dataService.getMsg() + name)
}
$('body').css('background','green')
//暴露模块
return { showMsg }
})
// main.js文件
(function() {
require.config({
baseUrl: 'js/', //基本路径 出发点在根目录下
paths: {
//自定义模块
alerter: './modules/alerter', //此处不能写成alerter.js,会报错
dataService: './modules/dataService',
// 第三方库模块
jquery: './libs/jquery-1.10.1' //注意:写成jQuery会报错
}
})
require(['alerter'], function(alerter) {
alerter.showMsg()
})
})()
10.CMD语法
define(function(require,exports){
exports.each = function(arr){
//实现代码
}
exports.log = function(str){
console.log(str)
}
var testdemo = require('./comment.js')
});
define(function(require,exports){
var util = require('./util.js');
exports.init = function(){
//实现代码
}
})
//通过require引入
require('./util.js')