注:本文的技术实现是基于非AMD方式的。 最近用dojo开发了个系统,大约有40个widget,之前的做法是在首页里面一开始就通过dojo.require将这40多个小部件引入了,由于是在本机测试,所以一
注:本文的技术实现是基于非AMD方式的。
最近用dojo开发了个系统,大约有40个widget,之前的做法是在首页里面一开始就通过dojo.require将这40多个小部件引入了,由于是在本机测试,所以一直没有速度问题,后来部署到外网后发现效率太慢了,最长的一次需要30多秒才能完成页面初始化。虽然我一开始引入了40多个widget,但是我并不是一上来就new这么多个小部件,一开始我只用到了三四个小部件,所以只实例化了三四个。后面那37个小部件都是在用户单击某些按钮进行分析完成时实例化的,假设如果用户没有进行分析操作,那我这37个widget就白引入了,没有用到,而且造成页面初始化时间较长,所以我开始想办法优化require的机制。
我想达到的效果是:假设有一个小部件A,我想在执行var a = new A()操作之前才执行dojo.require("A"),这样不会require那些无用的小部件的js文件,就能最大限度的优化请求。如下所示:
dojo.provide("widgets.B"); dojo.require("widgets.BaseWidget"); dojo.declare("widgets.B",[widgets.BaseWidget],{ templateString:dojo.cache("widgets.B","templates/B.html"), postCreate:function(){ this.inherited(arguments); dojo.connect(this.finish,"onclick",this,this.createA); }, createA:function(){ dojo.require("widgets.A"); var a = new widgets.A(); a.placeAt(dojo.body()); a.startup(); }, startup:function(){ this.inherited(arguments); } });
我想在单击B小部件中的finish按钮的时候执行createA方法,该方法会首先通过dojo.require("widgets.A")引入A.js,然后执行new操作。事实上这样确实也可以运行。但是我通过Chrome Developer Tools中的Network发现,在B.js这个文件被下载到浏览器中的时候就已经执行了dojo.require("widgets.A"),奇怪了,我明明写的是在createA的函数中引入的A的啊,怎么会提前引入了呢?后来发现dojo的require机制有这么个特点:当把一个文件下载到浏览器时,dojo会自动查找该文件中是否存在dojo.require()这样的语句,只要发现有,就立马去执行该语句去请求相应的js文件,而不管该语句写在文件的头部还是写在某个函数内部。 但是这样有个问题,如果在js文件中写入了dojo.require("A"),无论这样代码出现在js文件的哪个位置,dojo都会自己去请求A.js这个文件,而不是等到执行
createA:function(){ eval("(dojo.re"+"quire('widgets.A'))"); var a = new widgets.A(); a.placeAt(dojo.body()); a.startup(); }
上面我把dojo.require拆分成了dojo.re+quire,这样dojo自身在B.js文件中找不到完整的dojo.require字符串了,也就不会一开始就去请求A.js文件了,后来通过Chrome下的Network检查发现确实是在执行createA的时候才去执行的dojo.require("widgets.A"),哈哈。但是我发现还是存在问题:dojo.require()是异步方法,不是阻塞式的,所以语句eval("(dojo.re"+"quire('widgets.A'))")执行完后不会停滞,而会立即执行下面的var a = new widgets.A()操作,但是此时A.js还没有下载到浏览器中,造成了windgets.A不存在,导致报错,这可咋整呢?
为了彻底解决这个问题,我又重新想了下,思路是:还是通过eval的方式动态的去执行dojo.require请求,用setInterval每隔一段时间判断我请求的A.js文件是否已经下载到浏览器中,如果下载完成了,那么才执行new widgets.A()操作。
全局函数如下:
//检查小部件是否已经加载 function checkWidgetLoaded(widgetName){ var names = widgetName.split("."); var obj = window; for(var i=0;i<names.length;i++){ var name = names[i]; obj = obj[name]; if(!obj){ return false; } } var loaded = dojo.isFunction(obj); return loaded; } //按需加载小部件 function loadWidget(widgetName,callback){ var loaded = window.checkWidgetLoaded(widgetName); if(loaded){ if(dojo.isFunction(callback)){ callback(); } } else{ var shelterDom = window.showLoading("Loading..."); eval("(dojo.re"+"quire('"+widgetName+"'))"); var sumTime = 0; var smallTime = 50;//每隔50毫秒就判断js文件是否已经引入到本地 var maxTime = 15000;//超时时间15秒 var handle = setInterval(dojo.hitch(this,function(shelterDom){ var loaded = window.checkWidgetLoaded(widgetName); if(loaded){ window.hideLoading(shelterDom); clearInterval(handle); if(dojo.isFunction(callback)){ callback(); } } else{ sumTime += smallTime; if(sumTime >= maxTime){ window.hideLoading(shelterDom); clearInterval(handle); } } },shelterDom),smallTime); } }
使用方法如下:
createA:function(){ window.loadWidget("widgets.A",dojo.hitch(this,function(){ var a = new widgets.A(); a.placeAt(dojo.body()); a.startup(); })); }至此,我们就完成了按需请求小部件js的加载机制,这样可以大大减少在系统一开始引入的小部件的数量。