Backbone Model 源码简谈 (版本:1.1.0 基础部分完毕)

Model工厂

  作为model的主要函数,其实只有12行,特别的简练

 1 var Model = Backbone.Model = function(attributes, options) {
 2     var attrs = attributes || {};
 3     options || (options = {});
 4     this.cid = _.uniqueId('c');
 5     this.attributes = {};
 6     if (options.collection) this.collection = options.collection;
 7     if (options.parse) attrs = this.parse(attrs, options) || {};//parse 889
 8     attrs = _.defaults({}, attrs, _.result(this, 'defaults'));//将参数1, _.result(this, 'defaults')对象的属性合并且返回给attr
 9     this.set(attrs, options);
10     this.changed = {};
11     this.initialize.apply(this, arguments);
12   };
View Code

 通常情况下,我们在使用Model之前,需要调用extend函数对Model进行自定义扩展后,再new 一下,

 1 var Man=Backbone.Model.extend({
 2          initialize:function(){
 3            this.bind('change:name',function(){
 4               console.log('change');
 5            });
 6          },
 7          defaults:{name:'jack',sex:'man'},
 8          sayName:function(){
 9             console.log(this.get('name'));
10          },
11          validate:function(attributes){
12             if(attributes.name==''){
13                console.log('fuck');
14             }
15          }
16       });
17       var mm=new Man();
View Code

这个过程触发了两个十分重要的函数:

   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)函数是一个复杂的函数,下面请看源代码: 

 1 set: function(key, val, options) {
 2       var attr, attrs, unset, changes, silent, changing, prev, current;
 3       if (key == null) return this;
 4 
 5       // Handle both `"key", value` and `{key: value}` -style arguments.
 6       if (typeof key === 'object') {
 7         attrs = key;
 8         options = val;
 9       } else {
10         (attrs = {})[key] = val;
11       }
12 
13       options || (options = {});
14 
15       // Run validation.
16       if (!this._validate(attrs, options)) return false;
17 
18       // Extract attributes and options.
19       unset           = options.unset;
20       silent          = options.silent;
21       changes         = [];
22       changing        = this._changing;
23       this._changing  = true;
24 
25       if (!changing) {
26         this._previousAttributes = _.clone(this.attributes);
27         this.changed = {};
28       }
29       current = this.attributes, prev = this._previousAttributes;
30 
31       // Check for changes of `id`.
32       if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
33 
34       // For each `set` attribute, update or delete the current value.
35       for (attr in attrs) {
36         val = attrs[attr];
37         if (!_.isEqual(current[attr], val)) changes.push(attr);
38         if (!_.isEqual(prev[attr], val)) {
39           this.changed[attr] = val;
40         } else {
41           delete this.changed[attr];
42         }
43         unset ? delete current[attr] : current[attr] = val;
44       }
45 
46       // Trigger all relevant attribute changes.
47       if (!silent) {
48         if (changes.length) this._pending = true;
49         for (var i = 0, l = changes.length; i < l; i++) {
50           this.trigger('change:' + changes[i], this, current[changes[i]], options);
51         }
52       }
53 
54       // You might be wondering why there's a `while` loop here. Changes can
55       // be recursively nested within `"change"` events.
56       if (changing) return this;
57       if (!silent) {
58         while (this._pending) {
59           this._pending = false;
60           this.trigger('change', this, options);
61         }
62       }
63       this._pending = false;
64       this._changing = false;
65       return this;
66     }
View Code

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一个私有方法,具体实现方法如下:

 1 var extend = function(protoProps, staticProps) {
 2     var parent = this;
 3     var child;
 4 
 5     // The constructor function for the new subclass is either defined by you
 6     // (the "constructor" property in your `extend` definition), or defaulted
 7     // by us to simply call the parent's constructor.
 8     if (protoProps && _.has(protoProps, 'constructor')) {//检查protoProps是否为原型
 9       child = protoProps.constructor;
10     } else {
11       child = function(){ return parent.apply(this, arguments); };
12     }
13 
14     // Add static properties to the constructor function, if supplied.
15     _.extend(child, parent, staticProps);
16 
17     // Set the prototype chain to inherit from `parent`, without calling
18     // `parent`'s constructor function.
19     var Surrogate = function(){ this.constructor = child; };
20     Surrogate.prototype = parent.prototype;
21     child.prototype = new Surrogate;
22 
23     // Add prototype properties (instance properties) to the subclass,
24     // if supplied.
25     if (protoProps) _.extend(child.prototype, protoProps);
26 
27     // Set a convenience property in case the parent's prototype is needed
28     // later.
29     child.__super__ = parent.prototype;
30 
31     return child;
32   };
33 
34   // Set up inheritance for the model, collection, router, view and history.
35   Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
View Code

  这个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的源码:

 1 // Set a hash of model attributes, and sync the model to the server.
 2     // If the server returns an attributes hash that differs, the model's
 3     // state will be `set` again.
 4     save: function(key, val, options) {
 5       var attrs, method, xhr, attributes = this.attributes;
 6 
 7       // Handle both `"key", value` and `{key: value}` -style arguments.
 8       if (key == null || typeof key === 'object') {
 9         attrs = key;
10         options = val;
11       } else {
12         (attrs = {})[key] = val;
13       }
14 
15       options = _.extend({validate: true}, options);
16 
17       // If we're not waiting and attributes exist, save acts as
18       // `set(attr).save(null, opts)` with validation. Otherwise, check if
19       // the model will be valid when the attributes, if any, are set.
20       if (attrs && !options.wait) {
21         if (!this.set(attrs, options)) return false;
22       } else {
23         if (!this._validate(attrs, options)) return false;
24       }
25 
26       // Set temporary attributes if `{wait: true}`.
27       if (attrs && options.wait) {
28         this.attributes = _.extend({}, attributes, attrs);
29       }
30 
31       // After a successful server-side save, the client is (optionally)
32       // updated with the server-side state.
33       if (options.parse === void 0) options.parse = true;
34       var model = this;
35       var success = options.success;
36       options.success = function(resp) {
37         // Ensure attributes are restored during synchronous saves.
38         model.attributes = attributes;
39         var serverAttrs = model.parse(resp, options);
40         if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
41         if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
42           return false;
43         }
44         if (success) success(model, resp, options);
45         model.trigger('sync', model, resp, options);
46       };
47       wrapError(this, options);
48 
49       method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
50       if (method === 'patch') options.attrs = attrs;
51       xhr = this.sync(method, this, options);
52 
53       // Restore attributes.
54       if (attrs && options.wait) this.attributes = attributes;
55 
56       return xhr;
57     },
View Code

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.收集错误并抛出错误:

1 // Wrap an optional error callback with a fallback error event.
2   var wrapError = function(model, options) {
3     var error = options.error;
4     options.error = function(resp) {
5       if (error) error(model, resp, options);
6       model.trigger('error', model, resp, options);
7     };
8   };
View Code

5.判定使用何种协议
6.手动执行sync ajax请求函数

7.返回xhr对象

save源码分析结束

Model获取服务端数据

上面我们提到的save方法涉及了ajax向服务端提交数据做保存,所以跟着ajax获取这条线,我们看看fetch是怎么从服务端获取数据的。

fetch(options)

参数options是一个对象,里面包含了我们fetch函数需要操作的一些配置项,包括:

常用的有:url    success    error

 1 // Fetch the default set of models for this collection, resetting the
 2     // collection when they arrive. If `reset: true` is passed, the response
 3     // data will be passed through the `reset` method instead of `set`.
 4     fetch: function(options) {
 5       options = options ? _.clone(options) : {};
 6       if (options.parse === void 0) options.parse = true;
 7       var success = options.success;
 8       var collection = this;
 9       options.success = function(resp) {
10         var method = options.reset ? 'reset' : 'set';
11         collection[method](resp, options);
12         if (success) success(collection, resp, options);
13         collection.trigger('sync', collection, resp, options);
14       };
15       wrapError(this, options);
16       return this.sync('read', this, options);
17     },
View Code

 通过查看源码,我们发现,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)

源码如下:

1 // Remove an attribute from the model, firing `"change"`. `unset` is a noop
2     // if the attribute doesn't exist.
3     unset: function(attr, options) {
4       return this.set(attr, void 0, _.extend({}, options, {unset: true}));
5     },
View Code

是不是很简单,直接就套用了 set方法嘛。unset会触发相应的事件change事件
clear(options)

源码如下:

1 // Clear all attributes on the model, firing `"change"`.
2     clear: function(options) {
3       var attrs = {};
4       for (var key in this.attributes) attrs[key] = void 0;
5       return this.set(attrs, _.extend({}, options, {unset: true}));
6     },
View Code

也是套用了set方法,同样触发了所有和属性相关的事件。

有set必有get,同样实现方式超级简单:

get(attr)

以下是源码:

1 // Get the value of an attribute.
2     get: function(attr) {
3       return this.attributes[attr];
4     },
View Code

简单的大快人心
has(attr)

检测是否存在该属性的实现也是操作attributes属性,套用了get

1 // Returns `true` if the attribute contains a value that is not null
2     // or undefined.
3     has: function(attr) {
4       return this.get(attr) != null;
5     },
View Code

关于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 事件

源码:

 1  // Run validation against the next complete set of model attributes,
 2     // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
 3     _validate: function(attrs, options) {
 4       if (!options.validate || !this.validate) return true;
 5       attrs = _.extend({}, this.attributes, attrs);
 6       var error = this.validationError = this.validate(attrs, options) || null;
 7       if (!error) return true;
 8       this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
 9       return false;
10     }
View Code

2.escape(attr)

参数:属性名 string

作用:将该属性进行html转移

原理:调用underscore的escape方法实现。

           主要就是讲该对象下的特殊字符进行html转移替换而已:          

1 var escapeMap = {
2     '&': '&amp;',
3     '<': '&lt;',
4     '>': '&gt;',
5     '"': '&quot;',
6     "'": '&#x27;',
7     '`': '&#x60;'
8   };
View Code

源码:

1 // Get the HTML-escaped value of an attribute.
2     escape: function(attr) {
3       return _.escape(this.get(attr));
4     },
View Code
原文地址:https://www.cnblogs.com/JhoneLee/p/4192435.html