zoukankan      html  css  js  c++  java
  • 【原创】backbone1.1.0源码解析之Model

    趁热打铁,将Backbone.Model的源代码注释也发出来。

    Model是用来干嘛的?写过mvc的同学应该都知道,说白了就是model实例用来存储数据表中的一行数据(row)

    Backbone利用model的attributes与数据库的字段一一对应,通过ajax获取数据后,在前端进行存储,又提供了一系列的方法,

    在改变model实例的同时,完成关联视图view的更新,和服务器端数据的更新,从而达到mvc的效果。

    下面就是Backbone.Model的源码注释了,如果错误了还望指出来,或者不清晰,可以给我留言

      1   // Backbone.Model
      2   // --------------
      3 
      4   // Backbone **Models** are the basic data object in the framework --
      5   // frequently representing a row in a table in a database on your server.
      6   // A discrete chunk of data and a bunch of useful, related methods for
      7   // performing computations and transformations on that data.
      8 
      9   // Create a new model with the specified attributes. A client id (`cid`)
     10   // is automatically generated and assigned for you.
     11   // Model构造器,大概在this对象上添加的属性
     12   // {
     13   //   cid : 'cxxx',
     14   //   attributes : {...},
     15   //   changed : {}
     16   // }
     17   // options可选参数(在进行model实例方法调用时还会见到更多的options参数)
     18   // {
     19   //   collection : true(false),
     20   //   parse : true(false),
     21   // }
     22   var Model = Backbone.Model = function(attributes, options) {
     23     var attrs = attributes || {};
     24     options || (options = {});
     25     // model对象唯一id
     26     this.cid = _.uniqueId('c');
     27     // model的属性集合
     28     this.attributes = {};
     29     // 指定model所属的collection
     30     if (options.collection) this.collection = options.collection;
     31     // 对attrs进行过滤,默认parse函数返回原attrs,继承时可以根据需要进行重写
     32     if (options.parse) attrs = this.parse(attrs, options) || {};
     33     // 将attrs中没有被设置的属性,设置默认值
     34     // 这里的defaults是自定义的,可以是{...},也可以是返回{...}的函数
     35     attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
     36     this.set(attrs, options);
     37     // 因为是构造函数,所以重置上述调用set操作带来的引起changed变化
     38     this.changed = {};
     39     // 执行初始化操作,用户可自定义
     40     this.initialize.apply(this, arguments);
     41   };
     42 
     43   // Attach all inheritable methods to the Model prototype.
     44   // Model实例公有方法(添加了事件机制)
     45   _.extend(Model.prototype, Events, {
     46 
     47     // A hash of attributes whose current and previous value differ.
     48     // 改变过的属性集合
     49     changed: null,
     50 
     51     // The value returned during the last failed validation.
     52     // 执行validate方法时,如果验证失败,那么失败的结果会放在该变量里
     53     validationError: null,
     54 
     55     // The default name for the JSON `id` attribute is `"id"`. MongoDB and
     56     // CouchDB users may want to set this to `"_id"`.
     57     // 自定义数据库返回的唯一键的名字
     58     idAttribute: 'id',
     59 
     60     // Initialize is an empty function by default. Override it with your own
     61     // initialization logic.
     62     // 一般会覆盖用于自定义初始化操作
     63     initialize: function(){},
     64 
     65     // Return a copy of the model's `attributes` object.
     66     toJSON: function(options) {
     67       return _.clone(this.attributes);
     68     },
     69 
     70     // Proxy `Backbone.sync` by default -- but override this if you need
     71     // custom syncing semantics for *this* particular model.
     72     // 调用Backbone.sync,可以通过改写Backbone.sync来实现自己的异步操作
     73     sync: function() {
     74       return Backbone.sync.apply(this, arguments);
     75     },
     76 
     77     // Get the value of an attribute.
     78     // 获取属性值
     79     get: function(attr) {
     80       return this.attributes[attr];
     81     },
     82 
     83     // Get the HTML-escaped value of an attribute.
     84     // 获取经过html-escaped过的属性值,主要事为了防止xss攻击
     85     escape: function(attr) {
     86       return _.escape(this.get(attr));
     87     },
     88 
     89     // Returns `true` if the attribute contains a value that is not null
     90     // or undefined.
     91     // 判断是否含有某个属性值
     92     has: function(attr) {
     93       return this.get(attr) != null;
     94     },
     95 
     96     // Set a hash of model attributes on the object, firing `"change"`. This is
     97     // the core primitive operation of a model, updating the data and notifying
     98     // anyone who needs to know about the change in state. The heart of the beast.
     99     // 设置属性值,触发对应属性的的change事件,触发对象的change事件
    100     set: function(key, val, options) {
    101       var attr, attrs, unset, changes, silent, changing, prev, current;
    102       if (key == null) return this;
    103 
    104       // Handle both `"key", value` and `{key: value}` -style arguments.
    105       // 处理属性集合,({key1: value1, key2: value2, ...}, [options])
    106       if (typeof key === 'object') {
    107         attrs = key;
    108         options = val;
    109       } else {
    110       // 处理(key1, value1, [options])
    111         (attrs = {})[key] = val;
    112       }
    113 
    114       options || (options = {});
    115 
    116       // Run validation.
    117       // 验证失败了,set也就失败了
    118       if (!this._validate(attrs, options)) return false;
    119 
    120       // Extract attributes and options.
    121       // unset 用于删除属性
    122       unset           = options.unset;
    123       // silent 用于控制是否触发对应属性的change事件
    124       silent          = options.silent;
    125       changes         = [];
    126       changing        = this._changing;
    127       this._changing  = true;
    128 
    129       // 这里判断其实是介于接下来的trigger change事件里面还可能在进行set操作
    130       // 而_previousAttributes和changed依附于this对象实现共享
    131       // 这里是考虑到用户自定义change回调时,循环迭代的问题
    132       if (!changing) {
    133         // 保存修改前的属性
    134         this._previousAttributes = _.clone(this.attributes);
    135         this.changed = {};
    136       }
    137       current = this.attributes, prev = this._previousAttributes;
    138 
    139       // Check for changes of `id`.
    140       // 设置id,这里的id是只数据库中的唯一键,不同于cid
    141       if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
    142 
    143       // For each `set` attribute, update or delete the current value.
    144       for (attr in attrs) {
    145         val = attrs[attr];
    146         // 与当前值就行比较,如果不一样,那么记录这个改变
    147         if (!_.isEqual(current[attr], val)) changes.push(attr);
    148         if (!_.isEqual(prev[attr], val)) {
    149           // 记录改变的属性
    150           this.changed[attr] = val;
    151         } else {
    152           // 如果在迭代中属性又还原不变了,那么删除掉
    153           delete this.changed[attr];
    154         }
    155         // 删除或者更新属性
    156         unset ? delete current[attr] : current[attr] = val;
    157       }
    158 
    159       // Trigger all relevant attribute changes.
    160       // 触发属性改变的事件,如:change:attr1
    161       if (!silent) {
    162         if (changes.length) this._pending = true;
    163         for (var i = 0, l = changes.length; i < l; i++) {
    164           this.trigger('change:' + changes[i], this, current[changes[i]], options);
    165         }
    166       }
    167 
    168       // You might be wondering why there's a `while` loop here. Changes can
    169       // be recursively nested within `"change"` events.
    170       // 这里返回this,是因为上面进行trigger change:attr1时,用户自定义函数可能会再次进行set操作
    171       // 而接下来的有些操作是针对对象本身的,只需要被执行一次
    172       if (changing) return this;
    173       if (!silent) {
    174         // 如果属性有改变,那么触发对象的change事件
    175         // 这里的循环同样是为了处理change事件的回调函数里面包含set操作而引发change事件
    176         // 因为上述的changing会进行return,那么通过this._pending来进行while判断,来执行多次对象的change事件回调
    177         // 这里依然是循环迭代的问题
    178         while (this._pending) {
    179           this._pending = false;
    180           this.trigger('change', this, options);
    181         }
    182       }
    183       // 恢复状态
    184       this._pending = false;
    185       this._changing = false;
    186       return this;
    187     },
    188 
    189     // Remove an attribute from the model, firing `"change"`. `unset` is a noop
    190     // if the attribute doesn't exist.
    191     // unset实质是利用set方法,只需将options.unset设置为true
    192     unset: function(attr, options) {
    193       return this.set(attr, void 0, _.extend({}, options, {unset: true}));
    194     },
    195 
    196     // Clear all attributes on the model, firing `"change"`.
    197     // 清除所有属性
    198     clear: function(options) {
    199       var attrs = {};
    200       for (var key in this.attributes) attrs[key] = void 0;
    201       return this.set(attrs, _.extend({}, options, {unset: true}));
    202     },
    203 
    204     // Determine if the model has changed since the last `"change"` event.
    205     // If you specify an attribute name, determine if that attribute has changed.
    206     hasChanged: function(attr) {
    207       // 如果attr为空,那么通过查看this.changed来判断对象是否改变(即是否有某一属性改变)
    208       if (attr == null) return !_.isEmpty(this.changed);
    209       // 返回是否某一个特定属性改变了
    210       return _.has(this.changed, attr);
    211     },
    212 
    213     // Return an object containing all the attributes that have changed, or
    214     // false if there are no changed attributes. Useful for determining what
    215     // parts of a view need to be updated and/or what attributes need to be
    216     // persisted to the server. Unset attributes will be set to undefined.
    217     // You can also pass an attributes object to diff against the model,
    218     // determining if there *would be* a change.
    219     // 返回changed属性列表,没有改变则返回false
    220     // 如果有传递diff,那么就进行attributes和diff的比较,筛选出属性值不一样的属性
    221     changedAttributes: function(diff) {
    222       if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
    223       var val, changed = false;
    224       // 还是考虑循环迭代的问题
    225       var old = this._changing ? this._previousAttributes : this.attributes;
    226       for (var attr in diff) {
    227         if (_.isEqual(old[attr], (val = diff[attr]))) continue;
    228         (changed || (changed = {}))[attr] = val;
    229       }
    230       return changed;
    231     },
    232 
    233     // Get the previous value of an attribute, recorded at the time the last
    234     // `"change"` event was fired.
    235     // 返回修改前的某个属性值
    236     previous: function(attr) {
    237       if (attr == null || !this._previousAttributes) return null;
    238       return this._previousAttributes[attr];
    239     },
    240 
    241     // Get all of the attributes of the model at the time of the previous
    242     // `"change"` event.
    243     // 返回修改前的属性集合
    244     previousAttributes: function() {
    245       return _.clone(this._previousAttributes);
    246     },
    247 
    248     // Fetch the model from the server. If the server's representation of the
    249     // model differs from its current attributes, they will be overridden,
    250     // triggering a `"change"` event.
    251     // 从服务器端获取数据,填充model
    252     fetch: function(options) {
    253       options = options ? _.clone(options) : {};
    254       if (options.parse === void 0) options.parse = true;
    255       var model = this;
    256       var success = options.success;
    257       // 回调success函数
    258       options.success = function(resp) {
    259         // 设置model的值(可能会验证失败),那么返回false
    260         if (!model.set(model.parse(resp, options), options)) return false;
    261         if (success) success(model, resp, options);
    262         model.trigger('sync', model, resp, options);
    263       };
    264       // 包装回调error函数
    265       wrapError(this, options);
    266       // 异步获取数据
    267       return this.sync('read', this, options);
    268     },
    269 
    270     // Set a hash of model attributes, and sync the model to the server.
    271     // If the server returns an attributes hash that differs, the model's
    272     // state will be `set` again.
    273     save: function(key, val, options) {
    274       var attrs, method, xhr, attributes = this.attributes;
    275 
    276       // Handle both `"key", value` and `{key: value}` -style arguments.
    277       // 上述所说的参数兼容
    278       if (key == null || typeof key === 'object') {
    279         attrs = key;
    280         options = val;
    281       } else {
    282         (attrs = {})[key] = val;
    283       }
    284 
    285       // 保存前要要做前端验证
    286       options = _.extend({validate: true}, options);
    287 
    288       // If we're not waiting and attributes exist, save acts as
    289       // `set(attr).save(null, opts)` with validation. Otherwise, check if
    290       // the model will be valid when the attributes, if any, are set.
    291       // options.wait为true用于等待服务器返回结果,再进行属性值的设置(进而view变化)
    292       // 为false的话,先进行数据验证,再设置属性值(改变view, 拥有更好的用户体验),再提交数据,
    293       //(这样的话等待服务器返回结果先还原attributes,进行属性值的set操作,包含验证)
    294       if (attrs && !options.wait) {
    295         if (!this.set(attrs, options)) return false;
    296       } else {
    297         if (!this._validate(attrs, options)) return false;
    298       }
    299 
    300       // Set temporary attributes if `{wait: true}`.
    301       // 因为接下来的Backbone.async中的再取数据时,会先取options.attrs的值,
    302       // 取不到就会执行model.toJson()来获得所有属性作为数据
    303       // 这里暂时改变this.attributes的值,不用set(因为set还要触发change等等)
    304       if (attrs && options.wait) {
    305         this.attributes = _.extend({}, attributes, attrs);
    306       }
    307 
    308       // After a successful server-side save, the client is (optionally)
    309       // updated with the server-side state.
    310       // 数据过滤
    311       if (options.parse === void 0) options.parse = true;
    312       var model = this;
    313       var success = options.success;
    314       options.success = function(resp) {
    315         // Ensure attributes are restored during synchronous saves.
    316         // 还原属性值
    317         model.attributes = attributes;
    318         var serverAttrs = model.parse(resp, options);
    319         // 用服务端的修正的数据覆盖并合并attrs
    320         if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
    321         // 服务器端返回的数据更新attributes
    322         // 对于wait为true的情况,此时view才刚得以变化
    323         // 对于wait为false的情况,应该再此时再一次对view进行改变或者纠正,
    324         if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
    325           return false;
    326         }
    327         // 成功回调函数
    328         if (success) success(model, resp, options);
    329         model.trigger('sync', model, resp, options);
    330       };
    331       wrapError(this, options);
    332 
    333       // 新数据用create来创建
    334       // 否则用patch表示只提交改变的attrs,或者update表示提交所有的属性
    335       method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
    336       if (method === 'patch') options.attrs = attrs;
    337       xhr = this.sync(method, this, options);
    338 
    339       // Restore attributes.
    340       // 还原之前为了提交数据的临时改变
    341       if (attrs && options.wait) this.attributes = attributes;
    342 
    343       return xhr;
    344     },
    345 
    346     // Destroy this model on the server if it was already persisted.
    347     // Optimistically removes the model from its collection, if it has one.
    348     // If `wait: true` is passed, waits for the server to respond before removal.
    349     // 删除服务器端model数据
    350     destroy: function(options) {
    351       options = options ? _.clone(options) : {};
    352       var model = this;
    353       var success = options.success;
    354 
    355       var destroy = function() {
    356         model.trigger('destroy', model, model.collection, options);
    357       };
    358 
    359       options.success = function(resp) {
    360         // 等待服务器端返回或者新model,执行前端destory
    361         if (options.wait || model.isNew()) destroy();
    362         if (success) success(model, resp, options);
    363         // 不是新model执行远程请求成功的sync事件
    364         if (!model.isNew()) model.trigger('sync', model, resp, options);
    365       };
    366 
    367       // 新数据,服务器端不存在,那么只需要进行本地操作就行了
    368       if (this.isNew()) {
    369         options.success();
    370         return false;
    371       }
    372       wrapError(this, options);
    373 
    374       // 异步删除
    375       var xhr = this.sync('delete', this, options);
    376       // 不等待服务器返回结果,先前端删除
    377       if (!options.wait) destroy();
    378       return xhr;
    379     },
    380 
    381     // Default URL for the model's representation on the server -- if you're
    382     // using Backbone's restful methods, override this to change the endpoint
    383     // that will be called.
    384     // 返回当前model实例对应的url
    385     // 默认规则是:[collection.url]/[id]
    386     url: function() {
    387       var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
    388       if (this.isNew()) return base;
    389       return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
    390     },
    391 
    392     // **parse** converts a response into the hash of attributes to be `set` on
    393     // the model. The default implementation is just to pass the response along.
    394     // 对数据进行解析过滤,用户可根据需求自定义
    395     parse: function(resp, options) {
    396       return resp;
    397     },
    398 
    399     // Create a new model with identical attributes to this one.
    400     // 克隆当前对象(即用相同attributes初始化)
    401     clone: function() {
    402       return new this.constructor(this.attributes);
    403     },
    404 
    405     // A model is new if it has never been saved to the server, and lacks an id.
    406     // 通过this.id来判断对象数据是否是从服务器取过来的,因为数据库的数据都是有唯一id的嘛
    407     isNew: function() {
    408       return this.id == null;
    409     },
    410 
    411     // Check if the model is currently in a valid state.
    412     // 检查当前的attributes是否合法,返回验证结果
    413     isValid: function(options) {
    414       return this._validate({}, _.extend(options || {}, { validate: true }));
    415     },
    416 
    417     // Run validation against the next complete set of model attributes,
    418     // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
    419     // 对进行set或者save操作时,会对设置的属性数据进行验证
    420     _validate: function(attrs, options) {
    421       // 验证的条件
    422       // 1. options有设置validate参数为true
    423       // 2. 存在自定义的validate函数
    424       if (!options.validate || !this.validate) return true;
    425       // 将需要验证的attrs和对象原有的this.attributes属性合并到一个空对象上
    426       // 目的是,用户用validate时,可能需要参考一些已经设置好的属性值
    427       attrs = _.extend({}, this.attributes, attrs);
    428       // 验证函数验证失败返回错误信息,成功返回空值
    429       var error = this.validationError = this.validate(attrs, options) || null;
    430       if (!error) return true;
    431       // 验证失败,触发invalid事件
    432       this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
    433       return false;
    434     }
    435 
    436   });
    437 
    438   // 添加几个用的underscore.js里的方法到model中,用于处理this.attributes,
    439   // 毕竟this.attributes是个对象也是个集合嘛,用underscore会很方便
    440   // Underscore methods that we want to implement on the Model.
    441   var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
    442 
    443   // Mix in each Underscore method as a proxy to `Model#attributes`.
    444   _.each(modelMethods, function(method) {
    445     Model.prototype[method] = function() {
    446       var args = slice.call(arguments);
    447       // 第一个参数时属性集合
    448       args.unshift(this.attributes);
    449       return _[method].apply(_, args);
    450     };
    451   });
  • 相关阅读:
    HTTP概述
    【HTTP权威指南】第二章URL与资源
    【HTTP权威指南】第三章HTTP报文
    列表生成式
    六一问候
    NYOJ 528 找球号(三)
    NYOJ 138 找球号(二)
    HDU3790 最短路径问题
    NYOJ 228 士兵杀敌(五)
    NYOJ3 3 多边形重心问题
  • 原文地址:https://www.cnblogs.com/lovesueee/p/3502099.html
Copyright © 2011-2022 走看看