AMD加载器实现笔记(五)
发布日期:2021-05-09 02:06:56 浏览次数:21 分类:原创文章

本文共 15073 字,大约阅读时间需要 50 分钟。

  前几篇文章对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 dir205         var tts = target.split('/'); //target parts206         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元素保留原始模块Id247         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.js300         var stack;301         try {302             a.b.c(); //强制报错,以便捕获e.stack303         } catch (e) { //safari的错误对象只有line,sourceId,sourceURL304             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:1314              *firefox17:315              *@http://113.93.50.63/query.js:4316              *opera12:http://www.oldapps.com/opera.php?system=Windows_XP317              *@http://113.93.50.63/data.js:4318              *IE10:319              *  at Global code (http://113.93.50.63/data.js:4:1)320              *  //firefox4+ 可以用document.currentScript321              */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

 

上一篇:dojo的发展历史
下一篇:AMD加载器实现笔记(四)

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2025年05月13日 18时54分46秒