使用MVVM框架(avalonJS)进行快速开发

正文开始

<p>http://www.cnblogs.com/fengyuqing/p/avalon_use.html</p><p><br /></p><p></p><p>背景</p><p>在活动开发中,因为工作的重复性很大,同时往往开发时间短,某些情况下也会非常紧急,导致了活动开发时间被大大压缩,同时有些活动逻辑复杂,数据或者状态变更都需要手动渲染,容易出错,正是因为这些问题的存在,所以才有了MV*框架的诞生,比如大名鼎鼎的angularJS。今天就跟大家讲讲国产的MVVM框架avalonJS是如何快速进行开发的,同时大家也可以对比石器时代的开发模式(jquery或者zepto)与mv*模式的区别。</p><p>avalonJS简介</p><p> avalonJS是前端大牛司徒正美开发和维护的mvvm框架,它是一个基于Model驱动的开发框架,DOM操作近乎绝迹,可以让前端人员脱离DOM的苦海,来到数据的乐园,相比angularJS它有如下优势:<br />1.无任何依赖,压缩后只有50多kb,而angular的min版有100多kb;</p><p>2.爽快的编程体验,不再纠结于DOM操作;</p><p>3.兼容到IE6+,符合天朝国情;</p><p>4.效率更高,跑起来比angular和knockout都要更快,在移动端上该优势会更大(avalon有移动端专版的avalon.modern.js)。关于其性能更详细的介绍可以看<a href="http://www.html-js.com/article/Avalon-tutorial-22-the-performance-of-Avalon-revealed" target="_blank">这里</a>;</p><p>5.涵盖了angular的大部分功能,且实现方式更为便捷、上手更容易;</p><p>相关文档</p><p><a href="https://github.com/RubyLouvre/avalon" target="_blank">GitHub</a>(下载最新的avalon以及实例(examples文件夹里),通过实例来掌握某些功能的实现是很好的学习途径)</p><p><a href="http://limodou.github.io/avalon-learning/zh_CN/index.html" target="_blank">Avalon快速入门</a>(比较快捷的入门课程,只用了几篇文章来介绍了最常用的一些功能)</p><p><a href="http://www.cnblogs.com/rubylouvre/p/3181291.html" target="_blank">API文章</a>(正美的博文,篇幅较大,涵盖知识点很多,可以当作API来查阅),也可以在<a href="http://hotelued.qunar.com/oniui/index.html#pages/apis/index.html" target="_blank">这里</a>查看更规范的API。</p><p><a href="http://www.html-js.com/article/column/234?page=1" target="_blank">Avalon乱炖</a>(强烈推荐,用了20多篇文章较详细地、渐进地介绍avalon)</p><p><a href="http://edu.51cto.com/course/course_id-2533-page-1.html" target="_blank">Avalon入门视频</a>(推荐)</p><p>开始 </p><p>这里使用avalon的版本是移动端avalon.modern.shim.js-1.4.1版本,已经存在cdn上(http://imgcache.gtimg.cn/club/common/lib/avalon.js),需要使用直接引入即可,如下:</p><pre><!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>avalon初探</title> </head> <body> <div></div> <script src="http://imgcache.gtimg.cn/c/=/club/mobile_web/zepto.min.js,/club/common/lib/zero/zero.m.min-5.1.1.js,/club/common/lib/avalon.js?max_age=86400000"></script> </body> </html></pre><p>这里我们引入了zepto,zero以及avalon.js,因为zero依赖zepto,所以这几个文件必须要引入。</p><p>接着,类似于ng的“ng-controller”,avalon的控制域属性名叫做“ms-controller”,你可以把它当作一个监听器,把它绑定到一个容器后,avalon就能扫描和监听这个容器内所有(绑定了avalon方法或带有插值表达式的)元素了。</p><p>我们给这个div加上这个监听器,并在里面写一个avalon插值表达式{{a}};</p><pre><div ms-controller="wrap">{{a}}</div></pre><p>你现在运行的话页面没有任何效果,因为我们还没有写脚本让avalon工作起来,我们可以来一段简单代码让其运行起来:</p><pre><div ms-controller="wrap">{{a}}</div> </pre><p>在avalon中我们使用avalon.define('xx', function (vm) {})来定义一个Model实例,其中xx为所要扫描和监控的控制域名。</p><p>我们还在内部定义了一个属性“a”,故在对应的控制域(对应为ms-controller=“wrap”的div)里 ,我们使用avalon插值表达式{{a}}的话,可以自动绑定其值“你好啊”。</p><p>上述代码运行效果如下:</p><p><img src="http://img.li6.cc/content_img/1/article/115/532971-20151221165359890-474943794.png" alt="" /></p><p>数据和视图同步</p><p>上方我们实现了非常简单的数据绑定,将一个avalon属性a绑定到DOM元素上。不过,avalon更有意思和实用的功能是实现了视图和数据的同步,说的简单点,我们用脚本修改了a的值,那么DOM上绑定的数据也会跟着改变(反过来也一样)</p><pre><div ms-controller="wrap"> <span>{{a}}</span> <input ms-duplex="a" /> </div> <script src="http://imgcache.gtimg.cn/c/=/club/mobile_web/zepto.min.js,/club/common/lib/zero/zero.m.min-5.1.1.js,/club/common/lib/avalon.js?max_age=86400000"></script> <script> var model = avalon.define('wrap', function (vm) { vm.a = '你好啊'; }); avalon.scan(); </script> </body></pre><p>注意这里我们添加了一个,其中ms-duplex是avalon的双工绑定的属性,它除了负责将VM中对应的值(如本例是a)放到表单元素的value中,还偷偷对元素绑定一些事件,用于监听用户的输入从而自动刷新VM。</p><p>执行代码如下:</p><p><img src="http://img.li6.cc/content_img/1/article/115/532971-20151221171152640-806940450.gif" alt="" /></p><p>运行页面的开发</p><p>有了以上对avalon的基本了解,我们来看下在运行活动中如何使用avalon快速开发运行活动。</p><p>这里以<a href="http://youxi.vip.qq.com/m/act/201512/qgc_online/index.html?_wv=1" target="_blank">QGC情报站</a>为例,这个页面完全是一个静态页面,所有的数据都是在ams上手动配置的,先看下页面截图:</p><p><img src="http://img.li6.cc/content_img/1/article/115/532971-20151221172407624-1650408928.png" alt="" /></p><p>一个如此简单的页面,如果我们使用zepto来处理,可能会涉及到各种字符串拼接,然后通过innerHTML的方式插入到指定的div内,这些过程势必要选择多个dom,然后操作dom,如果还需要处理一些其他细节需求,比如根据ams有没有配置更多链接,来动态决定是否显示“更多数据”按钮,等等等等。我们发现使用zepto的方式来写代码很繁琐,更新页面状态时总是需要首先获取到dom,然后对dom进行其他操作,如果一个dom有多种状态,又在不同地方展示,那我们的代码量就会递增式增长,同时代码可读性差,不易维护。</p><p>而使用avalon来处理,我们处理的仅仅是数据,代码量不仅会大幅减少,而且代码结构会更清晰,也更易维护。</p><p>直接上代码:</p><p>html代码:</p><pre><div class="act-wrapper" ms-controller="main"> <div class="act-content"> <div class="act-header"> <h1 class="hide">全民竞技大赛</h1> <div class="nav"> <a href="javascript:" ms-class="cur:index==$index" ms-repeat="curGames" ms-click="switchTab($index, el.appid)">{{el.gameName}}</a> </div> </div> <div class="act-game"> <div class="act-main"> <div class="act-block act-news"> <div class="title"> <b class="t1"><strong class="hide">火线礼包</strong></b> <a href="javascript:" ms-click="openUrl(curOnlineLink.link)">赛事直播>></a> </div> <div class="cnts"> <div class="item" ms-repeat="curNews"> <a href="javascript:" ms-click="openUrl(el.link)" ms-if="$index==0"><img ms-attr-src="el.pic" width="281"/></a> <a href="javascript:" ms-click="openUrl(el.link)" ms-if="$index"> <div class="thumb"><img ms-attr-src="el.pic" width="80"/></div> <p>{{el.title}}</p> </a> </div> </div> </div> <div class="act-block act-ranking"> <div class="title"> <b class="t2"><strong class="hide">赛事数据</strong></b> <a href="javascript:" ms-click="openUrl(curDataMoreLink.link)" ms-if="curDataMoreLink.link!=''">更多数据>></a> </div> <div class="cnts"> <div class="item" ms-repeat="curData"> <span>{{el.name}}</span> <p>{{el.achievement}}</p> </div> </div> </div> <div class="act-block act-events"> <div class="title"> <b class="t3"><strong class="hide">赛事活动</strong></b> </div> <div class="cnts"> <div class="act-slider"> <div class="swiper-container gallery"> <div class="swiper-wrapper"> <div class="swiper-slide" ms-repeat="curAct"> <a href="javascript:" ms-click="openUrl(el.link)"> <img ms-attr-src="el.pic" width="255"/> </a> </div> </div> </div> <div class="swiper-pagination"></div> </div> </div> </div> <div class="act-block act-god"> <div class="title"> <b class="t4"><strong class="hide">大神助力</strong></b> <a href="javascript:" ms-click="openUrl(curGodLink.link)">大神名录>></a> </div> <div class="cnts"> <div class="list"> <dl> <dd ms-repeat="curQuot" ms-click="openUrl(el.link)"> <div class="avatar"><img ms-attr-src="el.pic" width="51"/></div> <div class="info"> <b>{{el.name}}</b> <p>{{el.content}}</p> </div> </dd> </dl> </div> </div> </div> <div class="act-video"> <div class="nav"> <a href="javascript:" ms-class="cur:videoIndex==0" ms-click="switchVideoTab(0, 1)">赛事视频</a> <a href="javascript:" ms-class="cur:videoIndex==1" ms-click="switchVideoTab(1, 2)">精彩集锦</a> <a href="javascript:" ms-class="cur:videoIndex==2" ms-click="switchVideoTab(2, 3)">大神视角</a> </div> <div class="cnts"> <div class="cnt"> <a href="javascript:" ms-repeat="curVideo" ms-click="openUrl(el.link)"> <img ms-attr-src="el.pic" width="123"/> <p>{{el.content}}</p> </a> </div> <a href="javascript:" class="act-more" ms-click="openUrl(videoMore)">更多视频>></a> </div> </div> </div> <div class="act-footer"> <div class="act-btn-group"> <a href="javascript:" ms-click="openUrl(btLink.more_game)">更多赛事</a> <a href="javascript:" ms-click="openUrl(btLink.gamecenter)">游戏中心</a> </div> <p class="game-info">手机QQ游戏中心出品</p> </div> </div> </div> <div class="act-bg"> <img src="http://imgcache.gtimg.cn/vipstyle/game/act/breezefeng/20151204_qgc/img/bg_01.jpg" width="320"/> <img src="http://imgcache.gtimg.cn/vipstyle/game/act/breezefeng/20151204_qgc/img/bg_02.jpg" width="320"/> <img src="http://imgcache.gtimg.cn/vipstyle/game/act/breezefeng/20151204_qgc/img/bg_03.jpg" width="320"/> </div> </div> <script src="http://imgcache.gtimg.cn/c/=/club/mobile_web/zepto.min.js,/club/common/lib/zero/zero.m.min-5.1.1.js,/club/common/lib/avalon.js?max_age=86400000"></script></pre><p> js代码:</p><pre>new qv.zero.Page({ jsonid: '76172', mqqEnv: true, game: 'cfm', onlyMobile: true, isOpenSQView: true, redirectUrl: "", preloads: ['mqqShare'], afterInit: function () { var me = this; qv.zero.Login.ensure(); qv.zero.mqqShare.initShare(); me.defineModel(); me.getData(); me.initData(); }, defineModel: function () { var me = this; main = avalon.define('main', function (vm) { //当前游戏appid vm.appid = 1104067326; //当前游戏tab索引 vm.index = 0; //切换游戏tab索引 vm.switchTab = function (index, appid) { main.index = index; main.appid = appid; me.changeData(appid); }; //当前视频tab索引 vm.videoIndex = 0; //当前视频类型 vm.videoType = 1; //切换视频tab索引 vm.switchVideoTab = function (videoIndex, videoType) { main.videoIndex = videoIndex; main.videoType = videoType; console.log(me.getVideoByType(main.$videos[main.appid] || [])); main.curVideo = me.getVideoByType(main.$videos[main.appid] || []); main.videoMore = main.curVideo[0] && main.curVideo[0].more_link || ''; }; //打开链接 vm.openUrl = function (url) { me.openUrl(url); }; //当前游戏 vm.curGames = []; //赛事直播连接 vm.$onlineLinks = {}; vm.curOnlineLink = {}; //赛事新闻 vm.$news = {}; vm.curNews = []; //赛事数据的更多数据 vm.$dataMoreLinks = {}; vm.curDataMoreLink = {}; //赛事数据 vm.$data = {}; vm.curData = []; //赛事活动 vm.$act = {}; vm.curAct = []; //大神名录链接 vm.$godLinks = {}; vm.curGodLink = {}; //大神语录 vm.$quot = {}; vm.curQuot = []; //赛事视频 vm.$videos = {}; vm.curVideo = []; //赛事视频更多链接 vm.videoMore = ''; //底部链接 vm.btLink = {}; }); }, changeData: function (appid) { var me = this; main.curOnlineLink = main.$onlineLinks[appid] && main.$onlineLinks[appid][0] || {}; main.curNews = main.$news[appid] || []; main.curDataMoreLink = main.$dataMoreLinks[appid] && main.$dataMoreLinks[appid][0] || {}; main.curData = main.$data[appid] || []; main.curAct = main.$act[appid] || []; main.curGodLink = main.$godLinks[appid] && main.$godLinks[appid][0] || {}; main.curQuot = main.$quot[appid] || []; main.curVideo = me.getVideoByType(main.$videos[appid] || []); main.videoMore = main.curVideo[0] && main.curVideo[0].more_link || ''; setTimeout(function () { mySwiper.update(); }, 30); }, getData: function () { var me = this; //赛事直播链接 main.$onlineLinks = me.getAmsData(2); //赛事新闻 main.$news = me.getAmsData(3); //赛事数据的更多数据 main.$dataMoreLinks = me.getAmsData(4); //赛事数据 main.$data = me.getAmsData(5); //赛事活动 main.$act = me.getAmsData(6); //大神名录链接 main.$godLinks = me.getAmsData(7); //大神语录 main.$quot = me.getAmsData(8); //赛事视频 main.$videos = me.getAmsData(9); }, initData: function () { var me = this; main.curOnlineLink = main.$onlineLinks[main.appid] && main.$onlineLinks[main.appid][0] || {}; main.curNews = main.$news[main.appid] || []; main.curDataMoreLink = main.$dataMoreLinks[main.appid] && main.$dataMoreLinks[main.appid][0] || {}; main.curData = main.$data[main.appid] || []; main.curAct = main.$act[main.appid] || []; main.curGodLink = main.$godLinks[main.appid] && main.$godLinks[main.appid][0] || {}; main.curQuot = main.$quot[main.appid] || []; main.btLink = zMsg.getFormData(10)[0]; main.curVideo = me.getVideoByType(main.$videos[main.appid] || []); main.videoMore = main.curVideo[0] && main.curVideo[0].more_link || ''; main.curGames = zMsg.getFormData(2); setTimeout(function () { mySwiper.update(); }, 30); $('#loading').css('display', 'none'); avalon.scan(); }, getVideoByType: function (videos) { var arr = []; for (var i = 0, len = videos.length; i< len; i++) { (videos[i].type == main.videoType) && (arr.push(videos[i])); } return arr; }, getAmsData: function (id) { return this.convert2ObjByAppId(zMsg.getFormData(id)); }, convert2ObjByAppId: function (arr) { var i, cur, len, obj = {}; for (i = 0, len = arr.length; i < len; i++) { cur = arr[i]; (obj[cur.appid] || (obj[cur.appid] = [])).push(cur); } return obj; }, setShareData: function () { var shareObj = zMsg.getFormData(11)[0]; return { title: shareObj.title,//分享标题 desc: shareObj.content, //分享内容 imageUrl: shareObj.pic, //分享图片 shareUrl: location.href, back: true//发送消息之后是否返回到web页面 } }, //手Q打开链接 openUrl: function (url, target) { var targetUrl = url; target = target || 1; if (typeof(mqq) != "undefined" && mqq.QQVersion != 0) { mqq.ui.openUrl({url: targetUrl, target: target, style: 0}); } else { window.location.href = targetUrl; } } });</pre><p>乍一看代码好多,感觉很复杂的样子,实际上这里因为模块比较多,所以代码量稍微有点大,大家看得时候按模块来看就很容易理解了。</p><p>我们看到使用avalon后代码结构非常清晰,html结构也很清晰,基本上看不到DOM操作。这里我们会看到很多以ms-开头的指令,比如ms-click是绑定click事件,ms-repeat是循环输出数组列表等,相关指令可以去上面推荐的文档内查阅,这里只是给大家如何使用avalon做一些抛砖引玉,avalon使用门槛很低,大家也可以多尝试下,有问题欢迎随时交流!</p><p>最后附上QGC情报站的页面交互效果图:</p><p><img src="http://img.li6.cc/content_img/1/article/115/532971-20151221184033296-656133632.gif" alt="" /></p><br />

正文结束

js 正则替换返回值做回调函数 vue-cli + webpack 多页面实例配置优化方法