MVC
是一种GUI软件的一种架构模式。它的目的是将软件的数据层(Model
)和视图(view
)分开。Model
链接数据库,实现数据的交互。用户不能直接和数据打交道,而是须要经过操做视图,而后经过controller
对事件做出响应,最后才得以改变数据。最后数据改变,经过观察者模式更新view
。(因此在这里须要用到设计模式中的观察者模式)前端
Smalltalk-80
是早期的对MVC
模式的一种实现。这种模式的目的是分离应用的内部逻辑和用户的交互界面。在书中讲述了这种模式有几个特色:node
用户操做界面(view
和controller
)和数据层(Model
)是分离的。python
数据的呈现是由view
和controller
完成的。它们二者没有明显的分界。(controller
并不是必须,能够用其余替代,所以就有了MVP
和MVVM
。)git
controller
的任务就是处理用户操做view
发出的事件。好比点击,输入等等。github
model
一旦发生改变就会经过观察者模式更新view
。ajax
我接触过python
的flask
和node
的express
框架,都是以MVC
的形式来组织的。V层
用模板引擎呈现页面,用户对V层
作操做,触发订阅好的事件,而后路由操做数据库,最后从新呈现页面,达到更新的效果。我的感受对后端来讲,MVC
的概念会更加直接和清晰。数据库
废话不少,下面直接进入正题了。MVC
在前端开始流行(固然如今什么MVVM
更火)仍是backbone
的功劳。backbone
的源码相对于其余框架来讲很短(1.3.3
版本的有2027行)。因此虽然感受用backbone
写应用很不容易,可是认真去读backbone
源码仍是能够加读懂很多的。我会分三篇文章去分析backbone
的源码。如下:express
backbone
的总结架构和Events
flask
model
& collection
& view
segmentfault
sync
& router
& history
我看过不少人想写backbone
的源码分析,写得都很不错,看了颇有收获,然而...大都都是些了一篇两篇就停更了,悲伤的故事...但愿我可以坚持下来吧。
终于开始啦!backbone
里代码结构和官方文档里面的组织方式几乎是如出一辙的,因此把官方文档当成索引来读也是很方便的~代码的总体架构以下:
(function(factory) { // 在这里是backbone模块化的一个接口。支持AMD,CMD和全局变量模式。代码很好理解。 })(function(root, factory, _, $) { // 各类参数和函数的定义 Backbone.noConflict = function(){}; var Events = Backbone.Events = {}; // 而后是各类Events方法的添加 // Events在Backbone里面很是重要,Model,Collection和View都extend了它。(不知道怎么说才天然...)因此他们均可以发起订阅事件,发起事件。固然,用户也能够本身拿本身的对象拓展一下,那样也能够订阅发起事件了~ var Model = Backbone.Model = function(){}; _.extend(Model.prototype, Events, { // 这里是各类对Model.prototype的拓展,定义各类方法 }); var Collection = Backbone.Collection = function(){}; _.extend(Collection.prototype, Events, { // 这里是各类对Collection.prototype的拓展,定义各类方法 }); var View = Backbone.View = function(){}; _.extend(View.prototype, Events, { // 这里是各类对View.prototype的拓展,定义各类方法 }); Backbone.sync = function(){}; Backbone.ajax = function(){}; var Router = Backbone.Router = function(){}; _.extend(Router.prototype, Events, { // 这里是各类对Router.prototype的拓展,定义各类方法 }); var History = Backbone.History = function(){}; _.extend(History.prototype, Events, { // 这里是各类对History.prototype的拓展,定义各类方法 }); // 用History定义实例 Backbone.history = new History; // 接下来是helper函数extend var extend = function(){}; Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend; // 其余的还有urlError,warpError函数 return Backbone; });
在这一小节我顺便把除了model
& collection
& view
& sync
& router
& history
相关以外的都讲了先吧
防止冲突,若是本身自己全局就有Backbone
,能够用noConflict
解决冲突。不过,通常都不会有人起一个会冲突的名字吧...
var previousBackbone = root.Backbone; Backbone.noConflict = function() { root.Backbone = previousBackbone; return this; };
这个函数返回了一个对象。这个对象的属性,方法,构造函数,原型都有了定义,很完整。
var extend = function(protoProps, staticProps) { var parent = this; var child; // 若是protoProps有构造函数就给child吧。 if (protoProps && _.has(protoProps, 'constructor')) { child = protoProps.constructor; } else { // 若是没有就用parent的。 child = function(){ return parent.apply(this, arguments); }; } // 把parent和staticProps的属性方法给child吧。 _.extend(child, parent, staticProps); // 定义child的prototype。child是继承自parent的。这里不直接调用构造函数。 child.prototype = _.create(parent.prototype, protoProps); child.prototype.constructor = child; child.__super__ = parent.prototype; return child; };
这个函数理解起来并不困难,但在整个backbone
里面很关键。由于无论是Model
仍是Collection
仍是Router
等都须要Events
的方法来作一些事件相关的操做。
// 你们都须要extend这个方法。
Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
backbone
的Events
和nodejs
的EventEmmiter
有不少相似的地方。其实本质上就是一个发布订阅模式的算是比较常见的实现。
开始这段代码我看了一个早上,不长,可是里面有一点绕。后来看到这篇文章以后(他讲得很棒!选取的角度很是不错!可是停!更!了!...悲伤...)就开始理解了。但因为版本不一样,差异仍是很多。
这里我打算从两个方面来说解这个Events
,一个是内部对象,一个是主要方法。事实上,在传统的发布订阅模式中,主要也是这两个组成部分。由内部对象来管理全部的事件,由方法来作订阅,发布,取消等的操做。具体来讲,就是经过Events
当中的this
的各个属性,来存储,管理事件。而外部,则经过on
,off
,listenTo
等方法来操做这些属性。
具体的bakcbone
代码能够看这里。在文中不会大段大段贴代码。不过强烈建议对照着看。
Events
中的内部对象this
起得做用是管理全部的事件,全部的监听和全部的被监听。能够尝试下把官方的todo
范例的view
里输出一下console.log(this)
,其中的_listeningTo
, _events
, _listenId
, _listeners
都是Events
带来的内部函数的属性。下面的关键方法,其实必定程度上都是操做这些内部属性的方法。其中有一点须要注意,并非每个有Events
方法的对象都会有着四个属性。记住一点,只有须要的时候才会建立,不须要的时候是没有的。设计模式在这里有不少涉及,能够去了解一下。下面讲讲这四个内部属性。
_listeningTo
: 当前对象所监听的对象。对象里面是一个或多个以被监听对象的_listenId
为名字的对象。每个对象结构以下:
{ count: 5, // 监听了几个事件 id: 13, // 监听方的id listeningTo: Object, // 自身相关的一些信息(颇有趣,里面能够无限点击下去,由于引用了自身。不知道有什么用意...) obj: child, // 被监听的对象 objId: "12" // 被监听对象id }
_listenId
: 监听与被监听时候的标示
_listeners
: 监听该对象的对象信息(有点绕,就是指“看着”它的对象)结构与_listeningTo
相似。
_events
: 通常是被监听对象或者说是用了on
的对象才有的。一个name
带有几个对象是通常常见的状况。
这里面有不少循环引用的地方,细细看才不会被看绕啊。
关键方法是写代码的时候用到的方法,算是一种接口,能够对内部对象的属性作出改变。
在看backbone
的时候,(其实不单只backbone
,大部分写得良好的,复用率高的代码),总会以为很繁琐。但其实这才是良好代码应该有的样子:函数分工明确,各司其职,没有重复代码。很值得学习。虽然略微增长了阅读的难度...要认真分析函数在哪里调用,数据的流向等等才能很好地理解。好比一个on
函数,里面调用了internalOn
,internalOn
函数传入了一个onApi
,调用了eventsApi
,onApi
在eventsApi
里面调用,往_events
里面添加了新的事件。这只是一个例子,其余的其实都相似。
这是一个有趣的函数,它只是提供一个api
接口,起到分流的做用。函数中根据不一样的name的形式做出不一样的调用调整。使得代码获得很好的复用。传入的参数及其做用是:
iteratee
实际真正要调用的函数
events
事件,有不少状况中传入的是this._events
name
本身起的名字或者以前起的名字,表明了一个事件
callback
回调函数,触发事件时触发
opts
参数,在iteratee
函数的内部有本身的做用
在进入它的函数的时候,会有一个判断,把整一个函数内部分红三个部分,分别处理三种不一样的状况。三种不一样的状况分别是name
是一个对象,一个有空格的字符串,一个普通字符串。根据三种不一样的状况,对name
进行处理,而后调用iteratee
函数。
on
方法的实质是把事件添加到this._events
里面,很是直观。可是因为函数调用感受好像复杂了。在on
里面调用了internalOn
,internalOn
把函数onApi
传给了eventsApi
,eventsApi
里面调用了onApi
,而后就把事件的信息push
进_events
中。
这个函数很简单,处理的事情就是往this._events
里面push
进相应的事件。通常是有添加进新函数的时候才会调用到这个函数。值的注意的是描述一个事件的时候每每还须要一些其余的参数,这时候就须要options
来提供了。
off
和on
其实相似,只是把上面的onApi
换成了offApi
函数,其余都是大致一致的。要看offApi
的具体实现能够看下面。
取消事件有几种状况。当stopListening
调用它的时候就不须要留下任何监听函数,而用off
的时候则还须要留下一些不该该删除的函数。删除分两步,第一步是删除本身的,把监听该对象的listener
删除。再第二部就是把那一个listener
的listeningTo
删除。其实这种删除方式和后端数据库的一些操做很是类似。删除是两个方面的。
其实看上去繁琐,这个函数的做用就是构建_listeningTo
的一个过程。这个对象具体的形式在上面已经讲解过了。
这个函数就是把对象全部监听的都清除掉。这个函数的内部原理也很简单,就是把_listeningTo
遍历一遍),最后调用off取消掉全部的被监听者listeners
里面的相应的监听者。
二者内部很相近,都是调用eventsApi
把要执行的函数onceApi
传进去。差异在于once
是最后是调用on
,而listenToOnce
最后调用listenTo
。他们都是调用了一次就off
掉的,原理在下面的onceMap
介绍里面有讲解。
这个地方很差懂的地方是这个offer
。offer
这里是一个特殊的options
。若是以前调用的once
,offer
就是off
,若是以前调用的是listenToOnce
就是stopListening
。意思都是取消放弃监听。而后才调用回调函数。这样作就达到“一次性”事件的要求。这里还保留了一个_callback
函数的目的是什么呢?
once._callback = callback;
这篇文章里说了,在offApi
里面有这么一行判断
callback !== handler.callback._callback
根据这个判断,就会让一次性函数不会得以保留,这样也就达到了用完一次就删除的目的。这样在调用offer
的时候才得以删除之。
trigger
函数和以前的同样,也是委托了eventsApi
,把辅助函数传进去了。具体能够看下面 triggerApi
& triggerEvents
有详细介绍。
这两个trigger
的辅助函数是这样工做的。在trigger
函数里面把triggerApi
函数传给了eventsApi
调用,而triggerApi
调用了triggerEvents
。在trigger
里面先是把参数取出来。后来参数会传到triggerApi
里面。而后会开始判断是否有这个事件啊,还有这个事件是否是“all”
事件啊,等等。而后再调用triggerEvents
,在这个函数里面就是循环执行回调函数。(本来代码注释写的迷之优化(difficult-to-believe
)其实很好理解,所谓可以枚举就枚举嘛,老是或多或少能优化的。)
写了一成天....真的好累...怪不得那么多人会放弃....但愿明天的本身可以抖擞精神,坚持更新...并且写得过于详细也不是很好。Model
& Collection
& View
这三个部分是不少人写过的部分。大体简略一点吧。
在backbone方面还算是小白,若是文章中有错误请轻喷,相互学习~
下面是所有的文章: