强烈建议一边看着源码一边读本文章,本文不贴大段代码。源码地址。
在写backbone
应用的时候,说实话,大部分的时间都是在写这三个模块的内容。关于这三个模块的分析网上随随便便就能找到一堆还不错的文章。但我但愿可以找到一条线索,能把各自模块的内部机理整理清楚。就像前一篇文章中介绍的Events
那样。Events
整个模块其实就是经过一些外部的方法来修改内部对象的属性,从而达到事件管理的目的。以一条线索来看待整个模块,一切都清晰了然了。下面就开始了~html
(最近要开学,要准备回学校上课,乱七八糟的东西不少,因此文章可能也会拖一阵子啦...可是我仍是很是但愿可以写下来,半途而废的感受真心很差...node
这一篇文章主要讲backbone
的Model
, Collection
和View
。这三个模块有不少类似的地方。这篇文章不会把模块的每个方法都介绍一遍,由于只要看源码就知道,其实主要的方法只有几个,而不少其余的模块实际上只是在调用这几个核心的方法而已。jquery
首先讲一下三者的类似之处。这一节让咱们来看看这三个模块一个整体结构。
这三个模块在结构上和Events
不一样。他们先经过如下方式来定义构造函数。(以View
为例)git
var View = Backbone.View = function(options) { // 构造函数的内容 };
构造函数的内部通常会作如下几个操做:github
各类给内部对象设置属性。(各类this.a = b
)web
调用preinitialize
ajax
this.preinitialize.apply(this, arguments);
调用initialize
数据库
this.initialize.apply(this, arguments);
各个模块的方法和属性是经过underscore
的extend
来得到的。注意在extend
新加入的方法和属性中,如下划线开头的变量是内部函数名。(其实理论上用户也能够调用这些方法,谁叫Javascript
没有内部变量呢...)这些内部方法是供本身模块内部调用的。segmentfault
_.extend(View.prototype, Events, { // 这里是各类对View.prototype的拓展,定义各类方法 });
还有一个比较大的共同点,就是slient
参数。这个参数决定了是否要trigger
一个事件,在源码用占了很大的篇幅对其进行分类讨论。后端
有一些关键的方法一进入函数就会根据传入的参数的形态进行变化。由于backbone一些方法支持两个参数传入或者一个数组传入,这时候须要有个判断。
set
方法在model
里面是个很很差理解的东西,看了网上大多数解析感受都很模糊(并且遇到难理解的就用一些借口蒙混过去)。不得不说set
里面复杂精妙程度是每读一遍惊叹一遍。
我想以变量的角度来说解多是一个比较好的角度。
changing
和this._changing
若是这个函数只是从头执行到尾,那说实话,这两个变量没有任何意义。由于他们的值是肯定的。看函数开头:
var changing = this._changing; this._changing = true;
在函数结尾:
this._changing = false;
这个changing
将永远永远是false
。我上网看到有人说多是webWorker
,多线程相关的东西,但我直接在源码console
的时候却发现,这个changing
是会变的,并且我用得是todo
范例。todo
范例没有任何相似webWorker
的东西。这个假设猜想应该来讲是不正确的。(不过这篇文章讲得也很不错啊)
因此这个changing
到底有什么用呢?答案就是递归函数。set
里明明没有递归啊?其实递归藏在了全部trigger
的事件的回调函数里面。源代码下面的这一段:
// You might be wondering why there's a `while` loop here. Changes can // be recursively nested within `"change"` events. while (this._pending) { options = this._pending; this._pending = false; this.trigger('change', this, options); }
这一个while
里的trigger
使得函数发生递归,而后从新调用set
。这样的话,下一次changing
就等于true
了,这个变量的做用才能发挥。能够看一下这个连接里面的讲解。
current
变量是用来做为引用改变attributes
的,实际上是set
能设置attributes
的本质。
changes
数组是用来存放改变了的key
的,用于后期的事件触发。
changed & _previousAttributes
把这两个放到一块儿是由于他们的一个特殊的地方。我在todo
的主函数的render
里面console
,发现不论我作什么操做,changed === {}
,_previousAttributes
没有发生改变。后来在查看官方文档的时候,才了解previous的用法:
var bill = new Backbone.Model({ name: "Bill Smith" }); bill.on("change:name", function(model, name) { alert("Changed name from " + bill.previous("name") + " to " + name); }); bill.set({name : "Bill Jones"});
set
方法在被调用的时候,previous
只有在回调函数里才能有用,也就是说,在回调函数外面想要用这个previous
获取前一个值是不可能的。它只能获取到当前值。为何呢?源码作出了解释。当用户作出操做须要用到set
方法的时候,其实set
方法并非直接执行完就结束了。在这个方法里面触发了不少的事件,而previous
只有在函数里触发了的事件的回调函数“里面”才能返回正确的“前一个值”。changed
也同理,由于不论中间如何变化,递归,到最后它会被设置为{}
。
save
方法的做用是把当前model
的状态保存到数据库中,所以不可避免地要用到ajax
。因为backbon
e已经有了一个封装好的方法sync
用于触发ajax
,所以在save
当中重点是设置参数。须要设置的有success
,error
,method
。
在success
里面会调用用户传入的回调函数并触发sync
事件表示已经同步了。
error
用封装好的wrapError
函数,这个函数用得不少,用于处理错误。
method
根据实际要用那种方法设置
其中比较值得注意的是wait
参数。这个参数会影响页面更新的时机。若是wait
是true
的话,就会须要等到服务器端相应才更新页面,不然就会当即更新。
destory
方法也是与ajax
有密切联系的。主要也是设置ajax
参数。它分了几种不一样的状况并做出了相应的处理:
wait
是false
,不用等待。发起delete
请求,触发内部函数destory
。
wait
是true
,发起ajax
,等待服务器响应才触发destory
更新页面。
这是一个新的model
,那就不须要发起请求了。
验证函数,经过调用内部函数_validate
,在经过这个函数调用validate
函数。而后返回一个错误,若是没有错误就返回true
,不然触发invalid
,返回false
。
Collection
相似一个数组,里面存放着各类以model
为结构的对象。在Collection
中也有这形式的判断,若是传入的参数是单个对象就会被转换成数组。
这是Collection
的一个很经常使用的方法,源码中这一段很长,也有点繁琐,可是没有特别难以理解的地方。整个set
的结构是:
设置几个数组(下面会详细讲)
设置实际的models
(修改this.models
)
trigger
事件
主要来讲就是有以下几个关键点:
若是不符合model
形式,转换之。
设置相应的插入位置at
。
设置set
数组。set
数组在里面做用是为给后面排序作准备。里面存放的是新的Collection
的models
。
设置toAdd
数组。这个数组是用于存储新建的合法的model
,而后须要调用内部函数_addReference
设置索引于_byId
数组,而且添加all
事件(后面就能够经过model
直接trigger
事件)。当slient
不是true
,后期能够经过遍历它来触发add
事件。
设置toMerge
数组。当这个model
是本来已经存在的model
的时候(cid
匹配),就会修改,而后被push
进这个数组中。
设置toRemove
数组。而后经过内部函数_removeModels
删除那些已经不在set
里面的models
。
修改this.models
,分两种状况,一种是直接整个替换掉,一种是后面再添加。
若是silent
不是true
就要触发事件。特别值得注意的一点是:这里面的事件有两种,一种事件是由Model
发出的,一种事件是有Collection
发出的。从Model
发出的事件能够很容易_addReference
函数中发现
model.on('all', this._onModelEvent, this);
在这里注册了,调用的是_onModelEvent
函数。而其余没有注册的函数应该是给使用者注册监听用的。
sort
所依据的是用户传入的comparator
参数,这个参数能够是一个字符串也能够是一个函数,若是是字符串就经过underscore
的sortBy
方法,若是是个函数就直接传入sort
的第二个参数中。
fetch
和create
是backbone
与服务器端交互的一个接口。两个方法内部处理其实都很好理解,就是设置ajax
参数。最终本质上都是触发sync
。可是惟一不一样的是fetch
是经过自身的sync
函数,但create
是经过调用model
的save
,而后触发sync
的。在
model.save(null, options);
跟着这个save
函数里面走,就会发现参数null传入是有意义的。在save
里面的参数设置会很好地赋值并最后触发sync
,并且有一个颇有趣的点,就是这个create
把model
传上服务器,可是这个model
是一个相对独立的状态,仅仅经过它的Collection
属性来维系和Collection
的关系。那就要求后端须要把这一个model
添加到相应的Collection
数据里面去。
在Collection
有一个值得关注的内部变量,那就是_byId
,这个变量用cid
和id
(因此model
是一对一对出现的)来存储Collection
里面的model
,方便直接性的存取。在源码中有不少操做目的就是删除,增长,获取这个内部变量的值。
这东西我以为颇有意思...在官方文档里面没有提到,可是因为涉及到ES6
的东西因此以为有点眼前一亮的感受(哈哈哈),backbone
在这里用了Symbol.iterator
,具体用法在这个连接里有介绍,仍是挺清晰的。经过设置CollectionIterator
的Symbol.iterator
和next
方法。它经过内部变量_kind
来区分种类,_index
来肯定对应的next
的结果,这个对于写迭代器仍是有点借鉴意义的~
在写backbone
应用的时候,View
写着写着会愈来愈大...追根溯源,就是View
的代码不多...(大雾)。关于View
,在写相关代码的时候有一些值是须要设置的(可选的)。下面的代码就展现了可设置的参数,这些参数在View的方法中会用到(若是有的话)。
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
下面我会从两个大的方面来解读源码,一个是element
,一个是Events
。整个View
的源码事实上就是这两组东西。
View
字面意思是视图,而在浏览器中,视图就是html
所呈现的页面。每个View
事实上就对应着html
的一个元素(固然这个元素里面能够有不少不少元素)。这个元素默认标签是div
。与元素相关的代码其实很简单,首先要认清this.el
和this.$el
。前者是真正的节点,后者则是jquery
对象的节点。后者因为是jquery
式的,所以就能够作相关的jquery
的操做。所以事件发起,删除节点,设置属性的操做都是jquery
的api
对this.$el
或其子节点的操做。在进入构造函数的时候会调用一个叫_ensureElement
的内部函数,在这个函数里会根据用户设置的参数去构建节点,最后展示到页面之上。
事件是View
中很是重要的组成。这是用户能够操做数据的一个接口。在View里面和数据相关的方法有delegateEvents
,delegates
,undelegateEvents
,undelegate
。里面经过使用者设置的events
属性来建立各类事件,操做各类事件。
{ 'mousedown .title': 'edit', 'click .button': 'save', 'click .open': function(e) { ... } }
events
相关代码很简单,可是有一个很是很是巧妙的地方:就是做者用了jquery
事件相关api
的命名空间。在delegate
被调用的时候就给事件加上了一个特定的命名空间。
delegate: function(eventName, selector, listener) { this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener); return this; }
所以在后续须要对总体的全部事件进行操做的时候就会方便不少不少。
此次源码解析不能百分百保证是正确的,有一些混杂了本身的思考。由于不想像其余大部分的源码解析那样,对于问题模糊处理。但我以为仍是有意义的,由于每一个人读的角度不同。兼听则明,也但愿读者可以包容,但愿深入理解backbone
的读者也请多读几篇文章,多读几遍源码。下一篇文章要写router & history
,这一个模块能够单独拆出来做为SPA
的一个入口,我的认为这部分时backbone
的backbone
(骨架)。
但愿可以坚持更下去吧,开学了,事情也开始多了起来...
本人仍是backbone
小白,若是哪里说错了或者怎样,请轻喷~相互学习~
下面是所有的文章: