【 js 基础 】【 源码学习 】backbone 源码阅读(一)

最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-study)进行参考交流,有详细的源码注释,以及知识总结,同时 google 一下 backbone 源码,也有很多优秀的文章可以用来学习。

我这里主要记录一些偏设计方向的知识点。具体从以下几个方面入手:
1、MVC 框架
2、观察者模式 以及 控制反转

一、MVC 框架
所谓 MVC 框架,包含三个部分,model 作为模型层、 view 作为视图层、而 controller 则作为控制层。
  * model 模型:用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。 model 有对数据直接访问的权力,例如对数据库的访问。“model”不依赖“view”和“controller”,也就是说, model 不关心它会被如何显示或是如何被操作。
  * view 视图:负责显示数据,也就是我们的用户界面。
  * controller 控制器: 起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据 model 上的改变。换句话说,它负责根据用户从"view视图层"输入的指令,选取"model数据层"中的数据,然后对其进行相应的操作,产生最终结果。

对于这三个部分是如何通信的,有很多种情况:

传统 mvc:

用户通过在 view 上点击或者输入等操作,传指令到 controller,controller 完成业务逻辑后,操作 model 改变数据,model 的变化触发 view 层的改变,显示更改后的数据。三者都是单向联系的。这样的设计,更加适用于视图会长时间存在并且需要频繁根随数据变化的场景,比如传统的客户端程序,web 前端页面。

model2:

model2 不同于传统 mvc 的主要区别就是 model 和 view 的完全隔离。直接通过 controller 接受指令,操作访问 model 层,并且 传递数据渲染 view。controller 与 view 和 model 都是单向联系的。这样的设计更加适用于 web 服务后端,控制器接受到的事件来源很统一,绝大部份是网络请求。而每个网络请求的结果多是产生一个 view 的 render,每一个 view 之间都是独立而短暂的,任何需要反映出 model 的变化,都需要产生一个新的 view。

再来说说 backbone 中的 mvc:
backbone 中有如下几个模块:

这里你会有个问题,backbone 中并没有定义 controller,那么还是真正的 mvc 吗?

先来说别的模块:

集合 collection ,它是一组 model 的集合,通过 collection 可以将一组数据结构相同的 model 有序地组织在一起,进行批量操作和管理等。
视图 view 是基于 Backbone.js 开发的 Web App 中的核心部分,负责用户交互事件的捕捉和处理、把用户输入导向 model 或 collection、渲染视图、操作DOM等。

可以看出来:
Backbone.js 中的 model 和 collection 共同构成了 MVC 中的 model 层。
Backbone.js 中的 view 既是 MVC 中的 view 层,同时也承担了 controller 的职责。这样就导致 view 非常厚,业务逻辑都部署在了 view 层。

在起初的 backbone 代码中,router 组件的名称是 controller,这很容易直接联系到 MVC 中的 C ,但事实上,backbone 中的 controller 仅仅是根据 URL hash 来在对应的行为和实践中做路由的,与真正意义上的 C 相比简单的多,因此在0.5版本前后 controller 改名为 router 了。

这些模块又是如何通信的?
model 和 view 和 sync
backbone 中 model ,可以被添加、验证、销毁或者是保存到服务器。当你进行交互操作,比如用户输入,引起一个 数据 model 中的属性变化时,model 会调用 sync 模块,用于保存数据到数据库,同时触发一个“change”事件,通知所有的和这个 model 有关的视图层也就是 view 层数据有改变,然后 view 会做出相应地反应,重新呈现新数据。

collection 和 view 和 sync
collection 是 一组 model 的集合,帮助你批量管理相关的 model。当你进行交互操作,比如用户输入,添加一个新的 model ,这个时候会在 collection 的创建一个 model ,然后调用 sync 模块,保存新 model 到数据库,同时触发一个“add”事件,通知所有的和这个 model 有关的视图层也就是 view 层数据有改变,然后 view 会做出相应地反应,重新呈现新数据。

那从技术角度是如何实现 model 变换通知 view 的呢?这里就要提到观察者模式以及控制反转了。

