JavaScript中的View-Model使用介绍


Posted in Javascript onAugust 11, 2011

构成
这是一个十分常见的微博列表页面,类似于新浪微博。上周末,在心无旁骛情况下,一共用了5个对象,产出400行代码,实践出一种代码组织模式。

使任务轻松完成的代码有4个方面的要素组成:

要素 组成
模型 Reply、Forward
视图 CommentEditor、ReplyList、ForwardList
模板 jQuery.tmpl
异步任务 jQuery.Deferred
分部介绍
模型
模型只与数据有关,它能够产生、过滤、保存、验证数据,并且仅此而已。

如下例,留言模型在调用保存方法时,只接收JSON参数,并且只返回一个异步任务,实际处理时同步或异步的返回结果并不重要。
在此进行的验证的原因是,它是一个开放的对象,是与服务器交互的最后一道门槛。
另外,它本身也不处理验证失败的情况——由视图调用时选择性地处理,可能会弹出一个消息提示或直接忽略再进行重试。

// 留言模型 
var Reply = { 
cache : {}, 
// { sourceid : id,page_size : 10,page_num : 1 } 
fetch : function(data) { 
return $.post('/ajax/blog/reply/list',data||{}).success(function(resp) { 
resp.ok && resp.list && 
$.each(resp.list,function(k,v) { 
return Reply.cache[v.id] = v; 
}); 
}); 
}, 
// filter('name','king') 
filter : function(prop,val) { 
return $.grep(this.cache,function(r){ return r[prop] === val }); 
}, 
// { content : '想说就说',sourceid : 1001 } 
create : function(data) { 
// promise 
var dfd = $.Deferred(), now = $.now(); 
if( (now - this.create.timestamp)/1000 < 10 ){ 
dfd.reject({message:'您发表得太快了,休息一下吧',type:'warn'}) 
}else if(!data || !data.sourceid){ 
dfd.reject({message:'非法操作',type:'error'}) 
}else if(!data.content){ 
dfd.reject({message:'评论内容不能为空',type:'warn'}) 
}else{ 
this.create.timestamp = now; 
dfd = $.post('/ajax/blog/reply/create',data); 
} 
return dfd.promise(); 
} 
}; 
Reply.create.timestamp = Forward.create.timestamp = $.now() - 1e4;

视图
视图是浏览器页面上的可视部分,每个视图对象含有一个关联的 jQuery 对象作为属性(instance.$el),类似于UI组件中的DOM容器。

视图还有两个一致的方法:

render 方法用于从模型获取数据,并且根据定义好的模板将数据渲染到HTML页面上。
activate 方法用于激活视图,同时绑定相关的DOM事件,所有事件至多委托到$el为止。
这个示例中,CommentEditor是父视图,ReplyList和ForwardList是互斥显示的两个子视图,父子视图之间相互保存引用。

// 回复列表视图 
var ReplyList = function(options) { 
var opt = this.opt = $.extend({ 
el : '', 
parent : null 
},options||{}); this.parent = opt.parent; 
this.$el = $(opt.el); 
this.activate(); 
}; 
ReplyList.prototype = { 
render : function() { 
var self = this; 
Reply.fetch({ 
page_size : 10, page_num : 1, 
sourceid : self.parent.getBlogId() 
}) 
.done(function(data) { 
self.$el.html( self.$list = $.tmpl(tpl_reply_list,data) ); 
}); 
return self; 
}, 
activate : function() { 
this.$el.delegate('a.del',$.proxy(this.del,this)) 
} 
// ... 
} 
// 评论编辑器视图 
CommentEditor.prototype = { 
activate : function() { 
this.$el.delegate('a.save',$.proxy(this.save,this)) 
}, 
save : function() { 
var self = this, data = { content : self.getContent(),sourceid : self.getBlogId() }; 
var task_r = Reply.create(data); 
var task_f = Forward.create(data); 
// 转发、评论同时进行 
$.when(task_r,task_f).then(function(t1,t2) { 
// 保存成功,更新视图或关闭 
},function(data) { 
// 模型验证出错,或远程服务器错误 
Sys.info(data.message,data.type); 
}); 
return self; 
}, 
switchView : function(type) { 
// 切换子视图 
var view_opt = {el:this.$sublist.empty(),parent:this}; 
if(type === 'reply'){ 
$label.show(); 
this.$submit.val('评论'); 
this.sublist = new ReplyList(view_opt).render(); 
}else{ 
$label.hide(); 
this.$submit.val('转发'); 
this.sublist = new ForwardList(view_opt).render(); 
} 
} 
// ... 
}

模板
模板可以消除繁琐、丑陋的字符串拼接,它的作用是能够直接由js对象生成HTML片断。

模板中可以直接遍历对象,套用预定义的函数,来对一些数据进行格式化,比如时间函数nicetime:

// 回复列表模板 
var tpl_reply_list = '<ul class="ui-reply-list">\ 
{{each list}}\ 
<li data-id="${id}">\ 
<a class="name" href="/${userid}">${name}:</a>\ 
<p>${content}</p>\ 
<time pubdate>${nicetime(timestamp)}</time><a class="del" href="javascript:;">删除</a>\ 
</li>\ 
{{/each}}\ 
</ul>';

异步任务
Deferred Object 的直译是延迟对象,但是理解成异步任务更为恰当。异步任务能够消除多层嵌套的回调,让代码书写和阅读更为便利。

从上面的模型和视图的代码中可以明显地看出,使用了异步任务之后,代码变得更加平面化了。

