Backbone Model 源码简谈 (版本:1.1.0 基础部分完毕)
Model工厂
作为model的主要函数,其实只有12行,特别的简练
var Model = Backbone.Model = function(attributes, options) {
var attrs = attributes || {};
options || (options = {});
this.cid = _.uniqueId('c');
this.attributes = {};
if (options.collection) this.collection = options.collection;
if (options.parse) attrs = this.parse(attrs, options) || {};//parse 889
attrs = _.defaults({}, attrs, _.result(this, 'defaults'));//将参数1, _.result(this, 'defaults')对象的属性合并且返回给attr
this.set(attrs, options);
this.changed = {};
this.initialize.apply(this, arguments);
};
通常情况下,我们在使用Model之前,需要调用extend函数对Model进行自定义扩展后,再new 一下,
var Man=Backbone.Model.extend({
initialize:function(){
this.bind('change:name',function(){
console.log('change');
});
},
defaults:{name:'jack',sex:'man'},
sayName:function(){
console.log(this.get('name'));
},
validate:function(attributes){
if(attributes.name==''){
console.log('fuck');
}
}
});
var mm=new Man();
这个过程触发了两个十分重要的函数:
1. this.set(attrs, options); 先调用一次set,将我们extend中的defaults对象的键值对儿设置到Model的attributes中去,set方法在Model中是一个重要方法
2. this.initialize.apply(this, arguments); 调用我们extend中定义的 initialize方法
也和backbone的其他模块有了瓜葛:
1.if (options.collection) this.collection = options.collection; 针对collection
调用extend函数扩展了Model
花开两朵各表一支,先来说说Model这个模块下的操作:
从set引出的一串操作:set是一个对象的属性,然后引用了underscore 的 extend方法将这个对象扩展到Model的原型上
set(key,val,options)函数是一个复杂的函数,下面请看源代码:
set: function(key, val, options) {
var attr, attrs, unset, changes, silent, changing, prev, current;
if (key == null) return this; // Handle both `"key", value` and `{key: value}` -style arguments.
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
} options || (options = {}); // Run validation.
if (!this._validate(attrs, options)) return false; // Extract attributes and options.
unset = options.unset;
silent = options.silent;
changes = [];
changing = this._changing;
this._changing = true; if (!changing) {
this._previousAttributes = _.clone(this.attributes);
this.changed = {};
}
current = this.attributes, prev = this._previousAttributes; // Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; // For each `set` attribute, update or delete the current value.
for (attr in attrs) {
val = attrs[attr];
if (!_.isEqual(current[attr], val)) changes.push(attr);
if (!_.isEqual(prev[attr], val)) {
this.changed[attr] = val;
} else {
delete this.changed[attr];
}
unset ? delete current[attr] : current[attr] = val;
} // Trigger all relevant attribute changes.
if (!silent) {
if (changes.length) this._pending = true;
for (var i = 0, l = changes.length; i < l; i++) {
this.trigger('change:' + changes[i], this, current[changes[i]], options);
}
} // You might be wondering why there's a `while` loop here. Changes can
// be recursively nested within `"change"` events.
if (changing) return this;
if (!silent) {
while (this._pending) {
this._pending = false;
this.trigger('change', this, options);
}
}
this._pending = false;
this._changing = false;
return this;
}
options参数的一些属性:
unset:如果为true,从attributes中delete掉某个属相
silent: 如果为true,不会触发change事件
validate:如果为true,将允许对set的属性进行验证操作
1.首先进行参数处理前的准备工作:
如果key是字符串一切都好办,把key和val存在attr这个临时对象里。如果key是object,那么val作为options,
2.运行model内置_validate验证函数:该函数功能参看下面的工具函数
3.分拣options中的属性,并且制造changing,其中this._changing属性仅在 set 和 changedAttributes 这两个函数中涉及。
4.开始做真正赋值之前的准备工作:把现有的attributes值存放到 _previousAttributes 中,并生成changed属性,这个操作就注定changed一次性保存属性更改。
5.开始进行循环校验赋值操作,将修改后的值保存到changed属性中。
6.针对options中约束进行处理:
如果options中定义了unset为true,那么从attributes中清除这些属性。
如果定义了silent为true,那么将触发在model-class绑定的 change事件,注意只能是 change事件。接下来执行没有确定属性名称的单纯 change 事件,这就意味着每又一次属性改变就要执行依次 纯change 事件
7.返回当前引用对象 也就是 model-object
set分析完毕。
从set函数分析我们得到的结论:
1.set所操作的attributes是model-object的attributes,而不是model-class原型上的attributes,这就导致我们在声明model-class extend的attributes毫无用处
2. unset 标注所执行的操作并不会计入到 changed属性中
3. extend方法参数中的default属性,在工厂函数执行的时候就用set方法放到model-object 的attributes中去了。
再说extend函数:
这个extend函数并没有存在于Model的原型中,而是Model一个私有方法,具体实现方法如下:
var extend = function(protoProps, staticProps) {
var parent = this;
var child; // The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent's constructor.
if (protoProps && _.has(protoProps, 'constructor')) {//检查protoProps是否为原型
child = protoProps.constructor;
} else {
child = function(){ return parent.apply(this, arguments); };
} // Add static properties to the constructor function, if supplied.
_.extend(child, parent, staticProps); // Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate; // Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps) _.extend(child.prototype, protoProps); // Set a convenience property in case the parent's prototype is needed
// later.
child.__super__ = parent.prototype; return child;
}; // Set up inheritance for the model, collection, router, view and history.
Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
这个extend实在有趣,因为它翻来覆去的操作原型链达到扩展对象具有私有原型的目的。看看我们extend一个Model需要进行哪些步骤:
1.先设立一个child函数,这个child函数返回了 Model自执行时的结果,很显然从前面我们知道这根本没有什么返回值,主要就是为了执行以下Model。
2.使用underscore的extend扩展child,当然这个扩展无非就是把Model的属性和extend第二个参数的属性加到child上。
3.建立一个surrogate函数,function(){ this.constructor = child; };
4.将surrogate函数的原型赋值为Model的原型,就是包含set的那一串
5.然后让child的原型变为 new surrogate;
6.然后再用underscore的extend将我们传个Model.extend函数的第一个参数扩展给child 这是关键的一步
7.返回child,让child成为我们要使用的那个我们自定义的model-class
这么做是为什么呢?它形成了由 model-object -> model-class原型 -> Model原型 的一条原型链,有了明确的继承与被继承的关系。
Model的验证
从上面的代码我们可以看到,我们在Model的扩展Man中写了验证函数,但是这个验证什么时候被触发呢?那就是在我们调用model-object的save方法的时候。
save(key,val,options)
save一共做了两档子事,上面是一件,还有一件是做一次ajax提交,还是通过trigger函数触发异步事件。
下面是save的源码:
// Set a hash of model attributes, and sync the model to the server.
// If the server returns an attributes hash that differs, the model's
// state will be `set` again.
save: function(key, val, options) {
var attrs, method, xhr, attributes = this.attributes; // Handle both `"key", value` and `{key: value}` -style arguments.
if (key == null || typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
} options = _.extend({validate: true}, options); // If we're not waiting and attributes exist, save acts as
// `set(attr).save(null, opts)` with validation. Otherwise, check if
// the model will be valid when the attributes, if any, are set.
if (attrs && !options.wait) {
if (!this.set(attrs, options)) return false;
} else {
if (!this._validate(attrs, options)) return false;
} // Set temporary attributes if `{wait: true}`.
if (attrs && options.wait) {
this.attributes = _.extend({}, attributes, attrs);
} // After a successful server-side save, the client is (optionally)
// updated with the server-side state.
if (options.parse === void 0) options.parse = true;
var model = this;
var success = options.success;
options.success = function(resp) {
// Ensure attributes are restored during synchronous saves.
model.attributes = attributes;
var serverAttrs = model.parse(resp, options);
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
return false;
}
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
wrapError(this, options); method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
if (method === 'patch') options.attrs = attrs;
xhr = this.sync(method, this, options); // Restore attributes.
if (attrs && options.wait) this.attributes = attributes; return xhr;
},
options的属性:
validate: bool 是否对值进行验证
wait: bool 是否等待异步提交后保存(前提 key不能为空)
parse: bool 默认 true 不明
success: save成功后回调
其余参数:参见 Backbone 上的方法
save的具体执行步骤是这样的:
1.前期对参数处理,同set方法
2.为options附加默认validate 为 true属性,默认对值进行验证
3.重写options的success函数,将原来的success在重写的里面执行
重写增加以下几点内容:整理ajax返回值并将其set到model-object的attributes中,然后执行一次自定义的success,最后再进行一次ajax造成递归ajax请求的递归回调。
4.收集错误并抛出错误:
// Wrap an optional error callback with a fallback error event.
var wrapError = function(model, options) {
var error = options.error;
options.error = function(resp) {
if (error) error(model, resp, options);
model.trigger('error', model, resp, options);
};
};
5.判定使用何种协议
6.手动执行sync ajax请求函数
7.返回xhr对象
save源码分析结束
Model获取服务端数据
上面我们提到的save方法涉及了ajax向服务端提交数据做保存,所以跟着ajax获取这条线,我们看看fetch是怎么从服务端获取数据的。
fetch(options)
参数options是一个对象,里面包含了我们fetch函数需要操作的一些配置项,包括:
常用的有:url success error
// Fetch the default set of models for this collection, resetting the
// collection when they arrive. If `reset: true` is passed, the response
// data will be passed through the `reset` method instead of `set`.
fetch: function(options) {
options = options ? _.clone(options) : {};
if (options.parse === void 0) options.parse = true;
var success = options.success;
var collection = this;
options.success = function(resp) {
var method = options.reset ? 'reset' : 'set';
collection[method](resp, options);
if (success) success(collection, resp, options);
collection.trigger('sync', collection, resp, options);
};
wrapError(this, options);
return this.sync('read', this, options);
},
通过查看源码,我们发现,fetch进行了以下三步重要的工作:
1.根据options的参数决定是否调用set 还是 reset
2.执行success方法
3.通过trigger触发sync 同步事件
4.返回 this.sync('read', this, options) 这是一个对象,这个对象包含各种类似xhr对象的操作,用来干预整个ajax的活动进程。
以上就是model-object操作一个对象一些简要的流程,当然model-object有很多的方法供大家使用:
操作属性方法:
set上面已经提到,跟它相对应的就是unset 和clear 清理对象属性方法
unset(attr,options)
源码如下:
// Remove an attribute from the model, firing `"change"`. `unset` is a noop
// if the attribute doesn't exist.
unset: function(attr, options) {
return this.set(attr, void 0, _.extend({}, options, {unset: true}));
},
是不是很简单,直接就套用了 set方法嘛。unset会触发相应的事件change事件
clear(options)
源码如下:
// Clear all attributes on the model, firing `"change"`.
clear: function(options) {
var attrs = {};
for (var key in this.attributes) attrs[key] = void 0;
return this.set(attrs, _.extend({}, options, {unset: true}));
},
也是套用了set方法,同样触发了所有和属性相关的事件。
有set必有get,同样实现方式超级简单:
get(attr)
以下是源码:
// Get the value of an attribute.
get: function(attr) {
return this.attributes[attr];
},
简单的大快人心
has(attr)
检测是否存在该属性的实现也是操作attributes属性,套用了get
// Returns `true` if the attribute contains a value that is not null
// or undefined.
has: function(attr) {
return this.get(attr) != null;
},
关于model的总结:
1.工厂函数传入attributes参数时,并不会触发change事件,只有model-object显示调用 set save 的时候才会触发。
因为model-class虽然在参数初始化attributes的时候虽然调用了set,但是this上并没有_events属性,所以没法触发绑定的方法。
2.set时至少触发2次all事件,save在url有配置的情况下则会至少触发3次all事件:sync 的request一次,回调success中的递归sync 一次,最后sync 中的request一次。
因为 all 事件只要执行 trigger函数 就会触发。
-----------------------------------------Model基础到此结束,下面是Model中的工具函数--------------------------
1._validate(attrs, options)
参数:attrs object 表示需要设置为属性的键值对
options object 该键值对设置时验证方法
作用: 使用model-class 中自定义的validate方法对attrs进行验证处理
原理:首先检查options中及this(model-object)中是否存在validate方法,如果没有返回true,表验证通过
其次将attrs和model-object的 attributes合并,作为被验证的对象
调用this.validate方法进行验证并把返回值放到 error 和 this.validationError中
最后触发 invalid 事件
源码:
// Run validation against the next complete set of model attributes,
// returning `true` if all is well. Otherwise, fire an `"invalid"` event.
_validate: function(attrs, options) {
if (!options.validate || !this.validate) return true;
attrs = _.extend({}, this.attributes, attrs);
var error = this.validationError = this.validate(attrs, options) || null;
if (!error) return true;
this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
return false;
}
2.escape(attr)
参数:属性名 string
作用:将该属性进行html转移
原理:调用underscore的escape方法实现。
主要就是讲该对象下的特殊字符进行html转移替换而已:
var escapeMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'`': '`'
};
源码:
// Get the HTML-escaped value of an attribute.
escape: function(attr) {
return _.escape(this.get(attr));
},
Backbone Model 源码简谈 (版本:1.1.0 基础部分完毕)的更多相关文章
- Backbone Collection 源码简谈
一切由一个例子引发: var Man=Backbone.Model.extend({ initilize:function(){ this.bind('change:name',function(){ ...
- backbone.Model 源码笔记
backbone.Model backbone的model(模型),用来存储数据,交互数据,数据验证,在view里面可以直接监听model来达到model一改变,就通知视图. 这个里面的代码是从bac ...
- Flink源码阅读(一)——Flink on Yarn的Per-job模式源码简析
一.前言 个人感觉学习Flink其实最不应该错过的博文是Flink社区的博文系列,里面的文章是不会让人失望的.强烈安利:https://ververica.cn/developers-resource ...
- [C#源码]VS各版本转换器(支持VS2012,VS2013)
项目名称:[C#源码]VS各版本转换器(支持VS2012,VS2013) 下载内容: (C#源码)VS各版本转换器 实现功能: 支持vs2003-vs2013的各版本转换,由高到低,由低到高都支持. ...
- 2017最新修复福运来完整运营中时时彩源码PC+手机版本功能齐全
QQ:1395239152 2017-3.14最新修复福运来完整运营版时时彩源码PC+手机版本功能齐全 使用php+mysql开发,并带有完整数据库.截图!!! 注意哈 带手机版 以下截图均为测 ...
- SpringMVC学习(一)——概念、流程图、源码简析
学习资料:开涛的<跟我学SpringMVC.pdf> 众所周知,springMVC是比较常用的web框架,通常整合spring使用.这里抛开spring,单纯的对springMVC做一下总 ...
- django-jwt token校验源码简析
一. jwt token校验源码简析 1.1 前言 之前使用jwt签发了token,里面的头部包含了加密的方式.是否有签名等,而载荷中包含用户名.用户主键.过期时间等信息,最后的签名还使用了摘要算法进 ...
- 04 drf源码剖析之版本
04 drf源码剖析之版本 目录 04 drf源码剖析之版本 1. 版本简述 2. 版本使用 3.源码剖析 4. 总结 1. 版本简述 API版本控制使您可以更改不同客户端之间的行为.REST框架提供 ...
- [源码解析] PyTorch 流水线并行实现 (1)--基础知识
[源码解析] PyTorch 流水线并行实现 (1)--基础知识 目录 [源码解析] PyTorch 流水线并行实现 (1)--基础知识 0x00 摘要 0x01 历史 1.1 GPipe 1.2 t ...
随机推荐
- LB
LB(LBaaS)及Load Balance as a Service是 Neutron 提供的一项高级网络服务. LBaaS 允许租户在自己的网络中创建和管理 load balancer,load ...
- LeetCode 全解(bug free 训练)
1.Two Sum Given an array of integers, return indices of the two numbers such that they add up to a s ...
- LeetCode 81——搜索旋转排序数组 II
1. 题目 2. 解答 2.1. 方法一 基于 LeetCode 33--搜索旋转排序数组 中的方法二. 当 nums[mid] = nums[right] 时,比如 [1, 1, 2, 1, 1], ...
- tensorflow学习笔记(2)-反向传播
tensorflow学习笔记(2)-反向传播 反向传播是为了训练模型参数,在所有参数上使用梯度下降,让NN模型在的损失函数最小 损失函数:学过机器学习logistic回归都知道损失函数-就是预测值和真 ...
- u盘中毒后文件夹没显示了
今日,我的U盘插了有毒的电脑,直接中毒了,先是显示有木马,后是自行产生一些文件,接着文件夹没了,后来自己终于解决了,分享一下,自己先用工具将有嫌疑的文件提炼出来,经自行检查后处理,接着打开文件夹选项, ...
- Python中运算符"=="和"is"的差别分析
前言 在讲is和==这两种运算符区别之前,首先要知道Python中对象包含的三个基本要素,分别是:id(身份标识).python type()(数据类型)和value(值).is和==都是对对象进行比 ...
- Java开发环境配置时的dt.jar与tools.jar是什么(转载)
你了解dt.jar吗 很多人在初学Java的时候,都要配置环境变量.在配置CLASSPATH的时候,都会加上一个当前目录.,还有两个jar:dt.jar和tools.jar.其实好多人都不了解这两个j ...
- ExtJS新手学习中常见问题
1.常常出现运行之后不出现应该出现的效果. 这种情况一般是引用ExtJS路径不正确,要确保路径正确. 示例: <!DOCTYPE html> <html lang="en& ...
- JavaScript 执行环境(作用域)总结
所有变量(包括基本类型和引用类型)都存在一个执行环境(也称为作用域)当中,这个执行环境决定了变量的生命周期,以及哪一部分可以访问其中的变量. 以下是关于执行环境的几点总结: 执行环境有全局执行环境(全 ...
- [HAOI2010]计数
题面在这里 description 你有一组非零数字(不一定唯一),你可以在其中插入任意个0,这样就可以产生无限个数. 比如说给定{1,2},那么可以生成数字12,21,102,120,201,210 ...