加载信息javascript【AMD模块加载器】浅析

近最研究加载信息,稍微总结一下,以后继续补充:

    久很没有写博客了,一来是任务比较忙,二来主要是得觉没什么可写。当然,自己的惰懒也是可不推辞的任责。
近最有点空余的时光,就看了一下AMD模块加载。关于它的定义和优缺点就不分析了,园子里大把。相信大家也都道知。主要说一下加载器的理原以及在开辟进程当中碰到的一些坑。当然由于加载器不是标准的一部分,所以实现方法也各不相同。我所用的方法也不是最优的,只是用来作当学习而已。

    【模块加载器理原】
1.开始
2.通过模块名解析出模块息信,以及计算出URL。
3.通过建创SCRIPT的情势把模块加载到页面中。(当然也可以采取其它方法,如XHR。 IFRAME等等)
4.判断被加载的本脚,如果现发它有依附就去加载依附模块。如果不依附其它模块,就直接执行factory方法。
5.等部全本脚都被加载毕完就执行加载实现以后的回调函数。
【实现的进程】
在弄懂了个整加载器的任务理原以后,就开始详细的码编进程。最开始,我使用了moduleCache,modules,moduleLoads这三个象对。分离记载加载中须要用到的息信。首先我把个整加载的息信存储到moduleCache中。其中的构结大致如下。

moduleCache[uuid] = {
    uuid : uuid,    //随即生成的id
    deps : deps, //此次加载需须要加载的模块
   args : args, //回调函数须要的方法
   callback : callback  //此次加载实现后须要调用的回调函数      
}
在modules中存储详细的模块息信,也就是moduleCache[uuid]的deps中的模块的详细息信。构结很简单,主要是记载一下名字,和url和态状。
modules[modname] = {
    name : name, //模块名字
    url : url, //模块的url
    state : state //模块的态状   
}
moduleLoads 中是存储的加载息信,也就是uuid的组数。 进程很顺遂,很快就实现了开辟。当然有一个前提,加载的模块是不能依附其它模块的。 实现的大致理原就是。分析毕完模块息信,存储完面上的息信后。建创一个script来加载模块。当被加载的模块的define执行的时候,就通过模块名去modules中把模块的态状为改1,然后执行模块的factory方法。到得exports 放入到modules[modname].exports 中。 然后循环moduleLoads,到得uuid去moduleCache中的据数。然后判断deps的模块否是部全加载毕完。如果加载毕完就执行callback方法。 然后把uuid从moduleLoads中除删。

    然后开始实现加载的模块依附别的模块,一开始的做法是当现发加载的模块存在依附的时候,就从新调用require方法去加载模块须要的模块。 然而这样就造成了一个问题。就是等部全的被依附的模块加载毕完以后,按照面上的程流执行毕完。没法知告须要依附的那个模块它依附的模块都被加载毕完了。可能这么说不是太直观,看一下在现变量里存储的息信就然了目一了。

//加载hello模块,hello模块又依附test模块。
moduleCache = {
     cb100001 : {
        uuid : cb10001
        deps: {hello}
        args:[hello]
        callback : callback   
     } ,
     cb100002:{
         uuid:cb10002,
         deps:{test}
         args:[test]
         callback : callback
     }  
}
modules : {
     hello : {
        name:hello,
        url:url,
        state : 1,
        exports : {} 
    },
    test: {
        name:test
        url:url
        state:2
        exports:{}
    }
}
就像面上这样,test经已加载毕完。uuid 为cb10002的moduleCache的息信经已执行毕完。但是没法让hello模块加载毕完。 苦思冥想了久许,终究找到了一个处理方案。就是在请求一个变量,来存储模块的依附息信。 构结如下
var moduledeps = {
    'hello' : ['test']
    'test' : ['module1','module2']
}
这样一来就处理了两个问题,一个是循环依附的问题,可以通过面上的构结被检测出来。二来就是可以在被依附模块被加载毕完以后遍历面上的moduledeps来将须要依附的那个模块态状为改加载实现。从而执行moduleCache中的回调。又经过一番码编以后,低级版本的模块加载器终究实现了。 实现了并行载下依附模块,可以检测循环依附。在各个浏览器下测试。仿佛都没什么问题。然后当我去测试加载在不同录目的两个同名模块的时候,问题产生了。后加载的模块,覆盖了后面的同名模块的息信。 后来在群里经过一番论讨,决议用URL来做modules的key。这样就避免了覆盖的问题。 同时又优化了加载器的构结,将3个变量为改两个变量。 保留了modules与moduleCache,去掉了moduleLoads与moduledeps。

    构结如下。