$.Deferred 方法新建的是一个双向任务队列:成功回调函数队列和失败回调函数队列;任务的状态也分为两种:成功和失败,分别可以用isResolved或isRejected来检查任务的当前状态、用resolve或reject修改任务状态。

promise 方法返回任务的只读副本,此副本上不能修改任务状态。毫无疑问,模型应该始终只返回 promise 对象。(注:只读副本仍然可以再次调用 promise 方法再次返回只读副本)

在Reply.create方法中,能够更好地处理自定义的异步任务,而不是直接返回原生的ajax异步任务:

// var dfd = $.Deferred(); 
$.post('/ajax/blog/reply/create',data) 
.success(function(json) { 
if(json && json.ok){ 
dfd.resolve(json.list); 
}else{ 
dfd.reject({message:json.message||'获取失败',type:'error'}); 
} 
}) 
.fail(function() { 
dfd.reject({message:'服务暂时不可用',type:'error'}) 
});

目的及结论
为什么拆散成这样?

收获:可维护性,清晰的API调用、消除二层以上的if语句、消除二层以上的回调语句、每个函数控制在二十行之内。

结果:没有过多的重复代码,所有的功能都被打包好了。

Javascript 相关文章推荐
jQuery实现可用于博客的动态滑动菜单
Mar 09 Javascript
JS正则表达式比较常见用法
Jan 26 Javascript
JavaScript+html5 canvas绘制缤纷多彩的三角形效果完整实例
Jan 26 Javascript
JS中如何实现复选框全选功能
Dec 19 Javascript
javascript实现Java中的Map对象功能的实例详解
Aug 21 Javascript
关于vue-router的那些事儿
May 23 Javascript
JS document文档的简单操作完整示例
Jan 13 Javascript
Vue实现 点击显示再点击隐藏效果(点击页面空白区域也隐藏效果)
Jan 16 Javascript
解决echarts vue数据更新,视图不更新问题(echarts嵌在vue弹框中)
Jul 20 Javascript
js实现右键弹出自定义菜单
Sep 08 Javascript
解决vue与node模版引擎的渲染标记{{}}(双花括号)冲突问题
Sep 11 Javascript
如何优化vue打包文件过大
Apr 13 Vue.js
仿jQuery的siblings效果的js代码
Aug 09 #Javascript
html中table数据排序的js代码
Aug 09 #Javascript
最常用的12种设计模式小结
Aug 09 #Javascript
silverlight线程与基于事件驱动javascript引擎(实现轨迹回放功能)
Aug 09 #Javascript
javascript 主动派发事件总结
Aug 09 #Javascript
JsDom 编程小结
Aug 09 #Javascript
IE 当eval遇上function的处理
Aug 09 #Javascript
You might like
php的zip解压缩类pclzip使用示例
2014/03/14 PHP
利用JS实现浏览器的title闪烁
2013/07/08 Javascript
javascript判断非数字的简单例子
2013/07/18 Javascript
JavaScript实现的日期控件具体代码
2013/11/18 Javascript
javascript中AJAX用法实例分析
2015/01/30 Javascript
javascript计时器详解
2015/02/28 Javascript
关于JavaScript 原型链的一点个人理解
2016/07/31 Javascript
常用js,css文件统一加载方法(推荐) 并在加载之后调用回调函数
2016/09/23 Javascript
浅谈MVC+EF easyui dataGrid 动态加载分页表格
2016/11/10 Javascript
angularJS+requireJS实现controller及directive的按需加载示例
2017/02/20 Javascript
基于jQuery实现一个marquee无缝滚动的插件
2017/03/09 Javascript
详解Vue 2.0封装axios笔记
2017/06/22 Javascript
vue表单绑定实现多选框和下拉列表的实例
2017/08/12 Javascript
BootStrap自定义popover,点击区域隐藏功能的实现
2018/01/23 Javascript
express如何使用session与cookie的方法
2018/01/30 Javascript
微信小程序学习笔记之获取位置信息操作图文详解
2019/03/29 Javascript
详解vue-cli+es6引入es5写的js(两种方法)
2019/04/19 Javascript
JavaScript+HTML5 canvas实现放大镜效果完整示例
2019/05/15 Javascript
微信小程序代码上传、审核发布小程序
2019/05/18 Javascript
Vue 实现简易多行滚动&quot;弹幕&quot;效果
2020/01/02 Javascript
Vue中添加滚动事件设置的方法详解
2020/09/14 Javascript
vue pages 多入口项目 + chainWebpack 全局引用缩写说明
2020/09/21 Javascript
[03:57]2016完美“圣”典风云人物:rOtk专访
2016/12/09 DOTA
Python环境下搭建属于自己的pip源的教程
2016/05/05 Python
Python利用公共键如何对字典列表进行排序详解
2018/05/19 Python
用openCV和Python 实现图片对比,并标识出不同点的方式
2019/12/19 Python
python网络编程:socketserver的基本使用方法实例分析
2020/04/09 Python
Python CategoricalDtype自定义排序实现原理解析
2020/09/11 Python
matplotlib 使用 plt.savefig() 输出图片去除旁边的空白区域
2021/01/05 Python
Python 生成短8位唯一id实战教程
2021/01/13 Python
2014年保密工作总结
2014/11/22 职场文书
2017大学生寒假社会实践心得体会
2016/01/14 职场文书
Pytorch distributed 多卡并行载入模型操作
2021/06/05 Python
口袋妖怪冰系十大最强精灵,几何雪花排第七,第六类似北极熊
2022/03/18 日漫
Minikube搭建Kubernetes集群
2022/03/31 Servers
Python之Matplotlib绘制热力图和面积图
2022/04/13 Python