backbone.Collection源碼筆記

Backbone.Collectionjavascript

backbone的Collection(集合),用來存儲多個model,而且能夠多這些model進行數組同樣的操做,好比添加,修改,刪除,排序,插入,根據索引取值,等等,數組有的方法,他基本上都有css

 

源碼註釋html

 

<!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的一些基本屬性java

length collection裏面包含moldel的個數jquery

models 該屬性引用一個數組,數組裏面就是model了數組

_byId 該屬性引用一個對象,value就是model,key是該model的cid(或者是id,若是給model設置了id,就取id當key,不然用cid當key),app

model 默認是Backbone.Model,能夠被覆蓋,若是傳入的參數是這樣的{name:"xx",age:"oo"}的對象,被實例化的對象就是用model這個基類來實例化的less

 

Collection被實例化的過程以及 _reset,reset的方法ide

實例化的過程就是設置默認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']

相關文章
相關標籤/搜索