backbone库学习-Collection

 

backbone库的结构:

http://www.cnblogs.com/nuysoft/archive/2012/03/19/2404274.html

本文所有例子来自于http://blog.csdn.net/eagle_110119/article/details/8842007

1.1  collection结构

var Collection = Backbone.Collection = function(models, options){}
var setOptions = {add: true, remove: true, merge: true};
var addOptions = {add: true, remove: false};
_.extend(Collection.prototype, Events,{})
var methods = [];
_.each(methods, function(method){})
var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
_.each(attributeMethods, function(method){})

先看第一个例子1.1.1

// 定义模型类  
var Book = Backbone.Model.extend({  
    defaults : {  
        name : ''  
    }  
});  
  
// 定义集合类  
var BookList = Backbone.Collection.extend({  
    model : Book  
});  
  
// 创建一系列模型对象  
var book1 = new Book({  
    name : 'Effective Java中文版(第2版)'  
});  
var book2 = new Book({  
    name : 'JAVA核心技术卷II:高级特性(原书第8版)'  
});  
var book3 = new Book({  
    name : '精通Hibernate:Java对象持久化技术详解(第2版)'  
});  
  
// 创建集合对象  
var books = new BookList([book1, book2, book3]);  

先看第一部分1.1.1-1

// 定义集合类
    var BookList = Backbone.Collection.extend({
        model : Book
    });

 

找到Backbone.Collection.extend()方法

Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
var Collection = Backbone.Collection = function(models, options) {}

extend方法

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')) {//检查protoProps是否拥有constructor属性(不考虑原型上)
            child = protoProps.constructor;
        } else {
            child = function(){ return parent.apply(this, arguments); };//借用构造器,this指向model构造器,让子类实例化时,可以获取父类构造器的成员
        }

        // Add static properties to the constructor function, if supplied.
        _.extend(child, parent, staticProps);//将父类和staticProps上的属性成员统统传给child的构造器上
        // Set the prototype chain to inherit from `parent`, without calling
        // `parent`'s constructor function.
        var Surrogate = function(){ this.constructor = child; };
        Surrogate.prototype = parent.prototype;
        child.prototype = new Surrogate;//临时构造器的方式完成继承,Surrogate属于中间件,子类实例修改不会影响父类原型,可以让子类实例获取父类原型上的成员

        // Add prototype properties (instance properties) to the subclass,
        // if supplied.
        if (protoProps) _.extend(child.prototype, protoProps);//将自定义信息绑定到子类原型上

        // Set a convenience property in case the parent's prototype is needed
        // later.
        child.__super__ = parent.prototype; //_super_属性方便子类直接访问父类原型

        return child; //返回子类构造器
    };

跟Backbone.model.extend一样,这里就不过多解释了。例子中的{model:Book}将被绑定到Collection的原型上,实例化Collection后,即可调用。

继续看例子1.1.1-2

 

// 创建集合对象
    var books = new BookList([book1]);

我们不用之前的例子,我先来个简单的。

看一下构造器

var Collection = Backbone.Collection = function(models, options) {
        options || (options = {});
        if (options.model) this.model = options.model;
        if (options.comparator !== void 0) this.comparator = options.comparator;
        this._reset();//原型上的_reset方法,第一次初始化,以后就是重置
        this.initialize.apply(this, arguments);//空的init方法,用的时候,可以自己修改
        if (models) this.reset(models, _.extend({silent: true}, options));
    };

实例化时,有三个方法,分别是_reset,initialize和reset三个方法。先看_reset方法

// 重置
        _reset: function() {
            this.length = 0;
            this.models = [];
            this._byId  = {};
        }

算是重置,也算是初始化。

initialize方法

initialize: function(){}

用的时候,需要我们自己去定义。

最后看一下reset方法

reset: function(models, options) {
            options || (options = {});
            for (var i = 0, l = this.models.length; i < l; i++) {
                this._removeReference(this.models[i]);
            }
            options.previousModels = this.models;
            this._reset();//重置
            this.add(models, _.extend({silent: true}, options));
            if (!options.silent) this.trigger('reset', this, options);
            return this;
        },

reset这个方法关联到两个方法_removeReference和add方法。

先看下_removeReference方法

_removeReference: function(model) {
            if (this === model.collection) delete model.collection;
            model.off('all', this._onModelEvent, this);
        }

