AMD加载器实现笔记(五)

  前几篇文章对AMD规范中的config属性几乎全部支持了,这一节主要是进一步完善。到目前为止我们的加载器还无法处理环形依赖的问题,这一节就是解决环形依赖。

  所谓环形依赖,指的是模块A的所有依赖项的依赖中有没有依赖A模块本身的模块。如果有那就说明存在环形依赖。所以检验的方式是利用递归,检查一个模块的依赖的依赖项中有没有依赖A模块,以及依赖项的依赖项的依赖项中有没有A模块,核心代码如下:

function checkCircleRef(start, target){
        var m = modules[start];
        if (!m) {
            return false;
        }
        var depModules = m.deps.map(function(dep) {
            return modules[dep] || null;
        });
        
        
        return depModules.some(function(m) {//检查依赖项的依赖项
            if (!m) {
                return false;
            }
            return m.deps.some(function(dep) {
                var equal = dep === target;
                if (equal) {
                    console.error("circle reference: ", target, m.id);
                }
                
                return equal;
            });
        }) ? true : depModules.some(function(m) {//检查依赖项的依赖项的依赖项
            if (!m) {
                return false;
            }
            return m.deps.some(function(dep) {
                return checkCircleRef(dep, target);
            });
        });
    };

  

  剩下的问题是我们把检查放到哪里去?我们的模块最先在require中注册,所以最佳的位置是放在require函数中去检查:

//require 函数
//。。。。。。。
var module = {
            id: id,
            deps: deps,
            factory: callback,
            state: 1,
            result: null
        };
        modules[id] = module;
        
        if (checkCircleRef(id, id)) {
            hasCircleReferece = true;
            return;
        }
// ......................
//.......................
//......................

  下一个要面临的问题就是:如果存在依赖项如何处理?对此,我的做法是如果存在环形依赖,结束整个加载过程。我们在加载器内部使用一个哨兵变量,一旦存在环形依赖,停止所有工作。如:loadJs中:

script.onload = function() {
            if (hasCircleReferece) {
                return;
            }
            var module = modules[url];
            if (module && isReady(module) && loadings.indexOf(url) > -1) {
                callFactory(module);
            }
            checkDeps();
        };

  如define函数中:

if (modules[id]) {
            console.error('multiple define module: ' + id);
        }
        
        if (!hasCircleReferece) {
            require(deps, callback, id);
        }

  测试:

require.config({
    baseUrl: "./",
    packages: [{
        name: "more",
        location: "./more"
    }, {
        name: "mass",
        location: "../"
    }, {
        name: "wab",
        location: "../../../"
    }],
    shim: {
        "something": {
            "deps": ['jquery'],
            exports: 'something',
            init: function(jq, ol) {
                console.log(jq);
                console.log($);
                return something + " in shim";
            }
        }
    },
    map: {
        '*': {
            'jquery': 'jquery-private'
        },
        'jquery-private': {
            'jquery': 'jquery'
        }
    },
    paths: {
        'jquery': "../../Bodhi/src/roots/jquery"
    }
  });
  require([
  'bbb',
  'aaa.bbb.ccc',
  'ccc',
  'ddd',
  'fff',
  'something'
  ], function(aaabbbccc){
    console.log('simple loader');
    console.log(arguments);
  });

  bbb中:

define(["aaa",
"ccc"],function(a, c){
    console.log("已加载bbb模块", 7)
    return {
        aaa: a,
        ccc: c.ccc,
        bbb: "bbb"
    }
})

  aaa中:

