backbone.Collection源码笔记

Backbone.Collection

backbone的Collection(集合),用来存储多个model,并且可以多这些model进行数组一样的操作,比如添加,修改,删除,排序,插入,根据索引取值,等等,数组有的方法,他基本上都有

 

源码注释

 

<!DOCTYPE html>

<html>

<head>

    <meta charset="utf-8"/>

    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />

    <title>backbone</title>

    <style type="text/css">

        *{padding:0;margin:0;}

        .wrap{width:960px; margin: 100px auto; padding: 20px 0;}

        ul{ list-style: none;}

    </style>

</head>

<body>

    <div class="wrap">

        <div id="a1"></div>

        <div id="a2"></div>

        <div id="a3"></div>

    </div>

<script src="http://files.cnblogs.com/wtcsy/jquery.js"></script> 

<script src="http://files.cnblogs.com/wtcsy/underscore.js"></script>

<script src="http://files.cnblogs.com/wtcsy/events.js"></script>

<script src="http://files.cnblogs.com/wtcsy/model.js"></script>

<script>

(function(){

    // Backbone.Collection

    // -------------------

    var array = [];

    var slice = array.slice;

    // If models tend to represent a single row of data, a Backbone Collection is

    // more analogous to a table full of data ... or a small slice or page of that

    // table, or a collection of rows that belong together for a particular reason

    // -- all of the messages in this particular folder, all of the documents

    // belonging to this particular author, and so on. Collections maintain

    // indexes of their models, both in order, and for lookup by `id`.



    // 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 = {});

        //默认的model 

        if (options.model) this.model = options.model;

        if (options.comparator !== void 0) this.comparator = options.comparator;

        //重置collection里面的的一些属性

        this._reset();

        this.initialize.apply(this, arguments);

        //如果传入的models有数据,可以进行设置

        if (models) 

        this.reset(models, _.extend({silent: true}, options));

    };



    // Default options for `Collection#set`.

    // 设置的参数 添加的参数 add

    var setOptions = {add: true, remove: true, merge: true};

    var addOptions = {add: true, remove: false};    



    _.extend(Collection.prototype, Backbone.Events, {

        // The default model for a collection is just a **Backbone.Model**.

        // This should be overridden in most cases.

        model: Backbone.Model,



        // Initialize is an empty function by default. Override it with your own

        // initialization logic.

        initialize: function(){},



        // The JSON representation of a Collection is an array of the

        // models' attributes.

        toJSON: function(options) {

            return this.map(function(model){ return model.toJSON(options); });

        },   



        // Proxy `Backbone.sync` by default.

        /*

        sync: function() {

          return Backbone.sync.apply(this, arguments);

        },

        */



        // **parse** converts a response into a list of models to be added to the

        // collection. The default implementation is just to pass it through.

        parse: function(resp, options) {

            return resp;

        },      



        // Add a model, or list of models to the set.

        add: function(models, options) {

            //其实就是调用set方法 只有add设置成true remove设置成false merge设置成false

            return this.set(models, _.extend({merge: false}, options, addOptions));

        },



        // 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) {

            options = _.defaults({}, options, setOptions);

            //parse 必须是一个函数  传入的后将models进行一次转换

            if (options.parse) models = this.parse(models, options);

            var singular = !_.isArray(models);

            models = singular ? (models ? [models] : []) : models.slice();

            var id, model, attrs, existing, sort;

            var at = options.at;

            // this.comparator 是排序的东西  如果是函数 sotrAttr为null  否则sotrAttr为true

            var sortable = this.comparator && (at == null) && options.sort !== false;

            var sortAttr = _.isString(this.comparator) ? this.comparator : null;

            var toAdd = [], toRemove = [], modelMap = {};

            var add = options.add, merge = options.merge, remove = options.remove;

            var order = !sortable && add && remove ? [] : false;

            var targetProto = this.model.prototype;



            // Turn bare objects into model references, and prevent invalid models

            // from being added.

            //对models进行一次遍历 找出要add的 要remove的

            for (var i = 0, length = models.length; i < length; i++) {

                attrs = models[i] || {};

                //通过查找model的属性找出id  可以是方便后面用 id可以是model本身 也可以是model的cid 或者是model的id

                if (this._isModel(attrs)) {

                    id = model = attrs;

                } else if (targetProto.generateId) {

                    id = targetProto.generateId(attrs);

                } else {

                    id = attrs[targetProto.idAttribute || Model.prototype.idAttribute];

                }



                // If a duplicate is found, prevent it from being added and

                // optionally merge it into the existing model.

                // return this._byId[obj] || this._byId[obj.id] || this._byId[obj.cid];

                // 看model是否在this.models里面存在 如果存在,并且设置了remove吧model放到modelMap中

                // 如果设置了merge model重新设置他的属性 如果设置了排序 排序标识sotr设置成true,数据改变了 肯定要排序一次的

                if (existing = this.get(id)) {

                    if (remove) modelMap[existing.cid] = true;

                    if (merge) {

                        attrs = attrs === model ? model.attributes : attrs;

                        if (options.parse) attrs = existing.parse(attrs, options);

                        existing.set(attrs, options);

                        if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;

                    }

                    models[i] = existing;



                // If this is a new, valid model, push it to the `toAdd` list.

                } else if (add) {

                    // 如果遍历的model不存在 变并且设置了add

                    //对这个model做一些操作_prepareModel  如果传入的attrs是backbone实例化的model则只设置model.collection指向this

                    //如果attrs只是数据  则实例化model并且model.collection指向this

                    // 然后把model放到 toAdd中 方便后面使用

                    //最后 以model的cid为key 存入this._byId中

                    model = models[i] = this._prepareModel(attrs, options);

                    if (!model) continue;

                    toAdd.push(model);

                    this._addReference(model, options);

                }



                // Do not add multiple models with the same `id`.

                model = existing || model;

                if (!model) continue;

                //order 如果是add或者是remove并且没有设置排序  并且model是新实例化的 添加到order里面去 后面会用到

                if (order && (model.isNew() || !modelMap[model.id])) order.push(model);

                modelMap[model.id] = true;          

            }



            // Remove nonexistent models if appropriate.

            if (remove) {

                // 做删除 先做一些准备  然后添加到toRemove里面去

                for (var i = 0, length = this.length; i < length; i++) {

                    if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);

                }

                if (toRemove.length) this.remove(toRemove, options);

            }



            // See if sorting is needed, update `length` and splice in new models.

            if (toAdd.length || (order && order.length)) {

                //符合添加条件就记性添加

                if (sortable) sort = true;

                this.length += toAdd.length;

                if (at != null) {

                    for (var i = 0, length = toAdd.length; i < length; i++) {

                        this.models.splice(at + i, 0, toAdd[i]);

                    }

                } else {

                    if (order) this.models.length = 0;

                    var orderedModels = order || toAdd;

                    for (var i = 0, length = orderedModels.length; i < length; i++) {

                        this.models.push(orderedModels[i]);

                    }

                }

            }



            // Silently sort the collection if appropriate.

            //符合排序条件 进行排序

            if (sort) this.sort({silent: true});



            // Unless silenced, it's time to fire all appropriate add/sort events.

            if (!options.silent) {

                //如果没有设置silent 触发每个model的add的回调

                for (var i = 0, length = toAdd.length; i < length; i++) {

                    (model = toAdd[i]).trigger('add', model, this, options);

                }

                if (sort || (order && order.length)) this.trigger('sort', this, options);

            }



            // Return the added (or merged) model (or models).

            return singular ? models[0] : models;

        },



        // Remove a model, or a list of models from the set.

        remove: function(models, options) {

            // 删除model 删除this._byId对model的引用

            //触发model本身的remove绑定的回调

            var singular = !_.isArray(models);

            models = singular ? [models] : _.clone(models);

            options || (options = {});

            for (var i = 0, length = models.length; i < length; i++) {

                var model = models[i] = this.get(models[i]);

                if (!model) continue;

                delete this._byId[model.id];

                delete this._byId[model.cid];

                var index = this.indexOf(model);

                this.models.splice(index, 1);

                this.length--;

                if (!options.silent) {

                    options.index = index;

                    model.trigger('remove', model, this, options);

                }

                this._removeReference(model, options);

            }

            return singular ? models[0] : models;

        },



        // Force the collection to re-sort itself. You don't need to call this under

        // normal circumstances, as the set will maintain sort order as each item

        // is added.

        sort: function(options) {

            if (!this.comparator) throw new Error('Cannot sort a set without a comparator');

            options || (options = {});



            // Run sort based on type of `comparator`.

            if (_.isString(this.comparator) || this.comparator.length === 1) {

                this.models = this.sortBy(this.comparator, this);

            } else {

                this.models.sort(_.bind(this.comparator, this));

            }



            if (options.reverse) this.models = this.models.reverse();



            if (!options.silent) this.trigger('sort', this, options);

            return this;

        },              

        // Get a model from the set by id.

        get: function(obj) {

            if (obj == null) return void 0;

            return this._byId[obj] || this._byId[obj.id] || this._byId[obj.cid];

        },



        // Private method to reset all internal state. Called when the collection

        // is first initialized or reset.

        _reset: function() {

            this.length = 0;

            this.models = [];

            this._byId  = {};

        },



        // 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) {

            options || (options = {});

            for (var i = 0, length = this.models.length; i < length; i++) {

                this._removeReference(this.models[i], options);

            }

            options.previousModels = this.models;

            this._reset();

            models = this.add(models, _.extend({silent: true}, options));

            if (!options.silent) this.trigger('reset', this, options);

            return models;

        },



        // Prepare a hash of attributes (or other model) to be added to this

        // collection.

        _prepareModel: function(attrs, options) {

            if (this._isModel(attrs)) {

                if (!attrs.collection) attrs.collection = this;

                return attrs;

            }

            options = options ? _.clone(options) : {};

            options.collection = this;

            var model = new this.model(attrs, options);

            if (!model.validationError) return model;

            this.trigger('invalid', this, model.validationError, options);

            return false;

        },

        // Method for checking whether an object should be considered a model for

        // the purposes of adding to the collection.

        _isModel: function (model) {

            return model instanceof Backbone.Model;

        },   



        // Internal method to create a model's ties to a collection.

        _addReference: function(model, options) {

            this._byId[model.cid] = model;

            if (model.id != null) this._byId[model.id] = model;

            model.on('all', this._onModelEvent, this);

        },



        // Internal method called every time a model in the set fires an event.

        // Sets need to update their indexes when models change ids. All other

        // events simply proxy through. "add" and "remove" events that originate

        // in other collections are ignored.

        _onModelEvent: function(event, model, collection, options) {

            if ((event === 'add' || event === 'remove') && collection !== this) return;

            if (event === 'destroy') this.remove(model, options);

            if (event === 'change-id') {

            if (collection != null) delete this._byId[collection];

            if (model.id != null) this._byId[model.id] = model;

            }

            this.trigger.apply(this, arguments);

        },

        // Internal method to sever a model's ties to a collection.

        _removeReference: function(model, options) {

            if (this === model.collection) delete model.collection;

            model.off('all', this._onModelEvent, this);

        },

    });



    // Underscore methods that we want to implement on the Collection.

    // 90% of the core usefulness of Backbone Collections is actually implemented

    // right here:

    var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',

    'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',

    'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',

    'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',

    'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',

    'lastIndexOf', 'isEmpty', 'chain', 'sample', 'partition'];



    // Mix in each Underscore method as a proxy to `Collection#models`.

    _.each(methods, function(method) {

        if (!_[method]) return;

        Collection.prototype[method] = function() {

            var args = slice.call(arguments);

            args.unshift(this.models);

            return _[method].apply(_, args);

        };

    });



    // Underscore methods that take a property name as an argument.

    var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy'];



    // Use attributes instead of properties.

    _.each(attributeMethods, function(method) {

        if (!_[method]) return;

        Collection.prototype[method] = function(value, context) {

            var iterator = _.isFunction(value) ? value : function(model) {

                return model.get(value);

            };

            return _[method](this.models, iterator, context);

        };

    });





    // Helper function to correctly set up the prototype chain, for subclasses.

    // Similar to `goog.inherits`, but uses a hash of prototype properties and

    // class properties to be extended.

    //第一个参数是要扩展到原型上的对象, 第2个参数是静态方法扩展到构造函数上去的

    var extend = function(protoProps, staticProps) {

        var parent = this;

        var child;



        // The constructor function for the new subclass is either defined by you

        // (the "constructor" property in your `extend` definition), or defaulted

        // by us to simply call the parent's constructor.

        if (protoProps && _.has(protoProps, 'constructor')) {

            child = protoProps.constructor;

        } else {

            child = function(){ return parent.apply(this, arguments); };

        }



        // Add static properties to the constructor function, if supplied.

        //将静态方法和 parent上的静态方法一起扩展到child上面去

        _.extend(child, parent, staticProps);



        // Set the prototype chain to inherit from `parent`, without calling

        // `parent`'s constructor function.

        //创建一个新的构造含糊Surrogate ; 

        //this.constructor = child的意思是  Surrogate实例化后的对象  让对象的构造函数指向child

        // Surrogate的原型就是parent的原型

        // 然后实例化给child的原型,

        // 这里不是直接从new parent给child.prototype 而是创建一个新的构造函数,我也不知道为啥要这样

        var Surrogate = function(){ this.constructor = child; };

        Surrogate.prototype = parent.prototype;

        child.prototype = new Surrogate;



        // Add prototype properties (instance properties) to the subclass,

        // if supplied.

        // 把第一个参数上的属性扩展到child.prototype

        if (protoProps) _.extend(child.prototype, protoProps);



        // Set a convenience property in case the parent's prototype is needed

        // later.

        // 拿一个属性引用父的原型, 以免以后要用到.

        child.__super__ = parent.prototype;



        return child;

    };



    Backbone.Collection.extend = extend;    

})();



