zl程序教程

您现在的位置是:首页 >  工具

当前栏目

Backbone源码分析(三)

源码 分析 Backbone
2023-09-14 08:57:59 时间

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 theyre 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, its 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 models 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 models 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;

 },

vue3源码分析——手写diff算法 通过上面的测试用例,可以看到是分了7种情况来的,在本篇文章,不采用编写测试用例,有兴趣的可以自己去github上面查看,这里主要使用图文加上代码,帮助大家更快的理解vue3中的diff算法
手写简易版flexible.js以及源码分析 我们的移动端布局通常会有rem结合媒体查询的实现,但是,淘宝有这样的一个flexible.js框架,根据不同的width给网页中html根节点设置不同的font-size,大大提高了我们的开发效率,今天阿牛便带你手写一个简易版flexible.js并解读,了解他的大致原理。
在 keras 源码中, layers/recurrent.py 中看到 RNN 实现方式 RNN 中的循环体使用 RNNCell 来进行定义的, 在 RNN(Layer) 中的 compute_output_shape 函数可以查看到 RNN 输出维度的计算方法, 可以看出维度为 (输入维度, 输出维度) .