zoukankan      html  css  js  c++  java
  • 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实例,也就达到了批量的作用。

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

  • 相关阅读:
    day09-文件的操作
    day08-字符编码
    day07补充-数据类型总结及拷贝
    day07-列表类型/元组类型/字典类型/集合类型内置方法
    auth-booster配置和使用(yii1.5)
    yii中常用路径
    yii中 columnszii.widgets.grid.CGridView
    yii框架widget和注册asset的例子
    yii后台模板标签
    yii中获取当前模块,控制器,方法
  • 原文地址:https://www.cnblogs.com/wumadi/p/3316899.html
Copyright © 2011-2022 走看看