</script>

</body>

</html>
View Code

 

Collection的一些基本属性

length collection里面包含moldel的个数

models 该属性引用一个数组,数组里面就是model了

_byId 该属性引用一个对象,value就是model,key是该model的cid(或者是id,如果给model设置了id,就取id当key,否则用cid当key),

model 默认是Backbone.Model,可以被覆盖,如果传入的参数是这样的{name:"xx",age:"oo"}的对象,被实例化的对象就是用model这个基类来实例化的

 

Collection被实例化的过程以及 _reset,reset的方法

实例化的过程就是设置默认model,默认的model的作用是如果实例化后的colletion添加model的时候,如果传入的参数是object,则会用默认model实例化

设置comparator,

重置属性, 

调用初始化方法initialize,

如果传入了值,重置值

_reset 

                this.length = 0;

                this.models = [];

                this._byId  = {};

就是将一些属性设置成初始化的值

reset  collection.reset([models], [options]) 

                reset: function(models, options) {

                    options || (options = {});

                    for (var i = 0, length = this.models.length; i < length; i++) {

                        this._removeReference(this.models[i], options);

                    }

                    options.previousModels = this.models;

                    this._reset();

                    models = this.add(models, _.extend({silent: true}, options));

                    if (!options.silent) this.trigger('reset', this, options);

                    return models;

                },

                _removeReference: function(model, options) {

                    if (this === model.collection) delete model.collection;

                    model.off('all', this._onModelEvent, this);

                }, 

