背景

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

    净心守志:可会至道.譬如磨镜:垢去明存.断欲无求:当得宿命

  2. oracle的resetlogs机制浅析(转)

    文章转自:http://blog.csdn.net/wyzxg/article/details/5869543 alter database open resetlogs 这个命令我想大家都很熟悉了, ...

  3. POJ 3662 Telephone Lines(二分+最短路)

    查看题目 最小化第K大值. 让我怀疑人生的一题目,我有这么笨? #include <cstdio> #include <queue> #include <cstring& ...

  4. Algorithmic Graph Drawing in TikZ

    最近在写模板时需要画个图 ("图论"的"图"). 本来打算用Windows画图 (mspaint) 的, 但是效果不好, 还是决定用LaTeX的TikZ画. 这 ...

  5. Appium for IOS testing on Mac

    一:环境 1.Mac OS X 10.9.1 2.Xcod 5.0.2 3.Appium 1.3.6 下载地址:https://bitbucket.org/appium/appium.app/down ...

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

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

  7. Servlet3.0中Servlet的使用

    目录 1.注解配置 2.异步调用 3.文件上传 相对于之前的版本,Servlet3.0中的Servlet有以下改进: l  支持注解配置. l  支持异步调用. l  直接有对文件上传的支持. 在这篇 ...

  8. mysql中max_allowed_packet参数的配置方法(避免大数据写入或者更新失败)

    修改方法 1.修改配置文件 可以编辑my.cnf来修改(windows下my.ini),在[mysqld]段或者mysql的server配置段进行修改. 代码如下: max_allowed_packe ...

  9. oracle--知识点汇总1

    同义词: -- e是scott.emp表的临时别名 select e.* from (select * from scott.emp) e; --创建私有同义词 create synonym myem ...

  10. const 和 readonly 修饰符的用法

    1. 只有C#内置类型(int,double,long等)可以声明为const;结果.类和数组不能声明为const. 2. readonly 是在字段上使用的修饰符,直接以类名.字段访问. 3. co ...