如果this.models存在model实例,这个方法主要是实现清除工作,删除掉model的collection属性(后面会提),去掉绑定的事件。

再看一下add方法

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

这里给出addOptions的初始值:

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

进入set方法

set: function(models, options) {
            options = _.defaults({}, options, setOptions);//如果options的某个参数名对应的参数值为空,则将setOptions对应参数名的参数值赋给它
if (options.parse) models = this.parse(models, options);//没重写之前,只返回models
            if (!_.isArray(models)) models = models ? [models] : [];//转成数组
var i, l, model, attrs, existing, sort;
            var at = options.at;
            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;//默认情况 true  false false
            var order = !sortable && add && remove ? [] : false;
            // Turn bare objects into model references, and prevent invalid models
            // from being added.
            for (i = 0, l = models.length; i < l; i++) {
                if (!(model = this._prepareModel(attrs = models[i], options))) continue;//主要是将Model实例绑定上collection属性
                // If a duplicate is found, prevent it from being added and
                // optionally merge it into the existing model.
                if (existing = this.get(model)) { //对象队列,在初始化没有任何信息
                    if (remove) modelMap[existing.cid] = true;
                    if (merge) {
                        attrs = attrs === model ? model.attributes : options._attrs;
                        existing.set(attrs, options);
                        if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
                    }

                    // This is a new model, push it to the `toAdd` list.
                } else if (add) {
                    toAdd.push(model);//将model实例推进数组中,保存

                    // Listen to added models' events, and index models for lookup by
                    // `id` and by `cid`.
                    model.on('all', this._onModelEvent, this);//绑定all事件,这样是执行两遍all事件
                    this._byId[model.cid] = model;//用cid与对应的mode实例关联
                    if (model.id != null) this._byId[model.id] = model;//有id的话,再用id与对应的model实例关联
                }
                if (order) order.push(existing || model);//初始化时order为false
                delete options._attrs;
            }
            // Remove nonexistent models if appropriate.
            if (remove) {//初始化时remove为false
                for (i = 0, l = this.length; i < l; ++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;//length保存model实例的个数
                if (at != null) {
                    splice.apply(this.models, [at, 0].concat(toAdd));
                } else {
                    if (order) this.models.length = 0;
                    push.apply(this.models, order || toAdd);//实例上创建models属性,存放toAdd
                }
            }

            // Silently sort the collection if appropriate.
            if (sort) this.sort({silent: true});//默认sort为false

            if (options.silent) return this;
            // Trigger `add` events.
            for (i = 0, l = toAdd.length; i < l; i++) {
                (model = toAdd[i]).trigger('add', model, this, options);
            }

            // Trigger `sort` if the collection was sorted.
            if (sort || (order && order.length)) this.trigger('sort', this, options);
            return this;
        }

重要的部分在for循环这块,详细看一下for部分

for (i = 0, l = models.length; i < l; i++) {
                if (!(model = this._prepareModel(attrs = models[i], options))) continue;//主要是将Model实例绑定上collection属性
                // If a duplicate is found, prevent it from being added and
                // optionally merge it into the existing model.
                if (existing = this.get(model)) { //对象队列,在初始化没有任何信息
                    if (remove) modelMap[existing.cid] = true;
                    if (merge) {
                        attrs = attrs === model ? model.attributes : options._attrs;
                        existing.set(attrs, options);
                        if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
                    }

                    // This is a new model, push it to the `toAdd` list.
                } else if (add) {
                    toAdd.push(model);//将model实例推进数组中,保存

                    // Listen to added models' events, and index models for lookup by
                    // `id` and by `cid`.
                    model.on('all', this._onModelEvent, this);//绑定all事件,这样是执行两遍all事件
                    this._byId[model.cid] = model;//用cid与对应的mode实例关联
                    if (model.id != null) this._byId[model.id] = model;//有id的话,再用id与对应的model实例关联
                }
                if (order) order.push(existing || model);//初始化时order为false
                delete options._attrs;
            }

先看一下this._prepareModel方法

_prepareModel: function(attrs, options) {
            //这里判断attrs,如果是Model创建的,那直接在attrs上加上collection即可
            if (attrs instanceof Model) {//instanceof 能在实例的原型对象链中找到该构造函数的prototype属性所指向的原型对象,attrs实际上通过Model构造器new出来的
                if (!attrs.collection) attrs.collection = this;//让实例的collection保存Collection的n实例化对象
                return attrs;
            }
            //如果不是,那系统会自动实例化一次,并为Model实例绑上collection属性
            options || (options = {});
            options.collection = this;
            //在options中也加入这个属性,这里为什么要在options里面加入该属性,其实是因为this的问题,此时this是Collection的实例,
            //一旦new过Model后,Model里的this就是model了,这里设置options.collection主要是让其传入Model中,实例化的时候,便于绑定,这样model实例也拥有collection属性
            var model = new this.model(attrs, options);//调用model的构造器,实例化对象 根据具体信息创建model实例
            if (!model.validationError) return model;
            this.trigger('invalid', this, attrs, options);//触发invalid事件
            return false;//返回不是Model实例
        }

if后面的continue,表示代码始终往下走,实例化对Model对象,为每个实例化对象添加collection属性。

再看一下get方法

get: function(obj) {
            if (obj == null) return void 0;
            return this._byId[obj.id] || this._byId[obj.cid] || this._byId[obj];
        }

因为this._byId在_reset方法中初始化中是空对象,默认get取出undefined,将加工好的每个model实例放入toAdd数组中,为所有model绑定all事件,触发函数是this._onModeEvent(),最后用cid与每个对应的model实例关联。

ok,回到reset方法

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

因为_.extend({silent: true}, options),这个reset事件没有触发。返回this,以上完成collection的实例化。

 

1.2 往collection添加model

集合提供了3个方法允许我们动态地向集合中动态插入模型:

add():向集合中的指定位置插入模型,如果没有指定位置,默认追加到集合尾部

push():将模型追加到集合尾部(与add方法的实现相同)

unshift():将模型插入到集合头部

 

1.2.1  add

先看下例子1.2.1-1:

var Book = Backbone.Model.extend({
    defaults : {
        name : '',
        price : 0
    }
});

// 创建集合对象
var books = new Backbone.Collection(null, {
    model : Book
});

books.add({
    name : '构建高性能Web站点',
    price : 56.30
});

注意这个collection实例化的方式,它传入的参数是null和{model:Book},和之前的定义比较一下

 

var books = new Backbone.Collection(null, {
    model : Book
});

var BookList = Backbone.Collection.extend({
        model : Book
    });

collection需要有一个与之关联的model,这两种方式都可以将model与collection关联,这里有疑问为什么一定要呢?原因在这(_prepareModel方法中)

 

var model = new this.model(attrs, options);//调用model的构造器,实例化对象

如果不将model与collection相互关联这个this.model将没有值,上述的两种方法都可以是现实this.model指向Model构造器。

 

看一下add方法

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

之前将实例化collection的时候描述过了将add的信息传入set方法中。我们看一下最后一部分的set代码:

if (sort) this.sort({silent: true});//默认sort为false

            if (options.silent) return this;
            // Trigger `add` events.
            for (i = 0, l = toAdd.length; i < l; i++) {//处理toAdd数组的model实例
                (model = toAdd[i]).trigger('add', model, this, options);//触发add事件
            }
            // Trigger `sort` if the collection was sorted.
            if (sort || (order && order.length)) this.trigger('sort', this, options);
            return this;

添加会触发add的监听事件。这个事件需要你自己定义,事件名为add。库会在这个时候从toAdd中依次取model实例来触发add事件。

add执行添加部分的代码

 if (order) this.models.length = 0;
                    push.apply(this.models, order || toAdd);//实例上创建models属性,存放toAdd。原型add所走的

 

1.2.2  push

例子1.2.2-1:(跟1.2.1-1差不多)

books.push({
    name : '深入分析Java Web技术内幕',
    price : 51.80
});

看一下原型上的push方法

push: function(model, options) {
            model = this._prepareModel(model, options);
            this.add(model, _.extend({at: this.length}, options));
            return model;
        }

push方法调用了add和_prepareModel方法,这里我们大概总结下,_prepareModel的作用

_prepareModel:主要将你传入的对象类似{name:xxx,price:xxxx}的形式,制作成Model的实例,这里我们需要强调一点就是,你可以将这些对象先传入Model的构造器生成model实例,再传入collection里来,也可以将这些对象直接传入collection中,collection会检测这写对象是否为Model的实例,如果不是会调用Model构造器,根据这个信息生成Model的实例。最后为每一个model添加collection属性

 

1.2.3  unshift

先上例子

books.unshift({
    name : '编写高质量代码:Web前端开发修炼之道',
    price : 36.80
});

看一下unshift方法

unshift: function(model, options) {
            model = this._prepareModel(model, options);
            this.add(model, _.extend({at: 0}, options));//注意这个at的值,这个值会影响插入的顺序
            return model;//返回插入的model实例
        }

at值的影响在set方法中的体现在这:

if (at != null) {
                    splice.apply(this.models, [at, 0].concat(toAdd));//使用splice方法完成插入,因为apply的参数需要一个数组,at值将决定在哪插入
                } else {
                    if (order) this.models.length = 0;
                    push.apply(this.models, order || toAdd);//实例上创建models属性,存放toAdd
                }

可以清楚的看到当我们使用unshift时,传入的at为0,也就是在数组的第一个位置插入,刚才的push则是传递的this.length,这个this.length哪里定义的,就在set方法中

this.length += toAdd.length;//length保存model实例的个数

这样push操作,就是在数组的最后一位插入。而add底层就是数组的原生push,也是在最后添加。

 

1.3  从collection中删除model

集合类提供了3个方法用于从集合中移除模型对象,分别是:

remove():从集合中移除一个或多个指定的模型对象

pop():移除集合尾部的一个模型对象

shift():移除集合头部的一个模型对象

 

1.3.1  remove

先看下例子1.3.1-1

// 定义模型类  
var Book = Backbone.Model.extend({  
     defaults : {  
        name : '',  
        price : 0  
    }  
});  
  
// 定义初始化数据  
var data = [{  
    name : '构建高性能Web站点',  
    price : 56.30  
}, {  
    name : '深入分析Java Web技术内幕',  
    price : 51.80  
}, {  
    name : '编写高质量代码:Web前端开发修炼之道',  
    price : 36.80  
}, {  
    name : '基于MVC的JavaScript Web富应用开发',  
    price : 42.50  
}, {  
    name : 'RESTful Web Services Cookbook中文版',  
    price : 44.30  
  
}]  
  
// 创建集合对象  
var books = new Backbone.Collection(data, {  
    model : Book  
});  
  
books.remove(books.models[2]);  
books.pop();  
books.shift();  
  
// 在控制台输出集合中的模型列表  
console.dir(books.models); 

先看remove方法。

remove: function(models, options) {
            //console.log(models);
            //console.log(options);
            models = _.isArray(models) ? models.slice() : [models];//将models这个类数组集合转成数组
            options || (options = {});
            var i, l, index, model;
            for (i = 0, l = models.length; i < l; i++) {
                model = this.get(models[i]);//根据cid取到该对象
                if (!model) continue;
                delete this._byId[model.id];//删除id关联的信息
                delete this._byId[model.cid];//删除cid关联的信息
                index = this.indexOf(model);//返回要删除Model所在的位置
                this.models.splice(index, 1);//获取该位置删除该Model实例
                this.length--;//长度减一
                if (!options.silent) {//如果设置了silent属性,将不执行remove回调。
                    options.index = index;
                    model.trigger('remove', model, this, options);
                }
                this._removeReference(model);//删除该Model实例拥有的collection属性
            }
            return this;
        }

看一下_removeReference()方法

_removeReference: function(model) {
            if (this === model.collection) delete model.collection;//删除掉对collection属性,即对
            model.off('all', this._onModelEvent, this);//去除all监听事件
        }

 

1.3.2  pop

将例子修改下1.3.2-1

books.pop();

看一下pop

pop: function(options) {
            var model = this.at(this.length - 1);//调用原型上的at方法,返回最后一个成员
            console.log(model);
            this.remove(model, options);//调用remove方法,删除最后一个
            return model;
        }

再来看下at方法

at: function(index) {
            return this.models[index];//类数组拥有这个属性
        }

pop实际上还是调用的remove方法,通过at方法找到最后一个成员将其删除

 

1.3.3   shift

将例子修改一下1.3.3-1

books.shift();

看一下shift方法

shift: function(options) {
            var model = this.at(0);//取到第一个成员
            this.remove(model, options);//执行remove方法
            return model;
        }

跟pop很类似。

 

 

1.4   在集合中查找模型

Collection定义了一系列用于快速从集合中查找我们想要的模型的方法,包括:

get():根据模型的唯一标识(id)查找模型对象

getByCid():根据模型的cid查找模型对象

at():查找集合中指定位置的模型对象

where():根据数据对集合的模型进行筛选

 

1.4.1  get

先看个例子1.4.1-1

// 定义模型类  
var Book = Backbone.Model.extend({  
    defaults : {  
        name : '',  
        price : 0  
    }  
});  
  
// 定义初始化数据  
var data = [{  
    id : 1001,  
    name : '构建高性能Web站点',  
    price : 56.30  
}, {  
    id : 1002,  
    name : '深入分析Java Web技术内幕',  
    price : 51.80  
}, {  
    id : 1003,  
    name : '编写高质量代码:Web前端开发修炼之道',  
    price : 36.80  
}, {  
    id : 1004,  
    name : '基于MVC的JavaScript Web富应用开发',  
    price : 42.50  
}, {  
    id : 1005,  
    name : 'RESTful Web Services Cookbook中文版',  
    price : 44.30  
}]  
  
// 创建集合对象  
var books = new Backbone.Collection(data, {  
    model : Book  
});  
  
// 根据id和cid查找模型对象  
var book1 = books.get(1001);  
var book2 = books.getByCid('c2');  
  
// 在控制台输出模型  
console.dir(book1);  
console.dir(book2);

 

我们一个个来,先看get

var book1 = books.get(1001); 

下面是get代码:

get: function(obj) {
            if (obj == null) return void 0;
            return this._byId[obj.id] || this._byId[obj.cid] || this._byId[obj];
        }

这段代码,我们之前有见过。在我们创建Model实例的时候,给每个Model实例配上一个id,我们可以根据这个id来查找。这个this._byId设定值的过程,是在set方法中完成的,如果有记不清的可以翻看之前的set方法部分。

 

1.4.2   getByCid

例子:

var book2 = books.getByCid('c2');  

关于cid的生成,我们在Model部分已经说过了。大家查看underscore的uniqueId方法,这里我的源码中没有getByCid的方法,这段我们先滤过。

 

1.4.3   at

例子

var book3 = books.at(1);  

at方法之前也已经说过,它传入的参数是数组的索引。还是看一下源码吧

at: function(index) {
            return this.models[index];//类数组拥有这个属性
        }

 

1.4.4   where

例子:

var book4 = books.where({  
    price : 51.80  
});  

我们来看一下where方法

where: function(attrs, first) {
            if (_.isEmpty(attrs)) return first ? void 0 : []; //检测attrs是否为空
            return this[first ? 'find' : 'filter'](function(model) {//这里调用的filter是underscore里的方法
                for (var key in attrs) {
                    if (attrs[key] !== model.get(key)) return false;
                }
                return true;
            });
        }

这里调用的是this.filter方法,但是原型上并没有很明显的命名出来,那它是在声明的呢?看代码:

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'];

    // Mix in each Underscore method as a proxy to `Collection#models`.
    _.each(methods, function(method) {
        Collection.prototype[method] = function() {
            var args = slice.call(arguments);
            args.unshift(this.models);
            return _[method].apply(_, args);//调用underscore相应的方法执行
        };
    });

看一下underscore的each方法

var each = _.each = _.forEach = function(obj, iterator, context) {
        if (obj == null) return;
        if (nativeForEach && obj.forEach === nativeForEach) { //是否支持原生forEach方法
            obj.forEach(iterator, context);//采用原生的forEach方法
        } else if (obj.length === +obj.length) {//如果obj是数组或者是类数组
            for (var i = 0, length = obj.length; i < length; i++) {
                if (iterator.call(context, obj[i], i, obj) === breaker) return;//让数组中的成员作为参数传入过滤器中运行
            }
        } else {
            var keys = _.keys(obj);//返回值集合,考虑对象的情况
            for (var i = 0, length = keys.length; i < length; i++) {
                if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;//让对象中的成员作为参数传入过滤器中运行
            }
        }
    }

我们简单理解下,_.each帮助了Collection.prototype绑定一系列方法,当Collection的实例需要调用这些方法时,注意看_.each的返回值是_[method].apply(_,args),表示调用的实际是underscore.js中对应的方法。注意之前还有一段代码

args.unshift(this.models);

将上下文也传给underscore,这样运行不会出错。

这样,分析where方法实则变得非常简单,核心也就是那个筛选器了。我们看一下

function(model) {//这里调用的filter是underscore里的方法
                for (var key in attrs) {
                    if (attrs[key] !== model.get(key)) return false;
                }
                return true;
            }

通过get方法,去匹配model实例集中相应属性的值,相同则返回true,在underscore中,filter方法会将其记录,存放入数组中,返回给你。

 

1.5  自动排序

在backbone集合对象中,为我们提供了实时排序,当任何模型对象被插入到集合中时,都会按照预定的规则放到对应的位置。

例子:

// 定义模型类  
var Book = Backbone.Model.extend({  
    defaults : {  
        name : '',  
        price : 0  
    }  
});  
  
// 创建集合对象  
var books = new Backbone.Collection(null, {  
    model : Book,  
    comparator : function(m1, m2) {  
        var price1 = m1.get('price');  
        var price2 = m2.get('price');  
  
        if(price1 > price2) {  
            return 1;  
        } else {  
            return 0;  
        }  
    }  
});  
  
books.add({  
    name : '构建高性能Web站点',  
    price : 56.30  
});  
  
books.push({  
    name : '深入分析Java Web技术内幕',  
    price : 51.80  
});  
  
books.unshift({  
    name : '编写高质量代码:Web前端开发修炼之道',  
    price : 36.80  
});  
  
books.push({  
    name : '基于MVC的JavaScript Web富应用开发',  
    price : 42.50  
}, {  
    at : 1  
});  
  
books.unshift({  
    name : 'RESTful Web Services Cookbook中文版',  
    price : 44.30  
  
}, {  
    at : 2  
});  
  
// 在控制台输出集合中的模型列表  
console.dir(books.models);

注意实例化Collection时,传入的参数

// 创建集合对象  
var books = new Backbone.Collection(null, {  
    model : Book,  
    comparator : function(m1, m2) {  
        var price1 = m1.get('price');  
        var price2 = m2.get('price');  
  
        if(price1 > price2) {  
            return 1;  
        } else {  
            return 0;  
        }  
    }  
});

在我们分析comparator之前,我们回头看一下Collection的构造器

var Collection = Backbone.Collection = function(models, options) {
        options || (options = {});
        if (options.model) this.model = options.model;
        if (options.comparator !== void 0) this.comparator = options.comparator;
        this._reset();//原型上的_reset方法,第一次初始化,以后就是重置
        this.initialize.apply(this, arguments);//空的init方法,用的时候,可以自己修改
        if (models) this.reset(models, _.extend({silent: true}, options));//models没有值,使用options的model
    };

如果我们将comparator:function(){}传入构造器中,这里的options.comparator将不等于undefined。将实例的comparator属性指向该方法。

在set方法中,找到相关内容:

var sortable = this.comparator && (at == null) && options.sort !== false;
var sortAttr = _.isString(this.comparator) ? this.comparator : null;

例子中用了如add,push,unshift等方法。之前我们分析发现像push和unshift方法使用时,都传递一个at值,前者为数组长度减一,后者为0.其实这里的代码存在问题,导致排序不成功。为了实现效果,我们先修改一下。

var sortable = this.comparator  && options.sort !== false;
var sortAttr = _.isFunction(this.comparator) ? this.comparator : null;

只要设置了comparator,就不需要管是通过如何方式加入model的sortable就一直会是true。

if (sortable) sort = true;//将sort置为true

继续

if (sort) this.sort({silent: true});//默认sort为false

我们看一下sort方法

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));//调用数组原生的sort进行排序
            }
            if (!options.silent) this.trigger('sort', this, options);//系统设置了silent,这里不触发sort事件
            return this;
        }

