backbone 学习之Model
直接看源码:
// Backbone.Model // -------------- // Backbone **Models** are the basic data object in the framework -- // frequently representing a row in a table in a database on your server. // A discrete chunk of data and a bunch of useful, related methods for // performing computations and transformations on that data. // Create a new model with the specified attributes. A client id (`cid`) // is automatically generated and assigned for you. var Model = Backbone.Model = function(attributes, options) { var defaults; var attrs = attributes || {}; // 数据 options || (options = {}); // 配置的可选项 this.cid = _.uniqueId('c'); // 每一个model都有一个cid属性 方便collection // 可以根据cid得到当前model this.attributes = {}; // 如果options指定了 url urlRoot 和 collection的话就添加到this上 _.extend(this, _.pick(options, modelOptions)); // 如果有parse解析数据的函数的话 就处理数据 if (options.parse) attrs = this.parse(attrs, options) || {}; // 存在defaults默认信息,有的话就给attrs设置上 if (defaults = _.result(this, 'defaults')) { attrs = _.defaults({}, attrs, defaults); } this.set(attrs, options); this.changed = {};// 记录当前model有那些东西更改了 this.initialize.apply(this, arguments); // 初始化 }; // A list of options to be attached directly to the model, if provided. var modelOptions = ['url', 'urlRoot', 'collection']; // Attach all inheritable methods to the Model prototype. _.extend(Model.prototype, Events, { // A hash of attributes whose current and previous value differ. changed: null, // The value returned during the last failed validation. validationError: null, // The default name for the JSON `id` attribute is `"id"`. MongoDB and // CouchDB users may want to set this to `"_id"`. // 数据的id属性值是什么 默认id idAttribute: 'id', // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function(){}, // Return a copy of the model's `attributes` object. // 得到新的对象 toJSON: function(options) { return _.clone(this.attributes); }, // Proxy `Backbone.sync` by default -- but override this if you need // custom syncing semantics for *this* particular model. // 请求 sync: function() { return Backbone.sync.apply(this, arguments); }, // Get the value of an attribute. // 得到数据attr属性信息 get: function(attr) { return this.attributes[attr]; }, // Get the HTML-escaped value of an attribute. // 转义数据attr属性信息 escape: function(attr) { return _.escape(this.get(attr)); }, // Returns `true` if the attribute contains a value that is not null // or undefined. has: function(attr) { return this.get(attr) != null; }, // Set a hash of model attributes on the object, firing `"change"`. This is // the core primitive operation of a model, updating the data and notifying // anyone who needs to know about the change in state. The heart of the beast. // model的核心操作 在这里会触发change事件 set: function(key, val, options) { var attr, attrs, unset, changes, silent, changing, prev, current; if (key == null) return this; // Handle both `"key", value` and `{key: value}` -style arguments. if (typeof key === 'object') { attrs = key; options = val; } else { (attrs = {})[key] = val; } options || (options = {}); // Run validation. // 验证数据合法性 if (!this._validate(attrs, options)) return false; // Extract attributes and options. unset = options.unset; // 设置数据 || 全部设置undefined silent = options.silent; // 静悄悄? 是否触发change事件 changes = []; // 所有的改变数据集合 changing = this._changing; // 是否已经在改变 this._changing = true; if (!changing) { this._previousAttributes = _.clone(this.attributes); // 为改变之前的数据 this.changed = {}; } current = this.attributes, prev = this._previousAttributes; // Check for changes of `id`. if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; // For each `set` attribute, update or delete the current value. // 更新或者删除当前要设置的值 并记录改变属性值 for (attr in attrs) { val = attrs[attr]; if (!_.isEqual(current[attr], val)) changes.push(attr); if (!_.isEqual(prev[attr], val)) { this.changed[attr] = val; } else { delete this.changed[attr]; } unset ? delete current[attr] : current[attr] = val; } // Trigger all relevant attribute changes. if (!silent) { if (changes.length) this._pending = true; // 如果有某些属性改变 触发change for (var i = 0, l = changes.length; i < l; i++) { this.trigger('change:' + changes[i], this, current[changes[i]], options); } } // You might be wondering why there's a `while` loop here. Changes can // be recursively nested within `"change"` events. // 避免change的递归嵌套问题 if (changing) return this; if (!silent) { while (this._pending) { this._pending = false; this.trigger('change', this, options); } } this._pending = false; this._changing = false; return this; }, // Remove an attribute from the model, firing `"change"`. `unset` is a noop // if the attribute doesn't exist. // 移除数据中的某属性值 unset: function(attr, options) { return this.set(attr, void 0, _.extend({}, options, {unset: true})); }, // Clear all attributes on the model, firing `"change"`. // 清楚所有数据 触发change事件 clear: function(options) { var attrs = {}; for (var key in this.attributes) attrs[key] = void 0; return this.set(attrs, _.extend({}, options, {unset: true})); }, // Determine if the model has changed since the last `"change"` event. // If you specify an attribute name, determine if that attribute has changed. // 是否有数据发生改变 || 某个属性是否发生了改变 hasChanged: function(attr) { if (attr == null) return !_.isEmpty(this.changed); return _.has(this.changed, attr); }, // Return an object containing all the attributes that have changed, or // false if there are no changed attributes. Useful for determining what // parts of a view need to be updated and/or what attributes need to be // persisted to the server. Unset attributes will be set to undefined. // You can also pass an attributes object to diff against the model, // determining if there *would be* a change. // 得到改变的属性信息 changedAttributes: function(diff) { if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; var val, changed = false; var old = this._changing ? this._previousAttributes : this.attributes; for (var attr in diff) { if (_.isEqual(old[attr], (val = diff[attr]))) continue; (changed || (changed = {}))[attr] = val; } return changed; }, // Get the previous value of an attribute, recorded at the time the last // `"change"` event was fired. // 在change之前的上一次数据 previous: function(attr) { if (attr == null || !this._previousAttributes) return null; return this._previousAttributes[attr]; }, // Get all of the attributes of the model at the time of the previous // `"change"` event. previousAttributes: function() { return _.clone(this._previousAttributes); }, // Fetch the model from the server. If the server's representation of the // model differs from its current attributes, they will be overridden, // triggering a `"change"` event. // 取得所有数据 请求 // 成功的时候会触发change事件 fetch: function(options) { options = options ? _.clone(options) : {}; if (options.parse === void 0) options.parse = true; var model = this; var success = options.success; options.success = function(resp) { if (!model.set(model.parse(resp, options), options)) return false; if (success) success(model, resp, options); model.trigger('sync', model, resp, options); }; wrapError(this, options); return this.sync('read', this, options); }, // Set a hash of model attributes, and sync the model to the server. // If the server returns an attributes hash that differs, the model's // state will be `set` again. // 保存数据到服务端 可能是创建 或者 删除 更改等 save: function(key, val, options) { var attrs, method, xhr, attributes = this.attributes; // Handle both `"key", value` and `{key: value}` -style arguments. if (key == null || typeof key === 'object') { attrs = key; options = val; } else { (attrs = {})[key] = val; } // If we're not waiting and attributes exist, save acts as `set(attr).save(null, opts)`. if (attrs && (!options || !options.wait) && !this.set(attrs, options)) return false; options = _.extend({validate: true}, options); // Do not persist invalid models. if (!this._validate(attrs, options)) return false; // Set temporary attributes if `{wait: true}`. if (attrs && options.wait) { this.attributes = _.extend({}, attributes, attrs); } // After a successful server-side save, the client is (optionally) // updated with the server-side state. if (options.parse === void 0) options.parse = true; var model = this; var success = options.success; options.success = function(resp) { // Ensure attributes are restored during synchronous saves. model.attributes = attributes; var serverAttrs = model.parse(resp, options); if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs); if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) { return false; } if (success) success(model, resp, options); model.trigger('sync', model, resp, options); }; wrapError(this, options); method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); if (method === 'patch') options.attrs = attrs; xhr = this.sync(method, this, options); // Restore attributes. if (attrs && options.wait) this.attributes = attributes; return xhr; }, // Destroy this model on the server if it was already persisted. // Optimistically removes the model from its collection, if it has one. // If `wait: true` is passed, waits for the server to respond before removal. // 销毁 删除 服务端同步更新 触发destroy事件 destroy: function(options) { options = options ? _.clone(options) : {}; var model = this; var success = options.success; var destroy = function() { model.trigger('destroy', model, model.collection, options); }; options.success = function(resp) { if (options.wait || model.isNew()) destroy(); if (success) success(model, resp, options); if (!model.isNew()) model.trigger('sync', model, resp, options); }; if (this.isNew()) { options.success(); return false; } wrapError(this, options); var xhr = this.sync('delete', this, options); if (!options.wait) destroy(); return xhr; }, // Default URL for the model's representation on the server -- if you're // using Backbone's restful methods, override this to change the endpoint // that will be called. // 此model请求的url url: function() { var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError(); if (this.isNew()) return base; return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id); }, // **parse** converts a response into the hash of attributes to be `set` on // the model. The default implementation is just to pass the response along. // 解析数据的函数 做一些操作什么的 parse: function(resp, options) { return resp; }, // Create a new model with identical attributes to this one. clone: function() { return new this.constructor(this.attributes); }, // A model is new if it has never been saved to the server, and lacks an id. // 此model是否保存到服务端过 没有的话就认为是新的 通过的是判断id isNew: function() { return this.id == null; }, // Check if the model is currently in a valid state. isValid: function(options) { return this._validate({}, _.extend(options || {}, { validate: true })); }, // Run validation against the next complete set of model attributes, // returning `true` if all is well. Otherwise, fire an `"invalid"` event. // 验证函数validate 是否会验证成功 失败的话会触发invalid事件 _validate: function(attrs, options) { if (!options.validate || !this.validate) return true; attrs = _.extend({}, this.attributes, attrs); var error = this.validationError = this.validate(attrs, options) || null; if (!error) return true; this.trigger('invalid', this, error, _.extend(options || {}, {validationError: error})); return false; } });
欢迎指导、纠错、建议。