微信号:FrontDev

介绍:分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯

Backbone源码分析(二)

2016-04-11 20:38 前端大全

(点击上方公众号,可快速关注)


来源:木的树

链接:http://www.cnblogs.com/dojo-lzz/p/5374218.html



在传统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;

    }

【今日微信公号推荐↓】

更多推荐请看值得关注的技术和设计公众号


其中推荐了包括技术设计极客 和 IT相亲相关的热门公众号。技术涵盖:Python、Web前端、Java、安卓、iOS、PHP、C/C++、.NET、Linux、数据库、运维、大数据、算法、IT职场等。点击《值得关注的技术和设计公众号》,发现精彩!


 
前端大全 更多文章 详解Javascript中的Object对象 结合个人经历总结的前端入门方法 前端不为人知的一面–前端冷知识集锦 一份优秀的前端开发工程师简历是怎么样的? 浅谈Web缓存
猜您喜欢 与新浪微博架构师陈飞交流docker在新浪中的使用 前端 IoC 理念入门 突破App启动时间的极限 大数据入门必学技术之Hadoop Modern Web 提升网站兼容性从这里开始 - 框架和类库