底层看来是调用了数组的sort方法进行排序,如果设置了silent将不会触发sort事件。细心的朋友会看到_.bind这个方法。我们来简单看一下:

_.bind = function(func, context) {
        var args, bound;
        if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));//是否支持原生bind
        if (!_.isFunction(func)) throw new TypeError;
        args = slice.call(arguments, 2);
        return bound = function() {
            if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));//判断调用者是否是function,如果不是调用传入的上下文context执行func
            ctor.prototype = func.prototype;//如果是的话,缺少上下文。
            var self = new ctor;//将self的原型指向func的原型
            ctor.prototype = null;//清空ctor构造器的原型
            var result = func.apply(self, args.concat(slice.call(arguments)));//调用func,此时func的上下文是self,那self的空间又可以扩展到func的原型。
            if (Object(result) === result) return result;//只有当是字符串是才返回匹配结果,这里也存在问题。
            return self;
        };
    };

 

这个_.bind提供了三种方式

1.第一种:使用原生bind,不清楚的朋友可以查询一下API

2.第二种:传入的信息拥有上下文的,采用func.apply(context)调用

3.第三种:如果没有上下文,我们需要创建上下文。文中给出了一个很不错的创建上下文的方式,这里源代码的判断写的并不是非常好,最后的判断有问题,应该修改一下:

return result

直接返回result,不用判断了。这是本人的一点想法,有问题或者不同意见的,大家一起讨论。

 

 

1.6   从服务器获取集合数据

backbone库的Collection提供几种方式与服务器交互

fetch():用于从服务器接口获取集合的初始化数据,覆盖或追加到集合列表中

create():在集合中创建一个新的模型,并将其同步到服务器

 

1.6.1  fetch

例子1.6.1-1

// 定义模型类  
var Book = Backbone.Model.extend({  
    defaults : {  
        name : '',  
        price : 0  
    }  
});  
  
// 定义集合类  
var BookList = Backbone.Collection.extend({  
    model : Book,  
    url : '/service'  
});  
  
// 创建集合对象, 并从服务器同步初始化数据  
var books = new BookList();  
books.fetch({  
    success: function(collection, resp) {  
        // 同步成功后在控制台输出集合中的模型列表  
        console.dir(collection.models);  
    }  
});

看一下fetch方法:

fetch: function(options) {
            options = options ? _.clone(options) : {};
            if (options.parse === void 0) options.parse = true;//将options.parse设置为true
            var success = options.success;//获取自定义的success方法,正确回调
            var collection = this;
            options.success = function(resp) {
                var method = options.reset ? 'reset' : 'set';
                collection[method](resp, options);
                if (success) success(collection, resp, options);
                collection.trigger('sync', collection, resp, options);
            };
            wrapError(this, options);//绑定错误回调
            return this.sync('read', this, options);//调用sync方法,参数名read
        }

这里可以看出,系统在内部,将我们自定义的success封装在系统定义的success方法中,因为系统还要在成功回调之后,触发一个叫sync的监听事件。

进入sync方法看一下:

sync: function() {
            return Backbone.sync.apply(this, arguments);//调用Backbone.sync,上下文为this
        }

好吧,到了Backbone.sync,这个方法之前model中分析过了。通过第三方ajax发送请求,这个方法主要是组装了请求内容。

 

1.6.2   create

看下例子1.6.2-1

var books = new BookList();  
// 创建一个模型  
books.create({  
    name : 'Thinking in Java',  
    price : 395.70  
}, {  
    success : function(model, resp) {  
        // 添加成功后, 在控制台输出集合中的模型列表  
        console.dir(books.models);  
    }  
});  

看一下create方法:

save: function(key, val, options) {
            var attrs, method, xhr, attributes = this.attributes;

            // Handle both `"key", value` and `{key: value}` -style arguments.
            if (key == null || typeof key === 'object') {
                attrs = key;
                options = val;
            } else {
                (attrs = {})[key] = val;
            }
            options = _.extend({validate: true}, options);//这个代码会导致bug
            // If we're not waiting and attributes exist, save acts as
            // `set(attr).save(null, opts)` with validation. Otherwise, check if
            // the model will be valid when the attributes, if any, are set.
            if (attrs && !options.wait) {
                if (!this.set(attrs, options)) return false;
            } else {
                if (!this._validate(attrs, options)) return false;//验证通过了
            }

            // Set temporary attributes if `{wait: true}`.
            if (attrs && options.wait) {
                this.attributes = _.extend({}, attributes, attrs);
            }

            // After a successful server-side save, the client is (optionally)
            // updated with the server-side state.
            if (options.parse === void 0) options.parse = true;
            var model = this;
            var success = options.success;//你可以在save方法的时候写成功回调
            options.success = function(resp) {//返回成功的回调
                // Ensure attributes are restored during synchronous saves.
                model.attributes = attributes;
                var serverAttrs = model.parse(resp, options);
                if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
                if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
                    return false;
                }
                if (success) success(model, resp, options);
                model.trigger('sync', model, resp, options);
            };
            wrapError(this, options);//将options绑定一个error方法
            method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');//判断id,如果没有则新建,如果有再判断options.patch,改为更新
            if (method === 'patch') options.attrs = attrs;
            xhr = this.sync(method, this, options);

            // Restore attributes.
            if (attrs && options.wait) this.attributes = attributes;

            return xhr;
        }

save方法的大致工作流程是这样的,检测是否model实例需要验证,如果有验证,进行验证。封装成功回调和错误回调穿件xhr对象,调用原型的sync方法调用第三方插件发送请求。这里我们分析下源码中关于验证的一点问题。之前model的验证,我们稍作修改达到要求,但是这个直接就报错了

 这个例子中我们确实没有为Model添加validate,那系统到底怎么会在没有validate的情况下,尝试去执行validate呢?

问题就在save方法里:

options = _.extend({validate: true}, options);//这个代码会导致bug

这里会莫名其妙的给options加上validate等于true,然后在_validate方法中

if(!this.validate && !options.validate) return true//没有验证,直接通过//这里有修改过

这段代码,会直接通过。可能之前修改Model的时候,源代码是||,这里我们将validate:true这段代码注释掉。则代码不报错了。但是又有一个问题。如果例子1.6.2-1修改为

例子1.6.2-2

var books = new BookList();
// 创建一个模型
books.create({
    name : 'Thinking in Java',
    price : 395.70
}, {
    success : function(model, resp) {
        // 添加成功后, 在控制台输出集合中的模型列表
        console.dir(books.models);
    },
    validate: function(data){
        if(data.price > 0){
            return 'hello world';
        }
    }
});

代码不会报错,但是终端不会显示hello world,细细一想,其实我们没有自定义那个invalid事件,但是问题来,如此定义,我们无法直接获取到我们想要监听的那个model实例对象,所以你也就无从绑定起。这也是backbone需要优化的地方。实际运作中,大家根据需求自行添加吧,这里方法很多,就不细说了。

 

