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

时间: 2024-10-01 20:52:09

backbond Model方法(set)的相关文章

backbond Model实现

backbond中的M,指的是模型,即存放数据以及数据相关逻辑的单位.在分析其结构之前,先看一下其调用过程. <script> (function ($) { World = Backbone.Model.extend({ initialize: function(){ alert('Hey, you create me!'); }, defaults: { name:'张三', age: '38' } }); var wodld = new World({x:1}); var x = new

在ASP.NET MVC中使用Knockout实践02,组合View Model成员、Select绑定、通过构造器创建View Model,扩展View Model方法

本篇体验使用ko.computed(fn)计算.组合View Model成员.Select元素的绑定.使用构造器创建View Model.通过View Model的原型(Prototype)为View Model添加扩展方法. □ 使用ko.computed(fn)计算成员 有时候,我们希望把View Model中的几个成员组合起来成为一个新成员,使用ko.computed(fn)可实现. 接着上一篇,为productViewModel这个json对象增加一个计算成员. <div data-bi

2. 数据库文件配置与简单操作 Model / M()

官方文档说明位置: Thinkphp/Conf/convention.php 内容说明如下: 'DB_TYPE' => '', // 数据库类型 'DB_HOST' => '', // 服务器地址 'DB_NAME' => '', // 数据库名 'DB_USER' => '', // 用户名 'DB_PWD' => '', // 密码 'DB_PORT' => '', // 端口 'DB_PREFIX' => '', // 数据库表前缀 复制放入项目的模块配置文

类的连续调用方法

class a{ private $b = 0; public function c( $Num = 0 ) { $this->b = $Num; return $this; //关键就在这里,有这条就可以连续调用了, } public function d($d) { $this->b = $this->b+$d; return $this; } public function e(){ echo $this->b; } } //接下来是调用 $Obj = new a; $Obj

Django model与数据库操作对应关系(转)

? Django对数据库的操作分用到三个类:Manager.QuerySet.Model. Manager的主要功能定义表级方法(表级方法就是影响一条或多条记录的方法),我们可以以models.Manager为父类,定义自己的manager,增加表级方法: QuerySet是Manager的方法返回的,是一个可遍历结构,包含一个或多个元素,每个元素都是一个Model 实例,它里面的方法也是表级方法. Model是一条记录的类,它的功能很强大,里面包含外键实体等,它的方法都是记录级方法(都是实例方

CakePHP使用方法

1.加载要使用的model方法: a.var $uses = array('model名'); b.$this->loadModel("model名"); 2.数据库的增删改查 a.增加 $this->Modelname->save($data); b.删除 $this->Modelname->delete($id); c.修改 $this->Modelname->save($data); d.查找 $this->Modelname-&g

详述 Spring MVC 框架中拦截器 Interceptor 的使用方法

1 前言 昨天新接了一个需要,"拦截 XXX,然后 OOO",好吧,说白了就是要用拦截器干点事(实现一个具体的功能).之前,也在网络上搜了很多关于Interceptor的文章,但感觉内容都大同小异,而且知识点零零散散,不太方便阅读.因此,正好借此机会,整理一篇关于拦截器的文章,在此分享给大家,以供大家参考阅读. 2 拦截器 2.1 概念 Java 里的拦截器是动态拦截 action 调用的对象.它提供了一种机制可以使开发者可以定义在一个 action 执行的前后执行的代码,也可以在一个

NopCommerce架构分析之五------Model绑定Action参数

asp.net MVC中Action参数不只是一些基本类型,也支持实体参数.那么从客户端传来的数据如何映射或转换成实体对象呢?就是通过实体绑定类ModelBinder.此系列类在请求转化为后台Controller的Action方法前,捕获传递过来的数据,并对其进行解析和转换,最终为实体类对象. 在系统启动前,Global.asax.cs中的方法Application_Start方法调用下面代码定义参数转换规则. [csharp] view plain copy //model binders M

【Laravel5.0框架】路由方法总结

Laravel 中的路由,跟其他 PHP 框架一样,作用是把各种请求分流到各个控制器. 最简单的路由由一个 URI 和一个闭包调用组成. 路由文件在:`learnlaravel5/app/Http/routes.php` : 基本 GET 路由 Route::get('/', function() { return 'Hello World'; }); 基本 POST 路由 Route::post('foo/bar', function() { return 'Hello World'; });