这部分是针对过去ES5的,ES6里面已经可以实现很好的模块化理念,但是考虑到目前仍有许多基于ES5的前端架构项目,还是有必要把这些写下来。想看干货的可以直接拉到最下。
Javascript不是一种模块化编程语言,它不支持"类"(class),更遑论"模块"(module)。
模块化的目的:
更好的代码组织方式、动态加载、避免命名冲突、更好的依赖处理、解耦和复用。
如何模块化?
第一种,可能也是平时大家最常用的一种方式,缺点也显而易见。
//函数m1()和m2(),组成一个模块。
//污染全局变量
function m1(){
//...
}
function m2(){
//...
}
于是乎,为了不污染全局变量,有人做了如下工作:
//对象写法:暴露所有模块成员,没有动态加载
var module1 = new Object({
_count : 0,
m1: function (){
//...
},
m2: function (){
//...
}
});
这种看着感觉不错,还能有属于自己的模块变量 _count,简直就像个温馨的小家!然而所有的模块成员对外部是可见的,并且无法支持动态加载,即无法按需处理依赖关系。那么接下来,熟悉闭包的同学可能会提出下面这种方式。
//使用"立即执行函数"(Immediately-Invoked Function Expression,IIFE),
//可以达到不暴露私有成员的目的。
var module1 = (function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
return {
m1 : m1,
m2 : m2
};
})();
上面的例子,从模块的角度已经非常实用了,唯一的缺陷可能就是无法实现动态加载依赖。想象一下,你刚学会了这种新的模块化JS写法,你理所当然的希望把你当前的前端项目用这种模块化方式管理起来,于是你把现有的系统功能封装成一个个模块,并把他们写成了单独的js文件,通过index.html统一引入。一切看上去非常的美好,但是还是遇到了问题!
//这段代码依次加载多个js文件。
//加载时停止网页渲染
//严格保证加载顺序
//代码维护困难。
<script src="Moudle1.js"></script>
<script src="Moudle2.js"></script>
<script src="Moudle3.js"></script>
<script src="Moudle4.js"></script>
<script src="Moudle5.js"></script>
<script src="Moudle6.js"></script>
你的index.html可能看上去是这样的,你的某个模块停止了运行,检查发现Moudle2中引用到了Moudle3的方法,但是此时Moudle3.js还未加载,系统提示未找到模块对象。
实际上当模块数量增多之后,随着复杂的依赖关系,顺序的调整变得逐渐不可控,维护的成本几何增加,是时候使用动态加载来解放我们的工作了!
什么是AMD?
AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
// 定义:新模块的名字,依赖的模块,实际定义
define('moduleB',['moduleA'],callback);
//请求:
require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
// some code here
});
AMD是一种规范!AMD是一种规范!AMD是一种规范!
事实上,目前有许许多多的js框架都已经实现了AMD规范。
接下来,我们可以试着自己实现一个遵循规范的动态加载实例。
//一个实现动态加载模块化的例子
var global={};
global.defs = {};
//通过id,对相应模块进行实例化
var instantiate = function (id) {
var actual = global.defs[id];
var dependencies = actual.deps;
var definition = actual.defn;
var len = dependencies.length;
var instances = new Array(len);
for (var i = 0; i < len; ++i)
instances[i] = dem(dependencies[i]);
var defResult = definition.apply(null, instances);
if (defResult === undefined)
throw 'module [' + id + '] returned undefined';
actual.instance = defResult;
};
//定义模块,将模块ID与它的依赖、定义绑定
var def = function (id, dependencies, definition) {
if (typeof id !== 'string')
throw 'module id must be a string';
else if (dependencies === undefined)
throw 'no dependencies for ' + id;
else if (definition === undefined)
throw 'no definition function for ' + id;
global.defs[id] = {
deps: dependencies,
defn: definition,
instance: undefined
};
};
//通过一个id,获取这个id对应的模块的实例,如果该实例不存在,则对其实例化
var dem = function (id) {
var actual = global.defs[id];
if (actual === undefined)
throw 'module [' + id + '] was undefined';
else if (actual.instance === undefined)
instantiate(id);
return actual.instance;
};
//加载请求的模块,在加载完成后的环境中运行代码
var req = function (ids, callback) {
var len = ids.length;
var instances = new Array();
for (var i = 0; i < len; ++i)
instances.push(dem(ids[i]));
callback.apply(null, instances);
};
var define = def;
var require = req;
var demand = dem;
//定义一个数学模块
define(
'math',
[],
function () {
var obj={};
obj.run=function () {
console.log("你获得了数学能力");
}
console.log("数学能力以加载");
return obj;
}
);
//定义一个物理模块
define(
'physics',
[],
function () {
var obj={};
obj.run=function () {
console.log("你获得了物理能力");
}
console.log("物理能力以加载");
return obj;
}
);
//定义一个研究模块
define(
'research',
["math","physics"],
function (math,physics) {
var obj={};
obj.run=function () {
math.run();
physics.run();
console.log("你获得了研究能力");
}
console.log("研究能力以加载");
return obj;
}
);
//调用研究模块,在调用的时候,会动态加载研究模块的依赖,即物理模块和数学模块。
require(['research'],function (research) {
research.run();
});