二、观察者模式以及控制反转
所谓观察者模式:即订阅/发布模式,一种设计模式。它是由两类对象组成,主题和观察者,主题负责发布事件,同时观察者通过订阅这些事件来观察该主体,发布者和订阅者是完全解耦的,彼此不知道对方的存在,两者仅仅共享一个自定义事件的名称。发布者自动将自身的状态的任何变化通知给观察者。在mvc框架中,核心是m(模型)->v(视图)->c(控制器)的交互通信过程,观察者模式是驱动它们的核心模式之一。

举个生活中的例子方便理解:
对于报纸的订阅投送,首先是读者,它们是订阅者,可以选择自己的居住地点,让报纸送到自己的家中。另一个角色是发行方,它们负责出版报纸。作为订阅者,数据到来的时候我们收到通知,我们消费数据,然后根据数据作出反应。只要报纸到了订阅者手中,它们就可以自行处置,有些人读完之后会将其扔到一边,有些人会向朋友转述看到的新闻,甚至还有一些会把报纸送回去。总而言之,订阅者要从发行方接收数据。作为发行方,则要发送数据。一般说来,一个发行方可能有许多订阅者,同样一个订阅者也可能会订阅多家报社的报纸。这是一种多对多的关系,需要一种策略使得订阅者能够彼此独立的发生改变,发行方能够接受任何有消费意识的订阅者。

再举个例子:
去公司面试,结束的时候,面试官对我说:“请留下你的联系方式,有消息我们会通知你”。在这里 我 就是一个订阅者,面试官是发布者,我不需要每天打电话询问面试结果,通讯的主动权掌握在面试官手上,我只需要告诉他我的联系方式。

在很多资料上面,人们认为 订阅/发布模式 和 观察者模式 是有不同的。具体的区别体现在以下两方面:
1、观察者模式主要以同步方式实现,即当某些事件发生时,被观察者可以调用所有观察者的适当方法。 而发布/订阅模式主要以异步方式实现(使用消息队列)。
2、观察者模式中,观察者 知道 被观察者。 而在发布/订阅模式中,发布者 和 订阅者 不需要彼此了解。 他们只是在消息队列的帮助下进行沟通。

在我看来,你现在其实不需要去过分纠结它们的区别,重要的是要理解他们的思想。

回到 backbone 中, 我们看看 backbone 中如何利用观察者模式。

在backbone中, events 自定义事件 模块是核心模块之一。 它在 backbone 的开头最先定义,之后所有的模块都通过

1 _.extend(某个模块.prototype, Events, {
2      //...........这里定义了 某个模块 自己的一些方法...........
3 })

继承了Events,这样所有的模块,像 Backbone.Model,Backbone.Collection,Backbone.View,Backbone.Router 等都可以使用 Events 的属性。

 1 // 绑定事件。将一个事件绑定到 `callback` 函数上。事件触发时执行回调函数`callback`。
 2 Events.on = function(name, callback, context) {};
 3
 4 // “on”的控制反转版本。
 5 Events.listenTo = function(obj, name, callback) {};
 6
 7 // 此函数作用于删除一个或多个回调。
 8 Events.off = function(name, callback, context) {};
 9
10 // 解除 当前 object 监听的 其他对象上制定事件,或者说是所有当前监听的事件。
11 Events.stopListening = function(obj, name, callback) {};
12
13 // 绑定事件只能触发一次。在第一次调用回调之后,它的监听器将被删除。如果使用空格分隔的语法传递多个事件,则处理程序将针对每个事件触发一次,而不是一次所有事件的组合。
14 Events.once = function(name, callback, context) {};
15
16 // once的反转控制版本
17 Events.listenToOnce = function(obj, name, callback) {};
18
19 // 触发一个或者多个事件,并触发所有的回调函数
20 Events.trigger = function(name) {};
21
22 // 实例,保存当前对象所监听的对象
23 var Listening = function(listener, obj) {
24     this.id = listener._listenId; //监听方的id
25     this.listener = listener; // 监听方
26     this.obj = obj; // 被监听的对象
27     this.interop = true;
28     this.count = 0; //监听了几个事件
29     this._events = void 0; // 监听事件的回调函数序列
30 };
31
32 // Listening的实例可以有 on 方法绑定事件
33 Listening.prototype.on = Events.on;
34
35 // Listening的实例用来解除正在监听的一个或多个回调。
36 Listening.prototype.off = function(name, callback) {};
37
38 // 清理监听方和事件列表之间的内存绑定。
39 Listening.prototype.cleanup = function() {};
40
41 // 等价函数命名
42 Events.bind = Events.on;
43 Events.unbind = Events.off;

