backbond的Model,其中存在一些操作属性的方法,而在这些方法中,最重要的就是set方法,其余的方法大部分都基于这个方法实现的,在backbond开发版中,也说了该方法是model中的核心方法。
在分析之前,先看一下官方文档的描述:
也就是说,可以传入{key-value}{obj}两个对象作为参数,也可以传入key,value,{obj}三个作为参数,其中obj是用来实现当调用set方法时,是否进行其他操作。
接下来,可以先看代码:
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. // 根据用户传入参数调用validate函数 if (!this._validate(attrs, options)) return false; // Extract attributes and options. unset = options.unset; silent = options.silent; changes = []; changing = this._changing; this._changing = true; //存储变化之前的attributes if (!changing) { this._previousAttributes = _.clone(this.attributes); this.changed = {}; } current = this.attributes, prev = this._previousAttributes; // Check for changes of `id`. // 修改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 = options; for (var i = 0, length = changes.length; i < length; 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. if (changing) return this; if (!silent) { while (this._pending) { options = this._pending; this._pending = false; this.trigger(‘change‘, this, options); } } this._pending = false; this._changing = false; return this; }
在分析代码的时候,有一句代码 if (!this._validate(attrs, options)) return false;
这是调用我们定义model时的validate方法,判断设置的数据是否正确,在这之前,可以先看一下validate的用法:
var Chapter = Backbone.Model.extend({ validate: function(attrs, options) { if (attrs.end < attrs.start) { return "can‘t end before it starts"; } } }); var one = new Chapter({ title : "Chapter One: The Beginning" }); one.on("invalid", function(model, error) { console.log(model.get("title") + " " + error); //Chapter One: The Beginning can‘t end before it starts }); one.set({ start: 15, end: 10 },{validate:true});
当我们调用set方法时,并在第二个参数中传入{validate:true},则会调用validate方法,进行判断是否错误。
validate的内部实现方法如下,其中也存在注释,在这里不再赘述。
_validate: function(attrs, options) { //options和this必须同时包含validate //即需要在属性里定义validate方法 也需要在传入的options里定义validate属性为真值 //否则 直接返回true if (!options.validate || !this.validate) return true; //获得实例的所有属性和新传入的属性 attrs = _.extend({}, this.attributes, attrs); //设置error信息为用户定义的validate返回值 var error = this.validationError = this.validate(attrs, options) || null; //如果返回值不存在 即没有错误信息 那么返回true if (!error) return true; //否则 执行invlid事件 并返回false this.trigger(‘invalid‘, this, error, _.extend(options, {validationError: error})); return false; }
set接下来的代码大多数都比较容易理解,只是其中有几句代码使我困惑了很久,如this.changing、this._pengding等变量,如下:
set: function(key, val, options) { ... ... options || (options = {}); ... ... changes = []; changing = this._changing; this._changing = true; .... ... ... // Trigger all relevant attribute changes.触发事件 if (!silent) { if (changes.length) this._pending = options; ... } // You might be wondering why there‘s a `while` loop here. Changes can // be recursively nested within `"change"` events. if (changing) return this; if (!silent) { while (this._pending) { options = this._pending; this._pending = false; this.trigger(‘change‘, this, options); } } this._pending = false; this._changing = false; return this; }
在一开始调用的时候,this._changing就是false,那么无论如何,changing都是false,为什么还要设置一个changing变量呢?
分析源码的最大一个好处就是,他的代码肯定不是没有意义的! 顾名思义,changing就是正在改变的意思。
假如我们调用如下代码时,
var z = 0; var model = new Backbone.Model(); model.on(‘change‘, function() { console.log(++z) }); model.on(‘change:a‘, function() { model.set({b: true}); }); model.set({a: true});
控制台只输出了1,也就是调用了一次console.log(++z);因为设置了changing变量,在model.set({b:true})时,因为之前调用了model.set({a:true}),changing变量处于true状态,所以model.set({b:true})调用时不会调用 this.trigger(‘change‘, this, options);
紧接着就是下面的语句:
if (!silent) { if (changes.length) this._pending = options; ... }
if (!silent) { while (this._pending) { options = this._pending; this._pending = false; this.trigger(‘change‘, this, options); } }
当changes的长度不为0时,this._pengding = options,所以this._pengding是一个对象,而while({})是可以正常执行的。
该语句的作用就是当调用set方法时,属性没有改变,即this._pending为false时,不执行this.trigger(‘change‘, this, options);语句。
所以这里的changing变量和while语句就是为了避免事件嵌套。