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;
    },


目录
相关文章
|
2月前
|
机器学习/深度学习 人工智能 前端开发
构建AI智能体:六十九、Bootstrap采样在大模型评估中的应用:从置信区间到模型稳定性
Bootstrap采样是一种通过有放回重抽样来评估模型性能的统计方法。它通过从原始数据集中随机抽取样本形成多个Bootstrap数据集,计算统计量(如均值、标准差)的分布,适用于小样本和非参数场景。该方法能估计标准误、构建置信区间,并量化模型不确定性,但对计算资源要求较高。Bootstrap特别适合评估大模型的泛化能力和稳定性,在集成学习、假设检验等领域也有广泛应用。与传统方法相比,Bootstrap不依赖分布假设,在非正态数据中表现更稳健。
603 113
|
2月前
|
弹性计算 人工智能 自然语言处理
QoderCLI 智能编程实战指南:5 分钟部署,一句话生成服务
QoderCLI 不只是工具——它是你 24 小时在线的智能编程伙伴,秒级理解项目上下文,一行指令生成完整功能模块,测试、解释、补全一气呵成。开发效率飙升 300%,代码质量肉眼可见提升!
QoderCLI 智能编程实战指南:5 分钟部署,一句话生成服务
|
8月前
|
JSON API 数据格式
淘宝商品评论API接口,json数据示例参考
淘宝开放平台提供了多种API接口来获取商品评论数据,其中taobao.item.reviews.get是一个常用的接口,用于获取指定商品的评论信息。以下是关于该接口的详细介绍和使用方法:
|
4月前
|
Ubuntu 编译器 开发工具
在Ubuntu系统上搭建RISC-V交叉编译环境
以上步骤涵盖了在Ubuntu系统上搭建RISC-V交叉编译环境的主要过程。这一过程涉及了安装依赖、克隆源码、编译安装工具链以及设置环境变量等关键步骤。遵循这些步骤,可以在Ubuntu系统上搭建一个用于RISC-V开发的强大工具集。
478 22
|
4月前
|
前端开发 算法 Java
【CSS】前端三大件之一,如何学好?从基本用法开始吧!(九):强势分析Animation动画各类参数;从播放时间、播放方式、播放次数、播放方向、播放状态等多个方面,完全了解CSS3 Animation
Animation属性 css3为Animation动画提供的几个属性如下: 属性名 属性值 animation-name 指定动画名称,该属性指定一个已有的关键帧定义。 animation-duration 指定动画持续时间。 animation-timing-funtion 指定动画变化速度。 animation-delay 指定动画延迟多长时间才开始执行。 animation-iteration-count 指定动画的循环执行次数。 animation:这是一个复合属性。
342 2
|
8月前
|
开发者
鸿蒙仓颉语言开发教程:网络请求和数据解析
本文介绍了在仓颉开发语言中实现网络请求的方法,以购物应用的分类列表为例,详细讲解了从权限配置、发起请求到数据解析的全过程。通过示例代码,帮助开发者快速掌握如何在网络请求中处理数据并展示到页面上,减少开发中的摸索成本。
鸿蒙仓颉语言开发教程:网络请求和数据解析
|
8月前
|
存储 监控 安全
电商API接口安全防护全流程详解:认证加密筑牢安全防线
本文深入解析电商API接口安全防护,涵盖认证、授权、数据加密及其他安全措施,探讨如何构建全方位的安全体系,保障电商平台数据与业务安全。
|
前端开发 JavaScript 网络架构
实现动态路由与状态管理的SPA——使用React Router与Redux
【10月更文挑战第1天】实现动态路由与状态管理的SPA——使用React Router与Redux
425 95
|
7月前
|
Web App开发 JavaScript 测试技术
Playwright 极速入门:1 小时搞定环境搭建与首个测试脚本
本文带你1小时快速入门Playwright,完成环境搭建并编写首个测试脚本。Playwright是微软推出的现代化Web自动化测试工具,支持Chromium、Firefox和WebKit三大浏览器引擎,具备跨平台、多语言(Python/JS/Java/C#)特性。其核心优势包括:智能自动等待机制减少失败率、内置录制工具实时生成脚本、多语言灵活选择,以及真移动端设备模拟能力,显著提升测试效率和可靠性。