它会遍历this.models里面的东西,然后执行this._removeReference,this._removeReference会把model对象的属性collection给删除掉(model在加入this.models里面的时候会加上collection这个属性,所以删除的时候要这个属性也干掉),然后触发model的all监听的回调(如果model绑定了all)

用previousModels保存之前的this.models

如果传入的参数中有对象,要添加到this.models里面去,所以在调用this.add方法

silent表示是否触发事件,没有设置就触发reset监听的回调

一些例子

var c = new Backbone.Collection

c.add({a:1})

var m = c.models[0];

alert(m.constructor === Backbone.Model)  //true

//可以看到默认的model是Backbone.Model



var newModel = Backbone.Model.extend({});

var newC = new Backbone.Collection(null,{model:newModel});

newC.add({a:1})

var m = newC.models[0];

alert(m.constructor === newModel) //true

alert(m.constructor === Backbone.Model)//false

//可以看到默认的Backbone.Model变成了新的newModel了

 

var c = new Backbone.Collection({a:1})

console.log(c.models)

//如果初始化的时候传一些数据进去,会自动变成model存在models里面的



var cc = new Backbone.Collection([{a:1},{b:1},{c:1}])

console.log(cc.models.length)

//传入的数据也可以是一个数组

 

Collection的set ,add,remove,sort方法(set算是里面最重要的东西了)

