使用MVVM框架(avalonJS)进行快速开发
背景
在运营活动开发中,因为工作的重复性很大,同时往往开发时间短,某些情况下也会非常紧急,导致了活动开发时间被大大压缩,同时有些活动逻辑复杂,数据或者状态变更都需要手动渲染,容易出错,正是因为这些问题的存在,所以才有了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),需要使用直接引入即可,如下:
- <!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>
这里我们引入了zepto,zero以及avalon.js,因为zero依赖zepto,所以这几个文件必须要引入。
接着,类似于ng的“ng-controller”,avalon的控制域属性名叫做“ms-controller”,你可以把它当作一个监听器,把它绑定到一个容器后,avalon就能扫描和监听这个容器内所有(绑定了avalon方法或带有插值表达式的)元素了。
我们给这个div加上这个监听器,并在里面写一个avalon插值表达式{{a}};
- <div ms-controller="wrap">{{a}}</div>
你现在运行的话页面没有任何效果,因为我们还没有写脚本让avalon工作起来,我们可以来一段简单代码让其运行起来:
- <div ms-controller="wrap">{{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) { //model是随便命名的,用作该Model的载体,wrap为avalon的作用域名称
- vm.a = '你好啊'; //a为avalon定义的一个属性,其值为“你好啊”
- });
- avalon.scan(); //开启avalon扫描,这句话必须要加
- </script>
- </body>
在avalon中我们使用avalon.define('xx', function (vm) {})来定义一个Model实例,其中xx为所要扫描和监控的控制域名。
我们还在内部定义了一个属性“a”,故在对应的控制域(对应为ms-controller=“wrap”的div)里 ,我们使用avalon插值表达式{{a}}的话,可以自动绑定其值“你好啊”。
上述代码运营效果如下:
数据和视图同步
上方我们实现了非常简单的数据绑定,将一个avalon属性a绑定到DOM元素上。不过,avalon更有意思和实用的功能是实现了视图和数据的同步,说的简单点,我们用脚本修改了a的值,那么DOM上绑定的数据也会跟着改变(反过来也一样)
- <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>
注意这里我们添加了一个<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代码:
- <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>
js代码:
- 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;
- }
- }
- });
乍一看代码好多,感觉很复杂的样子,实际上这里因为模块比较多,所以代码量稍微有点大,大家看得时候按模块来看就很容易理解了。
我们看到使用avalon后代码结构非常清晰,html结构也很清晰,基本上看不到DOM操作。这里我们会看到很多以ms-开头的指令,比如ms-click是绑定click事件,ms-repeat是循环输出数组列表等,相关指令可以去上面推荐的文档内查阅,这里只是给大家如何使用avalon做一些抛砖引玉,avalon使用门槛很低,大家也可以多尝试下,有问题欢迎随时交流!
最后附上QGC情报站的页面交互效果图:
使用MVVM框架(avalonJS)进行快速开发的更多相关文章
- 迷你MVVM框架 avalonjs 0.95发布
迷你MVVM框架 avalonjs 0.95发布 本版本最主要的改进是ms-with 深层绑定的实现,至少,avalon1.0所有重要的feature已经开发完毕,之后就是小补小漏,性能优化了. ms ...
- 迷你MVVM框架 avalonjs 0.85发布
迷你MVVM框架 avalonjs 0.85发布 本版本对循环绑定做了巨大改进,感谢@soom, @limodou, @ztz, @Gaubee 提供的大量测试文件. fix scanNodes, 在 ...
- 迷你MVVM框架 avalonjs 0.82发布
迷你MVVM框架 avalonjs 0.82发布 本版本最大的改进是启用全新的parser. parser是用于干什么的?在视图中,我们通过绑定属性实现双向绑定,比如ms-text="fir ...
- 迷你MVVM框架 avalonjs 入门教程
新官网 请不要无视这里,这里都是链接,可以点的 OniUI组件库 学习教程 视频教程: 地址1 地址2 关于AvalonJs 开始的例子 扫描 视图模型 数据模型 绑定 作用域绑定(ms-contro ...
- 迷你MVVM框架 avalonjs 1.3.9发布
本次升级,avalon改进了许多内部方法,大大提升性能,并且带来异步刷新视图的新功能. ms-html内部不再使用异步 head元素中的avalon元素加入ms-skip指令 重构计算属性,现在超级轻 ...
- 迷你MVVM框架 avalonjs 1.3.7发布
又到每个月的15号了,现在avalon已经固定在每个月的15号发布新版本.这次发布又带来许多新特性,让大家写码更加轻松,借助于"操作数据即操作DOM"的核心理念与双向绑定机制,现在 ...
- 迷你MVVM框架 avalonjs 1.2发布
avalon1.2 带来了许多新特性,让开发更轻松!详见如下: 升级路由系统与分页组件. 对ms-duplex的绑定值进行增强,以前只能prop或prop.prop2,现在可以prop["x ...
- 迷你MVVM框架 avalonjs 1.3.8发布
avalon1.3.8主要是在ms-repeat. ms-each. ms-with等循环绑定上做重大性能优化,其次是对一些绑定了事件的指令添加了roolback,让其CG回收更顺畅. 重构ms-re ...
- 迷你MVVM框架 avalonjs 学习教程19、avalon历史回顾
avalon最早发布于2012.09.15,当时还只是mass Framework的一个模块,当时为了解决视图与JS代码的分耦,参考knockout开发出来. 它的依赖收集机制,视图扫描,绑定的命名d ...
- 迷你MVVM框架 avalonjs 1.3.6发布
本版本是一次重要的升级,考虑要介绍许多东西,也有许多东西对大家有用,也发到首页上来了. 本来是没有1.36的,先把基于静态收集依赖的1.4设计出来后,发现改动太多,为了平缓升级起见,才减少了一部分新特 ...
随机推荐
- css3的@media媒体查询
css3新功能,根据屏幕大小进行识别. 参考: http://www.runoob.com/cssref/css3-pr-mediaquery.html
- 洛谷P2242 公路维修问题(Road)
题目描述 在一个夜黑风高,下着暴风雨的夜晚,farmer John的牛棚的屋顶.门被吹飞了. 好在许多牛正在度假,所以牛棚没有住满. 牛棚一个紧挨着另一个被排成一行,牛就住在里面过夜. 有些牛棚里有牛 ...
- Windows下安装paramiko
windows下python IDE我用的是pycharm 在pycharm下安装paramiko import paramiko后提示没有pycrypto这个模块 在pycharm下又安装不上pyc ...
- BackGroundWorker控件的使用注意
该控件有三个事件: DoWork .ProgressChanged 和 RunWorkerCompleted 在程序中调用RunWorkerAsync方法则会启动DoWork事件的事件处理,当在事件处 ...
- [IOS 实现TabBar在Push后的隐藏 以及 两级Tabbar的切换]
翻了好多网页都没找到资料,自己试了下终于成功了,遂分享一下. 1.实现TabBar在Push后的隐藏 假如结构是这样 NavController->A->B,我们想要实现在A里有Tabba ...
- scala中集合的交集、并集、差集
scala中有一些api设计的很人性化,集合的这几个操作是个代表: 交集: scala> Set(1,2,3) & Set(2,4) // &方法等同于interset方法 sc ...
- 第二次作业———“A+B Format”思路与总结
GitHub链接: https://github.com/zzy19961112/object-oriented "A+B Format" 题目 解题思路: 一开始粗略看这道题,熟 ...
- How to overcome “datetime.datetime not JSON serializable” in python?
json.dumps(datetime.now) 意思是datetime.now不可json序列化,解决办法是转化成str或者加一个参数 cls=xxx 详细见: http://stackoverfl ...
- Redis 学习笔记
1 Redis优势 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s . 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes ...
- MyBatis详解 与配置MyBatis+Spring+MySql
MyBatis 是一个可以自定义SQL.存储过程和高级映射的持久层框架.MyBatis 摒除了大部分的JDBC代码.手工设置参数和结果集重获.MyBatis 只使用简单的XML 和注解来配置和映射基本 ...