背景

在运营活动开发中,因为工作的重复性很大,同时往往开发时间短,某些情况下也会非常紧急,导致了活动开发时间被大大压缩,同时有些活动逻辑复杂,数据或者状态变更都需要手动渲染,容易出错,正是因为这些问题的存在,所以才有了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)进行快速开发的更多相关文章

  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. MVC3的安装方法(含安装包)

    安装方式: 1.直接官方下载安装包安装.链接:http://pan.baidu.com/s/1nvLfG8p 密码:534g 2.整个项目通过nuget进行安装.(推荐) 总结: MVC3有个尴尬的问 ...

  2. Tomcat访问日志详细配置

    在server.xml里的<host>标签下加上 <Valve className="org.apache.catalina.valves.AccessLogValve&q ...

  3. Zabbix low-level discovery

    Version: zabbix 3.0.1 概述 Low-Level discovery 可以自动创建items,triggers,graphs为不同的实体对象. 例如:zabbix能自动监控服务器上 ...

  4. 【Alpha】团队贡献分配计划

    在仔细看过邹老师的博客和一些主流公司的绩效管理考核方面的内容后,本来我们小组在讨论后决定简化Google的OKR制度,加入一些自己的元素作为我们团队的主要贡献评定制度. OKR就是“目标和关键成果”( ...

  5. 关于markdown的学习

    标题 标题(一个等号) 小标题 深层标题 深层标题2 深层标题6 深层标题 左边一个空格 深层标题 左边两个空格 此处省略很多字 左边有回车 一个空格 两个空格 三个空格 十个空格 字体属性:斜体.加 ...

  6. Beta版本——第七次冲刺博客

    我说的都队 031402304 陈燊 031402342 许玲玲 031402337 胡心颖 03140241 王婷婷 031402203 陈齐民 031402209 黄伟炜 031402233 郑扬 ...

  7. Beta版本——第二次冲刺博客

    我说的都队 031402304 陈燊 031402342 许玲玲 031402337 胡心颖 03140241 王婷婷 031402203 陈齐民 031402209 黄伟炜 031402233 郑扬 ...

  8. Jquery 基本知识(一)

    1. DOM对象:通过例如getElementById方法获取到DOM树中的元素就是DOM对象 jQuery对象:通过jQuery包装DOM对象后产生的对象 --- 注意:jQuery对象和DOM对象 ...

  9. php 如何造一个简短原始的数据库类用来增加工作效率

    class DBDA{ public $host="localhost"; public $uid="root"; public $pwd="123& ...

  10. Myeclipse如何设置字体大小

    由于Myeclipse一般是英文版的,这就给英语不太好的人带来了一定的麻烦,有时连设置个字体都无法顺利进行!!! 工具/原料   Myeclipse 方法/步骤   双击启动Myeclipse 点击& ...