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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": ''',
'`': '`'
};

源码:

 // Get the HTML-escaped value of an attribute.
escape: function(attr) {
return _.escape(this.get(attr));
},

Backbone Model 源码简谈 (版本:1.1.0 基础部分完毕)的更多相关文章

  1. Backbone Collection 源码简谈

    一切由一个例子引发: var Man=Backbone.Model.extend({ initilize:function(){ this.bind('change:name',function(){ ...

  2. backbone.Model 源码笔记

    backbone.Model backbone的model(模型),用来存储数据,交互数据,数据验证,在view里面可以直接监听model来达到model一改变,就通知视图. 这个里面的代码是从bac ...

  3. Flink源码阅读(一)——Flink on Yarn的Per-job模式源码简析

    一.前言 个人感觉学习Flink其实最不应该错过的博文是Flink社区的博文系列,里面的文章是不会让人失望的.强烈安利:https://ververica.cn/developers-resource ...

  4. [C#源码]VS各版本转换器(支持VS2012,VS2013)

    项目名称:[C#源码]VS各版本转换器(支持VS2012,VS2013) 下载内容: (C#源码)VS各版本转换器 实现功能: 支持vs2003-vs2013的各版本转换,由高到低,由低到高都支持. ...

  5. 2017最新修复福运来完整运营中时时彩源码PC+手机版本功能齐全

    QQ:1395239152 2017-3.14最新修复福运来完整运营版时时彩源码PC+手机版本功能齐全 使用php+mysql开发,并带有完整数据库.截图!!!  注意哈  带手机版  以下截图均为测 ...

  6. SpringMVC学习(一)——概念、流程图、源码简析

    学习资料:开涛的<跟我学SpringMVC.pdf> 众所周知,springMVC是比较常用的web框架,通常整合spring使用.这里抛开spring,单纯的对springMVC做一下总 ...

  7. django-jwt token校验源码简析

    一. jwt token校验源码简析 1.1 前言 之前使用jwt签发了token,里面的头部包含了加密的方式.是否有签名等,而载荷中包含用户名.用户主键.过期时间等信息,最后的签名还使用了摘要算法进 ...

  8. 04 drf源码剖析之版本

    04 drf源码剖析之版本 目录 04 drf源码剖析之版本 1. 版本简述 2. 版本使用 3.源码剖析 4. 总结 1. 版本简述 API版本控制使您可以更改不同客户端之间的行为.REST框架提供 ...

  9. [源码解析] PyTorch 流水线并行实现 (1)--基础知识

    [源码解析] PyTorch 流水线并行实现 (1)--基础知识 目录 [源码解析] PyTorch 流水线并行实现 (1)--基础知识 0x00 摘要 0x01 历史 1.1 GPipe 1.2 t ...

随机推荐

  1. LB

    LB(LBaaS)及Load Balance as a Service是 Neutron 提供的一项高级网络服务. LBaaS 允许租户在自己的网络中创建和管理 load balancer,load ...

  2. 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 ...

  3. LeetCode 81——搜索旋转排序数组 II

    1. 题目 2. 解答 2.1. 方法一 基于 LeetCode 33--搜索旋转排序数组 中的方法二. 当 nums[mid] = nums[right] 时,比如 [1, 1, 2, 1, 1], ...

  4. tensorflow学习笔记(2)-反向传播

    tensorflow学习笔记(2)-反向传播 反向传播是为了训练模型参数,在所有参数上使用梯度下降,让NN模型在的损失函数最小 损失函数:学过机器学习logistic回归都知道损失函数-就是预测值和真 ...

  5. u盘中毒后文件夹没显示了

    今日,我的U盘插了有毒的电脑,直接中毒了,先是显示有木马,后是自行产生一些文件,接着文件夹没了,后来自己终于解决了,分享一下,自己先用工具将有嫌疑的文件提炼出来,经自行检查后处理,接着打开文件夹选项, ...

  6. Python中运算符"=="和"is"的差别分析

    前言 在讲is和==这两种运算符区别之前,首先要知道Python中对象包含的三个基本要素,分别是:id(身份标识).python type()(数据类型)和value(值).is和==都是对对象进行比 ...

  7. Java开发环境配置时的dt.jar与tools.jar是什么(转载)

    你了解dt.jar吗 很多人在初学Java的时候,都要配置环境变量.在配置CLASSPATH的时候,都会加上一个当前目录.,还有两个jar:dt.jar和tools.jar.其实好多人都不了解这两个j ...

  8. ExtJS新手学习中常见问题

    1.常常出现运行之后不出现应该出现的效果. 这种情况一般是引用ExtJS路径不正确,要确保路径正确. 示例: <!DOCTYPE html> <html lang="en& ...

  9. JavaScript 执行环境(作用域)总结

    所有变量(包括基本类型和引用类型)都存在一个执行环境(也称为作用域)当中,这个执行环境决定了变量的生命周期,以及哪一部分可以访问其中的变量. 以下是关于执行环境的几点总结: 执行环境有全局执行环境(全 ...

  10. [HAOI2010]计数

    题面在这里 description 你有一组非零数字(不一定唯一),你可以在其中插入任意个0,这样就可以产生无限个数. 比如说给定{1,2},那么可以生成数字12,21,102,120,201,210 ...