优化EXTJS的按模块上载JS的性能

优化EXTJS的按模块下载JS的性能。
最近有不少用户跟我反馈,访问Joffice页面的某些功能,需要等几秒钟才能出来。鉴于这种情况,在此分析一下原因,同时也给出一些解决方案,可以帮助Joffice 1.2,Joffice 1.3的开发用户解决这种根本的问题,可以让这种按模块下载js速度提高7-8倍,特别是有一些模块需要加载很多js的时候,其下载速度还高更多。

joffice 1.3以前的版本,按模块下载的原理如下:

在此我们以流程管理模块为例:

在App.import.js中定义该模块所依赖的js,如下:
FlowManagerView:[   
           __ctxPath+'/js/flow/ProTypeForm.js',   
           __ctxPath+'/js/flow/ProDefinitionForm.js',   
           __ctxPath+'/js/flow/ProDefinitionView.js',   
           __ctxPath+'/js/flow/FlowManagerView.js',   
           __ctxPath+'/js/flow/ProDefinitionDetail.js',   
           __ctxPath+'/js/flow/ProcessRunStart.js',   
           __ctxPath+'/js/flow/ProDefinitionSetting.js',   
           __ctxPath+'/js/flow/MyTaskView.js',   
           __ctxPath+'/js/flow/ProcessNextForm.js',   
           __ctxPath+'/js/flow/FormDesignWindow.js',   
           __ctxPath+'/js/flow/FormEditorWindow.js',   
           __ctxPath+'/js/flowDesign/FlowDesignerWindow.js'  
    ]  

在此可以看出,该模块所依赖的js比较多,不过每个js都不大。

当点击左菜单的“流程管理”时,其就通过ScriptMgr来下载其所依赖的js,全部下载完成后,才创建这个流程管理的Panel,并且加到TabCenterPanel中去。

我们的调用下载的js代码如下:
function $ImportJs(viewName,callback,params) {   
    var b = jsCache[viewName];   
       
    if (b != null) {   
        var view =newView(viewName,params);   
        callback.call(this, view);   
        } else {    
        var jsArr = eval('App.importJs.' + viewName);   
        if(jsArr==undefined || jsArr.length==0){   
            try{   
                var view = newView(viewName,params);   
                callback.call(this, view);   
            }catch(e){   
            }   
            return ;   
        }   
        ScriptMgr.load({   
                    scripts : jsArr,   
                    callback : function() {   
                        jsCache[viewName]=0;   
                        var view = newView(viewName,params);   
                        callback.call(this, view);   
                    }   
        });   
    }   
}  


即我们调用:
$ImportJs('FlowManagerView',function(){   
      return new FlowManagerView();   
});  

当传入FlowManagerView时,告诉我们就是需要在App.Import.js中取出该依赖的js数组,然后传给ScriptMgr的load中的scripts参数,告诉他们我们要完成这些js的加载,并且完成后,创建FlowManagerView对象。

现在我们来看一下ScriptMgr的Load方法:
ScriptLoaderMgr = function() {   
    this.loader = new ScriptLoader();   
  
    this.load = function(o) {   
        if (!Ext.isArray(o.scripts)) {   
            o.scripts = [o.scripts];   
        }   
  
        o.url = o.scripts.shift();   
  
        if (o.scripts.length == 0) {   
            this.loader.load(o);   
        } else {   
            o.scope = this;   
            this.loader.load(o, function() {   
                        this.load(o);   
                    });   
        }   
    };   
};  

ScriptLoader的代码如下:
/**  
 * 用于动态加载js  
  *  sample is here  
  *   ScriptMgr.load({  
  *   scripts: ['/js/other-prerequisite.js', '/js/other.js'],  
  *   callback: function() {  
  *     var other = new OtherObject();  
  *     alert(other); //just loaded  
  *   }  
  * });   
  */  
ScriptLoader = function() {   
    this.timeout = 10;   
    this.scripts = [];   
    this.disableCaching = true;//false   
    this.loadMask = null;   
};   
  
