Backbone源码解析(二):Model(模型)模块
Model(模型)模块在bk框架中的作用主要是存储处理数据,它对外和对内都有很多操作数据的接口和方法。它与视图(Views)模块精密联系着,通过set函数改变数据结构从而改变视图界面的变化。下面我们来看一下它的构造函数:
//传入两个参数,a需要保存的数据,b是该模型所属的colltion,如果没有可以为空
e.Model = function(a, b) {
var c;
a || (a = {});
//defaults 是默认的属性,在extend该模型的时候可以设置一些默认必选的属性。
if (c = this.defaults) f.isFunction(c) && (c = c.call(this)),
a = f.extend({},
c, a);
//设置内部对外的默认属性集合
this.attributes = {};
//设置私有属性集合
this._escapedAttributes = {};
//设置模型的cid值,该值唯一,可以作为访问该对象的key
this.cid = f.uniqueId("c");
//将数据填充到模型中去,不触发set事件,因为silent 设置是 true
this.set(a, {
silent: !0
});
//设置默认的_changed值,该值会在set事件作为该模型对象的值是否改变的判断标识。
this._changed = !1;
//克隆一份数据给私有变量_previousAttributes.
this._previousAttributes = f.clone(this.attributes);
//如果你传入b ,就表示这个model属于哪个collection
if (b && b.collection) this.collection = b.collection;
//执行initialize 初始化函数
this.initialize(a, b)
};
Model的构造函数比较简单,理解起来并不复杂,它只是一些属性和数据的初始化。在实际应用中我们可以来试试看怎么样构造一个模型对象:
上面的代码中我们构造了一个简单的模型对象,在那里我们并没有设置一些初始化函数和属性,而是给这个模型对象里面放入了些数据。那么现在可以看看,Model实例化后得到的结果,在浏览器中查看model我们会看到:
它拥有一些已经设置过的属性,并且最重要的是它继承了Model的所有方法。在__proto__里面我们可以看到这些方法的具体应用。下面我们就里面的这些方法做简要的说明:
//这一句我们在Event模块里面有介绍到,将e.Event和其他一些方法属性扩展到model里面去,
f.extend(e.Model.prototype, e.Events, {
_previousAttributes: null,
_changed: !1,
idAttribute: "id",
//初始化函数
initialize: function() {},
//取出储存在自身的数据,并且转换为json格式的数据,该方法为View提供基础的json数据进行界面渲染。
toJSON: function() {
/*
可以看到,这里使用的是underscore.js的clone,通过查阅underscore的clone方法,
我们可以找到clone是对象的克隆,也就是说使用clone方法返回的是开辟了新的内存的对象
之所以这样做,是为了保护this.attributes属性不被外部所篡改,因为bk只为我们提供了一个唯一合法改变model的attributes值的方法,那就是set方法。它不允许有其他的入口非法改变这个值。我们以后可以看到,通过其他接口是可以改变的,但是无法触发相应事件
因为bk没有监听直接修改值的方法,无法更新视图,这样做是违背观察者模式的原理的。所以比如在外部使用ModelA.toJSON().xxx = xxx;这个方法是无法改变this.attributes的。
*/
return f.clone(this.attributes)
},
/*访问某个属性,事实上我们在这里发现了bk的一个bug,或者说是缺陷:如果此时this.atributes[a]是以一个对象直接量的形式存在(比如{a:2}),那么我们就能通过外部赋值改变它。ModelA.get('xxx').a = 3;此时this.attributes['xxx'].a的值就是3了。*/
get: function(a) {
return this.attributes[a]
},
//这个方法与get方法相同,同样是返回某个属性的。
escape: function(a) {
var b;
if (b = this._escapedAttributes[a]) return b;
b = this.attributes[a];
return this._escapedAttributes[a] = (b == null ? "": "" + b).replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\//g, "/")
},
//检查模型是否有某个属性
has: function(a) {
return this.attributes[a] != null
},
/*
set方法是model中一个经常用到的方法,也是模型驱动视图概念中的一个主要方法。事实上我们可以看到这里采用的是在js设计模式中提到过的一种设计模式(此处说明的不是它的外在关联作用,而是内部设计)这就是:外观模式,也就是说
set本身只是一个对外借口,它实际上是多个接口的集成,构成了它作为一个观察者的角色。这个三个方法我们在下面的代码中说明。
*/
set: function(a, b) {
b || (b = {});
if (!a) return this;
if (a.attributes) a = a.attributes;
var c = this.attributes,
d = this._escapedAttributes;
//判断数据是否验证通过
if (!b.silent && this.validate && !this._performValidation(a, b)) return ! 1;
if (this.idAttribute in a) this.id = a[this.idAttribute];
var e = this._changing;
this._changing = !0;
for (var g in a) {
var h = a[g];
//第一个 isEqual 方法是underscore判断传入的对象和本身存储的对象是否相等,追踪underscore源码,可以发现这个方法是一个多次循环判断,它最终匹配的是数字和字符串的值,所以,不要传入多层嵌套对象进去进行对比(jq对象千万不要传!很容易造成内存泄露)。
if (!f.isEqual(c[g], h)) c[g] = h,
delete d[g],
this._changed = !0,
//其次是this.trigger方法,前面已经提到过是触发绑定的事件。在某个属性的值改变是,会触发该方法。我们看到change后面加了一个: g变量,这个g就是属性名。
b.silent || this.trigger("change:" + g, this, h, b)
//最后是change方法。这个方法的作用是重置私有属性和一些基础属性。
} ! e && !b.silent && this._changed && this.change(b);
this._changing = !1;
return this
},
//从内部属性散列表中删除指定属性(attribute)。工作原理和set方法基本相似。在此不做说明。
unset: function(a, b) {
if (! (a in this.attributes)) return this;
b || (b = {});
var c = {};
c[a] = void 0;
if (!b.silent && this.validate && !this._performValidation(c, b)) return ! 1;
delete this.attributes[a];
delete this._escapedAttributes[a];
a == this.idAttribute && delete this.id;
this._changed = !0;
b.silent || (this.trigger("change:" + a, this, void 0, b), this.change(b));
return this
},
//从model中删除所有属性,工作原理和set方法基本相似。在此不做说明。
clear: function(a) {
a || (a = {});
var b, c = this.attributes,
d = {};
for (b in c) d[b] = void 0;
if (!a.silent && this.validate && !this._performValidation(d, a)) return ! 1;
this.attributes = {};
this._escapedAttributes = {};
this._changed = !0;
if (!a.silent) {
for (b in c) this.trigger("change:" + b, this, void 0, a);
this.change(a)
}
return this
},
/* 以下三个方法都本地模型和服务器之间的关联方法。通过zepto或者jq的ajax方法将本地模型序列化后关联到服务器上,然后通过查找,修改,更新,销毁来操作那些数据。这些数据一般不保存在本地*/
//fethc是读数据的方法我们可以看到里面调用了set方法,说明如果读入的模型和不本地的不一致会触发视图改变。
fetch: function(a) {
a || (a = {});
var b = this,
c = a.success;
a.success = function(d, e, f) {
if (!b.set(b.parse(d, f), a)) return ! 1;
c && c(b, d)
};
a.error = i(a.error, b, a);
return (this.sync || e.sync).call(this, "read", this, a)
},
//save提供的事create和更新的接口。将本地的传到服务器上做保存。
save: function(a, b) {
b || (b = {});
if (a && !this.set(a, b)) return ! 1;
var c = this,
d = b.success;
b.success = function(a, e, f) {
if (!c.set(c.parse(a, f), b)) return ! 1;
d && d(c, a, f)
};
b.error = i(b.error, c, b);
var f = this.isNew() ? "create": "update";
return (this.sync || e.sync).call(this, f, this, b)
},
//摧毁一个服务器的模型,如果你不打算再用它的话。
destroy: function(a) {
a || (a = {});
if (this.isNew()) return this.trigger("destroy", this, this.collection, a);
var b = this,
c = a.success;
a.success = function(d) {
b.trigger("destroy", b, b.collection, a);
c && c(b, d)
};
a.error = i(a.error, b, a);
return (this.sync || e.sync).call(this, "delete", this, a)
},
//返回模型资源在服务器上位置的相对 URL
url: function() {
var a = k(this.collection) || this.urlRoot || l();
if (this.isNew()) return a;
return a + (a.charAt(a.length - 1) == "/" ? "": "/") + encodeURIComponent(this.id)
},
parse: function(a) {
return a
},
//返回该模型的具有相同属性的新实例。
clone: function() {
//this.constructor指的就是model的构造函数
return new this.constructor(this);
},
//判断Model是否是新建的。
isNew: function() {
return this.id == null
},
//上文提到过,重置一些基础,触发change事件。使用clone方法,确保两份数据独立开来。
change: function(a) {
this.trigger("change", this, a);
this._previousAttributes = f.clone(this.attributes);
this._changed = !1
},
//判断某个属性是否发生了变化
hasChanged: function(a) {
if (a) return this._previousAttributes[a] != this.attributes[a];
return this._changed
},
//这个方法也是检测模型的属性是否发生了变化,我们可以看到里面同样是用到了isEqual方法进行验证,所以最好不要传入jq对象去比较验证。
changedAttributes: function(a) {
a || (a = this.attributes);
var b = this._previousAttributes,
c = !1,
d;
for (d in a) f.isEqual(b[d], a[d]) || (c = c || {},
c[d] = a[d]);
return c
},
//返回原始值得某个属性。
previous: function(a) {
if (!a || !this._previousAttributes) return null;
return this._previousAttributes[a]
},
//返回一个原始的值,当model的attribute属性发生改变,我们可以通过此方法放回最原始的值。_previousAttributes一直保存的是最初传入的值
previousAttributes: function() {
return f.clone(this._previousAttributes)
},
//对传入的数据做验证
_performValidation: function(a, b) {
//bk本身不提供validate方法,是我们在实例化的时候自己扩展的上去的方法。
var c = this.validate(a);
if (c) return b.error ? b.error(this, c, b) : this.trigger("error", this, c, b),
!1;
return ! 0
}
});
这些方法都挂载到了Model的原型链上,通过extend函数我们可以自定义一些基础的方法给它,或者说,单你希望某个内置的方法不如意的时候你可以写一个同名方法覆盖它们,不过除了少数几个方法(比如initialize)外不建议你那样做,除非你希望改写它的整个框架。然后再通过new 关键字实例化一个对象model,于是model得到了Model模块的所有方法。需要特别说明的是,通过var x = Backbone.model.extend()这样的形式扩展的方法只会应用到x的实例化对象上,因为和源码里面的方式不同,一个是在对象上扩展,一个是在prototype原型上扩展。为了验证我们所说的,我们给Model对象上附加自己的方法:
接下来我们再浏览器中控制台中输入model,得到如下结果:
第一个箭头所指的是o也就是Model上的方法,我们可以看到myFunction就在里面,第二个箭头指的是e.model,也就是e.mode原型上的方法,我们可以看到所有其他的没被覆盖的方法都在里面。下面,我们可以看一下这些方法的具体应用:
toJSON方法的应用:获取内部数据;
perviousAttributes();当attributes在外部被改变的时候,可以恢复原始的数据:
接下来我们对一开始实例化函数最一些小小的修改,来测试set方法的效果:
首先,我们扩展了Model对象,并且覆盖了initialize方法,也就是说实例化后立即绑定了change:a方法,即单a属性改变的时候,要求浏览器会输出一段字符串。在set方法里面我们对trigger做了说明,change:a是对a属性的监听。我们执行set函数后会看到如下结果:
Model的核心概念就是数据,它最主要的方法还是存储和处理数据,至于驱动视图这个概念,我们在前面的Event模块中已经提到过了,这个工作是一个各个模块交互的工作,Events,Views和Model都提供了接口来实现该设计模式。
小提示:change:a方法的时候键值和键名之间不能有空格,源码里面做了死规定!当然你可以改改。卤煮曾经被此问题困惑很久。都是吃了没仔细阅读API的大亏。
Backbone源码解析(二):Model(模型)模块的更多相关文章
- Laravel源码解析之model(代码)
本篇文章给大家带来的内容是关于Laravel源码解析之model(代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 前言 提前预祝猿人们国庆快乐,吃好.喝好.玩好,我会在电视上看 ...
- Mybatis源码解析(二) —— 加载 Configuration
Mybatis源码解析(二) -- 加载 Configuration 正如上文所看到的 Configuration 对象保存了所有Mybatis的配置信息,也就是说mybatis-config. ...
- RxJava2源码解析(二)
title: RxJava2源码解析(二) categories: 源码解析 tags: 源码解析 rxJava2 前言 本篇主要解析RxJava的线程切换的原理实现 subscribeOn 首先, ...
- Sentinel源码解析二(Slot总览)
写在前面 本文继续来分析Sentinel的源码,上篇文章对Sentinel的调用过程做了深入分析,主要涉及到了两个概念:插槽链和Node节点.那么接下来我们就根据插槽链的调用关系来依次分析每个插槽(s ...
- erlang下lists模块sort(排序)方法源码解析(二)
上接erlang下lists模块sort(排序)方法源码解析(一),到目前为止,list列表已经被分割成N个列表,而且每个列表的元素是有序的(从大到小) 下面我们重点来看看mergel和rmergel ...
- iOS即时通讯之CocoaAsyncSocket源码解析二
原文 前言 本文承接上文:iOS即时通讯之CocoaAsyncSocket源码解析一 上文我们提到了GCDAsyncSocket的初始化,以及最终connect之前的准备工作,包括一些错误检查:本机地 ...
- jQuery 源码解析二:jQuery.fn.extend=jQuery.extend 方法探究
终于动笔开始 jQuery 源码解析第二篇,写文章还真是有难度,要把自已懂的表述清楚,要让别人听懂真的不是一见易事. 在 jQuery 源码解析一:jQuery 类库整体架构设计解析 一文,大致描述了 ...
- Common.Logging源码解析二
Common.Logging源码解析一分析了LogManager主入口的整个逻辑,其中第二步生成日志实例工厂类接口分析的很模糊,本随笔将会详细讲解整个日志实例工厂类接口的生成过程! (1).关于如何生 ...
- element-ui 源码解析 二
Carousel 走马灯源码解析 1. 基本原理:页面切换 页面切换使用的是 transform 2D 转换和 transition 过渡 可以看出是采用内联样式来实现的 举个栗子 <div : ...
随机推荐
- MFC学习笔记
获取窗口句柄 FindWindow 根据窗口名获取 GetSafehWnd 取你程序所在窗口类的句柄 GetActiveWindow ...
- mysql行列调换方法
行变列,列变行 财务样式模板: CREATE TABLE `grade` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `name` VARCHAR ...
- 急!JDBC问题,发生通信错误。错误位置:Reply.fill()。消息:数据不足。 ERRORCODE=-4499, SQLSTATE=08001
代码如下:Class.forName("com.ibm.db2.jcc.DB2Driver");Connection conn = DriverManager.getConnect ...
- 关于几种编码详解(Unicode,UTF-8,GB系列)
最近学Python,老是被编码的问题搞得晕乎乎的,晚上看了好多篇博客,整理出来一个比较清晰的关于几种编码以及字符集的思路. 主要参考:http://blog.sina.com.cn/s/blog_6d ...
- adb devices 端口占用
一. 1.通过cmd命令,输入adb devices查看连接设备时,报错 2 .通过adb nodaemon server 查看adb server绑定的端口.提示“通过每个套接字地址只能使用一次” ...
- SQL Server 大数据量分页建议方案
简单的说就是这个 select top(20) * from( select *, rowid = row_number() over(order by xxx) from tb with(noloc ...
- Xamarin +vs2015 Android 开发GPS loaction 返回 null 小结
最近公司要开发android 所以研究了一下Xamarin to android 中个GPS 废话不多说,说重点. 想获取手机上的gps信息必不可少的就是要使用 LocationManager Lo ...
- Qt Qwt之坐标轴移动
最近接触到个pro需要做到这方面,于是找了相关材料,也跟好些人讨论,目前就最简单的使用方法,通过按钮触发去控制 X,Y轴的移动,比例自己定义 这个是X轴放大的 QwtInterval tempInte ...
- 数位DP
题意:(hdu 4734) 我们定义十进制数x的权值为f(x) = a(n)*2^(n-1)+a(n-1)*2(n-2)+...a(2)*2+a(1)*1,a(i)表示十进制数x中第i位的数字. 题目 ...
- 学习maven的使用,看到一篇很实用的入门教程(菜鸟级入门)
一.前言 早就知道maven 在java 项目的管理方面名声显赫,于是就想着学习掌握之,于是查阅了大量文档.发现这些文档的作者都是java 的大腕,大多都是站在掌握了一定maven 基 ...