modules = {
     url1 : {
        name: hello,
        url : url1,
        state : 1
        exports : {}
     },
     url2 : {
        name: test,
        url : url2,
        state : 2
        exports : {}
     }
}

moduleCache : {
     cbi10001 : {
          state: 1,
          uuid : cbi10001,
          factory : callback,
          args : [url1],l
          deps :{url1:'lynx'}
     },
    url1 : {
          state: 1,
          uuid : url1,
          factory : callback,
          args : [url2],
          deps :{url2:'lynx'}
     }
}
这样通过url既可以得获模块的依附,又能够得获模块。 所以就用不modoleLoads与moduleDeps了。然后在define中得获url又有一个坑就是在safari下没法得获正在被解析的script。得获正在被解析的script请参见 正美大大的这篇文章。 不过在safari下又另外一个特性就是在本脚解析实现以后会立即调用本脚的onload事件,如此一来就找到了处理办法。 就是在本脚解析的时候,存入一个函数到某个组数中,然后在它的onload事件中取出这个函数。将node的url传入函数中就可以了,唯一的坏处就是比可以得获url要慢上一点点。想到办法以后便开始改代码,经过半天左右的码编终究实现了。 下面是部全源码。在各个浏览器中测试都通过。但由于个人能力有限,其中未被现发的bug定所难免,如果各位现发其中的bug或有什么不足的地方请知告。
    每日一道理