这里先简单提一个概念--控制反转,上面的 Events.listenTo 就是 Events.on 的控制反转实现形式,Events.listenToOnce 就是 Events.once 的控制反转实现形式。

控制反转(Inversion of Control,缩写为IoC),这是一种主从关系的转变,一种是 A 直接控制 B ,另一种用控制器(listenTo方法)间接的让 A 控制 B 。
举个例子:
B 对象上面发生 b 事件的时候,通知 A 调用回调函数。

A.listenTo(B, “b”, callback);

当然也可以用 on 来实现同样的功能

B.on(“b”, callback, A);

控制反转 的思想其实应用在了很多地方,这里不详细讲了,后面会有专门一篇文章说一下控制反转。这里你只要知道 调用了 Event.listenTo 方法,会使得B 对象上面发生 b 事件的时候,通知 A 调用回调函数。那么应用在 mvc 之间的通信中,view.listenTo(model,”change”,changeView); 就可以实现当 model 发生变化的时候通知相应的 View 发生改变。

回到观察者模式,咱们从头梳理一下,它是如何实现的。
首先看绑定事件:
所有的绑定事件,无论是 listenTo 还是 once,最后都会通过调用 Events.on 方法进行绑定,而在 on 方法中

 1 // 绑定事件。将一个事件绑定到 `callback` 函数上。事件触发时执行回调函数`callback`。
 2 // 典型调用方式是`object.on(‘name‘, callback, context)`.
 3 // `name`是监听的事件名, `callback`是事件触发时的回调函数, `context`是回调函数上下文,即回调函数中的This(未指定时就默认为当前`object`).
 4 // 如果传递参数 `"all",任何事件的发生都会触发该回调函数。回调函数的第一个参数会传递该事件的名称。举个例子,将一个对象的所有事件代理到另一对象:
 5 // 例子:
 6 // proxy.on("all", function(eventName) {
 7 //   object.trigger(eventName);
 8 // });
 9 Events.on = function(name, callback, context) {
10     // this._events 保存所有监听事件
11     // 调用 onApi 用来绑定事件
12     // eventsApi函数参数(iteratee, events, name, callback, opts)
13     // 参数中 如果还没有this._events,那么就初始化为空对象。
14     //
15     // opts中参数:
16     // callback 事件的回调函数
17     // context 回调函数的上下文对象(即当调用on时,为context参数,当调用view.listenTo(....)时,为调用的对象如:view。)
18     // ctx 为context ,当context不存在时,为被监听的对象,如:model.on(…)或view.on(model,…)中的model
19     // listening 其实就是view._listeningTo中的某个属性值,可以看成: listening == view._listeningTo[‘l1’]
20     this._events = eventsApi(onApi, this._events || {},
21     name, callback, {
22         context: context,
23         ctx: this,
24         listening: _listening
25     });
26
27     // 处理通过 listenTo 方法调用 on 绑定的情况
28     // 在下方定义的 Events.listenTo 中会调用 on 方法来绑定事件,当你调用listenTo方法的时候(如下一行的例子1)这个时候就会产生有 _listening 的情况。
29     // 例子1:A.listenTo(B, “b”, callback);
30     // _listening:在下方 Events.listenTo 方法中,被赋值为正在监听的对象的id,例子1中的 B 的 id。赋值语句如下:
31     // var listening = _listening = listeningTo[id];
32     // 结合下方的 listenTo 方法来理解这个变量
33     if (_listening) {
34         // 定义变量监听者 listener,赋值 this._listeners;如果还没有this._listeners,初始化为空对象。
35         var listeners = this._listeners || (this._listeners = {});
36         // 将上文定义的私有全局变量_listening 赋值给 listeners[_listening.id]; 即 监听者监听的对象id。
37         listeners[_listening.id] = _listening;
38         // Allow the listening to use a counter, instead of tracking
39         // callbacks for library interop
40         // todo
41         // 允许 listening 使用计数器,而不是跟踪库互操作性回调
42         _listening.interop = false;
43     }
44
45     // 返回 this
46     return this;
47 };

