【原创】backbone1.1.0源码解析之View
作为MVC框架,M(odel) V(iew) C(ontroler)之间的联系是必不可少的,今天要说的就是View(视图)
通常我们在写逻辑代码也好或者是在ui组件也好,都需要跟dom打交道,我们好讨厌在逻辑代码里面参杂dom的代码,特别是需要生产dom的代码,
因为这样的缺点:
1. 耦合,难于维护(虽然模版引擎能解决一些问题,但是事件的添加呢?)
2. 代码无法做到美观,节俭,感觉和dom参杂在一起就是一个字,乱!!
介于这样的缺点,Backbone提供了一个View类,用于构造对象,它可以做到一下几点:
1. 帮你创建dom,你只要负责去渲染这个dom就行了(我们用模板引擎就行dom的渲染就很合理)
2. 帮你为dom添加(代理)事件,所以无需自己去手动绑定事件啦,而只需要提供events的参数。
这样下来,上面的两个缺点基本都解决了~~
View(视图)
视图对象用来封装前端的dom元素,也就是说跟dom元素关联在一起,视图的改变即是对应dom元素的改变。
因此我们在创建视图类或者在构造视图对象的时候,我们需要传递dom元素,我们可以这样:
1. 构造视图对象时,传递el参数,可以是jquery对象,也可以是dom对象(前提是该dom元素在页面里面已存在)
var bookView = new Backbone.View({
el : $('#id') // jquery对象,或dom对象
});
而接下来我们要做的就只是往这个dom元素里添加内容了,像这样:
initialize : function () {
var tpl ='xxx';
var data = {...};
this.$el.html(_.template(tpl, data));
}
这里我们更建议你去使用_.template这样的的模板函数就渲染dom,会显得代码更节俭,美观
2. 创建视图类时,传递tagName(默认是div),让View来帮你创建dom元素
var BookView = Backbone.View.extend({
tagName : 'span'
});
var bookView = new BookView({
attributes : {
'title' : 'xxx' // span的标签的属性
},
id : 'demo'
});
记住最后render时要将这个元素append到页面里面去,因为这里的dom元素是通过tagName,Backbone在内部创建的,并未添加到文档流中去。
说到dom元素,当然就有事件,向click,hover等事件是必不可少的,有了事件才有了交互,有了交互才会有数据(model)的变化才会有dom(view)的更新。
所以接下来要说一下如何给已有dom添加事件
1. 在创建视图类的时候,可以通过events参数添加事件,像这样:
var BookView = Backbone.View.extend({
tagName : 'span',
events : {
'mouseenter' : function () {...}, // 事件,某一函数(this对象指向view实例)
'click a.item' : 'say' // 事件代理,为class为item的a标签代理,调用view对象的say方法
},
say : function () {
console.log(this);
}
});
注意:
(1)Backbone的事件都是添加在视图关联的那个dom元素(el)上,那么该元素就有可能成为其子元素的事件代理元素(冒泡原理)
(2)事件的回调函数中的this是该视图对象
(3)'click a.item' 这里是一个事件代理,
(4)回调函数如果不是函数,是字符串,那么会到view对象中获取对应的方法,想这里的say其实是viewObj.say
2. 当然我们也可以通过调用delegateEvents方法添加事件,像这样:
var bookView = new BookView(...);
bookView.delegateEvents({
'click' : function () {...},
'click a.item' : 'say'
});
但是这里要注意的是:每次调用这个方法,前面添加的事件会被先清除
Backbone是如何更新视图的呢?
很明显,视图还会关联一个model实例,通过监听model实例的变化,从而相应的更新视图
这里是一个简单的小例子:
首先,我们在initialize函数中,要求view实例监听model实例的change事件
然后,在回调函数中将model,change后的状态表现出来,这里是model的price属性变化了
var BookModel = Backbone.Model.extend();
var BookView = Backbone.View.extend({
tagName : 'span',
initialize : function () {
this.listenTo(this.model, 'change', this._change);
this.$el.html(this.model.get('price'));
$('body').append(this.$el);
},
_change : function (model) {
this.$el.html('更新:' + model.get('price'));
}
});
var bookModel = new BookModel({
price : 10
});
var bookView = new BookView({
model : bookModel,
attributes : {
'title' : 'span'
},
id : 'demo'
});
setTimeout(function () {
bookModel.set('price', 30);
}, 1000);
例子在这:demo
当然,这里只是简单演示一下,其实,一般最后的bookModel.set('price', 30),都是用户通过交互来做到的,也就是说这段代码应该在
dom的事件函数中表现出来,于是我们就可以为dom添加对应的事件了
ok,接下来把源码解析的中文注释附上,如果错误了,还望指出来,谢谢~~
// Backbone.View
// ------------- // Backbone Views are almost more convention than they are actual code. A View
// is simply a JavaScript object that represents a logical chunk of UI in the
// DOM. This might be a single item, an entire list, a sidebar or panel, or
// even the surrounding frame which wraps your whole app. Defining a chunk of
// UI as a **View** allows you to define your DOM events declaratively, without
// having to worry about render order ... and makes it easy for the view to
// react to specific changes in the state of your models. // Creating a Backbone.View creates its initial element outside of the DOM,
// if an existing element is not provided...
var View = Backbone.View = function(options) { // 实例唯一id
this.cid = _.uniqueId('view'); options || (options = {}); // 从options里挑出存在于viewOptions里的有意义的属性值,
// 并赋值到this里对象里,作为属性
_.extend(this, _.pick(options, viewOptions)); // view是跟dom有关的
// 所以这里要先确定dom元素是否指定
this._ensureElement(); // 初始化,一般用户来自定义
this.initialize.apply(this, arguments); // 添加(代理)事件
this.delegateEvents();
}; // Cached regex to split keys for `delegate`.
// 在添加事件时,分开事件名和选择器
var delegateEventSplitter = /^(\S+)\s*(.*)$/; // List of view options to be merged as properties.
// 将可能作为view实例的一些属性名(由用户传入)
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; // Set up all inheritable **Backbone.View** properties and methods.
// 原型方法
_.extend(View.prototype, Events, { // The default `tagName` of a View's element is `"div"`.
// 用于构造dom元素,默认是div元素i,用户可以传递参数覆盖
tagName: 'div', // jQuery delegate for element lookup, scoped to DOM elements within the
// current view. This should be preferred to global lookups where possible.
// 将对元素的查找缩小到与试图关联的dom元素($el)内,提高效率
$: function(selector) {
return this.$el.find(selector);
}, // Initialize is an empty function by default. Override it with your own
// initialization logic.
// 初始化函数,一般用户自定义覆盖
initialize: function(){}, // **render** is the core function that your view should override, in order
// to populate its element (`this.el`), with the appropriate HTML. The
// convention is for **render** to always return `this`.
// 渲染函数,用来填充视图关联的dom元素($el)
// 用户自定义覆盖之
// 注意:建议使用模板引擎对处理html,例如已有的_.template()
render: function() {
return this;
}, // Remove this view by taking the element out of the DOM, and removing any
// applicable Backbone.Events listeners.
// 将视图关联的dom元素删除
// 停止该视图对象的所有的事件监听(一般view视图会监听model的变化,从而相应地更新视图)
remove: function() {
this.$el.remove();
this.stopListening();
return this;
}, // Change the view's element (`this.el` property), including event
// re-delegation.
// 设置与视图关联的dom元素($el)
setElement: function(element, delegate) { // 删除之前的$el的事件监听
if (this.$el) this.undelegateEvents(); // dom对象还是jquery对象的判断,统一转换成jquery对象,赋值给$el
this.$el = element instanceof Backbone.$ ? element : Backbone.$(element); // 将dom对象赋值给el
this.el = this.$el[0]; // 是否添加事件
if (delegate !== false) this.delegateEvents();
return this;
}, // Set callbacks, where `this.events` is a hash of
//
// *{"event selector": "callback"}*
//
// {
// 'mousedown .title': 'edit',
// 'click .button': 'save',
// 'click .open': function(e) { ... }
// }
//
// pairs. Callbacks will be bound to the view, with `this` set properly.
// Uses event delegation for efficiency.
// Omitting the selector binds the event to `this.el`.
// This only works for delegate-able events: not `focus`, `blur`, and
// not `change`, `submit`, and `reset` in Internet Explorer.
// 给视图关联的dom元素添加事件或者是给它的子元素添加代理事件
delegateEvents: function(events) { // 参数events为空,则用this.events代替
if (!(events || (events = _.result(this, 'events')))) return this; // 在调用时,会先去除之前的所有的事件监听
this.undelegateEvents(); // 遍历events对象
for (var key in events) { // method为方法名(字符串)或者函数
var method = events[key]; // 如果不是函数(即是字符串),那么表示调用的是view对象对应名字的方法
if (!_.isFunction(method)) method = this[events[key]]; // 如果方法为空,跳过
if (!method) continue; // 匹配出事件名(eventName)和选择器(selector)
var match = key.match(delegateEventSplitter);
var eventName = match[1], selector = match[2]; // 将方法的this对象指定为该视图对象
method = _.bind(method, this); // 修改事件名,添加命名空间,便于以后事件的删除
// .delegateEvents用于区分其他程序在同一个dom元素上绑定的事件
// cid用于区分不同的视图对象共享同一个dom元素
eventName += '.delegateEvents' + this.cid; // 如果选择器为空,为视图关联的这个dom元素绑定事件
if (selector === '') {
this.$el.on(eventName, method); // 如果选择器不为空,则为dom元素下匹配selector的子元素绑定代理事件
} else {
this.$el.on(eventName, selector, method);
}
}
return this;
}, // Clears all callbacks previously bound to the view with `delegateEvents`.
// You usually don't need to use this, but may wish to if you have multiple
// Backbone views attached to the same DOM element.
// 删除绑定的所有(代理)事件
undelegateEvents: function() {
this.$el.off('.delegateEvents' + this.cid);
return this;
}, // Ensure that the View has a DOM element to render into.
// If `this.el` is a string, pass it through `$()`, take the first
// matching element, and re-assign it to `el`. Otherwise, create
// an element from the `id`, `className` and `tagName` properties.
// 确保视图有关联的dom元素
// 如果没有,试图通过tagName构造
_ensureElement: function() { // 如果构造时没有传递了el参数
// 那么,用tagName参数构造dom元素,并为该dom元素添加一系列的属性(参数传进来来的)
if (!this.el) { // 将要添加到dom元素上的属性列表
// 注意:attributes可以是函数,这样可以通过条件判断返回不同的属性
// 下面的id,className,tagName,el同样是这样,都通过调用_.result
var attrs = _.extend({}, _.result(this, 'attributes')); // dom元素id
if (this.id) attrs.id = _.result(this, 'id'); // dom元素class
if (this.className) attrs['class'] = _.result(this, 'className'); // 生成dom元素
var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs); // 设置dom元素
this.setElement($el, false);
} else { // 设置dom元素
this.setElement(_.result(this, 'el'), false);
}
} });
【原创】backbone1.1.0源码解析之View的更多相关文章
- 【原创】backbone1.1.0源码解析之Collection
晚上躺在床上,继续完成对Backbone.Collection的源码解析. 首先讲讲它用来干嘛? Backbone.Collection的实例表示一个集合,是很多model组成的,如果用model比喻 ...
- 【原创】backbone1.1.0源码解析之Model
趁热打铁,将Backbone.Model的源代码注释也发出来. Model是用来干嘛的?写过mvc的同学应该都知道,说白了就是model实例用来存储数据表中的一行数据(row) Backbone利用m ...
- 【原创】backbone1.1.0源码解析之Events
最近在看些node的源代码,发现backbone的应用还是挺广泛的,但是之前的学习忘得一干二净了,后悔当时没做笔记啊. 所以,无奈想用的更好,就是得把源代码看楚,所以还是把源代码的注释笔记留下来,供自 ...
- solr&lucene3.6.0源码解析(四)
本文要描述的是solr的查询插件,该查询插件目的用于生成Lucene的查询Query,类似于查询条件表达式,与solr查询插件相关UML类图如下: 如果我们强行将上面的类图纳入某种设计模式语言的话,本 ...
- solr&lucene3.6.0源码解析(三)
solr索引操作(包括新增 更新 删除 提交 合并等)相关UML图如下 从上面的类图我们可以发现,其中体现了工厂方法模式及责任链模式的运用 UpdateRequestProcessor相当于责任链模式 ...
- Heritrix 3.1.0 源码解析(三十七)
今天有兴趣重新看了一下heritrix3.1.0系统里面的线程池源码,heritrix系统没有采用java的cocurrency包里面的并发框架,而是采用了线程组ThreadGroup类来实现线程池的 ...
- solr&lucene3.6.0源码解析(二)
上文描述了solr3.6.0怎么采用maven管理的方式在eclipse中搭建开发环境,在solr中,为了提高搜索性能,采用了缓存机制,这里描述的是LRU缓存,这里用到了 LinkedHashMap类 ...
- solr&lucene3.6.0源码解析(一)
本文作为系列的第一篇,主要描述的是solr3.6.0开发环境的搭建 首先我们需要从官方网站下载solr的相关文件,下载地址为http://archive.apache.org/dist/luc ...
- apache mina2.0源码解析(一)
apache mina是一个基于java nio的网络通信框架,为TCP UDP ARP等协议提供了一致的编程模型:其源码结构展示了优秀的设计案例,可以为我们的编程事业提供参考. 依照惯例,首先搭建a ...
随机推荐
- centos7 RabbitMQ部署
一.RabbitMQ简单介绍在日常工作环境中,你是否遇到过两个(多个)系统间需要通过定时任务来同步某些数据?你是否在为异构系统的不同进程间相互调用.通讯的问题而苦恼.挣扎?如果是,那么恭喜你,消息服务 ...
- 1分钟,通过docker-compose 搭建zookeeper 集群
一.创建三节点 zookeeper 集群 将 docker-compose.yml 保存到当前命令行目录下 docker-compose.yml 文件 version: '2' networks: z ...
- Jenkins+Maven+SVN+Nexus自动化部署代码实例
本文接着上篇安装jenkins,安装相关插件,使用我们公司持续集成的测试环境实例进行演示 ========= 完美的分割线 ========== 1.安装jenkins的maven插件 如果要使用je ...
- VMware在Centos7上配置静态IP的方法
使用NAT模式 在这里记下192.168.161.2 进入系统,为系统自动分配一个ip 记录下 192.168.161.129 进入网络管理器配置文件目录 cd /etc/sysconfig/netw ...
- linux一切皆文件之tcp socket描述符(三)
一.知识准备 1.在linux中,一切皆为文件,所有不同种类的类型都被抽象成文件(比如:块设备,socket套接字,pipe队列) 2.操作这些不同的类型就像操作文件一样,比如增删改查等 二.环境准备 ...
- 开源通用爬虫框架YayCrawler-运行与调试
本节我将向大家介绍如何运行与调试YayCrawler.该框架是采用SpringBoot开发的,所以可以通过java –jar xxxx.jar的方式运行,也可以部署在tomcat等容器中运行. 首先 ...
- Linux基础二(挂载、关机重启与系统等级)
一.Linux 基础之挂载 1. 挂载和查询 1.1 挂载 什么叫挂载?装系统的时候要给硬盘分区,在 Windows 中要分 C 盘 D 盘 DEF 盘,这个操作我们叫做分配盘符,分配盘符之后我们就可 ...
- [转帖]Vim编辑器使用方法详解
Vim编辑器使用方法详解 程序员小新人学习 2018-12-16 12:26:23 转载于https://www.cnblogs.com/libaoliang/articles/6961676.htm ...
- mybatis之关联(2)
MapperAssoDao.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE map ...
- java 强制转换之降级
大空间的数据类型向小空间的数据类型去转换. 语法:(目标数据类型)变量名 当大空间数据类型转换给小空间时且超过小空间的容量时,编译不会出错,但是会丢失精度 例如 int a = 128 抓换byte ...