1 概述
1.1 前端为什么需要模块化
- 解决命名冲突
- 提供代码复用性和可维护性
- 灵活架构,焦点分离,方便模块间组合、分解
- 多人协作互不干扰
1.2 js模块化发展历程
1.2.1 script标签
随着前端复杂度提高,为什么能够提高项目代码的复用性和可维护性等,就将一个功能封装成一个文件,把一个js文件当成一个模块,这样js文件也就多了起来。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="other3.js"></script>
简单的将js文件放在一起,但是彼此的引用顺序不能出错。比如必须先引入jquery,才能使用jquery_scroller,不然就会报错
优点:
相对于所有逻辑都在同一个js文件,模块化思想是进步的。
缺点:
污染全局作用域。因为每一个都是暴露在全局的,会导致全局变量命名冲突,可通过命名空间解决
依赖关系不明显,不利于维护。 比如main.js需要使用jquery,但是,从上面的文件中,我们是看不出来的,如果jquery忘记了,那么就会报错。
1.2.2 CommonJS规范
CommonJS规范是一个对js模块化的规范,最初用在服务端node上。webpack是支持CommonJS规范的。
根据规范,每一个文件都是模块,其内部定义的变量属于这个模块,不会对外暴露,即不会污染全局变量。中心思想是通过require方法同步加载所依赖的模块,然后通过exports或者module.exports来导出需要暴露的接口。如下所示:
// a.js
var x = 5;
var addX = function (value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
//b.js
var a = require('./a.js');
console.log(example.x); // 5
console.log(example.addX(1)); // 6
这里的a.js就是CommonJS规范的模块了。module代表这个模块,exports属性就是对外暴露的接口,可以对外导出可访问的变量和方法,比如x和addX
exports是对module.exports的引用,所以我们可以认为模块顶部有exports = module.exports这样一个定义,所以我们不能直接给exports赋值
这样我们在b.js中就能获取到a.js暴露的变量和方法
优点:
解决了依赖和全局变量污染的问题
缺点:
因为CommonJS是同步加载的,服务器端没有问题,但是在浏览器端需要将文件从服务器端请求过来,只有加载完成才能执行后面的操作,会阻塞渲染,所以不适用在浏览器端
1.2.3 AMD规范
AMD规范是非同步加载模块,允许指定回调函数。AMD规范的实现就是require.js
AMD标准中定义了下面两个api
- require([module], callback)
- define(id, [depends], callback)
即通过define定义一个模块,然后使用require来加载一个模块,并且require还支持CommonJS的导出方式
定义alert模块:
define(function () {
var alertName = function (str) {
alert("I am " + str);
}
var alertAge = function (num) {
alert("I am " + num + " years old");
}
return {
alertName: alertName,
alertAge: alertAge
};
});
引入模块:
require(['alert'], function (alert) {
alert.alertName('zhansan');
alert.alertAge(18);
});
优点:
支持浏览器环境异步加载模块,可以并行加载多个模块
缺点:
提高了开发成本,并且不能按需加载,必须提前加载所有的依赖
1.2.4 CMD规范
CMD和AMD类似,即一个js文件就是一个模块,但是CMD是按需加载,不是在模块开始加载所有的依赖,如下所示:
define(function(require, exports, module) {
var $ = require('jquery');
var Spinning = require('./spinning');
exports.doSomething = ...
module.exports = ...
})
优点:
实现了浏览器端的模块话加载
按需加载,依赖就近
缺点:
依赖SPM打包,模块的加载逻辑偏重。
这时我们就可以看出AMD和CMD的区别了,前者是对于依赖的模块提前执行,而后者是延迟执行。 前者推崇依赖前置,而后者推崇依赖就近,即只在需要用到某个模块的时候再require。 如下:
// AMD
define(['./a', './b'], function(a, b) { // 依赖必须一开始就引入
a.doSomething()
b.doSomething()
//...
});
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
var b = require('./b')
// 依赖可以就近引入
b.doSomething()
// ...
});
1.2.5 ES6模块化
ES6模块化方案是真正的规范,上面的几种方案只是前段社区自己实现的。在ES6中,通过import关键字引入模块,export关键字导出模块。ES6模块化和CommonJs的一个区别就是前者导入导出都是引用,而后者导入的是值的拷贝。
/**
* 首字母大写
* @param str
*/
export const capitalize = (str: string) => {
str = str || '';
if (str.length > 0) {
const first = str.substr(0, 1).toUpperCase();
const spare = str.substr(1, str.length);
return first + spare;
}
};