同步和异步 要理解Easyloader就必须对阻塞和非阻塞加载(对应同步和异步加载)有个大体的认识,所以这两个概念,必须钉在脑子里。 同步(阻塞)加载 我们平时最常使用是将"script"或者"style"标签直接写在html文档中的方式就是典型的同步加载方式。同步模式,又称阻塞模式,会阻止浏览器的后续处理,停止了后续的解析,因此停止了后续的文件加载(如图像)、渲染、代码执行。 javascript之所以要同步执行,是因为脚本中可能有输出document内容、修改dom、重定向等行为,所以默认同步执行才是安全的。以前的一般建议是把"script"放在页面末尾"body"之前,这样尽可能减少这种阻塞行为,而先让页面展示出来。 异步(非阻塞)加载 function loadJs(url, callback) { var done = false; var script = document.createElement('script'); script.type = 'text/javascript'; script.language = 'javascript'; script.src = url; //onreadystatechange和script.readyState属性事件是IE特有的,IE不支持script.onload //firefox,chrome等其它浏览器支持只script.onload事件 script.onload = script.onreadystatechange = function() { if(!done && (!script.readyState || script.readyState == 'loaded' || script.readyState == 'complete')) { done = true; script.onload = script.onreadystatechange = null; if(callback) { callback.call(script); } } } document.getElementsByTagName("head")[0].appendChild(script); } 以上方式就是实现javascript异步加载的典型代码,用js创建一个script元素并插入到document中,这样就做到了非阻塞的下载js代码。此方法被称为"Script DOM Element"法,不要求js同源。Easyui的Easyloader便是用的这种方式实现异步加载的。 需要注意两点: 这种加载方式在加载执行完之前会阻止"onload"事件的触发,所以从某种意义上讲,这种方式实现的异步并不是完全的非阻塞; 异步加载并不一定能减少整个页面完全加载完所需的时间,有的时候按照模块加载恰恰是进行了多次http请求导致加载变慢,这就跟拷贝一个1G的大文件速度明显优于拷贝500个1M大小文件的道理是一样的。 Easyui的加载器 加载器的使用场景 你觉得一次性导入easyui的核心min Js和css太大,影响页面的相应速度,想先展示基础的文档结构给用户 你只用到easyui的其中几个组件,实在没必要导入所有组件的js和css文件 你想使用其中的一个组件,但是你又不知道这个组件依赖了那些组件 你甚至想把jQuery这样的基础库和自己写的js库,以提高基础文档结构展示给用户的速度 加载器的属性与事件 EasyLoader的API文档请参见:jQuery Easyui EasyLoader(加载器) API文档 加载器的使用方式 Easyui的加载器的使用方式有两种: 加载模块方式 //注意jquery,panelExtend,layoutExtend三个模块是我自定义的modul //因为jquery也是异步加载的,jquery的相关用法必须写在回调函数里。 using(['jquery', 'parser', 'layout', 'menu', 'tabs', 'linkbutton', 'accordion', 'tree', 'panelExtend', 'layoutExtend'], function() { alert('全部模块异步加载完成'); $(function(){ alert('全部文档装载完成'); }); }); 加载单个文件方式 //加载单个文件方式,必须写绝对的url using("http://wwww.easyui.info/easyui/js/index2.js", function() { alert('异步加载文件完成'); }); 如何自定义模块 Easyui的作为认为一个模块应该至少包含一个js文件,至于css文件并不是必要的,所以我们定义模块的时候,不能仅仅将某个css文件定义为某个模块。 同时,对于模块扩展的写法也是很有考究的,很多同学想当然用下面的写法来扩展模块: easyloader.modules = $.extand({},{ "mymodule1":{ js:'http://www.easyui.info/mymodule1.js', css:'http://www.easyui.info/mymodule1.css'}, "mymodule2":{ js:'http://www.easyui.info/mymodule2.js' css:'http://www.easyui.info/mymodule2.css' }, "mymodule3":{ js:'http://www.easyui.info/mymodule3.js' css:'http://www.easyui.info/mymodule3.css' }, ....... },easyloader.modules); 这样做遇到的第一个问题是,如果jquery库也是使用异步加载的话,那么$.extend函数是不能被使用的,当然了,这个还是比较容易理解的,而且大多数童鞋的jquery库是用同步加载的,并非异步,所以不用担心这个问题。 然而第二个问题你就不得不面对了,因为这样扩展模块,加载器根本识别不了你增加的模块,为什么呢?我们来看看加载器的源码: (function(){ /** *定义一个对象modules,加载模块的时候,用的就是这个modules作为所有模块集合的 *modules究竟是什么,它只不过是一个引用罢了,对某段堆内存的引用,真正的对象是存储在堆内存中的 *如果在定义modules之后写有以下代码: *var modulesCopy = modules; *modulesCopy = new Object(); *modulesCopy.draggable.js = "唐老鸭"; *大家应该能能反应过来,modulesCopy.draggable.js重新设置值并不会影响到modules.draggable.js的值 *而jquery的easyloader.modules = $.extend({},modulesCopy,easyloader.modules);也是将easyloader.modules指向了另一段内存堆 *执行过这句代码后easyloader.modules和modules已经指向两个不同内存堆 *那么正确的应该如何写呢? *easyloader.modules = $.extend(easyloader.modules,modulesCopy);这样写可以确保easyloader.modules和modules引用同一内存堆 **/ var modules = { draggable:{ js:'jquery.draggable.js' } //此处省略诺干 }; var locales = { 'af':'easyui-lang-af.js' //此处省略诺干 }; var queues = {}; /** 此处省略诺干干干 **/ //是不是很蛋疼,用了一个没加var申明的easyloader,于是他就是全局变量了…… easyloader = { modules:modules,//easyloader.modules跟modules指向同一堆内存 locales:locales, base:'.', theme:'default', css:true, locale:null, timeout:2000, load: function(name, callback){ //省略诺干 }, onProgress: function(name){}, onLoad: function(name){} }; //省略诺干 window.using = easyloader.load; //已经存在jq的话,先把parser组件加载了,继而解析整个页面 //很多组件还未加载,怎么能解析的呢? //奥妙就在parser组件内部了,parser组件会自动检测页面包含的敏感easyui样式 //进而在使用easyloader将这个组件加载了 if (window.jQuery){ jQuery(function(){ easyloader.load('parser', function(){ jQuery.parser.parse(); }); }); } })(); 为了节省篇幅,没多大相关的代码我都省去了,注释部分我已经讲的比较清楚了,如果还不清楚的,要好好补一下javascript基础和jquery的extend函数的用法了 最终我们要想成功增加模块,必须按照以下写法才行: $.extand(easyloader.modules,{ "mymodule1":{ js:'http://www.easyui.info/mymodule1.js', css:'http://www.easyui.info/mymodule1.css'}, "mymodule2":{ js:'http://www.easyui.info/mymodule2.js' css:'http://www.easyui.info/mymodule2.css' }, "mymodule3":{ js:'http://www.easyui.info/mymodule3.js' css:'http://www.easyui.info/mymodule3.css' } }); 推荐使用加载模块数组的方式 如果我们使用加载单个文件或者模块的方式,势必会牵扯到一个问题,如果文件或者模块间有依赖关系,我们就必须在回调函数里加载高级模块,如此一来回调函数可能就要嵌套很多层了。 而是用加载模块数组的方式,EasyLoder的内部实现了按照数组元素先后顺序同步加载,即,只要基础库写在数组前面就可以确保被同步优先加载。 对于Easyui自带组件之间的依赖,EasyLoder也有自身的算法,确保Easyui的基础类组件会被同步优先加载。如此一来,至少在代码的可读和可维护性上讲,优雅了很多。 最后的总结 异步加载的水还是比较深的,个人觉得easyui的核心文件也不是很大,没有特殊需求的同学就不要使用异步方式了,别把自己搞得焦头烂额,如果实在要使用而又没经验的,请细读本文,我想不会让你失望的。 要异步加载的演示?其实我的那个依旧待完善的API在线文档就是演示,它演示了异步方式如果是比同步方式更加的慢,典型的反面教材: