【原创】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   });
原文地址:https://www.cnblogs.com/lovesueee/p/3502099.html