• 模块加载站在巨人肩膀上的版本


      好久没有更新博客了。最近基本上也一直在闭门造车轮。在模块加载的问题上苦苦纠结。距离上次发的狗屁不通的一个版本,也过去了大概两个星期的时间了。

      说上次那篇博文狗屁不通的确不无道理。具体情况可以参考前文《关于前端基础框架的思考》。现在看来的确到处都是漏洞,没有处理不同操作的并行,也没有处理依赖和并行混淆的情况,连但操作,无并发的递归的依赖情况可能都有漏洞。甚至犯了极其低级的错误都还不知道。幸亏一位园友的提醒... 真是惭愧。

      其他的也不多说了,还是回到正题吧。又经过两个星期的辗转反侧,查阅了各大框架的源码,最终有了一个稍微有一点可用性的版本。只敢说是站在巨人肩膀上的版本,自知若没有前人的成果作为引导和启发,恐怕不是两个星期的事了...

    【写代码前要考虑的问题】

      关于模块加载器,我个人觉得是一个框架不能缺的东西,或者说是核心的东西之一。这也是我为什么会想从这个地方开始我的自造车轮的原因。就是为了方便的管理我们需要颗粒化的js模块文件。并且通过异步加载的方式平衡颗粒化管理和多http链接的矛盾。

      1.在web技术发展迅猛的时候时候,出于页面的性能原因,web设计者们不得不开始考虑所谓的“按需加载”,比如当某个地方需要用户交互的地方,当获取到事件handle的时候才去加载对应的实现功能的js。曾经一段时间是用xhr的方式来加载,后来因为网络环境依赖程度高,依旧阻塞式的加载难以缓解页面性能等等原因。被新的异步方式取代了。

    2.异步的加载方式其实和jsonp的形式很像,通过动态创建scriptNode,完全可控的插入到head中的方式,可以自定义js什么时候load,并且处理load后的回调。

    3.当可以异步的实现文件加载的时候,人们的贪念就来了,开始考虑并发的异步,串行的异步,以及两种方式混淆的异步。比如有一个操作A需要两个js文件支持,a.js和b.js,他们俩是互不依赖的,当异步的去加载他们的时候,没必要等到一个加载完毕之后才去加载第二个,完全可以两个同时进行load,这就是最简单的并行。逻辑处理也很简单, 设个计数器为当前并发加载js的总数,每个js加载完毕之后计数器减一,检测到计数器为0时,证明并发加载已经完毕,可以执行操作A。

    4.需要串行的情况一般都发生在有模块依赖的时候,比如操作A需要a.js 支持,a.js又需要b.js,那么这一条依赖链就形成了,加载顺序必须得是先加载b.js,然后才能加载a.js,要不然a.js中需要调用b.js中的函数的地方就会报错。这是个最简单的异步的,串行的加载情况。可是问题往往没那么简单...比如有操作B需要a.js和b.js,然后a.js需要c.js和d.js,然后d.js又需要b.js。我随便举个例子,这种多依赖并且有交叉的时候,往往就需要针对每一个需要加载的js针对他的依赖进去递归了。这其实就可以理解成一种递归型的依赖了。

    5.即便我们处理好了递归的依赖情况,自动的帮所有依赖理清顺序,我们仍然可以把问题继续考虑复杂一些,比如还是上面的操作B,假如他需要a.js,b.js,e.js,f.js这四个, 然后同样a.js需要c和d,d需要b,但是e.js和f.js是无任何依赖的,也就是说理想状态是把没有任何依赖的e,f,单独看做两条独立的链,然后和需要递归或者串行的b->d->c->a这条链进行并发。这就是较为复杂的并行和串行混杂的模式。

    6.最后,单一操作有并行,串行混杂的情况,多操作的时候就更为麻烦一点了。而且也是必然需要考虑的一点。一个页面多操作,这肯定是常态,而且多操作没理由串行,肯定是并行的。比如一个页面有两个操作,操作A,操作B,操作A需要模块a,b。操作B需要模块b,c,模块c依赖于a,这种情况是可能的。那么操作A,操作B并发的话,模块a在操作A中属于应该并发加载的模块,在操作B中又属于应该由a->c串行加载的模块。这样在判断逻辑的时候很容易起冲突,于是乎,一个页面中看似没什么关联的多操作就没那么简单的可以独立开了。或许需要把页面内的多操作也全局的统一管理起来,才可能理得清操作与操作之间的微妙关系了,类似于YUI的add。

    说了这么多,看来一个完善的模块加载器需要考虑的东西还真不少。通过这两周“站在巨人肩膀”上的尝试,解决了上面大部分的问题,但还是不够完善,等会会接着说。还是先贴代码吧:

    代码
    /**
     * @Author: hongru.chen
     * @version: 0.2
    *
    */
    if (typeof HR === 'undefined' || !HR) HR = {};

    (
    function () {
        
    //-- namespace from YUI
        HR.namespace = function () {
            
    var a = arguments, o = null, i, j, d;
            
    for (i=0; i<a.length; i++) {
                d 
    = ('' + a[i]).split('.');
                o 
    = this;
                
    for (j = (d[0== 'HR'? 1 : 0; j<d.length; j++) {
                    o[d[j]] 
    = o[d[j]] || {};
                    o 
    = o[d[j]];
                }
            }
            
    return o;
        }

    /**
     * Method 使用模块的主函数
     * @param (String or Array) 要使用的模块名
     * @param (Function) *optional 加载模块后的回调函数
     * @param (Object) *optional 回调绑定对象
     * @return undefined
    *
    */ 
    var _module = function (moduleName, callback, context) {
        
    var argIndex=-1;
        
        
    // private method 监测moduleName,如果是url(http://*)路径形式,register后load
            function checkURL(src) {
                
    var dsrc = src;
                
    if (src && src.substring(04== "url(") {
                    dsrc 
    = src.substring(4, src.length - 1);
                }
                
    var r = _module.registered[dsrc];
                
    return (!&& (!_module.__checkURLs || !_module.__checkURLs[dsrc]) && src && src.length > 4 && src.substring(04== "url(");
            }
            
        
    // 并发调用的模块列表
        var moduleNames = new Array();
        
        
    if (typeof(moduleName) != "string" && moduleName.length) {
            
    var _moduleNames = moduleName;
            
    for (var s=0;s<_moduleNames.length; s++) {
                
    if (_module.registered[_moduleNames[s]] || checkURL(_moduleNames[s])) {
                    moduleNames.push(_moduleNames[s]);
                }
            }
            moduleName 
    = moduleNames[0];
            argIndex 
    = 1;
        } 
    else {
            
    while (typeof(arguments[++argIndex]) == "string") {
                
    if (_module.registered[moduleName] || checkURL(moduleName)) {
                    moduleNames.push(arguments[argIndex]);
                }
            }
        }
        callback 
    = arguments[argIndex];
        context 
    = arguments[++argIndex];
        
        
    if (moduleNames.length > 1) {
            
    var cb = callback;
            callback 
    = function() {
                _module(moduleNames, cb, context);
            }
        }
        
        
    // 已经register过的模块hash
        var reg = _module.registered[moduleName];
        
    // 处理直接使用url的情况
        if (!_module.__checkURLs) _module.__checkURLs = {};
        
    if (checkURL(moduleName) && moduleName.substring(04== "url(") {
            moduleName 
    = moduleName.substring(4, moduleName.length - 1);
            
    if (!_module.__checkURLs[moduleName]) {
                moduleNames[
    0= moduleName;
                _module.register(moduleName, moduleName);
                reg 
    = _module.registered[moduleName];
                
    var callbackQueue = _module.prototype.getCallbackQueue(moduleName);
                
    var cbitem = new _module.prototype.curCallBack(function() {
                    _module.__checkURLs[moduleName] 
    = true;
                });
                callbackQueue.push(cbitem);
                callbackQueue.push(
    new _module.prototype.curCallBack(callback, context));
                callback 
    = undefined;
                context 
    = undefined;
            }
        }
        
        
    if (reg) {
            
    // 先处理被依赖的模块
            for (var r=reg.requirements.length-1; r>=0; r--) {
                
    if (_module.registered[reg.requirements[r].name]) {
                    _module(reg.requirements[r].name, 
    function() {
                        _module(moduleName, callback, context); 
                    }, context);
                    
    return;
                }
            }
            
            
    // load每个模块
            for (var u=0; u<reg.urls.length; u++) {
                
    if (u == reg.urls.length - 1) {
                    
    if (callback) {
                        _module.load(reg.name, reg.urls[u], reg.asyncWait, 
    new _module.prototype.curCallBack(callback, context));
                    } 
    else {
                        _module.load(reg.name, reg.urls[u], reg.asyncWait);
                    }
                } 
    else {
                    _module.load(reg.name, reg.urls[u], reg.asyncWait);
                }
            }
            
        } 
    else {
            
    !!callback && callback.call(context);
        }
    }
        
    _module.prototype 
    = {

        
    /**
         * Method 模块注册
         * @param (String or Object) 注册的模块名或者对象字面量
         * @param (Number) *optional 异步等待时间
         * @param (String or Array) 注册模块对应的url地址
         * @return (Object) 注册模块的相关信息对象字面量
        *
    */
        register : 
    function(name, asyncWait, urls) {
            
    var reg;
            
    if (typeof(name) == "object") {
                reg 
    = name;
                reg 
    = new _module.prototype.__register(reg.name, reg.asyncWait, urls);
            } 
    else {
                reg 
    = new _module.prototype.__register(name, asyncWait, urls);
            }
            
    if (!_module.registered) _module.registered = { };
            
    if (_module.registered[name] && window.console) {
                window.console.log(
    "Warning: Module named \"" + name + "\" was already registered, Overwritten!!!");
            }
            _module.registered[name] 
    = reg;
            
    return reg;
        },
        
    // -- 注册模块的行动函数,并提供链式调用
        __register : function(_name, _asyncWait, _urls) {
            
    this.name = _name;
            
    var a=0;
            
    var arg = arguments[++a];

            
    if (arg && typeof(arg) == "number") { this.asyncWait = _asyncWait } else { this.asyncWait = 0 }
            
    this.urls = new Array();
            
    if (arg && arg.length && typeof(arg) != "string") {
                
    this.urls = arg;
            } 
    else {
                
    for (a=a; a<arguments.length; a++) {
                    
    if (arguments[a] && typeof(arguments[a]) == "string"this.urls.push(arguments[a]);
                }
            }
            
    // 依赖列表
            this.requirements = new Array();
            
            
    this.require = function(resourceName) {
                
    this.requirements.push({ name: resourceName });
                
    return this;
            }
            
    this.register = function(name, asyncWait, urls) {
                
    return _module.register(name, asyncWait, urls);
            }
            
    return this;
        },

        defaultAsyncTime: 
    5000,
        
        
    // -- 处理加载模块逻辑
        load: function(moduleName, scriptUrl, asyncWait, cb) {
            
    if (asyncWait == undefined) asyncWait = _module.defaultAsyncTime;
            
            
    if (!_module.loadedscripts) _module.loadedscripts = new Array();

             
    var callbackQueue = _module.prototype.getCallbackQueue(scriptUrl);
             callbackQueue.push(
    new _module.prototype.curCallBack( function() {
                 _module.loadedscripts.push(_module.registered[moduleName]);
                 _module.registered[moduleName] 
    = undefined;
             }, 
    null));
             
    if (cb) {
                 callbackQueue.push(cb);
                 
    if (callbackQueue.length > 2return;
             }
            
             _module.loadScript(scriptUrl, asyncWait, callbackQueue);
        }, 
        
        
    // -- 加载模块行动函数
        loadScript : function(scriptUrl, asyncWait, callbackQueue) {
            
    var scriptNode = _module.prototype.createScriptNode();
            scriptNode.setAttribute(
    "src", scriptUrl);
            
    if (callbackQueue) {
                
    // 执行callback队列
                var execQueue = function() {
                    _module.__callbackQueue[scriptUrl] 
    = undefined;
                    
    for (var q=0; q<callbackQueue.length; q++) {
                        callbackQueue[q].runCallback();
                    }
                    
    // 重置callback队列
                    callbackQueue = new Array(); 
                }
                scriptNode.onload 
    = scriptNode.onreadystatechange = function() {
                    
    if ((!scriptNode.readyState) || scriptNode.readyState == "loaded" || scriptNode.readyState == "complete" || scriptNode.readyState == 4 && scriptNode.status == 200) {
                        asyncWait 
    > 0 ? setTimeout(execQueue, asyncWait) : execQueue();
                    }
                };
            }
            
    var headNode = document.getElementsByTagName("head")[0];
            headNode.appendChild(scriptNode);
        },    
        
        
    // -- 执行当前 callback
        curCallBack : function(_callback, _context) {
            
    this.callback = _callback;
            
    this.context = _context;
            
    this.runCallback = function() {
                
    !!this.context ? this.callback.call(this.context) : this.callback();
            };
        },
        
    // -- 获取callback列表
        getCallbackQueue: function(scriptUrl) {
            
    if (!_module.__callbackQueue) _module.__callbackQueue = {};    
             
    var callbackQueue = _module.__callbackQueue[scriptUrl];        
             
    if (!callbackQueue) callbackQueue = _module.__callbackQueue[scriptUrl] = new Array();
             
    return callbackQueue;
        },
        
        createScriptNode : 
    function() {
            
    var scriptNode = document.createElement("script");
            scriptNode.setAttribute(
    "type""text/javascript");
            scriptNode.setAttribute(
    "language""Javascript");
            
    return scriptNode;    
        }
        
    }
    // 提供静态方法
    _module.register = _module.prototype.register;
    _module.load 
    = _module.prototype.load;
    _module.defaultAsyncTime 
    = _module.prototype.defaultAsyncTime;
    _module.loadScript 
    = _module.prototype.loadScript;
        
    // 公开接口    
    HR.module = _module;
    })();
    代码逻辑复杂了不少,比起之前那个漏洞百出的版本考虑的东西更多了一些,也改变了一些设计的思路。核心依然是提供了两个主要的方法 ,在命名空间HR下,模块注册HR.module.register(),它的arguments有3个,分别为模块名,异步加载等待时间,模块对应js文件路径。其中模块名和js路径是必须得。异步等待时间为可选,默认为0.可以这样使用它:HR.module.register('test1''scripts/test-1.js?')
    同时一个模块可以由多个js文件组成,如:
    HR.module.register('Test', ['scripts/test-1.js','scripts/test-2.js'])

     为了更方便的标记它的依赖,把依赖作为它链式的方法来处理了。比如模块有依赖的时候直接在其后添加require()即可。链式的风格参照jQuery。

    HR.module.register('test1''test-script/test-1.js').require('a').require('b')
        .register(
    'a''test-script/a.js')
        .register(
    'b''test-script/b.js')

    模块注册之后,不会加载,调用的时候才会异步的加载。并通过回调来执行加载后的操作。可以直接在页面load的时候进行异步加载,也可放在任何一个事件handle里面来进行按需加载。

    HR.module('test1'function () {
        
    //TODO
    });

    或者:多模块一起调用,执行回调:

     HR.module('test1', 'test2', function () {

        //TODO
    });

    或者写成数组的形式:

      HR.module(['test1', 'test2'], function () {

        //TODO
    });

    都行 

    后来考虑到每次都必须先注册模块,才能使用,有点不方便。于是加了一个功能,如果直接用 url(...)的方式使用模块,可以省略注册模块这一步。
    比如,可以不用register,直接
    HR.module('url(scripts/test.js)'function () {
        
    //TODO
    });

     当然,这种方式就不能对当前调用的这个js添加依赖,所以只适合一些简单的情况。

    【结束语】 

    经过我简单的测试,上面贴的代码基本能处理我上面列的几个问题。(但不排除我考虑不周,测试不全,以及犯低级错误的情况。)可还有问题在这一版本中没有解决,就是依赖死循环的问题。比如a模块依赖b模块,b模块又依赖a模块。虽然这实际是不可能的,但不能排除编码者的书写失误或者某些极端情况。

     好了,大致就到此为止吧。代码仅仅做了简单的测试。感兴趣的朋友可以拍拍砖,一个人能力有限,在大家的指正下可能才能真正的完善,以及有可用性。

  • 相关阅读:
    GIT 基本语句
    SpringBoot查看哪些配置类自动生效
    LeetCode第一题 两数之和
    static{} java中的静态代码块
    mybatis引入mapper映射文件的4种方法(转)
    MySQL Charset/Collation(字符集/校对)(转)
    MySQL数据库的创建(详细)
    Eclipse出现Tomcat无法启动:Server Tomcat v8.5 Server at localhost failed to start问题
    判断一个int类型数字的奇偶性
    linux中安装erlang时使用make命令报错问题
  • 原文地址:https://www.cnblogs.com/hongru/p/1935670.html
Copyright © 2020-2023  润新知