Backbone源码分析(二)

  在传统MVC框架模式中,Model承担业务逻辑的任务。Backbone作为一个mvc框架,主要的业务逻辑交由Model与Collection来实现。Model代表领域对象,今天主要学一下Model源码中几个重要的函数。

  我们先看一下Model的构造函数做了哪些事情:

// 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 attrs = attributes || {};
    options || (options = {});

    this.cid = _.uniqueId(this.cidPrefix);//利用underscore生成一个客户端的唯一标识符cid
    this.attributes = {};//this.attributes是backbone中存放所有数据属性的对象
    //collection在获取model对应的后端url时使用,在model上设置collection并不会自动将model加入collection
    if (options.collection) this.collection = options.collection;
    //调用parse方法解析数据
    if (options.parse) attrs = this.parse(attrs, options) || {};
    //处理defaults默认数据,用attrs覆盖defaults
    var defaults = _.result(this, 'defaults');
    attrs = _.defaults(_.extend({}, defaults, attrs), defaults);
    this.set(attrs, options);//接收attrs将数据处理后放入this.attributes
    this.changed = {};//changed属性用来保存修改过的属性数据,第一次set,不需要changed数据
    this.initialize.apply(this, arguments);//调用initialize初始化model,这个方法需要子类来覆盖
  };

  Model的构造函数主要做了以下几件事:

  • 处理参数
  • 处理model的属性:cid、attributes、collection
  • 解析数据、处理属性的默认值
  • set方法接收处理参数
  • 调用initialize做初始化操作

  

  接下来是一个重要的set函数,这个函数是Model最核心的一个方法