风,那么轻柔,带动着小树、小草一起翩翩起舞,当一阵清风飘来,如同母亲的手轻轻抚摸自己的脸庞,我喜欢那种感觉,带有丝丝凉意,让人心旷神怡。享受生活,不一定要有山珍海味、菱罗绸缎为伴,大自然便是上帝所赐予人类最为珍贵的。
信息和方法 信息和方法 View Code
  1 View Code 
  2 
  3 (function(win, undefined){
  4     win = win || window;
  5     var doc = win.document || document,
  6         head = doc.head || doc.getElementsByTagName("head")[0];
  7         hasOwn = Object.prototype.hasOwnProperty,
  8         slice = Array.prototype.slice,
  9         basePath = (function(nodes){
 10             var node = nodes[nodes.length - 1],
 11             url = (node.hasAttribute ? node.src : node.getAttribute("src", 4)).replace(/[?#].*/, "");
 12             return url.slice(0, url.lastIndexOf('/') + 1);
 13         }(doc.getElementsByTagName('script')));
 14 
 15     function lynxcat(){
 16 
 17     }
 18     lynxcat.prototype = {
 19         constructor : lynxcat,
 20         init : function(){
 21 
 22         }
 23     }
 24     lynxcat.prototype.init.prototype = lynxcat.prototype;
 25 
 26     /**
 27      * mix
 28      * @param  {Object} target   目标象对
 29      * @param  {Object} source   源象对
 30      * @return {Object}          目标象对
 31      */
 32     lynxcat.mix = function(target, source){
 33         if( !target || !source ) return;
 34         var args = slice.call(arguments), i = 1, override = typeof args[args.length - 1] === "boolean" ? args.pop() : true, prop;
 35         while ((source = args[i++])) {
 36             for (prop in source) {
 37                 if (hasOwn.call(source, prop) && (override || !(prop in target))) {
 38                     target[prop] = source[prop];
 39                 }
 40             }
 41         }
 42         return target;
 43     };
 44 
 45     lynxcat.mix(lynxcat, {
 46         modules : {},
 47         moduleCache : {},
 48         loadings : [],
 49 
 50         /**
 51          * parse module
 52          * @param {String} id 模块名
 53          * @param {String} basePath 基础路径
 54          * @return {Array} 
 55          */
 56         parseModule : function(id, basePath){
 57             var url, result, ret, dir, paths, i, len, ext, modname, protocol = /^(\w+\d?:\/\/[\w\.-]+)(\/(.*))?/;
 58             if(result = protocol.exec(id)){
 59                 url = id;
 60                 paths = result[3] ? result[3].split('/') : [];
 61             }else{
 62                 result = protocol.exec(basePath);
 63                 url = result[1];
 64                 paths = result[3] ? result[3].split('/') : [];
 65                 modules = id.split('/');
 66                 paths.pop();
 67                 for(i = 0, len = modules.length; i < len; i++){
 68                     dir = modules[i];
 69                     if(dir == '..'){
 70                         paths.pop();
 71                     }else if(dir !== '.'){
 72                         paths.push(dir);
 73                     }
 74                 }
 75                 url = url + '/' + paths.join('/');
 76             }
 77             modname = paths[paths.length - 1];
 78             ext = modname.slice(modname.lastIndexOf('.'));
 79             if(ext != '.js'){
 80                 url = url + '.js';
 81             }else{
 82                 modname = modname.slice(0, modname.lastIndexOf('.'));
 83             }
 84             if(modname == ''){
 85                 modname = url;
 86             }
 87             return [modname, url]
 88         },
 89 
 90         /**
 91          * get uuid
 92          * @param {String} prefix
 93          * @return {String} uuid
 94          */
 95         guid : function(prefix){
 96             prefix = prefix || '';
 97             return prefix + (+new Date()) + String(Math.random()).slice(-8);
 98         },
 99 
100         /**
101          * error 
102          * @param {String} str
103          */
104         error : function(str){
105             throw new Error(str);
106         }
107     });
108 
109 
110     //================================ 模块加载 ================================
111     /**
112      * 模块加载方法
113      * @param {String|Array}   ids      须要加载的模块
114      * @param {Function} callback 加载实现以后的回调
115      * @param {String} parent 父路径
116      */
117     win.require = lynxcat.require = function(ids, callback, parent){
118         ids = typeof ids === 'string' ? [ids] : ids;
119         var i = 0, len = ids.length, flag = true, uuid = parent || lynxcat.guid('cb_'), path = parent || basePath,
120             modules = lynxcat.modules, moduleCache = lynxcat.moduleCache, 
121             args = [], deps = {}, id, result;
122         for(; i < len; i++){
123             id = ids[i];
124             result = lynxcat.parseModule(id, path);
125 
126             if(!modules[result[1]]){
127                 modules[result[1]] = {
128                     name : result[0],
129                     url : result[1],
130                     state : 0,
131                     exports : {}
132                 }
133                 flag = false;
134             }else if(modules[result[1]].state != 2){
135                 flag = false;
136             }
137             if(!deps[result[1]]){
138                 if(checkCircularDeps(uuid, result[1])){
139                     lynxcat.error('模块[url:'+ uuid +']与模块[url:'+ result[1] +']循环依附');
140                 }
141                 deps[result[1]] = 'lynxcat';
142                 args.push(result[1]);
143             }
144             lynxcat.loadJS(result[1]);
145         }
146 
147         moduleCache[uuid] = {
148             uuid : uuid,
149             factory : callback,
150             args : args,
151             deps : deps,
152             state : 1
153         }
154 
155         if(flag){
156             fireFactory(uuid);
157             return checkLoadReady();
158         }
159     };
160     require.amd = lynxcat.modules;
161 
162     /**
163      * @param  {String} id           模块名
164      * @param  {String|Array} [dependencies] 依附列表
165      * @param  {Function} factory      工厂方法
166      */
167     win.define = function(id, dependencies, factory){
168         if((typeof id === 'array' || typeof id === 'string') && typeof dependencies === 'function'){
169             factory = dependencies;
170             dependencies = [];
171         }else if (typeof id == 'function'){
172             factory = id;
173             dependencies = [];
174         }
175         id = lynxcat.getCurrentScript();
176         if(!id){
177             lynxcat.loadings.push(function(id){
178                 require(dependencies, factory, id);
179             });
180         }else{
181             require(dependencies, factory, id);
182         }
183     }
184 
185     /**
186      * fire factory
187      * @param  {String} uuid
188      */
189     function fireFactory(uuid){
190         var moduleCache = lynxcat.moduleCache, modules = lynxcat.modules,
191         data = moduleCache[uuid], deps = data.args, result,
192         i = 0, len = deps.length, args = [];
193         for(; i < len; i++){
194             args.push(modules[deps[i]].exports)
195         }
196         result = data.factory.apply(null, args);
197         if(modules[uuid]){
198             modules[uuid].state = 2;
199             modules[uuid].exports = result;
200             delete moduleCache[uuid];
201         }else{
202             delete lynxcat.moduleCache;
203         }
204         return result;
205     }
206 
207     /**
208      * 检测否是部全加载毕完
209      */
210     function checkLoadReady(){
211         var moduleCache = lynxcat.moduleCache, modules = lynxcat.modules,
212             i, data, prop, deps, mod;
213         loop: for (prop in moduleCache) {
214             data = moduleCache[prop];
215             deps = data.args;
216             for(i = 0; mod = deps[i]; i++){
217                 if(hasOwn.call(modules, mod) && modules[mod].state != 2){
218                     continue loop;
219                 }
220             }
221             if(data.state != 2){
222                 fireFactory(prop);
223                 checkLoadReady();
224             }
225         }
226     }
227 
228     /**
229      * 检测循环依附
230      * @param  {String} id         
231      * @param  {Array} dependencie
232      */
233     function checkCircularDeps(id, dependencie){
234         var moduleCache = lynxcat.moduleCache, depslist = moduleCache[dependencie] ? moduleCache[dependencie].deps : {}, prop;
235         for(prop in depslist){
236             if(hasOwn.call(depslist, prop) && prop === id){
237                 return true;
238             }
239         }
240         return false;
241     }
242 
243     lynxcat.mix(lynxcat, {
244         /**
245          * 加载JS文件
246          * @param  {String} url
247          */
248         loadJS : function(url){
249             var node = doc.createElement("script");
250             node[node.onreadystatechange ? 'onreadystatechange' : 'onload'] = function(){
251                 if(!node.onreadystatechange || /loaded|complete/i.test(node.readyState)){
252                     var fn = lynxcat.loadings.pop();
253                     fn && fn.call(null, node.src);
254                     node.onload = node.onreadystatechange = node.onerror = null;
255                     head.removeChild(node);
256                 }
257             }
258             node.onerror = function(){
259                 lynxcat.error('模块[url:'+ node.src +']加载失败');
260                 node.onload = node.onreadystatechange = node.onerror = null;
261                 head.removeChild(node);
262             }
263             node.src = url;
264             head.insertBefore(node, head.firstChild);
265         },
266 
267         /**
268          * get current script [此方法来自司徒正美的博客]
269          * @return {String}
270          */
271         getCurrentScript : function(){
272             //取得正在解析的script节点
273             if (doc.currentScript) { //firefox 4+
274                 return doc.currentScript.src;
275             }
276             // 参考 https://github.com/samyk/jiagra/blob/master/jiagra.js
277             var stack;
278             try {
279                 a.b.c(); //强制报错,以便捕获e.stack
280             } catch (e) { //safari的错误象对只有line,sourceId,sourceURL
281                 stack = e.stack;
282                 if (!stack && window.opera) {
283                     //opera 9没有e.stack,但有e.Backtrace,但不能直接取得,须要对e象对转字符串进行抽取
284                     stack = (String(e).match(/of linked script \S+/g) || []).join(" ");
285                 }
286             }
287             if (stack) {
288                 /**e.stack最后一行在部全支持的浏览器大致如下:
289                  *chrome23:
290                  * at http://113.93.50.63/data.js:4:1
291                  *firefox17:
292                  *@http://113.93.50.63/query.js:4
293                  *opera12:http://www.oldapps.com/opera.php?system=Windows_XP
294                  *@http://113.93.50.63/data.js:4
295                  *IE10:
296                  *  at Global code (http://113.93.50.63/data.js:4:1)
297                  */
298                 stack = stack.split(/[@ ]/g).pop(); //取得最后一行,最后一个空格或@以后的部分
299                 stack = stack[0] === "(" ? stack.slice(1, -1) : stack;
300                 return stack.replace(/(:\d+)?:\d+$/i, ""); //去掉行号与或许存在的出错字符起始位置
301             }
302             var nodes = head.getElementsByTagName("script"); //只在head标签中寻找
303             for (var i = 0, node; node = nodes[i++]; ) {
304                 if (node.readyState === "interactive") {
305                     return node.src;
306                 }
307             }    
308         }
309     });
310     win.lynxcat = lynxcat;
311 }(window));
 

    使用方法

//hello.js文件
define('hello', function(){
    return {world : 'hello, world!'};
});

//主文件
lynxcat.require('hello',function(hello){
    console.log(hello.world);  //hello, world!;
});


//hello.js 有依附的情况
//hello.js文件
define('hello', 'test',function(test){
    return {world : 'hello, world!' + test};
});

//test.js文件
define('test', function(){
    return 'this is test';
});
//主文件
lynxcat.require('hello',function(hello){
    console.log(hello.world);  //hello, world!this is test;
});
 

文章结束给大家分享下程序员的一些笑话语录: IT业众生相
第一级:神人,天资过人而又是技术狂热者同时还拥有过人的商业头脑,高瞻远瞩,技术过人,大器也。如丁磊,求伯君。
第二级:高人,有天赋,技术过人但没有过人的商业头脑,通常此类人不是顶尖黑客就是技术总监之流。
第三级:牛人,技术精湛,熟悉行业知识,敢于创新,有自己的公司和软件产品。
第四级:工头,技术精湛,有领导团队的能力,此类人大公司项目经理居多。
第五级:技术工人,技术精湛,熟悉行业知识但领导能力欠加,此类人大多为系分人员或资深程序员,基本上桀骜不逊,自视清高,不愿于一般技术人员为伍,在论坛上基本以高手面目出现。
第六级:熟练工人,技术有广度无深度,喜欢钻研但浅尝辄止。此类人大多为老程序员,其中一部分喜欢利用工具去查找网上有漏洞的服务器,干点坏事以获取成绩感。如果心情好,在论坛上他们会回答菜鸟的大部分问题。此级别为软件业苦力的重要组成部分。
第七级:工人,某些技术较熟练但缺乏深度和广度,此类人大多为程序员级别,经常在论坛上提问偶尔也回答菜鸟的问题。为软件产业苦力的主要组成部分。
第八级:菜鸟,入门时间不长,在论坛上会反复提问很初级的问题,有一种唐僧的精神。虽然招人烦但基本很可爱。只要认真钻研,一两年后就能升级到上一层。
第九级:大忽悠,利用中国教育的弊病,顶着一顶高学历的帽子,在小公司里混个软件部经理,设计不行,代码不行,只会胡乱支配下属,拍领导马屁,在领导面前胡吹海侃,把自己打扮成技术高手的模样。把勾心斗角的办公室文化引入技术部门,实在龌龊!
第十级:驴或傻X,会写SELECT语句就说自己精通ORALCE,连寄存器有几种都不知道就说自己懂汇编,建议全部送到日本当IT产业工人,挣了日本人的钱还严重打击日本的软件业!


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值