20~25行:this._events 用于将订阅者缓存到对象中,

而在触发事件 Events.trigger 方法中

 1 // 触发一个或者多个事件,并触发所有的回调函数
 2 Events.trigger = function(name) {
 3     // 每个Events对象内部有一个_events对象,保存某一个事件的回调函数队列。
 4     // 如果没有监听事件,则直接返回
 5     if (!this._events) return this;
 6
 7     // 参数长度
 8     var length = Math.max(0, arguments.length - 1);
 9     // 新建一个数组
10     var args = Array(length);
11     // 在数组args中保存传递进来的除了第一个之外的其余参数,提取出来的参数最终回传递给下方定义的函数 triggerApi
12     for (var i = 0; i < length; i++) args[i] = arguments[i + 1];
13
14     // 调用下方定义的triggerApi
15     eventsApi(triggerApi, this._events, name, void 0, args);
16     return this;
17 };

其中 15 行:通过 eventsApi(triggerApi, this._events, name, void 0, args); 用于发布之前的缓存方法。

有了这两个方法 就可以实现 订阅 与 发布模式了,那么该模式到底在 backbone 中的哪里体现了呢?

Events 模块应用到的地方非常之多,在上面我们就已经说过,backbone 的所有模块都通过 extend 方法继承了 Events 中所有方法。在backbone中,我们需要自行实现数据(model)和视图(view)绑定,也就是说在 view 初始化的时候,我们需要绑定对应 model 的关系,下面是一个 view 和 model 绑定的例子:

 1 var Todo = Backbone.Model.extend({
 2     model.trigger(‘destroy‘);
 3 });
 4
 5 var TodoView = Backbone.View.extend({
 6     events: {
 7       "click a.destroy" : "clear",
 8     },
 9
10     initialize: function() {
11       this.listenTo(this.model, ‘destroy‘, this.remove);
12     },
13
14     clear: function() {
15       this.model.destroy();
16     },
17
18     remove: function() {
19       this.$el.remove();
20     }
21
22 });

这段代码不难看懂。页面中有个 a 标签,当你点击之后 会执行 clear 方法,使得当前绑定的 model 执行 destroy 方法,而这就会触发 当前 view 的 $el 被删除,这是因为 initialize 方法中

this.listenTo(this.model, ‘destroy‘, this.remove);

这里就用到了控制反转。当前 view 监听了 当前 model 的 destroy 方法,如果 model 的destroy 被触发,view 会调用 自身的 remove 方法。

此处 view 就相当于 订阅者,他订阅了 model 的 destroy 方法的调用,而 model 就相当于 发布者,他 trigger 了 destroy 方法,通知了 view 调用了 this.remove 方法。

最后:具体的 backbone 代码关于这部分的实现,还是推荐大家自己研究一遍源码,可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-study)进行参考交流,有详细的源码注释,以及知识总结。

学习并感谢:

http://neekey.net/2016/05/07/%E7%90%86%E8%A7%A3-mvc-model2-mvp-mvvm-flux/   (推荐想要了解 理解 MVC / Model2 / MVP / MVVM / Flux 之间区别的同学阅读一下)

http://www.reqianduan.com/2729.html

https://stackoverflow.com/questions/15594905/difference-between-observer-pub-sub-and-data-binding

http://www.cnblogs.com/constantince/p/5243064.html

https://segmentfault.com/a/1190000000480727

时间: 2024-12-28 00:32:34

【 js 基础 】【 源码学习 】backbone 源码阅读(一)的相关文章

【 js 基础 】【 源码学习 】源码设计 (持续更新)

学习源码,除了学习对一些方法的更加聪明的代码实现,同时也要学习源码的设计,把握整体的架构.(推荐对源码有一定熟悉了之后,再看这篇文章) 目录结构:第一部分:zepto 设计分析第二部分:underscore 设计分析 第一部分: zepto 设计分析zepto 是一个轻量级的 Javascript 库.相对于 jquery 来说在 size 上更加小,主要是定位于移动设备.它是非常好的学习源码的入门级 javascript 库.这里重点说一下,这个库的设计,而对于详细的源码学习大家可以 star

