林秀栋的技术博客

设计模式(三十四) 异步模块模式

异步模块模式(AMD):请求发出后,继续其他业务逻辑,直到模块加载完成后执行后续逻辑,实现模块开发对模块加载完成后的引用

由于同步模块会立即引用模块,如果改模块在另一个js,而该js正在加载,无法立即得到该模块,就会出问题

//闭包环境
//向闭包传入模块管理器对象F,~屏蔽压缩文件时,前面漏写;报错
~(function(F){
	var moduleCache = {};
})((function(){
	//创建模块管理器对象F,并保存在全局作用域中
	return window.F = {};
})());

//创建与调度模块
//这里的module方法和同步模式中不太一样,集创建和调度于一身
//它会遍历所有模块,直到都存在才会运行回调,否则加载相应文件,直到文件加载完才执行回调
F.module = function(url, modDeps, modCallback){ //模块url,依赖模块,模块主函数
	var args = [].slice.call(arguments),	//将参数转为数组
		callback = args.pop(),	//获取模块构造函数
		//获取依赖模块
		deps = (args.length && args[args.length - 1] instanceof Array) ? args.pop() : [],
		url = args.length ? args.pop() : null,	//模块url(模块ID)
		params = [],	//依赖模块序列
		depsCount = 0,	//未加载的依赖模块数量统计
		i = 0,	//依赖模块序列中的索引值
		len;	//依赖模块长度
	//获取依赖模块长度
	if(len = deps.length){
		while(i < len){	//遍历依赖模块
			(function(i){	//闭包保存i
				depsCount++;	//增加未加载依赖模块数量统计
				loadModule(deps[i], function(mod){	//异步加载依赖模块
					params[i] = mod; //依赖模块序列中添加依赖模块接口引用
					depsCount--;	//依赖模块加载完成,依赖模块数量统计减一
					if(depsCount === 0){
						//若依赖模块全部加载,在模块缓存器中矫正该模块,并执行构造函数
						setModule(url, params, callback);
					}
				});
			})(i);
			i++;
		}
	}else{	//无依赖模块,直接执行回调
		setModule(url, [], callback);	//在模块缓存器矫正该模块,并执行构造函数
	}
}

var moduleCache = {};
//加载依赖模块对应文件并执行回调函数
//有三种情况
//1.文件已被加载过,区分已经加载完成还是正在加载,如果已经加载完成则调用
//2.未加载完成,将加载完成的回调缓存进模块加载完成的回调函数容器中(onload数组容器)
//3.文件未被加载,则加载该文件并将依赖模块的初始化信息写入模块缓存器
var loadModule = function(moduleName, callback){	//模块路径(id),回调
	var _module;	//依赖模块
	if(moduleCache[moduleName]){	//如果依赖模块被要求加载过
		_module = moduleCache[moduleName];
		if(_module.status === 'loaded'){	//如果模块加载完成
			setTimeout(callback(_module.exports), 0);	//执行回调
		}else{
			_module.onload.push(callback);	//缓存回调
		}
	}else{	//模块第一次被依赖引用
		moduleCache[moduleName] = {	//缓存该模块的初始化信息
			moduleName: moduleName,	//模块id
			status: 'loading',	//模块对应文件加载状态,默认加载中
			exports: null,	//模块接口
			onload: [callback]	//模块对应文件加载完的回调函数缓冲器
		};
		loadScript(getUrl(moduleName));	//加载模块对应文件
	}
};

//获取文件路径
var getUrl = function(moduleName){
	return String(moduleName).replace(/\.js$/g, '') + '.js';
};

//加载脚本文件
var loadScript = function(src){
	var _script = document.createElement('script');
	_script.type = 'text/JavaScript';
	_script.charsrt = 'UTF-8';
	_script.async = true;
	_script.src = src;
	document.getElementsByTagName('head')[0].appendChild(_script);
};


//执行模块回调函数
//对创建的模块,如果所有依赖模块加载完,则执行
//对被依赖的模块,所在文件加载后执行该依赖模块又间接地使用该方法
//对匿名模块(F.module方法中无url参数),执行过程也会使用该方法
var setModule = function(moduleName, params, callback){	//模块id,依赖模块,模块构造函数
	var _module, fn;	//模块容器,模块文件加载完成回调函数
	if(moduleCache[moduleName]){	//如果模块被调用过
		_module = moduleCache[moduleName];	//获取模块
		_module.status = 'loaded';	//设置模块已经加载完成
		_module.exports = callback ? callback.apply(_module, params) : null;	//矫正模块接口
		while(fn = _module.onload.shift()){	//执行模块文件加载完成回调函数
			fn(_module.exports);
		}
	}else{	//模块不存在(匿名模块),则直接执行构造函数
		callback && callback.apply(null, params);
	}
};


//使用

//模块dom
F.module('lib/dom', function(){ console.log(1) });
//模块event,依赖模块dom
F.module('lib/event', ['lib/dom'], function(dom){ console.log(2) })
//在html引用模块
F.module(['lib/event', 'lib/dom'], function(event, dom){ console.log(3) })