backbone初探(四)-数据流

前言

在前一篇读源码主要是把逻辑清楚部分了,但是MVC之间数据数据的处理和相关细节,还是存在一定的隔膜,这篇就来试试各个多个实例,看看数据在MVC之间怎样进行流动和操作。

step 1 View的简单使用

第一步还是从View开始,由于前端的特性,MVC模式在前端的应用归根结底不管中间如何玩花样,到了最后还是要把数据渲染到浏览器来,这是最最根本的事情。而数据渲染这个环节,无论如何该有一个目标渲染的数据,这里model和collection都是可以。先试试最简单的用model配合view输出一个helloworld。

纯View

我们先来看看View来渲染一个写死的数据(没有model和collection配合)是怎样的:

JS Bin on jsbin.com

在这个例子中,整个数据仅仅是直接传参进去人后通过render进行dom渲染。
最后出来的dom是这样的

1
<div data-index="1" data-test="test" id="view" class="viewClass">Hello World</div>

这里需要说的是三个参数:id,className和attributes,这三个函数在没有指定el的情况下会自动生成DOM,然后将id,className作为id和class给其设置好(事实上还有一个tagName属性也可以设置),然后attributes遍历也设置好。

这是其中一种用法,自动生成dom然后按逻辑渲染,但是除此以外我们还可以直接对已经有的DOM进行重绘:
JS Bin on jsbin.com

View&Model

这里先随便标记一下。回头再来看这个细节。我们先找个model配合view来渲染下页面:
JS Bin on jsbin.com
这个例子和之前变化不大,只是实例化了一个model,然后使用get的方法取出了值进行渲染

View&Collection

Collection往往是诸多数据的集合,常用的场景往往就是遍历输出。配合遍历输出的往往有复杂模板,但是这里仅仅输出纯文本好了。像下面的:
JS Bin on jsbin.com

分析

至此预想的step1实验到此就完毕了。我们来分析一下这些情况是如何产生的逻辑:

  • 设置el和不设置el时候View生成的dom
  • Model和Collection数据如何被View使用的。

el相关:

先看View的构造函数:

1
2
3
4
5
6
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
_.extend(this, _.pick(options, viewOptions));
this._ensureElement();
this.initialize.apply(this, arguments);
};

关于el不同的处理方式就在 _ensureElement方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
_ensureElement: function() {
if (!this.el) {
var attrs = _.extend({}, _.result(this, 'attributes'));
if (this.id) attrs.id = _.result(this, 'id');
if (this.className) attrs['class'] = _.result(this, 'className');
this.setElement(this._createElement(_.result(this, 'tagName')));
this._setAttributes(attrs);
} else {
this.setElement(_.result(this, 'el'));
}
}
...
_createElement: function(tagName) {
return document.createElement(tagName);
}
...
_setElement: function(el) {
this.$el = el instanceof Backbone.$ ? el : Backbone.$(el);
this.el = this.$el[0];
},

这段代码很容易,似乎无需说什么了,只有两个很简单的分支,顺便把用到的方法也贴出来。存在el还是不存在el情况下目标元素的设置和选定。
唯一需要说明下的_.result可以点击下进去查看细节,这里不详解。

Model和Collection数据如何被View使用

构造函数已经被贴过了,关于model和collection的设置其实还在el被设置之前。这里只贴关键代码行,整体看前面。

1
2
3
_.extend(this, _.pick(options, viewOptions));
...
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];

很简单的玩意儿,哈哈,但是不看用法时候还真不知道用来为何这样写。
这里顺便贴下_.pick的用法,我认为相当于一个过滤方法。
不过还是简单说一句吧。这段代码的意思,从options中取出数组中指定的属性,使用extend加到this上。而这个this一般使用时候指向new出来的实例。

View初始化

View还是存在一个初始化的接口的,这是因为很多代码需要一实例化立即进行渲染之类的操作,上面我们使用自己的设置的render来处理这个,这里我们换个方式来做:
JS Bin on jsbin.com
只是非常简单的换了下上面demo的写法,加了一个initialize属性,调用render方法初始化,这样后面就无需手动view.render()了。

1
this.initialize.apply(this, arguments);

这是这段代码的功劳了。但是需要注意的是initialize比自定义的render时机要早,很多情况下可能使用它来初始化this.template这个属性,也就是设置模板。

View的事件绑定(非View&Event)

现代WEB应用,几乎没有那个应用没有进行事件绑定了。这是一个刚需的功能性需求。
我们来看看如何在View中进行事件绑定。
先看个demo:
JS Bin on jsbin.com

这个demo上绑定了两个很简单的事件 click和mouseover
事件的绑定,写在了setElement方法中,这个方法在View的构造函数里面有调用。看看这个方法的细节:

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
// 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`.
delegateEvents: function(events) {
events || (events = _.result(this, 'events'));
if (!events) return this;
this.undelegateEvents();
for (var key in events) {
var method = events[key];
if (!_.isFunction(method)) method = this[method];
if (!method) continue;
var match = key.match(delegateEventSplitter);
this.delegate(match[1], match[2], _.bind(method, this));
}
return this;
},