async源码学习 - 全部源码

因为工作需要,可能我离前端走远了,偏node方向了.所以异步编程的需求很多,于是乎,不得不带着学习async了. 我有个习惯,用别人的东西之前,喜欢稍微搞明白点,so就带着看看其源码. github: https://github.com/caolan/async 文档:http://caolan.github.io/async/ 里面提供的工具方法,控制流程方法还是很多的.所以需要哪些方法,就看相应的源码. 下面是其全部源码. (function (global, factory) { typ

javascript(js)基础之dom学习

dom学习 <img id='xx'.. onclick='aa()'> functon aa(){ xx1=document.getElementById("xx") //下面对xx1进行操作 } bom介绍:浏览器对象模型 因为浏览器企业太多,w3c定义了一个做浏览器的规范 规定 ----------- dom介绍/学习:文档对象模型 dom树 例子 <script language="JavaScript"> function text

Spark 消息队列机制源码学习

源码学习 spark源码注释中有下面一句话: Asynchronously passes SparkListenerEvents to registered SparkListeners 即所有spark消息SparkListenerEvents 被异步的发送给已经注册过的SparkListeners. 在SparkContext中, 首先会创建LiveListenerBus实例,这个类主要功能如下: 保存有消息队列,负责消息的缓存 保存有注册过的listener,负责消息的分发 该类的继承层次

【 js 基础 】【 源码学习 】backbone 源码阅读(三)

最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-study)进行参考交流,有详细的源码注释,以及知识总结,同时 google 一下 backbone 源码,也有很多优秀的文章可以用来学习. 我这里主要记录一些偏设计方向的知识点.这篇文章主要讲 backbone.sync 中用到的 Rest 和 CRUD. 首先我们简单了解一下 REST: REST :

【 js 基础 】【 源码学习 】backbone 源码阅读(二)

最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-study)进行参考交流,有详细的源码注释,以及知识总结,同时 google 一下 backbone 源码,也有很多优秀的文章可以用来学习. 我这里主要记录一些偏设计方向的知识点.这篇文章主要讲 控制反转. 一.控制反转 上篇文章有说到控制反转,但只是简略的举了个例子,在这里我们详细说一下这个知识点,它其实

Vue.js 源码学习之Flag篇

The Progressive JavaScript Framework --vuejs.org 起因 第一次接触 Vue.js 是因为要做一个通讯录的外包项目,这个项目要有前台展示和中后台管理,从轮子做起肯定是不明智的选择,所以当时初步定下的是 Vue.js + Element UI 的技术栈. 项目过程很漫长,因为给的钱实在是可有可无,权当是学习了. 项目的接口是交给了同学. 整个项目采用的是钱后端分离的开发模式,我做我的页面,他做他的接口. 项目出了两个版本,做的时候,中间就强行的看文档.

jQuery源码学习感想

还记得去年(2015)九月份的时候,作为一个大四的学生去参加美团霸面,结果被美团技术总监教育了一番,那次问了我很多jQuery源码的知识点,以前虽然喜欢研究框架,但水平还不足够来研究jQuery源码,那时我不明白他们为何要求那么高,现在才知道,原来没那么高,他问的都是jQuery最基本的框架架构,不过对于不知道的来说,再简单我也是不知道,那时写了一篇博文去吐槽了一下,那时候也是我自己真正激发自己的时候,那时候我说我一定要搞好自己的jQuery基础,没想到那么快就实现了,一个月的源码学习时间就结束

Backbone源码解读(一)事件模块

Backbone源码浅读: 前言: Backbone是早起的js前端MV*框架之一,是一个依赖于underscore和jquery的轻量级框架,虽然underscore中基于字符串拼接的模板引擎相比如今基于dom元素双向绑定的模板引擎已显得落伍,但backbone作为引领前端mv*开发模式的先驱之一,依然是麻雀虽小却设计精妙,了解其设计与结构对于想一探mv*框架的初学者来说仍会获益匪浅. Backbone结构: Backbone分为几个部分:其中最核心的是Event事件模块,提供了实现事件与观察