就像是一棵树有很多果实一样,QWrap也有很多apps,本文讲解种子应用。“种子”是沿用YUI3的说法,种子应用是解决模块加载问题的应用,包括:模块预加载、异步按需加载、模块应用。
apps果实篇之:种子
或许有些同学对异步加载模块不大熟悉,没关系,我们先感性的看一下这段代码
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
- <html>
- <head>
- <title>QW应用之一:seed_youa</title>
- <meta http-equiv="Content-Type" content="text/html; charset=GB2312" />
- <script type="text/javascript" src="http://dev.qwrap.com/resource/js/apps/seed_youa.combo.js"></script>
- </head>
- <body>
- <div id="div1">div1-----<span class="content">时间</span></div>
- <div id="div2">div2-----<span class="content">时间</span></div>
- <input type=button value="点击后按需加载后并修改div1/div2" onclick="test()"/>
- <script type="text/javascript">
- function test(){
- QW.use('jQuery,YouaCore',function(){
- $('#div1').css('color','red').find('>.content').html(new Date()+'');//这段代码用到了jquery用法
- W('#div2').css('color','blue').query('.content').html(new Date().format('YYYY-MM-dd hh:mm:ss'));//这段代码用到了Youa版的QWrap
- });
- }
- </script>
- </body>
1. 如果用户浏览此网页时,只加载了一个体积很小的seed_youa.combo.js这个js(其实就是seed_youa.js(http://dev.qwrap.com/resource/js/apps/seed_youa.js)),这个js经yui压缩后的大小为4K。
2. 当用户点击按钮时,按钮对应的js用到jQuery与YouaCore,点击后才会去加载那两个jQuery与YouaCore对应的js,之后才有运行效果。
3. 第二次点击按钮,由于前面已经加载过js,所以这次会直接运行按钮事件。
分析一下以上三步,分两种情况。
A. 对于只进行了第一步操作的用户,他们用seed_youa.js替代了jQuery与YouaCore两个js的流量(这是好处甲);
B. 对于进行过第二步操作的用户,由于以上流量的节约,会让用户提前能够点击按钮(这是好处乙),点击后异步加载需要的js,加载完后事件才运行,即点击与运行存在一个时间差(这是坏处甲),并且三个js都用到了,相对于传统写法,http多了一个seed_youa.combo.js(这是坏处乙)。
另外,对于页面程序员来说,他在写按钮事件时,只需要知道自己的这段js用到了哪些模块,而不用关心这些模块是否已经加载(这是好处丙),不过,他引用模块的方式有了一些变化(少键入html代码,多键入js代码)。
好处甲、好处乙、好处丙、坏处甲、坏处乙,还有些没有分析到的好处与坏处,反正有利有弊。
那么,在什么情况下利大于弊、什么情况下弊大于利,而我们该如何兴利除弊?
如果这个页面的用户100%不会点击按钮,则几乎没坏处,只有好处甲、好处丙。
如果页面100%的用户会点击按钮,则需要权衡再权衡利弊再想办法。
好的,我们先暂且搁置这些细节,因为以上内容已足够让同学们对“异步按需加载”有了感性的认识。我们把提供这种异步按需加载的js叫种子(seed)。
我们看一下seed_youa.js,它是一个组合js(即前面一篇文章所说的B类apps:开发时可以是多个文件,但上线时会先合并后上线。)
它由三个js组成:
- document.write('<script type="text/javascript" src="' + srcPath + 'core/core_base.js"><\/script>');
- document.write('<script type="text/javascript" src="' + srcPath + 'core/module.h.js"><\/script>');
- document.write('<script type="text/javascript" src="' + srcPath + 'apps/youa_modules_config.js"><\/script>')
core/core_base.js是QWrap的主干js
core/module.h.js是模块管理器,也是一个Helper,提供三个方法:addConfig、use、provide,代码参见:http://dev.qwrap.com/resource/js/core/module.h.js
youa_modules_config.js是youa项目只所使用到的常用模块配置,通过addConfig进行模块配置,代码如下:
- /*Lib Module*/
- QW.ModuleH.addConfig({
- YouaCore: {
- url: '//apps/core_dom_youa_lazy.combo.js',
- loadedChecker:function(){
- return !!(QW.W);
- }
- },
- Ajax: {
- url: '//components/ajax/ajax.youa.js',
- requires: 'YouaCore'
- },
- Anim: {
- url: '//components/animation/anim.js',
- requires: 'YouaCore'
- },
- Cookie: {
- url: '//components/cache/cache.js',
- requires: 'YouaCore'
- },
- Storage: {
- url: '//components/cache/cache.js',
- requires: 'YouaCore'
- },
- Drag: {
- url: '//components/drag/drag.js',
- requires: 'YouaCore'
- },
- Editor: {
- url: '//components/editor/editor.js',
- requires: 'YouaCore,Panel'
- },
- Panel: {
- url: '//components/panel/panel.js',
- requires: 'YouaCore'
- },
- Suggest: {
- url: '//components/suggest/suggest.js',
- requires: 'YouaCore'
- },
- "Switch": {
- url: '//components/switch/switch.js',
- requires: 'YouaCore'
- },
- Tree: {
- url: '//components/tree/tree.js',
- requires: 'YouaCore'
- },
- Valid: {
- url: '//components/valid/valid.js',
- requires: 'YouaCore'
- },
- jQuery: {
- url: 'http://common.cnblogs.com/script/jquery.js',
- loadedChecker:function(){
- return !!(window.jQuery && window.jQuery.fn);
- }
- }
- });
- /*Logic Module*/
- QW.ModuleH.addConfig({
- "User": {
- url: '//global/userv3.js',
- requires: 'YouaCore',
- loadedChecker: function() {
- return !!window.topbar;
- }
- },
- ShopMap: {
- url: '//sp/map/shopmap.js',
- requires: 'YouaCore'
- }
- });
看到这个配置文件的同学也许会问:模块添加得越来越多会怎么办?为什么不像YUI一样,在各个模块的js里进行addConfig,而要集中起来addConfig?
不错,理论上是会越来越大,但是,实际上最大能大到多少?并且不一定是所有的模块配置都要放在这里面。在页面的js里也可以QW.addConfig('MyPageJs','//my.js')的。所谓的太大,只是个理论问题,不是个实际问题。
YUI在各个模块的js里进行add,有好处,也有坏处。
好处就是多了个沙箱机制,沙箱理论看起来很严谨,可以满足YUI的严谨性洁癖----其实我一直没有理解它对实用者都有啥好处。
而坏处,也有很多。例如:
1. 需要模块的代码来适应他的加载机制。----例如,jQuery也可以算是个模块,凭什么要让独立的jquery来改代码?
2. 假设我改了jQuery的代码来适应这loader,那是不是也损失了jQuery的原来的用法,即预加载模式用法(在HTML的Header里引用jquery.js,在页面直接用$('#id').show())。
3. 例如use('Editor',function(){})时,其实由于依赖关系,会按需加载YouaCore,Drag,Panel,Editor四个js,因为依赖关系是在各自的js里,理论上无法将四个请求合并成一个请求,也无法在请求editor.js时就并行请求drag.js。
4. 如果客户端缓存了js,我们更新Editor.js后,use('Editor',function(){})无法知道是否需要在editor.js后添加版本号。
5. 如果有复合模块,例如core_dom_youa_lazy.js其实是同时拥有很多小模块功能的一个js,那在drap.js里如何定义依赖?
而QWrap的“集中配置”方式,上面几条都不是什么问题。
对于第一条第二条,已经解决,即:addConfig时的loadedChecker参数。
例如,这样配置jQuery:
- QW.ModuleH.addConfig({
- jQuery: {
- url: 'http://common.cnblogs.com/script/jquery.js',
- loadedChecker:function(){
- return !!(window.jQuery && window.jQuery.fn);
- }
- }
- });
这个jQuery引用的是博客园网站的,我们没有作任何修改。
如果页面已经预加载了jquery,运行QW.use('jQuery',function(){})也不会再次加载jquery.js。
当然,用户还可以保持习惯,用以前的预加载经典用法。
对于第三条合并请求与并行请求,QW目前还没做,不过,只需调整一下module.h.js里的某一段代码即可。在源代码里有说明:
- function loadsJsInOrder() {
- //浏览器不能保证动态添加的ScriptElement会按顺序执行,所以人为来保证一下
- //参见:http://www.stevesouders.com/blog/2009/04/27/loading-scripts-without-blocking/
- //测试帮助:http://1.cuzillion.com/bin/resource.cgi?type=js&sleep=3&jsdelay=0&n=1&t=1294649352
- //todo: 目前没有充分利用部分浏览器的并行下载功能,可以改进。
- //todo: 如果服务器端能combo,则可修改以下内容以适应。
- var moduleI = loadingModules[0];
- function loadedDone() {
- moduleI.loadStatus = 2;
- var cbs = moduleI.__callbacks;
- for (var i = 0; i < cbs.length; i++) {
- cbs[i]();
- }
- isLoading = false;
- loadsJsInOrder();
- }
- if (!isLoading && moduleI) {
- //alert(moduleI.url);
- isLoading = true;
- loadingModules.splice(0, 1);
- var checker = moduleI.loadedChecker;
- if (checker && checker()) { //如果有loaderChecker,则用loaderChecker判断一下是否已经加载过
- loadedDone();
- } else {
- loadJs(moduleI.url.replace(/^\/\//, QW.PATH), loadedDone);
- }
- }
- }
对于第四条缓存,由于是统一配置,所以只需在发布时,在配置文件的js后面加上对应js的md5码的前N位即可。
例如,线上的配置可能是:
- Ajax: {
- url: '//components/ajax/ajax.youa.js?111111.js',
- requires: 'YouaCore'
- },
- Anim: {
- url: '//components/animation/anim.js?222222.js',
- requires: 'YouaCore'
- }
页面引用的种子,也是在后面加上它的md5码的前N位。
这样做的好处是:每次上线后,用户也只需下载有过修改的js。
对于第五条的复合模块问题,由于是统一配置,所以根本就不是问题。
说了seed_youa.js的这么多好处,当然还要说明一个限制:项目中需要有一个水平还过得去的js同学,来负责这个统一的模块配置文件。
关于module.h.js还有挺多内容要讲的,不过,绝大同学不用关心它的实现;关心他的实现的同学大多也看过NK行的YUI3种子,再看这个两百行的module.h.js,应该是小case,这里先略过。
小结一下,QWrap的这个种子应用是绿色版的,并且小巧、灵活,可以直接复制过去引用。不过需要注意一下你所放的路径,防止QW.PATH的值计算有误,或者强制改掉QW.PATH的值。
附:QWrap网址:http://www.qwrap.com
原文:http://www.cnblogs.com/jkisjk/archive/2011/04/15/qwrap_apps_seed_youa.html
【编辑推荐】