1.7  将数据批量同步到服务器

Backbone中集合提供了数据同步和创建的方法与服务器进行交互,但实际上这可能并不能满足我们的需求,backbone学习的作者给出了一些自定义的批量方法。

1.7.1  createAll

先看例子:

// 定义模型类  
var Book = Backbone.Model.extend({  
    defaults : {  
        name : '',  
        price : 0  
    }  
});  
  
// 定义BookList类  
var BookList = Backbone.Collection.extend({  
    model : Book,  
    url : '/service',  
    // 将集合中所有的模型id连接为一个字符串并返回  
    getIds : function() {  
        return _(this.models).map(function(model) {  
            return model.id;  
        }).join(',');  
    },  
    // 将集合中所有模型提交到服务器接口  
    createAll : function(options) {  
        return Backbone.sync.call(this, 'create', this, options);  
    },  
    // 修改集合中的所有模型数据  
    updateAll : function(options) {  
        return Backbone.sync.call(this, 'update', this, options);  
    },  
    // 删除集合中所有的模型  
    deleteAll : function(options) {  
        var result = Backbone.sync.call(this, 'delete', this, _.extend({  
            url : this.url + '/' + this.getIds()  
        }, options));  
        this.remove(this.models);  
        return result;  
    }  
});  
  
// 创建集合对象  
var books = new BookList();  
  
// 当集合触发reset事件时, 对数据进行批量同步  
books.on('reset', function() {  
    books.createAll();  
    books.updateAll();  
    books.deleteAll();  
});  
  
// 从服务器接口同步默认数据  
books.fetch();  

看一下,自定义方法绑定在collection原型上,实例可以调用。自定义方法基本都调用Backbone.sync方法,这是封装params的方法。如果客户端数据发生改变时,向服务器发出同步请求(更新,删除,修改)。以此达到同步目的。另外就是,自定义绑定到collection实例上,实例包含了很多个model实例,也就达到了批量的作用。

内容不多,时间刚好,以上是我的一点读码体会,如有错误,请指出,大家共通学习。 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Collection)