背景

在运营活动开发中,因为工作的重复性很大,同时往往开发时间短,某些情况下也会非常紧急,导致了活动开发时间被大大压缩,同时有些活动逻辑复杂,数据或者状态变更都需要手动渲染,容易出错,正是因为这些问题的存在,所以才有了MV*框架的诞生,比如大名鼎鼎的angularJS。今天就跟大家讲讲国产的MVVM框架avalonJS是如何快速进行开发的,同时大家也可以对比石器时代的开发模式(jquery或者zepto)与mv*模式的区别。

avalonJS简介

avalonJS是前端大牛司徒正美开发和维护的mvvm框架,它是一个基于Model驱动的开发框架,DOM操作近乎绝迹,可以让前端人员脱离DOM的苦海,来到数据的乐园,相比angularJS它有如下优势:
1.无任何依赖,压缩后只有50多kb,而angular的min版有100多kb;

2.爽快的编程体验,不再纠结于DOM操作;

3.兼容到IE6+,符合天朝国情;

4.效率更高,跑起来比angular和knockout都要更快,在移动端上该优势会更大(avalon有移动端专版的avalon.modern.js)。关于其性能更详细的介绍可以看这里

5.涵盖了angular的大部分功能,且实现方式更为便捷、上手更容易;

相关文档

GitHub(下载最新的avalon以及实例(examples文件夹里),通过实例来掌握某些功能的实现是很好的学习途径)

Avalon快速入门(比较快捷的入门课程,只用了几篇文章来介绍了最常用的一些功能)

API文章(正美的博文,篇幅较大,涵盖知识点很多,可以当作API来查阅),也可以在这里查看更规范的API。

Avalon乱炖(强烈推荐,用了20多篇文章较详细地、渐进地介绍avalon)

Avalon入门视频(推荐)

开始 