define(['bbb'], function(){
    console.log("已加载aaa模块", 7)
    return "aaa"
});

  测试结果:

 circle reference:  simpleAMDLoader/aaa simpleAMDLoader/bbb

  目前为止整个加载器代码如下:

  1 (function(global){
  2     global = global || window;
  3     var modules = {};
  4     var loadings = [];
  5     var loadedJs = [];
  6     var hasCircleReferece = false;
  7     //module: id, state, factory, result, deps;
  8     global.require = function(deps, callback, parent){
  9         var id = parent || "Bodhi" + Date.now();
 10         var cn = 0, dn = deps.length;
 11         var args = [];
 12         
 13         var oriDeps = deps.slice();//保留原始dep的模块Id
 14         
 15          // dep为非绝对路径形式,而modules的key仍然需要绝对路径
 16         deps = deps.map(function(dep) {
 17             if (modules[dep]) { //jquery 
 18                 return dep;
 19             } else if (dep in global.require.parsedConfig.paths) {
 20                 return dep;
 21             }
 22             var rel = "";
 23             if (/^Bodhi/.test(id)) {
 24                 rel = global.require.parsedConfig.baseUrl;
 25             } else {
 26                 var parts = parent.split('/');
 27                 parts.pop();
 28                 rel = parts.join('/');
 29             }
 30             return getModuleUrl(dep, rel);
 31         });
 32         
 33         var module = {
 34             id: id,
 35             deps: deps,
 36             factory: callback,
 37             state: 1,
 38             result: null
 39         };
 40         modules[id] = module;
 41         
 42         if (checkCircleRef(id, id)) {
 43             hasCircleReferece = true;
 44             return;
 45         }
 46         //checkCircleRef(id, id)
 47         
 48         deps.forEach(function(dep, i) {
 49             if (modules[dep] && modules[dep].state === 2) {
 50                 cn++
 51                 args.push(modules[dep].result);
 52             } else if (!(modules[dep] && modules[dep].state === 1) && loadedJs.indexOf(dep) === -1) {
 53                 loadJS(dep, oriDeps[i]);
 54                 loadedJs.push(dep);
 55             }
 56         });
 57         if (cn === dn) {
 58             callFactory(module);
 59         } else {
 60             loadings.push(id);
 61             checkDeps();
 62         }
 63     };
 64     
 65     global.require.config = function(config) {
 66         this.parsedConfig = {};
 67         if (config.baseUrl) {
 68             var currentUrl = getCurrentScript();
 69             var parts = currentUrl.split('/');
 70             parts.pop();
 71             var currentDir = parts.join('/');
 72             this.parsedConfig.baseUrl = getRoute(currentDir, config.baseUrl);
 73         }
 74         var burl = this.parsedConfig.baseUrl;
 75         // 得到baseUrl后,location相对baseUrl定位
 76         this.parsedConfig.packages = [];
 77         if (config.packages) {
 78             for (var i = 0, len = config.packages.length; i < len; i++) {
 79                 var pck = config.packages[i];
 80                 var cp = {
 81                     name: pck.name,
 82                     location: getRoute(burl, pck.location)
 83                 }
 84                 this.parsedConfig.packages.push(cp);
 85             }
 86         }
 87         
 88         
 89         this.parsedConfig.paths = {};
 90         if (config.paths) {
 91             for (var p in config.paths) {
 92                 this.parsedConfig.paths[p] = /^http(s)?/.test(config.paths[p]) ? config.paths[p] : getRoute(burl, config.paths[p]);
 93             }
 94         }
 95         
 96         this.parsedConfig.map = {};
 97         if (config.map) {
 98             this.parsedConfig.map = config.map;
 99         }
100         
101         this.parsedConfig.shim = {};
102         //shim 要放在最后处理
103         if (config.shim) {
104             this.parsedConfig.shim = config.shim;
105             for (var p in config.shim) {
106                 var item = config.shim[p];
107                 define(p, item.deps, function() {
108                     var exports;
109                     if (item.init) {
110                         exports = item.init.apply(item, arguments);
111                     }
112                     
113                     return exports ? exports : item.exports;
114                 });
115             }
116         }
117         
118         console.log(this.parsedConfig);
119     }
120     
121     global.define = function(id, deps, callback) {
122         //加上moduleId的支持
123         if (typeof id !== "string" && arguments.length === 2) {
124             callback = deps;
125             deps = id;
126             id = "";
127         }
128         var id = id || getCurrentScript();
129         
130         var mId = getModuleId(id);
131         if (mId || id in require.parsedConfig.shim) {
132             mId = mId ? mId : id;
133             var maping = getMapSetting(mId);
134             
135             if (maping) {
136                 deps = deps.map(function(dep) {
137                     return maping[dep] || dep;
138                 });
139             }
140         }
141         if (modules[id]) {
142             console.error('multiple define module: ' + id);
143         }
144         
145         if (!hasCircleReferece) {
146             require(deps, callback, id);
147         }
148     };
149     
150     global.define.amd = {};//AMD规范
151     
152     function getModuleId(url) {
153         var script = document.querySelector('script[src="' + url + '"]');
154         if (script) {
155             return script.getAttribute('data-moduleId');
156         } else {
157             return null;
158         }
159     };
160     
161     function getMapSetting(mId) {
162         if (mId in require.parsedConfig.map) {
163             return require.parsedConfig[mId];
164         } else if ('*' in require.parsedConfig.map) {
165             return require.parsedConfig.map['*'];
166         } else {
167             return null;
168         }
169     };
170     
171     function checkCircleRef(start, target){
172         var m = modules[start];
173         if (!m) {
174             return false;
175         }
176         var depModules = m.deps.map(function(dep) {
177             return modules[dep] || null;
178         });
179         
180         
181         return depModules.some(function(m) {
182             if (!m) {
183                 return false;
184             }
185             return m.deps.some(function(dep) {
186                 var equal = dep === target;
187                 if (equal) {
188                     console.error("circle reference: ", target, m.id);
189                 }
190                 
191                 return equal;
192             });
193         }) ? true : depModules.some(function(m) {
194             if (!m) {
195                 return false;
196             }
197             return m.deps.some(function(dep) {
198                 return checkCircleRef(dep, target);
199             });
200         });
201     };
202     
203     function getRoute(base, target) {
204         var bts = base.replace(//$/, "").split('/');  //base dir
205         var tts = target.split('/'); //target parts
206         while (isDefined(tts[0])) {
207             if (tts[0] === '.') {
208                 return bts.join('/') + '/' + tts.slice(1).join('/');
209             } else if (tts[0] === '..') {
210                 bts.pop();
211                 tts.shift();
212             } else {
213                 return bts.join('/') + '/' + tts.join('/');
214             }
215         }
216     };
217     
218     function isDefined(v) {
219         return v !== null && v !== undefined;
220     };
221     
222     function getModuleUrl(moduleId, relative) {
223         function getPackage(nm) {
224             for (var i = 0, len = require.parsedConfig.packages.length; i < len; i++) {
225                 var pck = require.parsedConfig.packages[i];
226                 if (nm === pck.name) {
227                     return pck;
228                 }
229             }
230             return false;
231         }
232         var mts = moduleId.split('/');
233         var pck = getPackage(mts[0]);
234         if (pck) {
235             mts.shift();
236             return getRoute(pck.location, mts.join('/'));
237         } else if (mts[0] === '.' || mts[0] === '..') {
238             return getRoute(relative, moduleId);
239         } else {
240             return getRoute(require.parsedConfig.baseUrl, moduleId);
241         }
242     };
243     
244     function loadJS(url, mId) {
245         var script = document.createElement('script');
246         script.setAttribute('data-moduleId', mId); //为script元素保留原始模块Id
247         script.type = "text/javascript";
248         //判断模块是否在paths中定义了路径
249         script.src = (url in global.require.parsedConfig.paths ? global.require.parsedConfig.paths[url] : url) + '.js';
250         script.onload = function() {
251             if (hasCircleReferece) {
252                 return;
253             }
254             var module = modules[url];
255             if (module && isReady(module) && loadings.indexOf(url) > -1) {
256                 callFactory(module);
257             }
258             checkDeps();
259         };
260         var head = document.getElementsByTagName('head')[0];
261         head.appendChild(script);
262     };
263     
264     function checkDeps() {
265         for (var p in modules) {
266             var module = modules[p];
267             if (isReady(module) && loadings.indexOf(module.id) > -1) {
268                 callFactory(module);
269                 checkDeps(); // 如果成功,在执行一次,防止有些模块就差这次模块没有成功
270             }
271         }
272     };
273     
274     function isReady(m) {
275         var deps = m.deps;
276         var allReady = deps.every(function(dep) {
277             return modules[dep] && isReady(modules[dep]) && modules[dep].state === 2;
278         })
279         if (deps.length === 0 || allReady) {
280             return true;
281         }
282     };
283     
284     function callFactory(m) {
285         var args = [];
286         for (var i = 0, len = m.deps.length; i < len; i++) {
287             args.push(modules[m.deps[i]].result);
288         }
289         m.result = m.factory.apply(window, args);
290         m.state = 2;
291         
292         var idx = loadings.indexOf(m.id);
293         if (idx > -1) {
294             loadings.splice(idx, 1);
295         }
296     };
297     
298     function getCurrentScript(base) {
299         // 参考 https://github.com/samyk/jiagra/blob/master/jiagra.js
300         var stack;
301         try {
302             a.b.c(); //强制报错,以便捕获e.stack
303         } catch (e) { //safari的错误对象只有line,sourceId,sourceURL
304             stack = e.stack;
305             if (!stack && window.opera) {
306                 //opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取
307                 stack = (String(e).match(/of linked script S+/g) || []).join(" ");
308             }
309         }
310         if (stack) {
311             /**e.stack最后一行在所有支持的浏览器大致如下:
312              *chrome23:
313              * at http://113.93.50.63/data.js:4:1
314              *firefox17:
315              *@http://113.93.50.63/query.js:4
316              *opera12:http://www.oldapps.com/opera.php?system=Windows_XP
317              *@http://113.93.50.63/data.js:4
318              *IE10:
319              *  at Global code (http://113.93.50.63/data.js:4:1)
320              *  //firefox4+ 可以用document.currentScript
321              */
322             stack = stack.split(/[@ ]/g).pop(); //取得最后一行,最后一个空格或@之后的部分
323             stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/s/, ""); //去掉换行符
324             return stack.replace(/(:d+)?:d+$/i, "").replace(/.js$/, ""); //去掉行号与或许存在的出错字符起始位置
325         }
326         var nodes = (base ? document : head).getElementsByTagName("script"); //只在head标签中寻找
327         for (var i = nodes.length, node; node = nodes[--i]; ) {
328             if ((base || node.className === moduleClass) && node.readyState === "interactive") {
329                 return node.className = node.src;
330             }
331         }
332     };
333 })(window)
View Code
原文地址:https://www.cnblogs.com/dojo-lzz/p/5156318.html