set

首先会对传入的models进行一次转换,变成数组,方便进行下面的遍历

然后遍历传入的models,第一步对每个单独的model查看它是否由Backbone.Model实例化而来,如果是直接把id=model,然后通过该Collection上的modle的原型上的方法查看,看能否找到id

当查找完id后,判断该id是否已经存在了,如果存在,则看参数中是否传入options.remove,如果存在放入modelMap中,在看参数中是否有options.merge,如果有则直接molde.set来改变model的值

如果id不存在,而且传入的参数中有options.add,先对遍历的model做一些操作操作如下,如果attrs是真实的model,则把attrs.collection设置成当前的这个Collection,如果attrs不是model,则用当前的Collection.model实例化一个model,把attrs当参数传进去,然后将完成的model放入toAdd数组里面,然后看该model是否设置id属性,设置了id属性则this._byId通过model.id来引用model,没有设置id属性,则通过model.cid来引用model,然后给model绑定一个"on"的监听回调事件

然后看此次操作不是排序并且是添加或者删除,则吧model加到order数组中

操作完对models的遍历后,我们可能拿到这样几个东西modelMap,toAdd,order

如果参数传入的参数中options.remove为真,则遍历modelMap,进行删除,这个modelMap是通过已存在的model来得到的

如果toAdd,order里面有数据则进行添加..