// 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.
    set: function(key, val, options) {
      if (key == null) return this;

      // Handle both `"key", value` and `{key: value}` -style arguments.
      var attrs;
      if (typeof key === 'object') {//{key: value}形式
        attrs = key;
        options = val;
      } else {// key, value, options形式
        (attrs = {})[key] = val;
      }

      options || (options = {});//设置options参数

      // Run validation.
      //首先验证参数,这里没有直接调用validate方法,而是调用_validate这个私有方法,该方法内部调用validate方法
      if (!this._validate(attrs, options)) return false;

      // Extract attributes and options.
      var unset      = options.unset;
      var silent     = options.silent;
      var changes    = [];//用来存放所有有变动的key
      var changing   = this._changing;//this._chaning用来标识set方法是否在处理中,我猜这里的设置跟webworker多线程有关
      this._changing = true;//这里代表属性的变动更新开始
      // this.changed = {};//不能放在这里,如果val没改变,所有changed都被清空掉了

      if (!changing) {//使用_previousAttributes来保留最近一次的attributes
        this._previousAttributes = _.clone(this.attributes);
        this.changed = {};//每次set时,changed都会被重置的{},这表示仅保留最近一次的变化
      }

      var current = this.attributes;
      var changed = this.changed;
      var prev    = this._previousAttributes;

      // For each `set` attribute, update or delete the current value.
      for (var attr in attrs) {//遍历attrs
        val = attrs[attr];
        //对于单线程环境,current与_previousAttributes是一样的,这里的处理也应当是应对多线程
        if (!_.isEqual(current[attr], val)) changes.push(attr); //changes是本次变化的keys
        if (!_.isEqual(prev[attr], val)) {
          changed[attr] = val; //存储变化量
        } else {
          delete changed[attr];
        }
        //这里根据unset的设置,如果unset为true移除,否则设置attributes中的对应属性
        unset ? delete current[attr] : current[attr] = val;
      }

      // Update the `id`.
      //idAttribute的目的是跟后端数据库记录的id保持一致
      if (this.idAttribute in attrs) this.id = this.get(this.idAttribute);

      // Trigger all relevant attribute changes.
      // 在所有赋值结束后,发送事件通知
      if (!silent) {
        if (changes.length) this._pending = options;
        for (var i = 0; i < changes.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; //这里我觉得也是跟多线程有关,如果多个线程同时更新model,最终只发出一个整体的change事件
      if (!silent) {
        while (this._pending) {//很奇怪的设置
          options = this._pending;
          this._pending = false;
          this.trigger('change', this, options);//触发事件
        }
      }
      this._pending = false;
      this._changing = false;
      return this;
    }

  来整理一下set方法做的几件事:

  • 根据api的参数声明来处理参数
  • 声明几个与属性变化相关的变量
  • 设置_previousAttributes与changed来保存上次属性和这次的变化数据
  • 更新属性,保存本次变化数据和对应的key
  • 将发生变化的属性广播出去,change:key形式
  • 在model层次上发出change事件

  

  接下来是与后端打交道的save方法:

// 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方法保持客户端与数据库内记录同步,前后端数据可能出现不一致情况,
    // 如果options中wait参数为true的话,会用后端返回的数据来更新前端数据
    save: function(key, val, options) {
      // Handle both `"key", value` and `{key: value}` -style arguments.
      var attrs;
      if (key == null || typeof key === 'object') {//`{key: value}`
        attrs = key;
        options = val;
      } else {//`"key", value`
        (attrs = {})[key] = val;
      }

      //在方法默认开启验证和解析
      options = _.extend({validate: true, parse: true}, options);
      var wait = options.wait;

      // If we're not waiting and attributes exist, save acts as
      // `set(attr).save(null, opts)` with validation. Otherwise, check if
      // the model will be valid when the attributes, if any, are set.
      // wait为false的话,首先在前端更新model,set中调用验证方法
      if (attrs && !wait) {
        if (!this.set(attrs, options)) return false;
      } else if (!this._validate(attrs, options)) {//否则利用_validate进行验证
        return false;
      }

      // After a successful server-side save, the client is (optionally)
      // updated with the server-side state.
      var model = this;//保存this关键字
      var success = options.success;
      var attributes = this.attributes;
      options.success = function(resp) {
        // Ensure attributes are restored during synchronous saves.
        model.attributes = attributes;//这里先确保数据与为同步时保持一致
        var serverAttrs = options.parse ? model.parse(resp, options) : resp;
        // wait属性为true,利用后端数据更新model的属性
        if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
        if (serverAttrs && !model.set(serverAttrs, options)) return false;
        // success带表更新成功后的回调函数。
        // save方法,将模型数据的同步完全封装起来,开发者只需专注于自身业务逻辑即可!
        if (success) success.call(options.context, model, resp, options);
        // 触发sync事件
        model.trigger('sync', model, resp, options);
      };
      wrapError(this, options);

      // Set temporary attributes if `{wait: true}` to properly find new ids.
      //wait 为true,临时更新attributes,目的是下文中将model更新到数据库内
      if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);

      //根据model是否拥有idAttribute属性,决定是创建还是更新
      var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
      if (method === 'patch' && !options.attrs) options.attrs = attrs;
      var xhr = this.sync(method, this, options);

      // Restore attributes.
      this.attributes = attributes;//恢复数据,等到success后利用后端数据结果更新属性

      return xhr;
    },

其中用到的wrapError方法,源码如下:

// Wrap an optional error callback with a fallback error event.
  //将options中的error回调函数,变成一个能够触发error事件的回调函数
  var wrapError = function(model, options) {
    var error = options.error;
    options.error = function(resp) {
      if (error) error.call(options.context, model, resp, options);
      model.trigger('error', model, resp, options);
    };
  }

  save方法做的几件事:

  • 处理参数
  • 如果以客户端为准,则首先跟新model,否则验证需保存的属性
  • 声明局部变量,替换options中的success回调函数和error回调
  • 如果以后端返回数据为准,则先直接将attributes属性暂时更改,方便sync方法同步model,而后将attributes恢复,等待succes毁掉中利用后端返回结果更新

  

  接下来是销毁model的destroy方法:

// 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方法用来销毁model,当wait属性为true时,等待后台销毁成功时做实际销毁工作
    destroy: function(options) {
      options = options ? _.clone(options) : {};
      var model = this;
      var success = options.success;
      var wait = options.wait;

      var destroy = function() {
        model.stopListening();//移除其他代码中监听的model事件
        // 触发destroy事件
        model.trigger('destroy', model, model.collection, options);
      };

      // 后台销毁成功后的success回调
      options.success = function(resp) {
        if (wait) destroy();//销毁操作
        // 回调函数,业务逻辑相关
        if (success) success.call(options.context, model, resp, options);
        //拥有idAttribute属性,则触发sync事件
        if (!model.isNew()) model.trigger('sync', model, resp, options);
      };

      var xhr = false;
      if (this.isNew()) {//数据库中并没有该条记录
        _.defer(options.success);//underscore函数,延迟调用function直到当前调用栈清空为止
      } else {
        wrapError(this, options);//包装错误
        xhr = this.sync('delete', this, options);// 与后台数据同步
      }
      if (!wait) destroy(); //无需后台等待的话,直接做销毁操作
      return xhr;
    }

  destroy方法做的事情:

  • 声明局部变量以及做销毁操作的destroy方法
  • 替换options中的success方法
  • 如果model未存储于数据库中,直接使用underscore的defer延迟执行success,否则向后台发送删除请求

 

  与验证相关的_validate方法如下:

// Run validation against the next complete set of model attributes,
    // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
    _validate: function(attrs, options) {
      if (!options.validate || !this.validate) return true;
      attrs = _.extend({}, this.attributes, attrs);
      //backbone希望在验证失败时候,validate方法返回一个error对象
      var error = this.validationError = this.validate(attrs, options) || null;
      if (!error) return true;
      //触发invalid事件,也就是说如果单独调用validate方法不会触发invalid事件
      this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
      return false;
    }
时间: 2024-11-03 13:05:05

Backbone源码分析(二)的相关文章

Backbone源码分析(三)

Backbone源码分析(一)Backbone源码分析(二) Backbone中主要的业务逻辑位于Model和Collection,上一篇介绍了Backbone中的Model,这篇文章中将主要探讨Collection的源码. 让我们先来看一下Collection的构造函数: // Create a new **Collection**, perhaps to contain a specific type of `model`. // If a `comparator` is specified

Backbone源码分析(一)

距离上一篇博客有一段时间了,期间各种琐事萦绕.最主要的一件是,当我差不多将整个dojo核心源码看完,惊讶的发现dojo1.*的设计以是老态龙钟之象,而我沉溺在dojo中太久,已经不知道前端世界变成了什么样.这无异于晴天霹雳,霹的我目瞪口呆.汗流满面,惶惶不可终日.索性亡羊补牢为时未晚,这段期间虽有各种烦心事,但还能于百烦之中腾出点时间,看看源码已经是万中之幸.各种前端类库如浩瀚星辰,面对它们才能感觉到自身技术的浅薄,自身能力的低微.初出茅庐天下无敌,再练三年寸步难行,这就是我当前最真切的体悟.现

Hhadoop-2.7.0中HDFS写文件源码分析(二):客户端实现之DFSPacket

一.简介       HDFS在数据传输过程中,针对数据块Block,不是整个block进行传输的,而是将block切分成一个个的数据包进行传输.而DFSPacket就是HDFS数据传输过程中对数据包的抽象. 二.实现       HDFS客户端在往DataNodes节点写数据时,会以数据包packet的形式写入,且每个数据包包含一个包头,n个连续的校验和数据块checksum chunks和n个连续的实际数据块 actual data chunks,每个校验和数据块对应一个实际数据块,被用来做

TOMCAT源码分析——生命周期管理(二)

前言 我在<TOMCAT源码分析--生命周期管理(一)>一文中介绍了TOMCAT生命周期类接口设计.JMX.容器以及基于容器的事件与监听等内容,本文将接着介绍Tomcat7.0中容器生命周期的管理. 容器生命周期 每个容器都会有自身的生命周期,其中也涉及状态的迁移,以及伴随的事件生成,本节详细介绍Tomcat中的容器生命周期实现.所有容器的转态转换(如新疆.初始化.启动.停止等)都是由外到内,由上到下进行,即先执行父容器的状态转换及相关操作,然后再执行子容器的转态转换,这个过程是层层迭代执行的

HBase源码分析之MemStore的flush发起时机、判断条件等详情(二)

        在<HBase源码分析之MemStore的flush发起时机.判断条件等详情>一文中,我们详细介绍了MemStore flush的发起时机.判断条件等详情,主要是两类操作,一是会引起MemStore数据大小变化的Put.Delete.Append.Increment等操作,二是会引起HRegion变化的诸如Regin的分裂.合并以及做快照时的复制拷贝等,同样会触发MemStore的flush流程.同时,在<HBase源码分析之compact请求发起时机.判断条件等详情(一

Backbone.js 0.9.2 源码分析收藏

Backbone 为复杂Javascript应用程序提供模型(models).集合(collections).视图(views)的结构.其中模型用于绑定键值数据和自定义事件:集合附有可枚举函数的丰富API: 视图可以声明事件处理函数,并通过RESRful JSON接口连接到应用程序. 源码分析转之网上它人的备注,特收藏一下,以免方便阅读. // Backbone.js 0.9.2 // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. // Ba

Hhadoop-2.7.0中HDFS写文件源码分析(二):客户端实现(1)

一.综述       HDFS写文件是整个Hadoop中最为复杂的流程之一,它涉及到HDFS中NameNode.DataNode.DFSClient等众多角色的分工与合作.       首先上一段代码,客户端是如何写文件的: Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(conf); Path file = new Path("demo.txt"); FSDataOutputStream

HDFS源码分析之UnderReplicatedBlocks(二)

        UnderReplicatedBlocks还提供了一个数据块迭代器BlockIterator,用于遍历其中的数据块.它是UnderReplicatedBlocks的内部类,有三个成员变量,如下: // 当前迭代级别 private int level; // 标志位:是否为特定复制优先级的迭代器 private boolean isIteratorForLevel = false; // 数据块Block迭代器Iterator列表,存储各级别数据块迭代器 private fina

java io学习(二)ByteArrayOutputStream的简介,源码分析和示例

ByteArrayOutputStream的简介,源码分析和示例(包括OutputStream) 前面学习ByteArrayInputStream,了解了"输入流".接下来,我们学习与ByteArrayInputStream相对应的输出流,即ByteArrayOutputStream. 本章,我们会先对ByteArrayOutputStream进行介绍,在了解了它的源码之后,再通过示例来掌握如何使用它. ByteArrayOutputStream 介绍 ByteArrayOutputS