MVC?MVC是GUI软件的架构模型。其目的是分离软件的数据层(Model)和视图(view)。模型连接数据库,实现数据交互。用户不能直接和数据打交道,需要操作视图,然后通过控制器响应事件,最终改变数据。***数据变化,通过观察者模式更新视图。(所以这里需要用到设计模式中的观察者模式)Smalltalk-80MVCSmalltalk-80是MVC模式的早期实现。此模式的目的是将应用程序的内部逻辑与用户界面分开。在书中,这种模式有几个特点:用户界面(视图和控制器)和数据层(模型)是分离的。数据的呈现由视图和控制器完成。两者之间没有明确的界限。(controller不是必须的,可以换成其他的,所以就有了MVP和MVVM。)controller的任务就是处理用户操作视图发送的事件。如点击、输入等。一旦模型发生变化,视图将通过观察者模式进行更新。后端接触过python的flask和node的express框架,都是以MVC的形式组织起来的。V层使用模板引擎渲染页面,用户操作V层,触发订阅事件,然后路由操作数据库,最后重新渲染页面,达到更新的效果。个人感觉对于后端来说,MVC的概念会更直接、更清晰。前端骨干废话很多,直接进入正题。MVC在前端流行起来(当然现在比较流行什么MVVM)还是backbone的功劳。backbone的源代码与其他框架相比非常短(1.3.3版本有2027行)。所以虽然觉得用backbone写应用不容易,但是认真读一下backbone的源码还是能明白很多的。我会分三篇分析backbone的源码。以下:backbone的概要结构和Eventsmodel&collection&viewsync&router&history我看到很多人想写backbone的源码分析。一两篇之后就停止更新了,悲催的故事。。。希望能坚持下去。整体架构终于开始了!backbone中的代码结构和官方文档中的组织结构几乎一致,所以作为索引阅读官方文档非常方便~代码的整体结构如下:(function(factory){//in这里是backbone模块化的接口,支持AMD、CMD和全局变量模式,代码简单易懂})(function(root,factory,_,$){//各种参数和函数的定义Backbone.noConflict=function(){};varEvents=Backbone.Events={};//然后添加各种Events方法//Events在Backbone中很重要,Model,Collection,View都继承了它。(不知道怎么说itNaturally...)所以他们都可以发起订阅事件,发起事件。当然用户也可以扩展自己的对象,这样也可以订阅发起事件~varModel=Backbone.Model=function(){};_.extend(Model.prototype,Events,{//这里有各种为Model.prototype的扩展,定义各种方法});varCollection=Backbone.Collection=function(){};_.extend(Collection.prototype,Events,{//这里是Collection.prototype的各种扩展,定义各种方法});varView=Backbone.View=function(){};_.extend(View.prototype,Events,{//这里是View.prototype的各种扩展,定义各种方法});Backbone.sync=function(){};Backbone.ajax=function(){};varRouter=Backbone.Router=function(){};_.extend(Router.prototype,Events,{//这里有各种对Router.prototype的扩展定义了各种方法});varHistory=Backbone.History=function(){};_.extend(History.prototype,Events,{//这里是History.prototype各种扩展和定义的各种方法});//使用History定义实例Backbone.history=newHistory;//接下来是辅助函数extendvarextend=function(){};Model.extend=Collection.extend=Router.extend=View.extend=History.extend=extend;//其他还有urlError,warpError函数returnBackbone;});在这一节中,顺便说一下,除了模型&集合&视图&同步&路由器&历史之外的所有内容。让我们避免冲突冲突,如果全局有Backbone,可以使用noConflict来解决冲突。但是,一般不会有人给出冲突的名字...varpreviousBackbone=root.Backbone;Backbone.noConflict=function(){root.Backbone=previousBackbone;returnthis;};extend这个函数返回一个对象。这个对象的属性、方法、构造函数和原型都定义完备了。varextend=function(protoProps,staticProps){varparent=this;varchild;//如果protoProps有构造函数,就给child。if(protoProps&&_.has(protoProps,'constructor')){child=protoProps.constructor;}else{//如果没有,就用parent。child=function(){returnparent.apply(this,arguments);};}//把parent和staticProps的属性方法给child。_.extend(child,parent,staticProps);//定义child的原型。孩子是从父母那里继承的。这里没有直接调用构造函数。child.prototype=_.create(parent.prototype,protoProps);child.prototype.constructor=child;child.__super__=parent.prototype;returnchild;};这个功能不难理解,但是在整个backbone中是很关键的。因为不管是Model、Collection还是Router等,都需要Events方法来做一些事件相关的操作。//每个人都需要扩展这个方法。Model.extend=Collection.extend=Router.extend=View.extend=History.extend=extend;Eventsbackbone的Events和nodejs的EventEmmiter有很多相似之处。其实本质上就是一种比较常见的发布-订阅模型的实现。我整个上午都开始阅读这段代码,它不长,但其中有一点曲折。后来看了这篇文章(他讲的很好!他选的角度很好!但是停了!更!完了!。。难过。。。)我开始明白了。但是,由于版本不同,还是有很多差异的。这里我打算从两个方面来解释这个Events,一个是内部对象,一个是main方法。事实上,在传统的发布-订阅模型中,这两个组件是主要的。所有事件都由内部对象管理,订阅、发布、取消等操作由方法执行。具体来说就是通过Events中this的属性来存储和管理事件。在外部,这些属性通过on、off和listenTo等方法进行操作。具体的bakcbone代码可以看这里。文中不会贴出大段代码。但是,强烈建议检查一下。Events中的thisEvents中的内部对象this用于管理所有事件、所有监听器和所有监听器。可以尝试在官方todo示例的视图中输出console.log(this),其中_listeningTo、_events、_listenId、_listeners都是Events自带的内部函数的属性。下面的几个关键方法实际上是在一定程度上对这些内部属性进行操作的方法。需要注意的一件事是,并非每个具有Events方法的对象都将具有四个属性。记住,只有需要的时候才会创建,不需要的时候不会创建。这里涉及到很多设计模式,大家可以去了解一下。下面说说这四个内部属性。_listeningTo:当前对象正在监听的对象。对象内部是一个或多个以被监控对象的_listenId命名的对象。每个对象的结构如下:{count:5,//已经监听了几个事件id:13,//监听器的idlisteningTo:Object,//自身相关的一些信息(很有意思,可以***点击它,因为它引用了自己。不知道是什么目的...)obj:child,//被监听的对象objId:"12"//被监听的对象id}_listenId:the监听和被监听时标记_listeners:监听一个对象的对象信息(有点绕口,就是“看着”它的对象)的结构类似于_listeningTo。_events:一般只是被监听的对象或者使用on的对象。一个名称有多个对象是很常见的。这里有很多循环引用,仔细看的话,是不会被人看到的。Events中的关键方法和功能关键方法是写代码时使用的方法。它是一个可以改变内部对象属性的接口。在看backbone的时候,(其实不仅仅是backbone,大部分都是写的很好,重用率很高的代码),总觉得很累赘。但实际上,好的代码应该是这样的:功能分工明确,各司其职,没有重复的代码。值得学习。虽然稍微增加了阅读难度。。。一定要仔细分析函数调用的地方,数据的流向等等,才能很好的理解。比如一个on函数调用internalOn,internalOn函数传入一个onApi,调用eventsApi,onApi在eventsApi中调用,在_events中添加新的事件。这只是一个例子,其他的类似。eventsApi(辅助函数)这是一个有意思的函数,它只是提供了一个api接口,起到分流的作用。函数中根据不同的命名形式做了不同的调用调整。使代码得到很好的重用。传入的参数及其作用是:iteratee实际要调用的函数事件。很多时候传入的是this._events这个名字,你自己创建的名字或者你之前创建的名字代表一个事件回调函数,当事件被触发时,opts参数在iteratee函数内部有自己的作用。在进入它的函数时,会有一个判断,将整个函数分为三个部分,分别处理三种不同的情况。三种不同的情况是名称是对象、带空格的字符串和普通字符串。根据三种不同的情况,对name进行处理,然后调用iteratee函数。onon方法的本质是给this._events添加事件,非常直观。但由于函数调用,它看起来很复杂。on中调用了InternalOn,internalOn将函数onApi传递给eventsApi,eventsApi调用onApi,然后将事件信息推送到_events中。onApi(辅助函数)这个函数很简单,处理的事情就是把相应的事件推送到this._events中。一般在添加新功能时调用该函数。值得注意的是,在描述一个事件的时候,往往还需要一些其他的参数,这时候就需要选项了。offoff其实和on类似,只是把上面的onApi换成了offApi函数,其他大致相同。offApi的具体实现可以看下面。offApi(辅助函数)取消事件有几种情况。stopListening调用时,不需要留下任何监听函数,而使用off时,需要留下一些不该删除的函数。删除分为两步。第一步是删除自己的,删除监听对象的监听器。第二部分是删除那个监听器的listeningTo。其实这种删除方式和后台数据库的一些操作很相似。删除是双重的。listenTo其实看起来比较繁琐,这个函数的作用就是一个构建_listeningTo的过程。这个对象的具体形式上面已经解释过了。stopListening函数是清除对象的所有监听器。这个函数内部原理也很简单,就是遍历_listeningTo),***调用off取消listenerslisteners中所有对应的监听器。once&listenToOnce内部非常相似,都是调用eventsApi传入要执行的函数onceApi。不同的是once是绝对调用,而listenToOnce是调用listenTo。它们都被调用一次然后关闭。原理在下面的onceMap介绍中有说明。OnceMap(辅助函数)难理解的地方就是这个offer。这里提供的是一种特殊的选择。如果之前调用过once,则关闭offer,如果之前调用过listenToOnce,则为stopListening。意思是取消,放弃听。然后调用回调函数。这样做符合“一次性”事件的要求。这里保留一个_callback函数的目的是什么?once._callback=回调;这篇文章说offApi中有这么一行判断callback!==handler.callback._callback根据这个判断,会使得一次性的函数不会被保留,所以用完一次就删掉的目的是实现了。这样调用offer的时候就可以删除了。triggertrigger函数和之前一样,同样委托eventsApi,传入辅助函数。具体可以看下面triggerApi&triggerEvents的详细介绍。triggerApi&triggerEvents(helperfunction)这两个触发辅助函数是这样工作的。在trigger函数中,将triggerApi函数传递给eventsApi调用,triggerApi调用triggerEvents。在触发器中,首先取出参数。稍后参数将传递给triggerApi。然后就会开始判断是否有这个事件,这个事件是不是“全部”事件等等。然后调用triggerEvents,其中循环执行回调函数。(原代码注释里写的难以置信,其实很容易理解,所谓枚举,可以枚举,总能或多或少的优化一下。)总结写了一整天。。。。。真的很累。。。难怪那么多人放弃。。。希望明天能振作起来继续更新。。。而且写的太详细也不太好。Model&Collection&View这三个部分是很多人都写过的部分。大致简要。在骨干方面,我还是个新手。文中如有错误,请轻喷,互相学习~
