【 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 的开头最先定义,之后所有的模块都通过
- _.extend(某个模块.prototype, Events, {
- //...........这里定义了 某个模块 自己的一些方法...........
- })
- // 绑定事件。将一个事件绑定到 `callback` 函数上。事件触发时执行回调函数`callback`。
- Events.on = function(name, callback, context) {};
- // “on”的控制反转版本。
- Events.listenTo = function(obj, name, callback) {};
- // 此函数作用于删除一个或多个回调。
- Events.off = function(name, callback, context) {};
- // 解除 当前 object 监听的 其他对象上制定事件,或者说是所有当前监听的事件。
- Events.stopListening = function(obj, name, callback) {};
- // 绑定事件只能触发一次。在第一次调用回调之后,它的监听器将被删除。如果使用空格分隔的语法传递多个事件,则处理程序将针对每个事件触发一次,而不是一次所有事件的组合。
- Events.once = function(name, callback, context) {};
- // once的反转控制版本
- Events.listenToOnce = function(obj, name, callback) {};
- // 触发一个或者多个事件,并触发所有的回调函数
- Events.trigger = function(name) {};
- // 实例,保存当前对象所监听的对象
- var Listening = function(listener, obj) {
- this.id = listener._listenId; //监听方的id
- this.listener = listener; // 监听方
- this.obj = obj; // 被监听的对象
- this.interop = true;
- this.count = 0; //监听了几个事件
- this._events = void 0; // 监听事件的回调函数序列
- };
- // Listening的实例可以有 on 方法绑定事件
- Listening.prototype.on = Events.on;
- // Listening的实例用来解除正在监听的一个或多个回调。
- Listening.prototype.off = function(name, callback) {};
- // 清理监听方和事件列表之间的内存绑定。
- Listening.prototype.cleanup = function() {};
- // 等价函数命名
- Events.bind = Events.on;
- 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 方法中
- // 绑定事件。将一个事件绑定到 `callback` 函数上。事件触发时执行回调函数`callback`。
- // 典型调用方式是`object.on('name', callback, context)`.
- // `name`是监听的事件名, `callback`是事件触发时的回调函数, `context`是回调函数上下文,即回调函数中的This(未指定时就默认为当前`object`).
- // 如果传递参数 `"all",任何事件的发生都会触发该回调函数。回调函数的第一个参数会传递该事件的名称。举个例子,将一个对象的所有事件代理到另一对象:
- // 例子:
- // proxy.on("all", function(eventName) {
- // object.trigger(eventName);
- // });
- Events.on = function(name, callback, context) {
- // this._events 保存所有监听事件
- // 调用 onApi 用来绑定事件
- // eventsApi函数参数(iteratee, events, name, callback, opts)
- // 参数中 如果还没有this._events,那么就初始化为空对象。
- //
- // opts中参数:
- // callback 事件的回调函数
- // context 回调函数的上下文对象(即当调用on时,为context参数,当调用view.listenTo(....)时,为调用的对象如:view。)
- // ctx 为context ,当context不存在时,为被监听的对象,如:model.on(…)或view.on(model,…)中的model
- // listening 其实就是view._listeningTo中的某个属性值,可以看成: listening == view._listeningTo[‘l1’]
- this._events = eventsApi(onApi, this._events || {},
- name, callback, {
- context: context,
- ctx: this,
- listening: _listening
- });
- // 处理通过 listenTo 方法调用 on 绑定的情况
- // 在下方定义的 Events.listenTo 中会调用 on 方法来绑定事件,当你调用listenTo方法的时候(如下一行的例子1)这个时候就会产生有 _listening 的情况。
- // 例子1:A.listenTo(B, “b”, callback);
- // _listening:在下方 Events.listenTo 方法中,被赋值为正在监听的对象的id,例子1中的 B 的 id。赋值语句如下:
- // var listening = _listening = listeningTo[id];
- // 结合下方的 listenTo 方法来理解这个变量
- if (_listening) {
- // 定义变量监听者 listener,赋值 this._listeners;如果还没有this._listeners,初始化为空对象。
- var listeners = this._listeners || (this._listeners = {});
- // 将上文定义的私有全局变量_listening 赋值给 listeners[_listening.id]; 即 监听者监听的对象id。
- listeners[_listening.id] = _listening;
- // Allow the listening to use a counter, instead of tracking
- // callbacks for library interop
- // todo
- // 允许 listening 使用计数器,而不是跟踪库互操作性回调
- _listening.interop = false;
- }
- // 返回 this
- return this;
- };
20~25行:this._events 用于将订阅者缓存到对象中,
而在触发事件 Events.trigger 方法中
- // 触发一个或者多个事件,并触发所有的回调函数
- Events.trigger = function(name) {
- // 每个Events对象内部有一个_events对象,保存某一个事件的回调函数队列。
- // 如果没有监听事件,则直接返回
- if (!this._events) return this;
- // 参数长度
- var length = Math.max(0, arguments.length - 1);
- // 新建一个数组
- var args = Array(length);
- // 在数组args中保存传递进来的除了第一个之外的其余参数,提取出来的参数最终回传递给下方定义的函数 triggerApi
- for (var i = 0; i < length; i++) args[i] = arguments[i + 1];
- // 调用下方定义的triggerApi
- eventsApi(triggerApi, this._events, name, void 0, args);
- return this;
- };
其中 15 行:通过 eventsApi(triggerApi, this._events, name, void 0, args); 用于发布之前的缓存方法。
有了这两个方法 就可以实现 订阅 与 发布模式了,那么该模式到底在 backbone 中的哪里体现了呢?
Events 模块应用到的地方非常之多,在上面我们就已经说过,backbone 的所有模块都通过 extend 方法继承了 Events 中所有方法。在backbone中,我们需要自行实现数据(model)和视图(view)绑定,也就是说在 view 初始化的时候,我们需要绑定对应 model 的关系,下面是一个 view 和 model 绑定的例子:
- var Todo = Backbone.Model.extend({
- model.trigger('destroy');
- });
- var TodoView = Backbone.View.extend({
- events: {
- "click a.destroy" : "clear",
- },
- initialize: function() {
- this.listenTo(this.model, 'destroy', this.remove);
- },
- clear: function() {
- this.model.destroy();
- },
- remove: function() {
- this.$el.remove();
- }
- });
这段代码不难看懂。页面中有个 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)进行参考交流,有详细的源码注释,以及知识总结。
【 js 基础 】【 源码学习 】backbone 源码阅读(一)的更多相关文章
- 【 js 基础 】【 源码学习 】源码设计 (更新了backbone分析)
学习源码,除了学习对一些方法的更加聪明的代码实现,同时也要学习源码的设计,把握整体的架构.(推荐对源码有一定熟悉了之后,再看这篇文章) 目录结构:第一部分:zepto 设计分析 第二部分:unders ...
- 【 js 基础 】【 源码学习 】源码设计 (持续更新)
学习源码,除了学习对一些方法的更加聪明的代码实现,同时也要学习源码的设计,把握整体的架构.(推荐对源码有一定熟悉了之后,再看这篇文章) 目录结构:第一部分:zepto 设计分析第二部分:undersc ...
- Vue2.x源码学习笔记-源码目录结构整理
先从github上下载或者clone一个vue分支项目 https://github.com/vuejs/vue 查看下目录结果 先列出一些目录 Vue |— build 打包相关的配置文件,其中最重 ...
- 【JS基础语法】---学习roadmap---6 parts
JS基础语法---roadmap Part 1 - 2: Part 3 - 4: Part 5 - 6
- VUE 源码学习01 源码入口
VUE[version:2.4.1] Vue项目做了不少,最近在学习设计模式与Vue源码,记录一下自己的脚印!共勉!注:此处源码学习方式为先了解其大模块,从宏观再去到微观学习,以免一开始就研究细节然后 ...
- async源码学习 - 全部源码
因为工作需要,可能我离前端走远了,偏node方向了.所以异步编程的需求很多,于是乎,不得不带着学习async了. 我有个习惯,用别人的东西之前,喜欢稍微搞明白点,so就带着看看其源码. github: ...
- Thrift 源码学习一——源码结构
Thrift 客户端与服务端的交互图 源码结构 传输层 TTransport: TTransport:客户端传输层抽象基础类,read.write.flush.close 等方法 TSocket 与 ...
- nginx源码学习_源码结构
nginx的优秀除了体现在程序结构以及代码风格上,nginx的源码组织也同样简洁明了,目录结构层次结构清晰,值得我们去学习.nginx的源码目录与nginx的模块化以及功能的划分是紧密结合,这也使得我 ...
- springFramework 源码学习之源码下载与编译
1.源码下载 Spring已经将源码从svn迁移到了git.对于习惯了svn的人来说依然可以svn checkout,最好checkout到英文路径下,本人中文路径编译不过,具体原因不明,路径: ht ...
- Java容器源码学习--ArrayList源码分析
ArrayList实现了List接口,它的底层数据结构是数组,因此获取容器中任意元素值的时间复杂度为O(1),新增或删除元素的时间复杂度为O(N).每一个ArrayList实例都有一个capacity ...
随机推荐
- [转]AngularJS 之 ng-options指令
原文地址 一. 基本下拉效果(lable for value in array) 其中select标签中的ng-model属性必须有,其值为选中的对象或属性值. <div ng-controll ...
- PHPStorm中的快捷键
1.Ctrl+Alt+L 格式化代码 2.windows下按下快捷`Ctrl`+`Shift`+`-`,这样就能折叠所有代码了. 3.windows下按下快捷`Ctrl`+`Shift`+`+`,这样 ...
- 移动端APP CSS Reset及注意事项CSS重置
@charset "utf-8"; * { -webkit-box-sizing: border-box; box-sizing: border-box; } //禁止文本缩放 h ...
- 查看java线程cpu占用情况的脚本
#!/bin/bash [ $# -ne ] && exit jstack $ >/tmp/jstack.log -o THREAD,tid,time|sort -k2nr| s ...
- 【LeetCode】91. Decode Ways
题目: A message containing letters from A-Z is being encoded to numbers using the following mapping: ' ...
- 【LeetCode】49. Group Anagrams
题目: Given an array of strings, group anagrams together. For example, given: ["eat", " ...
- Chapter 4. The MPEG-4 and H.264 Standards
本章节介绍一些关于MPEG-4标准与H.264标准的基本知识 比较重要的是第95页关于两种标准的对比表格.其他部分没有什么特别重要的细节.
- Linux之定时任务补充
定时任务两实例 例1: 每分钟打印一次自己的名字拼音全拼到“/server/log/自己的名字命名的文件”中. [root@chengliang log]# mkdir -p /server/log/ ...
- flex sqlite基本用法
flex sqlite基本用法 页面 <?xml version="1.0" encoding="utf-8"?> <s:WindowedAp ...
- Visiual Studio CLR20r3
问题事件名称: CLR20r3 解决方法: 步骤1:开始-->所有程序-->Microsoft Visual Studio 2012-->Visual Studio To ...