zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    eclipse 远程debug tomcat web项目
    阿里巴巴fastjson的使用
    STS 3.6.4 SpringMVC 4.1.6 Hibernate 4.3.8 MySQL
    Ubuntu su 认证失败
    mysql mha高可用架构的安装
    Swift开发教程--关于Existing instance variable &#39;_delegate&#39;...的解决的方法
    设计模式-适配器模式(Go语言描写叙述)
    Xcode6.3 怎样使用Leaks查看内存泄露
    java中的subString具体解释及应用
    出走三上海篇
  • 原文地址:https://www.cnblogs.com/JhoneLee/p/4192435.html
Copyright © 2011-2022 走看看