囧,文章昨儿就发布了,结果移出了首页。
好吧,我再详细说一下吧~~
作为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元素在页面里面已存在)
1
2
3
|
var
bookView = new
Backbone.View({
el : $( ‘#id‘ ) // jquery对象,或dom对象
}); |
而接下来我们要做的就只是往这个dom元素里添加内容了,像这样:
1
2
3
4
5
6
7
|
initialize : function
() {
var
tpl = ‘xxx‘ ;
var
data = {...};
this .$el.html(_.template(tpl, data));
} |
这里我们更建议你去使用_.template这样的的模板函数就渲染dom,会显得代码更节俭,美观
2. 创建视图类时,传递tagName(默认是div),让View来帮你创建dom元素
1
2
3
4
5
6
7
8
9
10
11
|
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参数添加事件,像这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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方法添加事件,像这样:
1
2
3
4
5
6
7
8
|
var
bookView = new
BookView(...);
bookView.delegateEvents({ ‘click‘
: function
() {...},
‘click a.item‘
: ‘say‘
}); |
但是这里要注意的是:每次调用这个方法,前面添加的事件会被先清除
Backbone是如何更新视图的呢?
很明显,视图还会关联一个model实例,通过监听model实例的变化,从而相应的更新视图
这里是一个简单的小例子:
首先,我们在initialize函数中,要求view实例监听model实例的change事件
然后,在回调函数中将model,change后的状态表现出来,这里是model的price属性变化了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
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,接下来把源码解析的中文注释附上,如果错误了,还望指出来,谢谢~~
1 // Backbone.View 2 // ------------- 3 4 // Backbone Views are almost more convention than they are actual code. A View 5 // is simply a JavaScript object that represents a logical chunk of UI in the 6 // DOM. This might be a single item, an entire list, a sidebar or panel, or 7 // even the surrounding frame which wraps your whole app. Defining a chunk of 8 // UI as a **View** allows you to define your DOM events declaratively, without 9 // having to worry about render order ... and makes it easy for the view to 10 // react to specific changes in the state of your models. 11 12 // Creating a Backbone.View creates its initial element outside of the DOM, 13 // if an existing element is not provided... 14 var View = Backbone.View = function(options) { 15 16 // 实例唯一id 17 this.cid = _.uniqueId(‘view‘); 18 19 options || (options = {}); 20 21 // 从options里挑出存在于viewOptions里的有意义的属性值, 22 // 并赋值到this里对象里,作为属性 23 _.extend(this, _.pick(options, viewOptions)); 24 25 // view是跟dom有关的 26 // 所以这里要先确定dom元素是否指定 27 this._ensureElement(); 28 29 // 初始化,一般用户来自定义 30 this.initialize.apply(this, arguments); 31 32 // 添加(代理)事件 33 this.delegateEvents(); 34 }; 35 36 // Cached regex to split keys for `delegate`. 37 // 在添加事件时,分开事件名和选择器 38 var delegateEventSplitter = /^(\S+)\s*(.*)$/; 39 40 // List of view options to be merged as properties. 41 // 将可能作为view实例的一些属性名(由用户传入) 42 var viewOptions = [‘model‘, ‘collection‘, ‘el‘, ‘id‘, ‘attributes‘, ‘className‘, ‘tagName‘, ‘events‘]; 43 44 // Set up all inheritable **Backbone.View** properties and methods. 45 // 原型方法 46 _.extend(View.prototype, Events, { 47 48 // The default `tagName` of a View‘s element is `"div"`. 49 // 用于构造dom元素,默认是div元素i,用户可以传递参数覆盖 50 tagName: ‘div‘, 51 52 // jQuery delegate for element lookup, scoped to DOM elements within the 53 // current view. This should be preferred to global lookups where possible. 54 // 将对元素的查找缩小到与试图关联的dom元素($el)内,提高效率 55 $: function(selector) { 56 return this.$el.find(selector); 57 }, 58 59 // Initialize is an empty function by default. Override it with your own 60 // initialization logic. 61 // 初始化函数,一般用户自定义覆盖 62 initialize: function(){}, 63 64 // **render** is the core function that your view should override, in order 65 // to populate its element (`this.el`), with the appropriate HTML. The 66 // convention is for **render** to always return `this`. 67 // 渲染函数,用来填充视图关联的dom元素($el) 68 // 用户自定义覆盖之 69 // 注意:建议使用模板引擎对处理html,例如已有的_.template() 70 render: function() { 71 return this; 72 }, 73 74 // Remove this view by taking the element out of the DOM, and removing any 75 // applicable Backbone.Events listeners. 76 // 将视图关联的dom元素删除 77 // 停止该视图对象的所有的事件监听(一般view视图会监听model的变化,从而相应地更新视图) 78 remove: function() { 79 this.$el.remove(); 80 this.stopListening(); 81 return this; 82 }, 83 84 // Change the view‘s element (`this.el` property), including event 85 // re-delegation. 86 // 设置与视图关联的dom元素($el) 87 setElement: function(element, delegate) { 88 89 // 删除之前的$el的事件监听 90 if (this.$el) this.undelegateEvents(); 91 92 // dom对象还是jquery对象的判断,统一转换成jquery对象,赋值给$el 93 this.$el = element instanceof Backbone.$ ? element : Backbone.$(element); 94 95 // 将dom对象赋值给el 96 this.el = this.$el[0]; 97 98 // 是否添加事件 99 if (delegate !== false) this.delegateEvents(); 100 return this; 101 }, 102 103 // Set callbacks, where `this.events` is a hash of 104 // 105 // *{"event selector": "callback"}* 106 // 107 // { 108 // ‘mousedown .title‘: ‘edit‘, 109 // ‘click .button‘: ‘save‘, 110 // ‘click .open‘: function(e) { ... } 111 // } 112 // 113 // pairs. Callbacks will be bound to the view, with `this` set properly. 114 // Uses event delegation for efficiency. 115 // Omitting the selector binds the event to `this.el`. 116 // This only works for delegate-able events: not `focus`, `blur`, and 117 // not `change`, `submit`, and `reset` in Internet Explorer. 118 // 给视图关联的dom元素添加事件或者是给它的子元素添加代理事件 119 delegateEvents: function(events) { 120 121 // 参数events为空,则用this.events代替 122 if (!(events || (events = _.result(this, ‘events‘)))) return this; 123 124 // 在调用时,会先去除之前的所有的事件监听 125 this.undelegateEvents(); 126 127 // 遍历events对象 128 for (var key in events) { 129 130 // method为方法名(字符串)或者函数 131 var method = events[key]; 132 133 // 如果不是函数(即是字符串),那么表示调用的是view对象对应名字的方法 134 if (!_.isFunction(method)) method = this[events[key]]; 135 136 // 如果方法为空,跳过 137 if (!method) continue; 138 139 // 匹配出事件名(eventName)和选择器(selector) 140 var match = key.match(delegateEventSplitter); 141 var eventName = match[1], selector = match[2]; 142 143 // 将方法的this对象指定为该视图对象 144 method = _.bind(method, this); 145 146 // 修改事件名,添加命名空间,便于以后事件的删除 147 // .delegateEvents用于区分其他程序在同一个dom元素上绑定的事件 148 // cid用于区分不同的视图对象共享同一个dom元素 149 eventName += ‘.delegateEvents‘ + this.cid; 150 151 // 如果选择器为空,为视图关联的这个dom元素绑定事件 152 if (selector === ‘‘) { 153 this.$el.on(eventName, method); 154 155 // 如果选择器不为空,则为dom元素下匹配selector的子元素绑定代理事件 156 } else { 157 this.$el.on(eventName, selector, method); 158 } 159 } 160 return this; 161 }, 162 163 // Clears all callbacks previously bound to the view with `delegateEvents`. 164 // You usually don‘t need to use this, but may wish to if you have multiple 165 // Backbone views attached to the same DOM element. 166 // 删除绑定的所有(代理)事件 167 undelegateEvents: function() { 168 this.$el.off(‘.delegateEvents‘ + this.cid); 169 return this; 170 }, 171 172 // Ensure that the View has a DOM element to render into. 173 // If `this.el` is a string, pass it through `$()`, take the first 174 // matching element, and re-assign it to `el`. Otherwise, create 175 // an element from the `id`, `className` and `tagName` properties. 176 // 确保视图有关联的dom元素 177 // 如果没有,试图通过tagName构造 178 _ensureElement: function() { 179 180 // 如果构造时没有传递了el参数 181 // 那么,用tagName参数构造dom元素,并为该dom元素添加一系列的属性(参数传进来来的) 182 if (!this.el) { 183 184 // 将要添加到dom元素上的属性列表 185 // 注意:attributes可以是函数,这样可以通过条件判断返回不同的属性 186 // 下面的id,className,tagName,el同样是这样,都通过调用_.result 187 var attrs = _.extend({}, _.result(this, ‘attributes‘)); 188 189 // dom元素id 190 if (this.id) attrs.id = _.result(this, ‘id‘); 191 192 // dom元素class 193 if (this.className) attrs[‘class‘] = _.result(this, ‘className‘); 194 195 // 生成dom元素 196 var $el = Backbone.$(‘<‘ + _.result(this, ‘tagName‘) + ‘>‘).attr(attrs); 197 198 // 设置dom元素 199 this.setElement($el, false); 200 } else { 201 202 // 设置dom元素 203 this.setElement(_.result(this, ‘el‘), false); 204 } 205 } 206 207 });