backbone 1.3.3源码解析-------------------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   var Model = Backbone.Model = function(attributes, options) {
 12     var attrs = attributes || {};
 13     options || (options = {});
 14     this.preinitialize.apply(this, arguments);
 15     this.cid = _.uniqueId(this.cidPrefix);
 16     this.attributes = {};
 17     if (options.collection) this.collection = options.collection;
 18     if (options.parse) attrs = this.parse(attrs, options) || {};
 19     var defaults = _.result(this, 'defaults');
 20     attrs = _.defaults(_.extend({}, defaults, attrs), defaults);
 21     this.set(attrs, options);
 22     this.changed = {};
 23     this.initialize.apply(this, arguments);
 24   };
 25 
 26   // Attach all inheritable methods to the Model prototype.
 27   _.extend(Model.prototype, Events, {
 28 
 29     // A hash of attributes whose current and previous value differ.
 30     changed: null,
 31 
 32     // The value returned during the last failed validation.
 33     validationError: null,
 34 
 35     // The default name for the JSON `id` attribute is `"id"`. MongoDB and
 36     // CouchDB users may want to set this to `"_id"`.
 37     idAttribute: 'id',
 38 
 39     // The prefix is used to create the client id which is used to identify models locally.
 40     // You may want to override this if you're experiencing name clashes with model ids.
 41     cidPrefix: 'c',
 42 
 43     // preinitialize is an empty function by default. You can override it with a function
 44     // or object.  preinitialize will run before any instantiation logic is run in the Model.
 45     preinitialize: function(){},
 46 
 47     // Initialize is an empty function by default. Override it with your own
 48     // initialization logic.
 49     initialize: function(){},
 50 
 51     // Return a copy of the model's `attributes` object.
 52     toJSON: function(options) {
 53       return _.clone(this.attributes);
 54     },
 55 
 56     // Proxy `Backbone.sync` by default -- but override this if you need
 57     // custom syncing semantics for *this* particular model.
 58     sync: function() {
 59       return Backbone.sync.apply(this, arguments);
 60     },
 61 
 62     // Get the value of an attribute.
 63     get: function(attr) {
 64       return this.attributes[attr];
 65     },
 66 
 67     // Get the HTML-escaped value of an attribute.
 68     escape: function(attr) {
 69       return _.escape(this.get(attr));
 70     },
 71 
 72     // Returns `true` if the attribute contains a value that is not null
 73     // or undefined.
 74     has: function(attr) {
 75       return this.get(attr) != null;
 76     },
 77 
 78     // Special-cased proxy to underscore's `_.matches` method.
 79     matches: function(attrs) {
 80       return !!_.iteratee(attrs, this)(this.attributes);
 81     },
 82 
 83     // Set a hash of model attributes on the object, firing `"change"`. This is
 84     // the core primitive operation of a model, updating the data and notifying
 85     // anyone who needs to know about the change in state. The heart of the beast.
 86     //这里是最重要的方法。
 87     // 1.key-val转换为attrs
 88     // 2.判断是否需要验证
 89     // 3.提取options中的属性,changing应该是为了防止异步操作造成了不可预知的错误。
 90     // 4.更新 _previousAttributes
 91     // 5.更新id
 92     // 6.将修改的属性存入私有变量changes数组中,修改this.changed对象
 93     // 7.处理unset,silient(静默更新,不处罚change事件)
 94     // 8.等待change事件执行完毕,因为有可能change事件中又触发了change事件
 95     set: function(key, val, options) {
 96       if (key == null) return this;
 97 
 98       // Handle both `"key", value` and `{key: value}` -style arguments.
 99       var attrs;
100       if (typeof key === 'object') {
101         attrs = key;
102         options = val;
103       } else {
104         (attrs = {})[key] = val;
105       }
106 
107       options || (options = {});
108 
109       // Run validation.
110       if (!this._validate(attrs, options)) return false;
111 
112       // Extract attributes and options.
113       var unset      = options.unset;
114       var silent     = options.silent;
115       var changes    = [];
116       var changing   = this._changing;
117       this._changing = true;
118 
119       if (!changing) {
120         this._previousAttributes = _.clone(this.attributes);
121         this.changed = {};
122       }
123 
124       var current = this.attributes;
125       var changed = this.changed;
126       var prev    = this._previousAttributes;
127 
128       // For each `set` attribute, update or delete the current value.
129       for (var attr in attrs) {
130         val = attrs[attr];
131         if (!_.isEqual(current[attr], val)) changes.push(attr);
132         if (!_.isEqual(prev[attr], val)) {
133           changed[attr] = val;
134         } else {
135           delete changed[attr];
136         }
137         unset ? delete current[attr] : current[attr] = val;
138       }
139 
140       // Update the `id`.
141       if (this.idAttribute in attrs) this.id = this.get(this.idAttribute);
142 
143       // Trigger all relevant attribute changes.
144       if (!silent) {
145         if (changes.length) this._pending = options;
146         for (var i = 0; i < changes.length; i++) {
147           this.trigger('change:' + changes[i], this, current[changes[i]], options);
148         }
149       }
150 
151       // You might be wondering why there's a `while` loop here. Changes can
152       // be recursively nested within `"change"` events.
153       if (changing) return this;
154       if (!silent) {
155         while (this._pending) {
156           options = this._pending;
157           this._pending = false;
158           this.trigger('change', this, options);
159         }
160       }
161       this._pending = false;
162       this._changing = false;
163       return this;
164     },
165 
166     // Remove an attribute from the model, firing `"change"`. `unset` is a noop
167     // if the attribute doesn't exist.
168     unset: function(attr, options) {
169       return this.set(attr, void 0, _.extend({}, options, {unset: true}));
170     },
171 
172     // Clear all attributes on the model, firing `"change"`.
173     clear: function(options) {
174       var attrs = {};
175       for (var key in this.attributes) attrs[key] = void 0;
176       return this.set(attrs, _.extend({}, options, {unset: true}));
177     },
178 
179     // Determine if the model has changed since the last `"change"` event.
180     // If you specify an attribute name, determine if that attribute has changed.
181     hasChanged: function(attr) {
182       if (attr == null) return !_.isEmpty(this.changed);
183       return _.has(this.changed, attr);
184     },
185 
186     // Return an object containing all the attributes that have changed, or
187     // false if there are no changed attributes. Useful for determining what
188     // parts of a view need to be updated and/or what attributes need to be
189     // persisted to the server. Unset attributes will be set to undefined.
190     // You can also pass an attributes object to diff against the model,
191     // determining if there *would be* a change.
192     changedAttributes: function(diff) {
193       if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
194       var old = this._changing ? this._previousAttributes : this.attributes;
195       var changed = {};
196       var hasChanged;
197       for (var attr in diff) {
198         var val = diff[attr];
199         if (_.isEqual(old[attr], val)) continue;
200         changed[attr] = val;
201         hasChanged = true;
202       }
203       return hasChanged ? changed : false;
204     },
205 
206     // Get the previous value of an attribute, recorded at the time the last
207     // `"change"` event was fired.
208     previous: function(attr) {
209       if (attr == null || !this._previousAttributes) return null;
210       return this._previousAttributes[attr];
211     },
212 
213     // Get all of the attributes of the model at the time of the previous
214     // `"change"` event.
215     previousAttributes: function() {
216       return _.clone(this._previousAttributes);
217     },
218 
219     // Fetch the model from the server, merging the response with the model's
220     // local attributes. Any changed attributes will trigger a "change" event.
221     fetch: function(options) {
222       options = _.extend({parse: true}, options);
223       var model = this;
224       var success = options.success;
225       options.success = function(resp) {
226         var serverAttrs = options.parse ? model.parse(resp, options) : resp;
227         if (!model.set(serverAttrs, options)) return false;
228         if (success) success.call(options.context, model, resp, options);
229         model.trigger('sync', model, resp, options);
230       };
231       wrapError(this, options);
232       return this.sync('read', this, options);
233     },
234 
235     // Set a hash of model attributes, and sync the model to the server.
236     // If the server returns an attributes hash that differs, the model's
237     // state will be `set` again.
238     save: function(key, val, options) {
239       // Handle both `"key", value` and `{key: value}` -style arguments.
240       var attrs;
241       if (key == null || typeof key === 'object') {
242         attrs = key;
243         options = val;
244       } else {
245         (attrs = {})[key] = val;
246       }
247 
248       options = _.extend({validate: true, parse: true}, options);
249       var wait = options.wait;
250 
251       // If we're not waiting and attributes exist, save acts as
252       // `set(attr).save(null, opts)` with validation. Otherwise, check if
253       // the model will be valid when the attributes, if any, are set.
254       if (attrs && !wait) {
255         if (!this.set(attrs, options)) return false;
256       } else if (!this._validate(attrs, options)) {
257         return false;
258       }
259 
260       // After a successful server-side save, the client is (optionally)
261       // updated with the server-side state.
262       var model = this;
263       var success = options.success;
264       var attributes = this.attributes;
265       options.success = function(resp) {
266         // Ensure attributes are restored during synchronous saves.
267         model.attributes = attributes;
268         var serverAttrs = options.parse ? model.parse(resp, options) : resp;
269         if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
270         if (serverAttrs && !model.set(serverAttrs, options)) return false;
271         if (success) success.call(options.context, model, resp, options);
272         model.trigger('sync', model, resp, options);
273       };
274       wrapError(this, options);
275 
276       // Set temporary attributes if `{wait: true}` to properly find new ids.
277       if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);
278 
279       var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
280       if (method === 'patch' && !options.attrs) options.attrs = attrs;
281       var xhr = this.sync(method, this, options);
282 
283       // Restore attributes.
284       this.attributes = attributes;
285 
286       return xhr;
287     },
288 
289     // Destroy this model on the server if it was already persisted.
290     // Optimistically removes the model from its collection, if it has one.
291     // If `wait: true` is passed, waits for the server to respond before removal.
292     destroy: function(options) {
293       options = options ? _.clone(options) : {};
294       var model = this;
295       var success = options.success;
296       var wait = options.wait;
297 
298       var destroy = function() {
299         model.stopListening();
300         model.trigger('destroy', model, model.collection, options);
301       };
302 
303       options.success = function(resp) {
304         if (wait) destroy();
305         if (success) success.call(options.context, model, resp, options);
306         if (!model.isNew()) model.trigger('sync', model, resp, options);
307       };
308 
309       var xhr = false;
310       if (this.isNew()) {
311         _.defer(options.success);
312       } else {
313         wrapError(this, options);
314         xhr = this.sync('delete', this, options);
315       }
316       if (!wait) destroy();
317       return xhr;
318     },
319 
320     // Default URL for the model's representation on the server -- if you're
321     // using Backbone's restful methods, override this to change the endpoint
322     // that will be called.
323     url: function() {
324       var base =
325         _.result(this, 'urlRoot') ||
326         _.result(this.collection, 'url') ||
327         urlError();
328       if (this.isNew()) return base;
329       var id = this.get(this.idAttribute);
330       return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
331     },
332 
333     // **parse** converts a response into the hash of attributes to be `set` on
334     // the model. The default implementation is just to pass the response along.
335     //可以复写这个方法,有待检测,没有测试
336     // parse:function(resp,options){
337         // return options.parse(resp);
338     // }
339     parse: function(resp, options) {
340       return resp;
341     },
342 
343     // Create a new model with identical attributes to this one.
344     clone: function() {
345       return new this.constructor(this.attributes);
346     },
347 
348     // A model is new if it has never been saved to the server, and lacks an id.
349     isNew: function() {
350       return !this.has(this.idAttribute);
351     },
352 
353     // Check if the model is currently in a valid state.
354     isValid: function(options) {
355       return this._validate({}, _.extend({}, options, {validate: true}));
356     },
357 
358     // Run validation against the next complete set of model attributes,
359     // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
360     _validate: function(attrs, options) {
361       if (!options.validate || !this.validate) return true;
362       attrs = _.extend({}, this.attributes, attrs);
363       var error = this.validationError = this.validate(attrs, options) || null;
364       if (!error) return true;
365       this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
366       return false;
367     }
368 
369   });
370 
371   // Underscore methods that we want to implement on the Model, mapped to the
372   // number of arguments they take.
373   var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0,
374       omit: 0, chain: 1, isEmpty: 1};
375 
376   // Mix in each Underscore method as a proxy to `Model#attributes`.
377   addUnderscoreMethods(Model, modelMethods, 'attributes');

 

 

转载于:https://www.cnblogs.com/wangwei1314/p/5560125.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值