JS的AMD

原创
2013/03/14 22:08
阅读数 979

            什么是AMD呢,引用维基百科的一段话吧:Asynchronous module definition (AMD) is a JavaScript API for defining modules such that the module and its dependencies can be asynchronously loaded. It is useful in improving the performance of websites by bypassing synchronous loading of modules along with the rest of the site content.

             更多信息可以直接查看维基百科:http://en.wikipedia.org/wiki/Asynchronous_module_definition

             我之前做JS开发,一般是这么做的:

              首先在页面的尾部写入script,script标签的src属性写入JS文件路径,然后再在这个JS文件中写入代码,如果有很多不同功能的代码,那么就多几个script。对一般的应用来说,这样就够了,但是如果你面临的是一个很复杂的工程,你现在需要写一个JS库来支持这些功能,你不可能把所有功能都写在一个JS文件中,所以就会按照一定的规则将这个库切分成多个文件,现在假设这个库有core,A,B,C,D这几块儿,core是基础,必须先载入,载入之后又要用到B,但是B又依赖于C,C又依赖于D,那么在写script标签的时候就必须特别注意引入这几块儿的顺序了,顺序错了,肯定JS就会报错。

              那么如果这样,每个页面我都需要特别注意script的顺序,并且一旦这个库修改了一下,比如B现在不光依赖C,还依赖E,那么只有在每个引入了B的html文件中修改,你想想,这样的工作量真心巨大啊。

              那么有什么办法可以解决这个问题呢?

              其实解决办法就是上面我提到的AMD。

              node.js有require,但是它是使用的同步方式,而我说的是采用异步方式。

              当然,现在市面上已经有很多这种加载器了,比如require.js,如果在实际项目中,可以直接使用这些成熟的加载器,但是为了学习,我觉得还是可以自己胡乱搞一搞。

              AMD规范中定义了define,定义如下:

                      define([module-name?], [array-of-dependencies?], [module-factory-or-object]);

             第一个参数是模块名,按照规范是可以省略的,第二个参数是这个模块所依赖的模块,第三个参数就是这个模块的实现。

             PS:我个人觉得第一个参数还是不要省略了,因为省略了感觉就不那么知名达意了。

             刚才的define函数是模块的定义,那么还需要有模块的加载,我个人JS比较弱,貌似没在AMD规范中找到模块加载的定义,因为我稍微看了一下kissy,kissy是使用的use,再想了一下,use这个名字的确不错,所以我也就使用use了。

             use这个函数有两个参数,第一个参数是要使用的模块名的列表,第二个参数是回调,也就是这些模块加载完成之后执行的函数,比如:use(["a","b"],function(a,b) {}),这里的回调函数需要将这两个模块作为参数传递进去。

             首先在讲define和use之前,需要写一下JS脚本加载的代码,这个代码实现的功能就是JS脚本的载入,当然,是异步的:

 

var _loadJs = function(file,callback) {
	callback = callback ? callback : function() {};
	var script = document.createElement("script");
	script.type = "text/javascript";
	script.async = true;
	if(script.readyState) {
		//IE
		script.onreadystatechange = function() {
			if(("loaded" === script.readyState) || ("complete" === script.readyState)) {
				script.onreadystatechange = null;
				callback();
			}
		}
	} else {
		script.onload = function() {
			callback();
		}
	}
	script.src = file;
	document.body.appendChild(script);
}


          首先说一下,我自己实现的这个加载器自己都感觉不是特别好,作为JS的初学者,希望各位大神不喜勿喷。

          首先是define,假设现在define('a',['b','c'],function(b,c) {}) ,因为a模块依赖b和c,那么就需要首先调用_loadJs这个函数将b和c载入,比如现在加载b,由于_loadJs有回调函数,所以我们可以在这个上面做一点文章,首先调用_loadJs之前,我将这个模块放入loading的队列中,加载完毕之后在回调函数中将b从loading队列中删除,并将它插入到loaded这个队列,define设定一个定时程序,定时去loaded队列中查询是否所有模块都已经在了,如果是,那么执行回调,否则,查看各个模块是否在loading队列,如果在,不操作,否则,调用_loadJs。

         use的时候,也需要查看loaded这个队列和loading队列,如果所有模块加载完成,那么执行回调。

         当然,在这儿,由于b和c需要作为参数传递进去,所以还需要有一个字面量的对象存储这些模块,这个我就不多说了。

          反正都丢人了,顺便也把自己这段垃圾代码贴出来吧!

     