ScriptLoader.prototype = {   
    showMask : function() {   
        if (!this.loadMask) {   
            this.loadMask = new Ext.LoadMask(Ext.getBody());   
            this.loadMask.show();   
        }   
    },   
  
    hideMask : function() {   
        if (this.loadMask) {   
            this.loadMask.hide();   
            this.loadMask = null;   
        }   
    },   
  
    processSuccess : function(response) {   
        this.scripts[response.argument.url] = true;   
        window.execScript ? window.execScript(response.responseText) : window   
                .eval(response.responseText);   
        //if (response.argument.options.scripts.length == 0) {   
            this.hideMask();   
        //}   
        if (typeof response.argument.callback == 'function') {   
            response.argument.callback.call(response.argument.scope);   
        }   
    },   
  
    processFailure : function(response) {   
        this.hideMask();   
        Ext.MessageBox.show({   
                    title : '应用程序出错',   
                    msg : 'Js脚本库加载出错,服务器可能停止,请联系管理员。',   
                    closable : false,   
                    icon : Ext.MessageBox.ERROR,   
                    minWidth : 200  
                });   
        setTimeout(function() {Ext.MessageBox.hide();}, 3000);   
    },   
  
    load : function(url, callback) {   
        var cfg, callerScope;   
       if (typeof url == 'object') { // must be config object   
            cfg = url;   
            url = cfg.url;   
            callback = callback || cfg.callback;   
            callerScope = cfg.scope;   
            if (typeof cfg.timeout != 'undefined') {   
                this.timeout = cfg.timeout;   
            }   
            if (typeof cfg.disableCaching != 'undefined') {   
                this.disableCaching = cfg.disableCaching;   
            } 
          }   
          if (this.scripts[url]) {   
            if (typeof callback == 'function') {   
                callback.call(callerScope || window);   
            }   
            return null;   
        }   
  
        this.showMask();   
        //alert('load now?');   
        Ext.Ajax.request({   
                    url : url,   
                    success : this.processSuccess,   
                    failure : this.processFailure,   
                    scope : this,   
                    timeout : (this.timeout * 1000),   
                    disableCaching : this.disableCaching,   
                     argument : {   
                        'url' : url,   
                        'scope' : callerScope || window,   
                        'callback' : callback,   
                        'options' : cfg   
                    }   
                });   
    }   
};  

从以上我们可以看出,其加载的js数组的时候,是加载完成一个js后,然后再加载另一个js,直到加载完成后才调用回调函数。若一个模块有20个js,每个js平均下载的时间需要0.5秒,即就需要10秒钟加载。当然我们可以把这些js合并至一个js,然后下载,这是理论上是可以的,不过不利于代码的划分。

我们知道浏览器是可以同时下载这些js的,只不过我们需要知道什么时候下载完成,下载完成后我们就可以调用回调函数。

鉴于此, 我们通过一个变量来记录其下载js,每完成下载一个就自动加1,若下载完成后,下载的数量就跟我们依赖的js的数量一样,就可以回调,这样我们有多少个js,就产生多少个下载器,每个下载器同时下载这些js,改进后的ScriptMgr的代码如下所示:
ScriptLoaderMgr = function() {   
    this.loader = new ScriptLoader();   
    this.load = function(o) {   
        if (!Ext.isArray(o.scripts)) {   
            o.scripts = [o.scripts];   
        }   
        //记数器   
        o.lfiles=0;   
        for(var i=0;i<o.scripts.length;i++){   
            o.url = o.scripts[i];   
            o.scope = this;   
            this.loader.load(o, function() {   
                o.lfiles++;   
                if(o.lfiles==o.scripts.length){   
                    if(o.callback!=null){   
                        this.loader.hideMask();   
                        o.callback.call(this);   
                    }   
                }   
            });   
        }   
    };   
};  


JOfficeOA 2.0电信访问地址:
http://office.jee-soft.cn:8080/login.jsp
JOfficeOA 2.0网通访问地址:
http://oa.jee-soft.cn:8080/login.jsp
账号:admin  密码:1

联系人:廖先生
QQ:571172008
电话:020-61151622