• 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
  • 相关阅读:
    java基础3 循环语句:While 循环语句、do while 循环语句、 for 循环语句 和 break、continue关键字
    java基础2 判断语句:if ... else 语句和 switch 语句
    IT行业经典面试技巧及方法思路。
    Java基础1,入门基础知识
    SVN的使用、分支合并及解决冲突详解
    VC工程产生文件后缀名解释
    ireport报表,打印时,报表加载失败的解决方法
    MySQL 事务、视图、索引
    MySQL高级查询
    MySQL中的主键约束和外键约束
  • 原文地址:https://www.cnblogs.com/dojo-lzz/p/5156318.html
Copyright © 2020-2023  润新知