这里使用avalon的版本是移动端avalon.modern.shim.js-1.4.1版本,已经存在cdn上(http://imgcache.gtimg.cn/club/common/lib/avalon.js),需要使用直接引入即可,如下:

  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>avalon初探</title>
  6. </head>
  7. <body>
  8. <div></div>
  9.  
  10. <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>
  11. </body>
  12. </html>

这里我们引入了zepto,zero以及avalon.js,因为zero依赖zepto,所以这几个文件必须要引入。

接着,类似于ng的“ng-controller”,avalon的控制域属性名叫做“ms-controller”,你可以把它当作一个监听器,把它绑定到一个容器后,avalon就能扫描和监听这个容器内所有(绑定了avalon方法或带有插值表达式的)元素了。

我们给这个div加上这个监听器,并在里面写一个avalon插值表达式{{a}};

  1. <div ms-controller="wrap">{{a}}</div>

你现在运行的话页面没有任何效果,因为我们还没有写脚本让avalon工作起来,我们可以来一段简单代码让其运行起来:

  1. <div ms-controller="wrap">{{a}}</div>
  2.  
  3. <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>
  4. <script>
  5. var model = avalon.define('wrap', function (vm) { //model是随便命名的,用作该Model的载体,wrap为avalon的作用域名称
  6. vm.a = '你好啊'; //a为avalon定义的一个属性,其值为“你好啊”
  7. });
  8. avalon.scan(); //开启avalon扫描,这句话必须要加
  9. </script>
  10. </body>

在avalon中我们使用avalon.define('xx', function (vm) {})来定义一个Model实例,其中xx为所要扫描和监控的控制域名。

我们还在内部定义了一个属性“a”,故在对应的控制域(对应为ms-controller=“wrap”的div)里 ,我们使用avalon插值表达式{{a}}的话,可以自动绑定其值“你好啊”。

上述代码运营效果如下:

数据和视图同步

上方我们实现了非常简单的数据绑定,将一个avalon属性a绑定到DOM元素上。不过,avalon更有意思和实用的功能是实现了视图和数据的同步,说的简单点,我们用脚本修改了a的值,那么DOM上绑定的数据也会跟着改变(反过来也一样)

  1. <div ms-controller="wrap">
  2. <span>{{a}}</span>
  3. <input ms-duplex="a" />
  4. </div>
  5.  
  6. <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>
  7. <script>
  8. var model = avalon.define('wrap', function (vm) {
  9. vm.a = '你好啊';
  10. });
  11. avalon.scan();
  12. </script>
  13. </body>

注意这里我们添加了一个<input ms-duplex="a" />,其中ms-duplex是avalon的双工绑定的属性,它除了负责将VM中对应的值(如本例是a)放到表单元素的value中,还偷偷对元素绑定一些事件,用于监听用户的输入从而自动刷新VM。

执行代码如下:

运营页面的开发

有了以上对avalon的基本了解,我们来看下在运营活动中如何使用avalon快速开发运营活动。

这里以QGC情报站为例,这个页面完全是一个静态页面,所有的数据都是在ams上手动配置的,先看下页面截图:

一个如此简单的页面,如果我们使用zepto来处理,可能会涉及到各种字符串拼接,然后通过innerHTML的方式插入到指定的div内,这些过程势必要选择多个dom,然后操作dom,如果还需要处理一些其他细节需求,比如根据ams有没有配置更多链接,来动态决定是否显示“更多数据”按钮,等等等等。我们发现使用zepto的方式来写代码很繁琐,更新页面状态时总是需要首先获取到dom,然后对dom进行其他操作,如果一个dom有多种状态,又在不同地方展示,那我们的代码量就会递增式增长,同时代码可读性差,不易维护。

而使用avalon来处理,我们处理的仅仅是数据,代码量不仅会大幅减少,而且代码结构会更清晰,也更易维护。

直接上代码:

html代码:

  1. <div class="act-wrapper" ms-controller="main">
  2. <div class="act-content">
  3. <div class="act-header">
  4. <h1 class="hide">全民竞技大赛</h1>
  5. <div class="nav">
  6. <a href="javascript:" ms-class="cur:index==$index" ms-repeat="curGames" ms-click="switchTab($index, el.appid)">{{el.gameName}}</a>
  7. </div>
  8. </div>
  9.  
  10. <div class="act-game">
  11. <div class="act-main">
  12. <div class="act-block act-news">
  13. <div class="title">
  14. <b class="t1"><strong class="hide">火线礼包</strong></b>
  15. <a href="javascript:" ms-click="openUrl(curOnlineLink.link)">赛事直播>></a>
  16. </div>
  17. <div class="cnts">
  18. <div class="item" ms-repeat="curNews">
  19. <a href="javascript:" ms-click="openUrl(el.link)" ms-if="$index==0"><img ms-attr-src="el.pic" width="281"/></a>
  20. <a href="javascript:" ms-click="openUrl(el.link)" ms-if="$index">
  21. <div class="thumb"><img ms-attr-src="el.pic" width="80"/></div>
  22. <p>{{el.title}}</p>
  23. </a>
  24. </div>
  25. </div>
  26. </div>
  27.  
  28. <div class="act-block act-ranking">
  29. <div class="title">
  30. <b class="t2"><strong class="hide">赛事数据</strong></b>
  31. <a href="javascript:" ms-click="openUrl(curDataMoreLink.link)" ms-if="curDataMoreLink.link!=''">更多数据>></a>
  32. </div>
  33. <div class="cnts">
  34. <div class="item" ms-repeat="curData">
  35. <span>{{el.name}}</span>
  36. <p>{{el.achievement}}</p>
  37. </div>
  38. </div>
  39. </div>
  40.  
  41. <div class="act-block act-events">
  42. <div class="title">
  43. <b class="t3"><strong class="hide">赛事活动</strong></b>
  44. </div>
  45. <div class="cnts">
  46. <div class="act-slider">
  47. <div class="swiper-container gallery">
  48. <div class="swiper-wrapper">
  49. <div class="swiper-slide" ms-repeat="curAct">
  50. <a href="javascript:" ms-click="openUrl(el.link)">
  51. <img ms-attr-src="el.pic" width="255"/>
  52. </a>
  53. </div>
  54. </div>
  55. </div>
  56. <div class="swiper-pagination"></div>
  57. </div>
  58. </div>
  59. </div>
  60.  
  61. <div class="act-block act-god">
  62. <div class="title">
  63. <b class="t4"><strong class="hide">大神助力</strong></b>
  64. <a href="javascript:" ms-click="openUrl(curGodLink.link)">大神名录>></a>
  65. </div>
  66. <div class="cnts">
  67. <div class="list">
  68. <dl>
  69. <dd ms-repeat="curQuot" ms-click="openUrl(el.link)">
  70. <div class="avatar"><img ms-attr-src="el.pic" width="51"/></div>
  71. <div class="info">
  72. <b>{{el.name}}</b>
  73. <p>{{el.content}}</p>
  74. </div>
  75. </dd>
  76. </dl>
  77. </div>
  78. </div>
  79. </div>
  80.  
  81. <div class="act-video">
  82. <div class="nav">
  83. <a href="javascript:" ms-class="cur:videoIndex==0" ms-click="switchVideoTab(0, 1)">赛事视频</a>
  84. <a href="javascript:" ms-class="cur:videoIndex==1" ms-click="switchVideoTab(1, 2)">精彩集锦</a>
  85. <a href="javascript:" ms-class="cur:videoIndex==2" ms-click="switchVideoTab(2, 3)">大神视角</a>
  86. </div>
  87. <div class="cnts">
  88. <div class="cnt">
  89. <a href="javascript:" ms-repeat="curVideo" ms-click="openUrl(el.link)">
  90. <img ms-attr-src="el.pic" width="123"/>
  91. <p>{{el.content}}</p>
  92. </a>
  93. </div>
  94. <a href="javascript:" class="act-more" ms-click="openUrl(videoMore)">更多视频>></a>
  95. </div>
  96. </div>
  97. </div>
  98. <div class="act-footer">
  99. <div class="act-btn-group">
  100. <a href="javascript:" ms-click="openUrl(btLink.more_game)">更多赛事</a>
  101. <a href="javascript:" ms-click="openUrl(btLink.gamecenter)">游戏中心</a>
  102. </div>
  103. <p class="game-info">手机QQ游戏中心出品</p>
  104. </div>
  105. </div>
  106. </div>
  107.  
  108. <div class="act-bg">
  109. <img src="http://imgcache.gtimg.cn/vipstyle/game/act/breezefeng/20151204_qgc/img/bg_01.jpg" width="320"/>
  110. <img src="http://imgcache.gtimg.cn/vipstyle/game/act/breezefeng/20151204_qgc/img/bg_02.jpg" width="320"/>
  111. <img src="http://imgcache.gtimg.cn/vipstyle/game/act/breezefeng/20151204_qgc/img/bg_03.jpg" width="320"/>
  112. </div>
  113. </div>
  114.  
  115. <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>

js代码:

  1. new qv.zero.Page({
  2. jsonid: '76172',
  3. mqqEnv: true,
  4. game: 'cfm',
  5. onlyMobile: true,
  6. isOpenSQView: true,
  7. redirectUrl: "",
  8. preloads: ['mqqShare'],
  9. afterInit: function () {
  10. var me = this;
  11.  
  12. qv.zero.Login.ensure();
  13.  
  14. qv.zero.mqqShare.initShare();
  15.  
  16. me.defineModel();
  17. me.getData();
  18. me.initData();
  19. },
  20. defineModel: function () {
  21. var me = this;
  22. main = avalon.define('main', function (vm) {
  23. //当前游戏appid
  24. vm.appid = 1104067326;
  25. //当前游戏tab索引
  26. vm.index = 0;
  27. //切换游戏tab索引
  28. vm.switchTab = function (index, appid) {
  29. main.index = index;
  30. main.appid = appid;
  31. me.changeData(appid);
  32. };
  33.  
  34. //当前视频tab索引
  35. vm.videoIndex = 0;
  36. //当前视频类型
  37. vm.videoType = 1;
  38. //切换视频tab索引
  39. vm.switchVideoTab = function (videoIndex, videoType) {
  40. main.videoIndex = videoIndex;
  41. main.videoType = videoType;
  42. console.log(me.getVideoByType(main.$videos[main.appid] || []));
  43. main.curVideo = me.getVideoByType(main.$videos[main.appid] || []);
  44. main.videoMore = main.curVideo[0] && main.curVideo[0].more_link || '';
  45. };
  46.  
  47. //打开链接
  48. vm.openUrl = function (url) {
  49. me.openUrl(url);
  50. };
  51.  
  52. //当前游戏
  53. vm.curGames = [];
  54.  
  55. //赛事直播连接
  56. vm.$onlineLinks = {};
  57. vm.curOnlineLink = {};
  58.  
  59. //赛事新闻
  60. vm.$news = {};
  61. vm.curNews = [];
  62.  
  63. //赛事数据的更多数据
  64. vm.$dataMoreLinks = {};
  65. vm.curDataMoreLink = {};
  66.  
  67. //赛事数据
  68. vm.$data = {};
  69. vm.curData = [];
  70.  
  71. //赛事活动
  72. vm.$act = {};
  73. vm.curAct = [];
  74.  
  75. //大神名录链接
  76. vm.$godLinks = {};
  77. vm.curGodLink = {};
  78.  
  79. //大神语录
  80. vm.$quot = {};
  81. vm.curQuot = [];
  82.  
  83. //赛事视频
  84. vm.$videos = {};
  85. vm.curVideo = [];
  86.  
  87. //赛事视频更多链接
  88. vm.videoMore = '';
  89.  
  90. //底部链接
  91. vm.btLink = {};
  92. });
  93. },
  94. changeData: function (appid) {
  95. var me = this;
  96. main.curOnlineLink = main.$onlineLinks[appid] && main.$onlineLinks[appid][0] || {};
  97. main.curNews = main.$news[appid] || [];
  98. main.curDataMoreLink = main.$dataMoreLinks[appid] && main.$dataMoreLinks[appid][0] || {};
  99. main.curData = main.$data[appid] || [];
  100. main.curAct = main.$act[appid] || [];
  101. main.curGodLink = main.$godLinks[appid] && main.$godLinks[appid][0] || {};
  102. main.curQuot = main.$quot[appid] || [];
  103. main.curVideo = me.getVideoByType(main.$videos[appid] || []);
  104. main.videoMore = main.curVideo[0] && main.curVideo[0].more_link || '';
  105.  
  106. setTimeout(function () {
  107. mySwiper.update();
  108. }, 30);
  109. },
  110. getData: function () {
  111. var me = this;
  112. //赛事直播链接
  113. main.$onlineLinks = me.getAmsData(2);
  114. //赛事新闻
  115. main.$news = me.getAmsData(3);
  116. //赛事数据的更多数据
  117. main.$dataMoreLinks = me.getAmsData(4);
  118. //赛事数据
  119. main.$data = me.getAmsData(5);
  120. //赛事活动
  121. main.$act = me.getAmsData(6);
  122. //大神名录链接
  123. main.$godLinks = me.getAmsData(7);
  124. //大神语录
  125. main.$quot = me.getAmsData(8);
  126. //赛事视频
  127. main.$videos = me.getAmsData(9);
  128. },
  129. initData: function () {
  130. var me = this;
  131. main.curOnlineLink = main.$onlineLinks[main.appid] && main.$onlineLinks[main.appid][0] || {};
  132. main.curNews = main.$news[main.appid] || [];
  133. main.curDataMoreLink = main.$dataMoreLinks[main.appid] && main.$dataMoreLinks[main.appid][0] || {};
  134. main.curData = main.$data[main.appid] || [];
  135. main.curAct = main.$act[main.appid] || [];
  136. main.curGodLink = main.$godLinks[main.appid] && main.$godLinks[main.appid][0] || {};
  137. main.curQuot = main.$quot[main.appid] || [];
  138. main.btLink = zMsg.getFormData(10)[0];
  139. main.curVideo = me.getVideoByType(main.$videos[main.appid] || []);
  140. main.videoMore = main.curVideo[0] && main.curVideo[0].more_link || '';
  141. main.curGames = zMsg.getFormData(2);
  142.  
  143. setTimeout(function () {
  144. mySwiper.update();
  145. }, 30);
  146. $('#loading').css('display', 'none');
  147. avalon.scan();
  148. },
  149. getVideoByType: function (videos) {
  150. var arr = [];
  151. for (var i = 0, len = videos.length; i< len; i++) {
  152. (videos[i].type == main.videoType) && (arr.push(videos[i]));
  153. }
  154. return arr;
  155. },
  156. getAmsData: function (id) {
  157. return this.convert2ObjByAppId(zMsg.getFormData(id));
  158. },
  159. convert2ObjByAppId: function (arr) {
  160. var i, cur, len, obj = {};
  161. for (i = 0, len = arr.length; i < len; i++) {
  162. cur = arr[i];
  163. (obj[cur.appid] || (obj[cur.appid] = [])).push(cur);
  164. }
  165. return obj;
  166. },
  167. setShareData: function () {
  168. var shareObj = zMsg.getFormData(11)[0];
  169. return {
  170. title: shareObj.title,//分享标题
  171. desc: shareObj.content, //分享内容
  172. imageUrl: shareObj.pic, //分享图片
  173. shareUrl: location.href,
  174. back: true//发送消息之后是否返回到web页面
  175. }
  176. },
  177. //手Q打开链接
  178. openUrl: function (url, target) {
  179. var targetUrl = url;
  180. target = target || 1;
  181. if (typeof(mqq) != "undefined" && mqq.QQVersion != 0) {
  182. mqq.ui.openUrl({url: targetUrl, target: target, style: 0});
  183. } else {
  184. window.location.href = targetUrl;
  185. }
  186. }
  187. });

乍一看代码好多,感觉很复杂的样子,实际上这里因为模块比较多,所以代码量稍微有点大,大家看得时候按模块来看就很容易理解了。

我们看到使用avalon后代码结构非常清晰,html结构也很清晰,基本上看不到DOM操作。这里我们会看到很多以ms-开头的指令,比如ms-click是绑定click事件,ms-repeat是循环输出数组列表等,相关指令可以去上面推荐的文档内查阅,这里只是给大家如何使用avalon做一些抛砖引玉,avalon使用门槛很低,大家也可以多尝试下,有问题欢迎随时交流!

最后附上QGC情报站的页面交互效果图:

使用MVVM框架(avalonJS)进行快速开发的更多相关文章

  1. 迷你MVVM框架 avalonjs 0.95发布

    迷你MVVM框架 avalonjs 0.95发布 本版本最主要的改进是ms-with 深层绑定的实现,至少,avalon1.0所有重要的feature已经开发完毕,之后就是小补小漏,性能优化了. ms ...

  2. 迷你MVVM框架 avalonjs 0.85发布

    迷你MVVM框架 avalonjs 0.85发布 本版本对循环绑定做了巨大改进,感谢@soom, @limodou, @ztz, @Gaubee 提供的大量测试文件. fix scanNodes, 在 ...

  3. 迷你MVVM框架 avalonjs 0.82发布

    迷你MVVM框架 avalonjs 0.82发布 本版本最大的改进是启用全新的parser. parser是用于干什么的?在视图中,我们通过绑定属性实现双向绑定,比如ms-text="fir ...

  4. 迷你MVVM框架 avalonjs 入门教程

    新官网 请不要无视这里,这里都是链接,可以点的 OniUI组件库 学习教程 视频教程: 地址1 地址2 关于AvalonJs 开始的例子 扫描 视图模型 数据模型 绑定 作用域绑定(ms-contro ...

  5. 迷你MVVM框架 avalonjs 1.3.9发布

    本次升级,avalon改进了许多内部方法,大大提升性能,并且带来异步刷新视图的新功能. ms-html内部不再使用异步 head元素中的avalon元素加入ms-skip指令 重构计算属性,现在超级轻 ...

  6. 迷你MVVM框架 avalonjs 1.3.7发布

    又到每个月的15号了,现在avalon已经固定在每个月的15号发布新版本.这次发布又带来许多新特性,让大家写码更加轻松,借助于"操作数据即操作DOM"的核心理念与双向绑定机制,现在 ...

  7. 迷你MVVM框架 avalonjs 1.2发布

    avalon1.2 带来了许多新特性,让开发更轻松!详见如下: 升级路由系统与分页组件. 对ms-duplex的绑定值进行增强,以前只能prop或prop.prop2,现在可以prop["x ...

  8. 迷你MVVM框架 avalonjs 1.3.8发布

    avalon1.3.8主要是在ms-repeat. ms-each. ms-with等循环绑定上做重大性能优化,其次是对一些绑定了事件的指令添加了roolback,让其CG回收更顺畅. 重构ms-re ...

  9. 迷你MVVM框架 avalonjs 学习教程19、avalon历史回顾

    avalon最早发布于2012.09.15,当时还只是mass Framework的一个模块,当时为了解决视图与JS代码的分耦,参考knockout开发出来. 它的依赖收集机制,视图扫描,绑定的命名d ...

  10. 迷你MVVM框架 avalonjs 1.3.6发布

    本版本是一次重要的升级,考虑要介绍许多东西,也有许多东西对大家有用,也发到首页上来了. 本来是没有1.36的,先把基于静态收集依赖的1.4设计出来后,发现改动太多,为了平缓升级起见,才减少了一部分新特 ...

随机推荐

  1. css3的@media媒体查询

    css3新功能,根据屏幕大小进行识别. 参考: http://www.runoob.com/cssref/css3-pr-mediaquery.html

  2. 洛谷P2242 公路维修问题(Road)

    题目描述 在一个夜黑风高,下着暴风雨的夜晚,farmer John的牛棚的屋顶.门被吹飞了. 好在许多牛正在度假,所以牛棚没有住满. 牛棚一个紧挨着另一个被排成一行,牛就住在里面过夜. 有些牛棚里有牛 ...

  3. Windows下安装paramiko

    windows下python IDE我用的是pycharm 在pycharm下安装paramiko import paramiko后提示没有pycrypto这个模块 在pycharm下又安装不上pyc ...

  4. BackGroundWorker控件的使用注意

    该控件有三个事件: DoWork .ProgressChanged 和 RunWorkerCompleted 在程序中调用RunWorkerAsync方法则会启动DoWork事件的事件处理,当在事件处 ...

  5. [IOS 实现TabBar在Push后的隐藏 以及 两级Tabbar的切换]

    翻了好多网页都没找到资料,自己试了下终于成功了,遂分享一下. 1.实现TabBar在Push后的隐藏 假如结构是这样 NavController->A->B,我们想要实现在A里有Tabba ...

  6. scala中集合的交集、并集、差集

    scala中有一些api设计的很人性化,集合的这几个操作是个代表: 交集: scala> Set(1,2,3) & Set(2,4) // &方法等同于interset方法 sc ...

  7. 第二次作业———“A+B Format”思路与总结

    GitHub链接: https://github.com/zzy19961112/object-oriented "A+B Format" 题目 解题思路: 一开始粗略看这道题,熟 ...

  8. How to overcome “datetime.datetime not JSON serializable” in python?

    json.dumps(datetime.now) 意思是datetime.now不可json序列化,解决办法是转化成str或者加一个参数 cls=xxx 详细见: http://stackoverfl ...

  9. Redis 学习笔记

    1 Redis优势 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s . 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes ...

  10. MyBatis详解 与配置MyBatis+Spring+MySql

    MyBatis 是一个可以自定义SQL.存储过程和高级映射的持久层框架.MyBatis 摒除了大部分的JDBC代码.手工设置参数和结果集重获.MyBatis 只使用简单的XML 和注解来配置和映射基本 ...