然后再options里面是否设置了排序属性sort,如果设置了,则进行排序

 

set是给collection重新设置models,是重新设置,之前的models会全部清掉

                var c = new Backbone.Collection();

                c.set([{a:1},{b:1},{c:1}]);

                console.log(c.models) //有3个

                c.set({d:1});

                console.log(c.models) //只有1个了

 

add

add 很简单的就是设置set方法 把options参数里面的remove,merge设置成false,add设置成true

                var c = new Backbone.Collection();

                c.add([{a:1},new Backbone.Model(),new (Backbone.Model.extend({name:"newModel"}))()])

                console.log(c.models)

                //存入moldes里面的model可以由不同的Model构建出来,

 

remove  collection.remove(models, [options]) 

首先将传入的models进行遍历,删除collection里面的_byId对象的属性,然后从collection的models里面删除对应的项,触发model的remove事件

var c = new Backbone.Collection;

var m = new Backbone.Model;

m.on("remove",function(){

	alert("你删除我了")

})

c.add(m);

alert(c.models.length);

c.remove(m);

alert(c.models.length);

 

comparator  sort

comparator如果是一个function,则是规定models按什么样子的规则排序的函数

如果是一个字符窜,则按models里面有的有的属性进行排序

sort就是调用排序了,除了用comparator,还可以用reverse直接对models进行倒叙

comparator是函数

var c = new Backbone.Collection([

{name:"a1",age:18,level:"一般"},

{name:"a2",age:16,level:"很差"},

{name:"a3",age:28,level:"很好"},

{name:"a4",age:11,level:"较好"},

],{

    comparator : function(a,b){

        return a.attributes.age-b.attributes.age

    }

})

c.sort();

console.log(c.models);

//这个可以按age排序

//用法和数组的sort一模一样的

//修改comparator可以自定义排序  很好 较好 一般 很差

c.comparator = function(a,b){

    var obj = {

        "很好" : 5,

        "较好" : 4,

        "一般" : 3,

        "很差" : 2

    }

    return obj[b.attributes.level] - obj[a.attributes.level]

}

c.sort();

console.log(c.models);

comparator是字符串

var c = new Backbone.Collection([

{name:"a1",age:18,level:"一般"},

{name:"a2",age:16,level:"很差"},

{name:"a3",age:28,level:"很好"},

{name:"a4",age:11,level:"较好"},

],{

    comparator : "age"

})

c.sort();

console.log(c.models);

//按age来排序

c.sort({reverse:true});

console.log(c.models);

//进行倒序

 

Backbone.Collection扩展了很多数据的方法

['forEach', 'each', 'map', 'collect', 'reduce', 'foldl','inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select','reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke','max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest','tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle','lastIndexOf', 'isEmpty', 'chain', 'sample', 'partition'];

还扩展了一些underscore的方法

['groupBy', 'countBy', 'sortBy', 'indexBy']

你可能感兴趣的:(Collection)