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. 【iOS开发】IOS界面开发使用viewWithTag:(int)findTag方法获取界面元素

    http://blog.csdn.net/lxp1021/article/details/43952551 今天在开发OS界面的时候,遇到通过界面UIview viewWithTag:(int)fin ...

  2. Chrome Extension & Dark Theme

    Chrome Extension & Dark Theme https://chrome.google.com/webstore/detail/eimadpbcbfnmbkopoojfekhn ...

  3. BZOJ4424/CF19E Fairy(dfs树+树上差分)

    即删除一条边使图中不存在奇环.如果本身就是个二分图当然任意一条边都可以,先check一下.否则肯定要删除在所有奇环的交上的边. 考虑怎么找这些边.跑一遍dfs造出dfs树,找出返祖边构成的奇环.可以通 ...

  4. Codeforces Round #391 div1 757F (Dominator Tree)

    首先先膜杜教orz 这里简单说一下支配树的概念 支配树是对一个有向图来讲的 规定一个起点s,如果s到v的路径上必须经过某些点u,那么离s最近的点u就是v的支配点 在树上的关系就是,v的父亲是u. 一般 ...

  5. [洛谷P4782]【模板】2-SAT 问题

    题目大意:有$n$个布尔变量 $x_1 \sim x_n$,另有$m$个需要满足的条件,每个条件的形式都是"$x_i$ 为$true/false$或$x_j$为$true/false$&qu ...

  6. P1641 [SCOI2010]生成字符串

    P1641 [SCOI2010]生成字符串 题目描述 lxhgww最近接到了一个生成字符串的任务,任务需要他把n个1和m个0组成字符串,但是任务还要求在组成的字符串中,在任意的前k个字符中,1的个数不 ...

  7. HDOJ.2064 汉诺塔III

    汉诺塔III Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submis ...

  8. BZOJ2631 tree(伍一鸣) LCT 秘制标记

    这个题一看就是裸地LCT嘛,但是我wa了好几遍,这秘制标记...... 注意事项:I.*对+有贡献 II.先下传*再下传+(因为我们已经维护了+,不能再让*对+产生贡献)III.维护+用到size # ...

  9. 微信小程序,设置所有标签样式

    page, view, scroll-view, swiper, movable-area, cover-view, text, icon, rich-text, progress, button, ...

  10. Codeforces Round #350 (Div. 2) A

    A. Holidays time limit per test 1 second memory limit per test 256 megabytes input standard input ou ...