判定模块加载时是否存在循环依赖

循环依赖是所有语言的加载器都是大头的问题,除非你的模块都是像spring那么利用Ioc进行控制反转,那么出现循环依赖就是死结。对此,我们唯一能做的事是,在第一个循环依赖出现时就立即通知用户查看它们的依赖列表。

在AMD大肆普及动态加载的好处后,大家应该隐藏知道有两个方法干这事。一个定义模块的define方法,另一个是加载模块的require方法。

define方法的参数为define(name?, deps? ,factroy), 换言之模块名与依赖列表都不是必须的,require参数为require(names , callback)。当我们用require一组模块,将它们的返回值传到回调中前,它实质上做了许多事情,其中为了实现跨域加载脚本,我们使用了script标签进行加载。但这不重点。script标签加载回来的脚本有个严格的格式,里面就是一个define方法。这与JSONP非常相近。为了实现匿名模块,我们通常不使用第1个参数,但第2参数有时就不能省略。不过没有问题,我们可以从请求的URL中还原回模块名,因此最终还是得到三个参数,第二个参数依赖列表不存在,我们就置为空数组。当一个模块被加载回来,并且它的依赖列表为空,我们就可以说它是可用的。若一个模块存在依赖列表,那么当它的所有模块都可用时,它才可用。当两个模块存在循环依赖时,就变成你等我,我等你的状态,因此在每加载一个模块回来时,就检测它的依赖列表,不断回溯它的上级依赖,上上级依赖,但这些链中是否存在我们当前这个模块名。如果有就是循环依赖,立即抛错。这个检测我们也可以做些优化,这因为这是个图,经过的路径会非常复杂,我们可以跳过所有已发出请求但没有加载回来的模块或所有可用的模块。

//这些是定义在种子模块(核心模块)中
var loadings = []; var modules = {};

function define(name, deps, factory){
    var el = modules[name] = {
        deps:deps,
        name: name,
        factory:factory,
        status: !deps.length//是否可用
    }

    if(!el.status){//踢进列队进行检测
        loadings.push(el);
    }
    _checkDeps();
    if( _checkCircle(deps, name) ){
        throw new Error( name +"模块与之前的某些模块存在循环依赖")
    }
}
function _checkCircle(list, nick){//检测是否存在循环依赖
    for(var i = 0, name; name = list[ i++ ];){
        var el =  modules[name];
        if(el ){
            if( el.name == nick || el.deps.length && _checkCircle(el.deps, nick)){
                return true;
            }
        }
    }
}
function _checkDeps(){//检测模块有没有加载成功
    loop:
    for ( var i = loadings.length, el; el = loadings[ --i ]; ) {
        var deps = el.deps;
        var ok = true;
        for(var j = 0, name; name = deps[j++];){
            if(!modules[name] || !modules[name].status){
                ok = false;
                continue loop
            }
        }
        if(ok){
            el.status = true;
            loadings.splice( i, 1 );
            _checkDeps()
        }
    }
}
//这是a.js的内容
define("a",["b"])
//这是b.js的内容
define("b",["c"])
//这是c.js的内容
define("c",["a"])
//方便自己用firebug肉眼确认
console.log(modules)
console.log(loadings)

上面只是判定依赖关系的代码,实质上模块加载系统非常复杂,分为拆分模块名,别名映射,转换为URL,创建加载用的iframe与script(iframe是为了更好地在opera与旧式IE判定死链),处理加载回来define方法的最后一个参数factory,还原它模块名,判定是否可用,是否循环依赖,如果存在依赖再加载它的依赖等步骤,具体搜我博客有关模块加载的其他文章。

机器瞎学/数据掩埋/模式混淆/人工智障/深度遗忘/神经掉线/计算机幻觉/专注单身二十五年
原文地址:https://www.cnblogs.com/rubylouvre/p/2780054.html