// Add a single event listener to the view's element (or a child element
// using `selector`). This only works for delegate-able events: not `focus`,
// `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
delegate: function(eventName, selector, listener) {
this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
return this;
},

事件绑定关键的代码在line22-23,我们聚焦这一块。

  1. 它首先将events属性名切割成事件名称和选择器两个,作为属性传入delegate,随之传入的还有method,这里使用_bind将method事件方法的上下文绑定到当前视图对象, 因此在事件被触发后, 事件方法中的this始终指向视图对象本身.
  2. 然后看delegate:看完有些sb的感觉了。。。它使用jQuery等库的on函数来处理的事件绑定,并没有自己实现一下,jQuery的on可以去看看语法。唯一的问题是 eventName + ‘.delegateEvents’ + this.cid运算的结果类似这样:keypress.delegateEventsview1,多了后面一截命名空间,何故呢?看看undelegateEvents内部通过’.delegateEvents’ + this.cid参数进行off解绑就明白了!

小结

Backbone.View的分析就此告落了。最后一段事件的分析,让我们可以联想到之前对事件机制的猜想,之前读源码时候还以为全部是Pub/Sub模式感到不解,现在可以得出最终结论了:Backbone确实是使用Pub/Sub模式,但是在View上它巧妙运用了第三方库来实现事件处理。实质上是混合了二者。

step2 Model

Model基本属于数据表里面代表一行的数据,这个类型的数据基本没有太多可以多说的数据流问题。
在Model的实例中,实例的数据存放在attributes这个对象中,诸多方法,也都是依托CURD这个对象来实现。这个事情也只能这样做。因为数据最终需要有个地方存放,即使不叫attributes,那么也可能叫做attrs或者data之流。

这里仅仅简要说明一下Model里面常用到的一些功能。并没有太多新意。

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
39
40
41
42
43
44
45
46
# get获取数据
var bird = Backbone.Model.extent({
defaults:{
color:"",
name:""
}
});
var maque = new bird({
name:"麻雀",
color:"灰不啦叽"
});
console.log(maque.get("name")+"颜色是:"+maque.color);

# 设置属性
maque.set({
color:"灰色"
});

# 删除属性
maque.unset("color"); //删除一个
maque.clear() //清除整个数据

# 数据回滚
console.log("before",maque.previousAttributes());
console.log("after",maque.attribuites);

# 数据持久化
var bird = Backbone.Model.extent({
defaults:{
color:"",
name:""
},
url:"test/update",
initilize:function(){
//XXXXX
}
});
var mq = new bird({
name:"麻雀1",
color:"灰色"
});
mq.save(null,{
success:function(model,data){
console.log(modal,data);
}
)

step3 Collection

如果说Model是一个row,那么collection就是一个table。Collection的方法应该是偏向集化操作的。
Collection是Model的Collection,明确这一点非常重要。这是数据集合的统一性质要求,是Sql数据表的存储要求,也是ORM这一原则的要求。
我们来看看如何设置一个简单的Collecion:

JS Bin on jsbin.com

大致就是专业的一个东西,这里简单的使用了一下Collection上的API,从思路上来说,一切都是对实例上models属性的操作。这个操作,非常大程度上参考了Array的api,有pop,push,shift,unshift,slice等等操作,细节上可能会有不同,但是都是对models属性的操作。

细节就不表了,感觉数组用了这么久,不需要。

不是总结的总结

本来以为要浪费N多少时间在Modle和Colletion上,后来发现其实真的不需要,反而是View占用了大多数篇幅。

有朋友说Backbone写起来很随意,看了这么多,发现真的是很随意。除了改了N年的细节和兼容,Backbone是一个你理解架构就可以自己去山寨的一个不算库的库,虽然不会比它好。

MVC是backbone的核心,到现在基本是理清了。虽然很多细节没有提及,但是感觉不需要了。

Model就像是对闭包内数据的读写操作,而Collecion则相当于对闭包内数组的读写。

而View算是Backbone整体架构的最新展现者,但是它功能和API也真的是太简单,差不多一个巴掌可以数过来的API,也就涵盖了DOM生成选定,模板渲染和数据绑定三个环节的东西,没有一丝累赘。

有一句实话是,backbone虽然设计精巧,但是看下来确实有些失望。因为此番收获的确实不太多。或许没有精熟各个细节是大原因。下一篇有空打算把Sync和Router整理一番。做个收尾。

最后贴个链接在这里,算是自己的读源码历程的一个反思: 程序员阅读源码是一种什么心态?源码对编程意义何在?如何才能更好阅读代码?

深以为:debug能力是程序员生涯中最重要的能力之一,源码不是小说没有情节,也记不住细节,读源码的意义,除了在文档缺乏情况下可以找到使用方法,也是熟悉各种编程思想的最佳位置之一。