1. 开场 1.1 MVC?
MVC是一种GUI软件的一种架构模式。它的目的是将软件的数据层(Model)和视图(view)分开。Model连接数据库,实现数据的交互。用户不能直接和数据打交道,而是需要通过操作视图,然后通过controller对事件作出响应,最后才得以改变数据。最后数据改变,通过观察者模式更新view。(所以在这里需要用到设计模式中的观察者模式)
1.2 Smalltalk-80 MVCSmalltalk-80是早期的对MVC模式的一种实现。这种模式的目的是分离应用的内部逻辑和用户的交互界面。在书中讲述了这种模式有几个特点:
用户操作界面(view和controller)和数据层(Model)是分离的。
数据的呈现是由view和controller完成的。它们两者没有明显的分界。(controller并非必须,可以用其他替代,因此就有了MVP和MVVM。)
controller的任务就是处理用户操作view发出的事件。比如点击,输入等等。
model一旦发生改变就会通过观察者模式更新view。
1.3 后端我接触过python的flask和node的express框架,都是以MVC的形式来组织的。V层用模板引擎呈现页面,用户对V层做操作,触发订阅好的事件,然后路由操作数据库,最后重新呈现页面,达到更新的效果。个人感觉对后端来说,MVC的概念会更加直接和清晰。
1.4 前端backbone废话很多,下面直接进入正题了。MVC在前端开始流行(当然现在什么MVVM更火)还是backbone的功劳。backbone的源码相对于其他框架来说很短(1.3.3版本的有2027行)。所以虽然感觉用backbone写应用很不容易,但是认真去读backbone源码还是可以加读懂不少的。我会分三篇文章去分析backbone的源码。以下:
backbone的总结架构和Events
model & collection & view
sync & router & history
我看过很多人想写backbone的源码分析,写得都很不错,看了很有收获,然而...大都都是些了一篇两篇就停更了,悲伤的故事...希望我能够坚持下来吧。
2. 总体架构终于开始啦!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相关之外的都讲了先吧
2.1 noConflict防止冲突,如果自己本身全局就有Backbone,可以用noConflict解决冲突。不过,一般都不会有人起一个会冲突的名字吧...
var previousBackbone = root.Backbone; Backbone.noConflict = function() { root.Backbone = previousBackbone; return this; };2.2 extend
这个函数返回了一个对象。这个对象的属性,方法,构造函数,原型都有了定义,很完整。
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;3. Events
backbone的Events和nodejs的EventEmmiter有很多类似的地方。其实本质上就是一个发布订阅模式的算是比较常见的实现。
开始这段代码我看了一个早上,不长,但是里面有一点绕。后来看到这篇文章之后(他讲得很棒!选取的角度非常不错!但是停!更!了!...悲伤...)就开始理解了。但由于版本不同,差别还是不少。
这里我打算从两个方面来讲解这个Events,一个是内部对象,一个是主要方法。事实上,在传统的发布订阅模式中,主要也是这两个组成部分。由内部对象来管理所有的事件,由方法来做订阅,发布,取消等的操作。具体来说,就是通过Events当中的this的各个属性,来存储,管理事件。而外部,则通过on,off,listenTo等方法来操作这些属性。
具体的bakcbone代码可以看这里。在文中不会大段大段贴代码。不过强烈建议对照着看。
3.1 Events中的thisEvents中的内部对象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带有几个对象是一般常见的情况。
这里面有很多循环引用的地方,细细看才不会被看绕啊。
3.2 Events中的关键方法与函数关键方法是写代码的时候用到的方法,算是一种接口,可以对内部对象的属性做出改变。
在看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中。
3.2.3 onApi(辅助函数)这个函数很简单,处理的事情就是往this._events里面push进相应的事件。一般是有添加进新函数的时候才会调用到这个函数。值的注意的是描述一个事件的时候往往还需要一些其他的参数,这时候就需要options来提供了。
3.2.4 offoff和on其实类似,只是把上面的onApi换成了offApi函数,其他都是大体一致的。要看offApi的具体实现可以看下面。
3.2.5 offApi(辅助函数)取消事件有几种情况。当stopListening调用它的时候就不需要留下任何监听函数,而用off的时候则还需要留下一些不应该删除的函数。删除分两步,第一步是删除自己的,把监听该对象的listener删除。再第二部就是把那一个listener的listeningTo删除。其实这种删除方式和后端数据库的一些操作非常相似。删除是两个方面的。
3.2.6 listenTo其实看上去繁琐,这个函数的作用就是构建_listeningTo的一个过程。这个对象具体的形式在上面已经讲解过了。
3.2.7 stopListening这个函数就是把对象所有监听的都清除掉。这个函数的内部原理也很简单,就是把_listeningTo遍历一遍),最后调用off取消掉所有的被监听者listeners里面的相应的监听者。
3.2.8 once & listenToOnce两者内部很相近,都是调用eventsApi把要执行的函数onceApi传进去。差别在于once是最后是调用on,而listenToOnce最后调用listenTo。他们都是调用了一次就off掉的,原理在下面的onceMap介绍里面有讲解。
3.2.9 onceMap(辅助函数)这个地方不好懂的地方是这个offer。offer这里是一个特殊的options。如果之前调用的once,offer就是off,如果之前调用的是listenToOnce就是stopListening。意思都是取消放弃监听。然后才调用回调函数。这样做就达到“一次性”事件的要求。这里还保留了一个_callback函数的目的是什么呢?
once._callback = callback;
这篇文章里说了,在offApi里面有这么一行判断
callback !== handler.callback._callback
根据这个判断,就会让一次性函数不会得以保留,这样也就达到了用完一次就删除的目的。这样在调用offer的时候才得以删除之。
3.2.10 triggertrigger函数和之前的一样,也是委托了eventsApi,把辅助函数传进去了。具体可以看下面 triggerApi & triggerEvents有详细介绍。
3.2.11 triggerApi & triggerEvents (辅助函数)这两个trigger的辅助函数是这样工作的。在trigger函数里面把triggerApi函数传给了eventsApi调用,而triggerApi调用了triggerEvents。在trigger里面先是把参数取出来。后来参数会传到triggerApi里面。然后会开始判断是否有这个事件啊,还有这个事件是不是“all”事件啊,等等。然后再调用triggerEvents,在这个函数里面就是循环执行回调函数。(原本代码注释写的迷之优化(difficult-to-believe)其实很好理解,所谓能够枚举就枚举嘛,总是或多或少能优化的。)
4. 总结写了一整天....真的好累...怪不得那么多人会放弃....希望明天的自己能够抖擞精神,坚持更新...而且写得过于详细也不是很好。Model & Collection & View这三个部分是很多人写过的部分。大致简略一点吧。
在backbone方面还算是小白,如果文章中有错误请轻喷,相互学习~
下面是全部的文章:
基于 Backbone + node 的个人简历生成器(个人学习总结)
Backbone源码解读(一)
Backbone源码解读(二)
Backbone源码解读(三)
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/80140.html
摘要:接受个参数,包括事件的名称,回调函数和回调函数执行的上下文环境。保留回调函数在数组中取出对应的以及中的函数。当然,你同样可以在绑定的回调函数执行前手动通过将其移除。 Backbone源码解读 Backbone在流行的前端框架中是最轻量级的一个,全部代码实现一共只有1831行1。从前端的入门再到Titanium,我虽然几次和Backbone打交道但是却对它的结构知之甚少,也促成了我想读...
摘要:事件关于路由触发事件是通过两个函数来完成的,它们分别是和前者会检测路由是否发生了改变,如果改变了就会触发函数并调用函数,而后者会通过路由片段来找到相关的事件函数来触发。 注意:强烈建议一边阅读源码一边阅读本文。 终于到了backbone源码解读的最后一篇,这一篇和前面几篇时间上有一定的间隔(因为要回学校有一堆乱七八糟的事...)。在这一篇里面会讲解Bakcbone的sync & rou...
摘要:以为例构造函数的内容构造函数的内部一般会做以下几个操作各种给内部对象设置属性。为什么呢源码做出了解释。在里面会调用用户传入的回调函数并触发事件表示已经同步了。整个的源码事实上就是这两组东西。 1. 开场 强烈建议一边看着源码一边读本文章,本文不贴大段代码。源码地址。在写backbone应用的时候,说实话,大部分的时间都是在写这三个模块的内容。关于这三个模块的分析网上随随便便就能找到一堆...
摘要:个人认为,读懂老牌框架的源代码比会用流行框架的要有用的多。另外,源代码中所有的以开头的方法,可以认为是私有方法,是没有必要直接使用的,也不建议用户覆盖。 写在前面 backbone是我两年多前入门前端的时候接触到的第一个框架,当初被backbone的强大功能所吸引(当然的确比裸写js要好得多),虽然现在backbone并不算最主流的前端框架了,但是,它里面大量设计模式的灵活运用,以及令...
摘要:个人认为,读懂老牌框架的源代码比会用流行框架的要有用的多。另外,源代码中所有的以开头的方法,可以认为是私有方法,是没有必要直接使用的,也不建议用户覆盖。 写在前面 backbone是我两年多前入门前端的时候接触到的第一个框架,当初被backbone的强大功能所吸引(当然的确比裸写js要好得多),虽然现在backbone并不算最主流的前端框架了,但是,它里面大量设计模式的灵活运用,以及令...
阅读 1138·2023-04-26 01:35
阅读 2494·2021-11-02 14:44
阅读 7505·2021-09-22 15:38
阅读 2186·2021-09-06 15:11
阅读 3627·2019-08-30 15:53
阅读 778·2019-08-29 16:54
阅读 614·2019-08-26 13:48
阅读 1695·2019-08-26 13:47