现在的位置: 首页 > 综合 > 正文

javascript【AMD模块加载器】浅析V3(添加CSS加载功能,重构内部流程)

2012年12月28日 ⁄ 综合 ⁄ 共 11381字 ⁄ 字号 评论关闭

由于今天正美大大的回复,然后前篇文章的评论里就出现了好多人站出来指责我抄袭,吓的我小心肝都扑通扑通的跳。

虽然这次的模块加载器是参照了正美大大的来写,但是至少我是一行一行代码自己写出来的。然后一个浏览器一个浏览器测试的,就连流程图都重画了好几次。

虽然大体上跟正美的差不多,但是细节上还是有很多不同的。看到哪些回复我也不想说啥。 抄没抄,会不会。明眼人一眼就能看出来,犯不着解释太多。

废话不多说,下面介绍这一版本的改进。额外增加了一个配置项控制并发的数量。因为浏览器的有效并发数是有限的。所以如果你一次性加载10个模块,就有可能阻塞掉其它的资源加载。

现在内部默认最大并发是4个。将原来的moduleCache变量删除,将所有加在信息都整合到modules中,并标记初始加载函数,在所有模块加载结束后删除初始加在函数。  

所有css加载,不计入模块加载中。而且加载css也不会在factory的参数中出现。也就是说如果你这样写也没关系。

require(['hello','test.css','test'], function(hello,test){
   console.log(hello,test); 
});

不过现在还有一个问题就是加载css检测加载完毕的问题。 由于浏览器对link标签的onload事件支持各不一样,加之就算为之做了兼容也是锦衣夜行。 因为根本不需要知道css的加载情况。

主要的改动就这些,还有一些细节性的改动。去掉了deps属性,检测循环依赖的方法改为使用args而不是之前的deps。将之前的loadJS方法改变为loadSource。 将require方法和load拆分开来。

使用文档碎片来将节点批量插入到页面中,尽量减少修改dom树,减少浏览器重排。

这一版中依然使用了正美大大博客中的哪个获得当前被解析的script的url的方法,不知道园子里的各位朋友有没有更好的办法。在最初的时候我是用模块名称来做唯一的,这样就不用获取正在解析的script的url。 但是重名模块就很难解决了。如果大家有更好的解决办法希望能告知一下。大家一起进步。 下面是最新的源码和使用的方法。

