• 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, the Collection will maintain
      // its models in sort order, as they're added and removed.
      var Collection = Backbone.Collection = function(models, options) {
        options || (options = {});
        if (options.model) this.model = options.model;//model对象指定collection管理的Model类型
        //comparator 排序使用
        if (options.comparator !== void 0) this.comparator = options.comparator;
        this._reset();// 重置集合
        this.initialize.apply(this, arguments);//调用初始化方法
        //如果参数中传递model数组,则利用models数组重置集合
        if (models) this.reset(models, _.extend({silent: true}, options));
      };
    

    接下来是在set, add, remove 中经常用到的splice函数:

      // Splices `insert` into `array` at index `at`.
      //将insert数组在at位置拼接到array中
      var splice = function(array, insert, at) {
        //防止at超出数组长度
        at = Math.min(Math.max(at, 0), array.length);
        //先创建一个空数组
        var tail = Array(array.length - at);
        var length = insert.length;
        var i;
        //将at之后的数组元素暂存到tail中
        for (i = 0; i < tail.length; i++) tail[i] = array[i + at];
        //用insert替换array中at之后的元素
        for (i = 0; i < length; i++) array[i + at] = insert[i];
        //将原at之后的array元素放到insert元素之后
        for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i];
      };
    

    下面我们来讲解set函数,set函数是Collection中非常重要的一个函数,集增删改查与一身,处理了大量的核心业务逻辑:

        // Update a collection by `set`-ing a new list of models, adding new ones,
        // removing models that are no longer present, and merging models that
        // already exist in the collection, as necessary. Similar to **Model#set**,
        // the core operation for updating the data contained by the collection.
        set: function(models, options) {
          if (models == null) return;
          // var setOptions = {add: true, remove: true, merge: true};
          options = _.extend({}, setOptions, options);//setOptions是预设参数
          // 如果models为原生对象,会利用Collection中model属性来转化成Model实例
          if (options.parse && !this._isModel(models)) {
            models = this.parse(models, options) || [];
          }
    
          var singular = !_.isArray(models);
          models = singular ? [models] : models.slice();
    
          // 处理at,确保at为合理的数字
          var at = options.at;
          if (at != null) at = +at; //转化为数字
          if (at > this.length) at = this.length;
          if (at < 0) at += this.length + 1;
    
          var set = [];// set表示经过本次处理后应当存在于this.models中的model
          var toAdd = [];// 本次操作增加的model数组
          var toMerge = [];// 本次操后修改的model数组
          var toRemove = [];// 本次操作删除掉的models
          var modelMap = {};//modelMap是本次变化后的应该存在于Collection中的models的key集合
    
          var add = options.add;
          var merge = options.merge;
          var remove = options.remove;
    
          var sort = false;
          //有comparator属性,没设置at,sort为true
          //如果对collection做了插入的话,需要自己手动排序
          var sortable = this.comparator && at == null && options.sort !== false;
          //comparator 是model中的属性
          var sortAttr = _.isString(this.comparator) ? this.comparator : null;
    
          // Turn bare objects into model references, and prevent invalid models
          // from being added.
          var model, i;
          //先过滤一遍,找出存在于collection中的和不存在于当前collection中的
          for (i = 0; i < models.length; i++) {//处理add和existing
            model = models[i];
    
            // If a duplicate is found, prevent it from being added and
            // optionally merge it into the existing model.
            // get根据idAttribute || cid 来查找
            var existing = this.get(model);
            if (existing) {
              if (merge && model !== existing) {
                var attrs = this._isModel(model) ? model.attributes : model;
                if (options.parse) attrs = existing.parse(attrs, options);//在这之前应该验证一下
                // 使用当前属性替换model中已存在属性
                existing.set(attrs, options);
                toMerge.push(existing);
                // 查看排序字段是否有更改
                if (sortable && !sort) sort = existing.hasChanged(sortAttr);
              }
              // 将更的model id存到modelMap中
              if (!modelMap[existing.cid]) {
                modelMap[existing.cid] = true;
                set.push(existing);
              }
              models[i] = existing;
    
            // If this is a new, valid model, push it to the `toAdd` list.
            } else if (add) {
              // _prepareModel将原始对象转化为Model实例
              model = models[i] = this._prepareModel(model, options);
              if (model) {
                toAdd.push(model);
                // _addReference 将model加入到Collection的_byId中,并绑定model的所有事件
                this._addReference(model, options);
                modelMap[model.cid] = true;
                set.push(model);
              }
            }
          }
    
          // Remove stale models.
          if (remove) {
            for (i = 0; i < this.length; i++) {
              model = this.models[i];
              // 在this.models中但不在本次set中的model,都要删除
              if (!modelMap[model.cid]) toRemove.push(model);
            }
            //移除model,this.models、this._byId;移除model的绑定事件
            if (toRemove.length) this._removeModels(toRemove, options);
          }
    
          // See if sorting is needed, update `length` and splice in new models.
          var orderChanged = false;
          var replace = !sortable && add && remove;
          if (set.length && replace) {//如果同时有加减操作,便将models放到this.models中
            //如果this.models中set的数据不一致,则认为order有变化。
            orderChanged = this.length !== set.length || _.some(this.models, function(m, index) {
              return m !== set[index];
            });//没有启用排序,但是this.models与set不一致时,仍会触发sort事件
    
            //处理完remove后,该删除的都删除掉;用set替换this.models
            this.models.length = 0;
            splice(this.models, set, 0);
            this.length = this.models.length;
          } else if (toAdd.length) {//如果仅仅是增加model,则将toAdd插入到指定位置去
            if (sortable) sort = true;
            splice(this.models, toAdd, at == null ? this.length : at);
            this.length = this.models.length;
          }
    
          // Silently sort the collection if appropriate.
          //这里排序一下,但不要触发事件,在下文统一处理
          if (sort) this.sort({silent: true});
    
          // Unless silenced, it's time to fire all appropriate add/sort/update events.
          //collection跟新完毕后,再发送事件
          // remove在上文删除时已触发
          if (!options.silent) {
            for (i = 0; i < toAdd.length; i++) {
              if (at != null) options.index = at + i;
              model = toAdd[i];
              model.trigger('add', model, this, options);
            }
            if (sort || orderChanged) this.trigger('sort', this, options);
            if (toAdd.length || toRemove.length || toMerge.length) {
              options.changes = {
                added: toAdd,
                removed: toRemove,
                merged: toMerge
              };
              this.trigger('update', this, options);
            }
          }
    
          // Return the added (or merged) model (or models).
          return singular ? models[0] : models;
        },
    

    Collection中存储model的属性有两个:this.models数组和_byId键值对,所有的增删改查都需要对这两个属性进行更新。
    set函数中主要做了以下几件事情:

    • 将models参数处理成Model的实例数组
    • 处理options中的at参数,将其变成一个合理的数字
    • 声明变量set, toAdd, toMerge, toRemove, modelMap
    • 遍历models参数,找出其中应当更改或者加入到Collection中的model并添加到上文的变量中
    • 从Collection的this.models中删除不存在本次set中的model
    • 统一更改Collection中的this.models数组
    • 排序,但不要触发sort事件
    • 统一处理事件: add、sort、update事件

    add函数内部就是利用set函数来处理的:

    // Add a model, or list of models to the set. `models` may be Backbone
        // Models or raw JavaScript objects to be converted to Models, or any
        // combination of the two.
        add: function(models, options) {//没有的会加进去,已存在的会根据options决定是否merge
          return this.set(models, _.extend({merge: false}, options, addOptions));
        },
    

    add之后,另一个重要的操作就是remove,Collection中的remove逻辑由remove, _removeModels, _removeReference 三个函数完成

    remove: function(models, options) {
          //处理参数,models处理成数组
          options = _.extend({}, options);
          var singular = !_.isArray(models);
          models = singular ? [models] : models.slice();
          //删除掉models,并触发removed事件
          var removed = this._removeModels(models, options);
          //从Collection层面上触发update事件
          if (!options.silent && removed.length) {
            options.changes = {added: [], merged: [], removed: removed};
            this.trigger('update', this, options);//触发update事件,注意options里面的change,这样可以方便很多事
          }
          return singular ? removed[0] : removed;//注意api的返回值和事件参数的设置
        },
    
        // Internal method called by both remove and set.
        _removeModels: function(models, options) {
          var removed = [];
          for (var i = 0; i < models.length; i++) {
            var model = this.get(models[i]);
            if (!model) continue;
    
            // 首先从this.models数组中删除model
            var index = this.indexOf(model);
            this.models.splice(index, 1);
            this.length--;
    
            // Remove references before triggering 'remove' event to prevent an
            // infinite loop. #3693
            // 从_byId中删除model的引用
            delete this._byId[model.cid];
            var id = this.modelId(model.attributes);
            if (id != null) delete this._byId[id];
    
            // 触发Collection的remove事件
            if (!options.silent) {
              options.index = index;
              model.trigger('remove', model, this, options); //被删除的model也触发remove事件
            }
    
            // 删除model的引用和移除model的绑定事件
            removed.push(model);
            this._removeReference(model, options);
          }
          return removed;
        },
    
        // Internal method to sever a model's ties to a collection.
        _removeReference: function(model, options) {
          //断开model与collection的关联
          delete this._byId[model.cid];
          var id = this.modelId(model.attributes);
          if (id != null) delete this._byId[id];
          if (this === model.collection) delete model.collection;
          // 移除所有的绑定事件
          model.off('all', this._onModelEvent, this);
        },
    

    remove整体逻辑如下:

    • 处理models参数
    • this.models_byId中删除model,触发Collection的remove事件
    • 断开model与Collection的关联,移除model的绑定事件
    • 触发Collection的update事件

    _removeReference对应的是_addReference,它的作用于_removeRenence相反:

    // Internal method to create a model's ties to a collection.
        _addReference: function(model, options) {
          //将model与collection关联起来,绑定model的各种事件
          this._byId[model.cid] = model;
          var id = this.modelId(model.attributes);
          if (id != null) this._byId[id] = model;
          model.on('all', this._onModelEvent, this);
        },
    

    最后要提一下的是reset函数:

    // When you have more items than you want to add or remove individually,
        // you can reset the entire set with a new list of models, without firing
        // any granular `add` or `remove` events. Fires `reset` when finished.
        // Useful for bulk operations and optimizations.
        reset: function(models, options) {//reset中不会触发add和remove事件
          options = options ? _.clone(options) : {};
          for (var i = 0; i < this.models.length; i++) {
            //断开与Collection的链接,和移除model的事件
            this._removeReference(this.models[i], options);
          }
          options.previousModels = this.models;//多看设计
          this._reset();//将Collection置空,length、this.models、this._byId
          models = this.add(models, _.extend({silent: true}, options));
          // 触发事件
          if (!options.silent) this.trigger('reset', this, options);
          return models;
        },
    
  • 相关阅读:
    PHP安装扩展mcrypt以及相关依赖项 【PHP安装PECL扩展的方法】
    linux设置开机自动启动
    php安装gd库
    php扩展库 说明
    把lighttpd配置为系统服务
    安装mysql5.6
    怎样当一个企业舍不得的人
    JQuery的$(document).ready(function(){})与JS的window.onload 的各自优势!
    JS中Null与Undefined的区别
    JS文本框输入限制
  • 原文地址:https://www.cnblogs.com/dojo-lzz/p/5414890.html
Copyright © 2020-2023  润新知