趁热打铁,将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 });