View Code

  1 ;(function(win, undefined){
2 win = win || window;
3 var doc = win.document || document,
4 head = doc.head || doc.getElementsByTagName("head")[0],
5 fragment = document.createDocumentFragment(),
6 hasOwn = Object.prototype.hasOwnProperty,
7 slice = Array.prototype.slice,
8 configure = {total : 4},
9 basePath = (function(nodes){
10 var node, url;
11 if(!configure.baseUrl){
12 node = nodes[nodes.length - 1];
13 url = (node.hasAttribute ? node.src : node.getAttribute("src", 4)).replace(/[?#].*/, "");
14 }else{
15 url = configure.baseUrl;
16 }
17 return url.slice(0, url.lastIndexOf('/') + 1);
18 }(doc.getElementsByTagName('script'))),
19 _lynx = win.lynx;
20
21 /**
22 * 框架入口
23 */
24 function lynx(exp, context){
25 return new lynx.prototype.init(exp, context);
26 }
27
28 lynx.prototype = {
29 constructor : lynx,
30
31 /**
32 * 初始化
33 * @param {All} expr
34 * @param {All} context
35 * @return {Object}
36 *
37 */
38 init : function(expr, context){
39 if(typeof expr === 'function'){
40 require('ready', expr);
41 }
42 //TODO
43 }
44 }
45 lynx.fn = lynx.prototype.init.prototype = lynx.prototype;
46
47 /**
48 * 继承方法
49 */
50 lynx.fn.extend = lynx.extend = function(){
51 var args = slice.call(arguments), deep = typeof args[args.length - 1] == 'bollean' ? args.pop() : false;
52
53 if(args.length == 1){
54 args[1] = args[0];
55 args[0] = this;
56 args.length = 2;
57 }
58
59 var target = args[0], i = 1, len = args.length, source, prop;
60
61 for(; i < len; i++){
62 source = args[i];
63 for(prop in source){
64 if(hasOwn.call(source, prop)){
65 if(typeof source[prop] == 'object'){
66 target[prop] = {};
67 this.extend(target[prop],source[prop]);
68 }else{
69 if(target[prop] === undefined){
70 target[prop] = source[prop];
71 }else{
72 deep && (target[prop] = source[prop]);
73 }
74 }
75 }
76 }
77 }
78 };
79
80 /**
81 * mix
82 * @param {Object} target 目标对象
83 * @param {Object} source 源对象
84 * @return {Object} 目标对象
85 */
86 lynx.mix = function(target, source){
87 if( !target || !source ) return;
88 var args = slice.call(arguments), i = 1, override = typeof args[args.length - 1] === "boolean" ? args.pop() : true, prop;
89 while ((source = args[i++])) {
90 for (prop in source) {
91 if (hasOwn.call(source, prop) && (override || !(prop in target))) {
92 target[prop] = source[prop];
93 }
94 }
95 }
96 return target;
97 };
98
99 lynx.mix(lynx, {
100 modules : { //保存加载模块
101 ready : {
102 state : 1,
103 type : 1,
104 args : [],
105 exports : lynx
106 }
107 },
108 urls : [],
109 loading : 0,
110 stacks : [], //getCurrentScript取不到值的时候用来存储当前script onload的回调函数数组
111
112 /**
113 * get uuid
114 * @param {String} prefix
115 * @return {String} uuid
116 */
117 guid : function(prefix){
118 prefix = prefix || '';
119 return prefix + (+new Date()) + String(Math.random()).slice(-8);
120 },
121
122 /**
123 * noop 空白函数
124 */
125 noop : function(){
126
127 },
128
129 /**
130 * error
131 * @param {String} str
132 */
133 error : function(str){
134 throw new Error(str);
135 },
136
137 /**
138 * @return {Object} lynx
139 */
140 noConflict : function(deep) {
141 if ( window.lynx === lynx ) {
142 window.lynx = _lynx;
143 }
144 return lynx;
145 }
146 });
147
148
149 //================================ 模块加载 ================================
150 /**
151 * 模块加载方法
152 * @param {String|Array} ids 需要加载的模块
153 * @param {Function} callback 加载完成之后的回调
154 */
155 win.require = lynx.require = function(ids, callback){
156 ids = typeof ids === 'string' ? [ids] : ids;
157 var modules = lynx.modules, urls = lynx.urls, uuid = lynx.guid('cb_'), data;
158 data = parseModules(ids, basePath);
159 modules[uuid] = {
160 name : 'initialize',
161 type : 2,
162 state : 1,
163 args : data.args,
164 factory : callback
165 };
166 urls = urls.concat(data.urls);
167 lynx.load(urls);
168 };
169
170 /**
171 * @param {String} id 模块名
172 * @param {String|Array} [dependencies] 依赖列表
173 * @param {Function} factory 工厂方法
174 */
175 win.define = function(id, dependencies, factory){
176 if(typeof dependencies === 'function'){
177 factory = dependencies;
178 if(typeof id === 'array'){
179 dependencies = id;
180 }else if(typeof id === 'string'){
181 dependencies = [];
182 }
183 }else if (typeof id == 'function'){
184 factory = id;
185 dependencies = [];
186 }
187 id = lynx.getCurrentScript();
188
189 dependencies = typeof dependencies === 'string' ? [dependencies] : dependencies;
190
191 var handle = function(id, dependencies, factory){
192 var modules = lynx.modules, urls = lynx.urls;
193 modules[id].factory = factory;
194 modules[id].state = 2;
195 if(!dependencies.length){
196 fireFactory(id);
197 }else{
198 var data = parseModules(dependencies, id, true);
199 urls = urls.concat(data.urls);
200 lynx.load(urls);
201 }
202 }
203 if(!id){
204 lynx.stacks.push(function(dependencies, factory){
205 return function(id){
206 handle(id, dependencies, factory);
207 id = null; dependencies = null; factory = null;
208 }
209 }(dependencies, factory));
210 }else{
211 handle(id, dependencies, factory);
212 }
213 }
214
215 require.amd = define.amd = lynx.modules;
216
217 /**
218 * 解析加载模块信息
219 * @param {Array} list
220 * @param {String} path
221 * @param {boolean} flag
222 * @return {Object}
223 */
224 function parseModules(list, basePath, flag){
225 var modules = lynx.modules, urls = [], args = [], uniqurl = {}, id, result;
226 while(id = list.shift()){
227 if(modules[id]){
228 args.push(id);
229 continue;
230 }
231 result = parseModule(id, basePath);
232 modules[basePath] && modules[basePath].args.push(result[1]);
233 flag && checkCircularDeps(result[1], basePath) && lynx.error('模块[url:'+ basePath +']与模块[url:'+ result[1] +']循环依赖');
234 modules[result[1]] = {
235 type : result[2] === 'js' ? 1 : 2,
236 name : result[0],
237 state : 0,
238 exports : {},
239 args : [],
240 factory : lynx.noop
241 };
242 (result[2] === 'js') && args.push(result[1]);
243 if(!uniqurl[result[1]]){
244 uniqurl[result[1]] = true;
245 urls.push(result[1]);
246 }
247 }
248
249 return {
250 args : args,
251 urls : urls
252 }
253 }
254
255 /**
256 * parse module
257 * @param {String} id 模块名
258 * @param {String} basePath 基础路径
259 * @return {Array}
260 */
261 function parseModule(id, basePath){
262 var url, result, ret, dir, paths, i, len, type, modname, protocol = /^(\w+\d?:\/\/[\w\.-]+)(\/(.*))?/;
263 if(result = protocol.exec(id)){
264 url = id;
265 paths = result[3] ? result[3].split('/') : [];
266 }else{
267 result = protocol.exec(basePath);
268 url = result[1];
269 paths = result[3] ? result[3].split('/') : [];
270 modules = id.split('/');
271 paths.pop();
272 for(i = 0, len = modules.length; i < len; i++){
273 dir = modules[i];
274 if(dir == '..'){
275 paths.pop();
276 }else if(dir !== '.'){
277 paths.push(dir);
278 }
279 }
280 url = url + '/' + paths.join('/');
281 }
282 modname = paths[paths.length - 1];
283 type = modname.slice(modname.lastIndexOf('.') + 1);
284 if(type !== 'js' && type !== 'css'){
285 type = 'js';
286 url += '.js';
287 }
288 return [modname, url, type];
289 }
290
291 /**
292 * fire factory
293 * @param {String} uuid
294 */
295 function fireFactory(uuid){
296 var modules = lynx.modules,
297 data = modules[uuid], deps = data.args,
298 i = 0, len = deps.length, args = [];
299 for(; i < len; i++){
300 args.push(modules[deps[i]].exports)
301 }
302 data.exports = data.factory.apply(null, args);
303 data.state = 3;
304 delete data.factory;
305 delete data.args;
306 if(data.type == 2 && data.name == 'initialize'){
307 delete modules[uuid];
308 }
309 checkLoadReady();
310 }
311
312 /**
313 * 检测是否全部加载完毕
314 */
315 function checkLoadReady(){
316 var modules = lynx.modules, flag = true, data, prop, deps, mod, i , len;
317 for (prop in modules) {
318 data = modules[prop];
319 if(data.type == 1 && data.state != 2){ //如果还没执行到模块的define方法
320 continue;
321 }
322 deps = data.args;
323 for(i = 0, len = deps.length; mod = deps[i], i < len ; i++){
324 if(hasOwn.call(modules, mod) && modules[mod].state != 3){
325 flag = false;
326 break;
327 }
328 }
329 if(data.state != 3 && flag){
330 fireFactory(prop);
331 }
332 }
333 }
334
335 /**
336 * 检测循环依赖
337 * @param {String} id
338 * @param {Array} dependencie
339 */
340 function checkCircularDeps(id, dependencie){
341 var modules = lynx.modules, depslist = modules[id] ? modules[id].args : [];
342 return ~depslist.join(' ').indexOf(dependencie);
343 }
344
345 /**
346 * create
347 * @param {String} type CSS|JS
348 * @param {String} url
349 * @param {Function} callback
350 */
351 function loadSource(type, url, callback){
352 var ndoe, modules = lynx.modules;
353 if(type == 'JS'){
354 var node = doc.createElement("script");
355 node[node.onreadystatechange ? 'onreadystatechange' : 'onload'] = function(){
356 if(!node.onreadystatechange || /loaded|complete/i.test(node.readyState)){
357 callback();
358 node.onload = node.onreadystatechange = node.onerror = null;
359 var fn = lynx.stacks.pop();
360 fn && fn.call(null, node.src);
361 head.removeChild(node);
362 }
363 }
364 node.src = url;
365 modules[url].state = 1;
366 lynx.loading++;
367 }else if(type == 'CSS'){
368 var node = doc.createElement("link");
369 node.rel = 'stylesheet';
370 node.href = url;
371 delete modules[url];
372 }
373 node.onerror = function(){
374 lynx.error('模块[url:'+ node.src +']加载失败');
375 node.onload = node.onreadystatechange = node.onerror = null;
376 lynx.loading--;
377 head.removeChild(node);
378 }
379 return node;
380
381 };
382
383 lynx.mix(lynx, {
384 load : function(urls){
385 var loading , total = configure.total,modules = lynx.modules, url, node = fragment, type;
386 while((loading = lynx.loading) < total && (url = urls.shift())){
387 type = url.slice(url.lastIndexOf('.') + 1).toUpperCase();
388 node.appendChild(loadSource(type, url, function(){
389 lynx.loading--;
390 var urls = lynx.urls;
391 urls.length && lynx.load(urls);
392 }));
393 }
394 head.insertBefore(node, head.firstChild);
395 },
396
397 /**
398 * 加载JS文件
399 * @param {String} url
400 * @param {Function} callback
401 */
402 loadJS : function(url, callback){
403 var node = loadSource('JS', url, callback)
404 head.insertBefore(node, head.firstChild);
405 },
406
407 /**
408 * 加载CSS文件
409 * @param {String} url
410 * @param {Function} callback
411 */
412 loadCSS : function(url, callback){
413 var node = loadSource('CSS', url, callback);
414 head.insertBefore(node, head.firstChild);
415 },
416
417 /**
418 * get current script [此方法来自司徒正美的博客]
419 * @return {String}
420 */
421 getCurrentScript : function(){
422 //取得正在解析的script节点
423 if (doc.currentScript) { //firefox 4+
424 return doc.currentScript.src;
425 }
426 // 参考 https://github.com/samyk/jiagra/blob/master/jiagra.js
427 var stack;
428 try {
429 a.b.c(); //强制报错,以便捕获e.stack
430 } catch (e) { //safari的错误对象只有line,sourceId,sourceURL
431 stack = e.stack;
432 if (!stack && window.opera) {
433 //opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取
434 stack = (String(e).match(/of linked script \S+/g) || []).join(" ");
435 }
436 }
437 if (stack) {
438 /**e.stack最后一行在所有支持的浏览器大致如下:
439 *chrome23:
440 * at http://113.93.50.63/data.js:4:1
441 *firefox17:
442 *@http://113.93.50.63/query.js:4
443 *opera12:http://www.oldapps.com/opera.php?system=Windows_XP
444 *@http://113.93.50.63/data.js:4
445 *IE10:
446 * at Global code (http://113.93.50.63/data.js:4:1)
447 */
448 stack = stack.split(/[@ ]/g).pop(); //取得最后一行,最后一个空格或@之后的部分
449 stack = stack[0] === "(" ? stack.slice(1, -1) : stack;
450 return stack.replace(/(:\d+)?:\d+$/i, ""); //去掉行号与或许存在的出错字符起始位置
451 }
452 var nodes = head.getElementsByTagName("script"); //只在head标签中寻找
453 for (var i = 0, node; node = nodes[i++]; ) {
454 if (node.readyState === "interactive") {
455 return node.src;
456 }
457 }
458 },
459
460 /**
461 * 配置模块信息
462 * @param {Object} option
463 */
464 config : function(option){
465 lynx.mix(configure, option);
466 },
467
468
469 //============================== DOM Ready =============================
470
471 /**
472 * dom ready
473 * @param {Function} callback
474 */
475 ready : function (){
476 var isReady = false;
477 var readyList = [];
478 var ready = function(fn){
479 if(isReady){
480 fn();
481 }else{
482 readyList.push(fn);
483 }
484 };
485
486 var fireReady = function(){
487 for(var i = 0,len = readyList.length; i < len; i++){
488 readyList[i]();
489 }
490 readyList = [];
491 lynx.modules.ready.state = 3;
492 checkLoadReady();
493 };
494
495 var bindReady = function(){
496 if(isReady){
497 return;
498 }
【上篇】
【下篇】

抱歉!评论已关闭.