(function() {
	/*
	思路:
	 tp.use中监测是否有依赖,若没有依赖,直接执行,如果有依赖,但依赖已经被载入,也直接执行,否则,将所依赖的模块进行for,并且查看是否这个模块正在被载入,如果没有正在被载入,那么进行load,
	 并且进行setInterval,如果检测到在loaded数组中找到所有的模块,进行clearInterval,并且执行
	 define中判定是否有依赖,如果没有,直接执行,如果有胆识依赖已经被载入,也直接执行,否则,同上进行for,setInterval发现所有已经loaded,clearInterval,执行回调,并且将这个模块放入loaded数组
	 */
	var _loaded = ['tp.core'], //已经被载入的模块,默认载入tp.core.js
		_loading = []; //正在被载入的模块
	/**
	 * AMD规范
	 * @param {String} moduleName 模块名,本来按照AMD规范是可以省略的,但此处不可省略
	 * @param {String|Array} deps 依赖的模块
	 * @param {Function} factory 回调
	 */
	tp.define = function(moduleName,deps,factory) {
		deps = _getDeps(deps);
		_checkWaitingStatus(deps,function() {
			_loaded.push(moduleName);
			_loading.remove(moduleName);
			factory.apply(factory,tp.modules.get(deps));
		});
	};
	window.define = tp.define; //将这个函数勾到window上面
	/**
	 * 使用模块
	 * @param {Array|String} deps 依赖的模块名
	 * @param {Function} callback 回调函数
	 */
	tp.use = function(deps,callback) {
		deps = _getDeps(deps);
		_checkWaitingStatus(deps,function() {
			callback.apply(callback,tp.modules.get(deps));
		});
	};
	function _getDeps(deps) {
		if(deps) {
			if(tp.type.isString(deps)) {
				//只有一个元素
				return [deps];
			} else {
				return deps;
			}
		}
		return [];
	}
	function _checkWaitingStatus(waiting,callback) {
		var waitingCount = waiting.length, //waiting的个数
			notLoadedCount = 0, //还没有被载入的模块个数
			suffix = tp.global.config.get("debug") ? ".js" : ".min.js",
			libBasePath = tp.global.config.get("libBasePath");
		for(var i = 0; i < waitingCount; i++) {
			if(!_loaded.isFind(waiting[i])) {
				//还没有被载入
				notLoadedCount ++;
				if(!_loading.isFind(waiting[i])) {
					_loading.push(waiting[i]);
					tp.load.asynJs(libBasePath + waiting[i] + suffix + "?time=" + tp.lastCompileTime,function() {});
				}
			}
		}
		if(0 == notLoadedCount) {
			callback();
		} else {
			var interval = setInterval(function() {
				if(_loaded.isFindAll(waiting)) {
					clearInterval(interval);
					callback();
				}
			},10);
		}
	}
})();


   这个地方的tp是一个字面量,定义如:var tp = tp || {},这个地方我把define挂在了window下面,但是use放在了tp这个命名空间下面,使用的时候是tp.use(),我写的这个库里面也有一些配置项,比如debug,如果debug为true,那么文件后缀就为.js,debug为false,那么文件后缀就为.min.js,因为我会对它进行压缩,由于浏览器会缓存JS,所以为了库有修改之后JS会更新,在JS文件名后面加了一个time,它的值是lstCompileTime,这个时间只要代码有修改就会改变。

     当时写这个的时候,网上有位童鞋说这种异步载入JS文件有一个好处:

        同步载入的时候即使缓存了JS文件,也就是304,浏览器还是会发出http请求,但是异步载入缓存之后不会发出http请求。

        我用fiddler抓包发现异步载入也还是有http请求,我也不知道孰对孰错,希望有知道的童鞋,不吝赐教。

        其实关于这个的思路,我参考了http://stylechen.com/easyjs-parallel-moduleloader.html,有兴趣的童鞋可以看看。

        好吧,差不多了。


              

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
11 收藏
0
分享
返回顶部
顶部