• backbond Model方法(set)


    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语句就是为了避免事件嵌套。

    至于这里为什么用while而不是if,可以先看一下下面的代码:

    var z = 0;
    
    
           var model = new Backbone.Model();
        model.on('change', function() {
          console.log(++z);
          model.set({b: true});
        });
        model.on('change:a', function() {
    
       });
        model.set({a: true});

    当我们执行上面的代码时,实际输出的是1,2.而不是像上面的一样,只调用了一次console。

    其原因就在while。

    当我们调用model.set({a:true})时,正常执行了while里面的语句,在调用change事件之前将this._pending改为false,紧接着调用了change事件,该事件首先打印出++z,也就是控制台输出了1.

    接着它调用了model.set({b:true}),此时changing为true,并且changes.length不为0,那么this._pending被置为options的值,函数在这里返回,重新回到调用model.set({a:true})的while循环,此时根据while循环,它在一次执行了while里面的语句,同样,在调用change事件之前将this._pending改为false,紧接着调用了change事件,该事件首先打印出++z,也就是控制台输出了2.

    接着它又调用了model.set({b:true}),此时changing为true,并且changes.length为0,那么this._pengding依然为原来false值,函数在这里返回,再次回到调用model.set({a:true})的while循环,此时退出while循环,接着进行下一次操作。

    也就是说,当我们在change事件的回调函数里再次或多次调用了set方法,那么change事件都会被触发两次,多次调用也是两次,具体原因根据以上思路可以得到结果。

  • 相关阅读:
    Heritrix源码分析(三) 修改配置文件order.xml加快你的抓取速度
    Heritrix源码分析(四) 各个类说明(二)
    Heritrix源码分析(二) 配置文件order.xml介绍
    Error running Tomcat 6: Address localhost:8080 is already in use
    NLP常用开源/免费工具
    Error: Read from storage 0 bytes, but requested 12 bytes 的解决方法
    Inproc 和 Outproc 的区别
    最搞怪面试问题TOP10:你也来试试看 (大家一起来做题)
    [废弃]想写一个玩魔方的游戏
    C++template中typename 和class有什么区别?
  • 原文地址:https://www.cnblogs.com/Darlietoothpaste/p/6636740.html
Copyright © 2020-2023  润新知