backbone库学习-model

backbone库学习-model

backbone库的结构:

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

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

 

1.1  先看model块的结构

var Model = Backbone.Model = function(attributes, options){}
_.extend(Model.prototype, Events,{..})
var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
_.each(modelMethods, function(method) {})

第一个是Model的构造器,第二个是Model的原型。注意,这里将Events和一系列的自定义参数都放进了Model的原型上,backbone必须依赖一个underscore库,我们在underscore库中找到相对应的方法。

复制代码
_.extend = function(obj) {
        each(slice.call(arguments, 1), function(source) {
           if (source) {
                for (var prop in source) {
                    obj[prop] = source[prop];
                }
            }
        });
        return obj;
    };
复制代码

model上的方法非常多,我们先从实例化开始,先上例子

复制代码
//定义Book模型类
    var Book = Backbone.Model.extend({
        defaults: {
            name: 'unknow',
            author: 'unknow',
            price: 0
        }
    })
复制代码

第一句话:

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

找到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; //返回子类构造器
    };
复制代码

所以我们最后得到那个Book其实是一个继承了Model的子类构造器。ok,使用它,必须要实例化它。

var javabook = new Book();

这里,我们可以在实例化的时候,传入我们的参数,如下:

var javabook = new Book({
        name : 'Thinking in Java',
        author : 'Bruce Eckel',
        price : 395.70
    })

 

我们看一下构造器

复制代码
var Model = Backbone.Model = function(attributes, options) {
        var defaults;
        var attrs = attributes || {};
        options || (options = {});
        this.cid = _.uniqueId('c');//生成唯一id
        this.attributes = {};
        if (options.collection) this.collection = options.collection;
        if (options.parse) attrs = this.parse(attrs, options) || {};

        options._attrs || (options._attrs = attrs);//让options的属性中拥有attributes,这在插件,库中很常见的写法
        if (defaults = _.result(this, 'defaults')) {//this指向Book的实例,因为defaults对象被绑定到了Book的原型上,所以this是可以访问的
            attrs = _.defaults({}, attrs, defaults);//合并
        }
        this.set(attrs, options);//执行原型上的set方法
        this.changed = {};//将changed(变化的)清空
        this.initialize.apply(this, arguments);//实例化时执行
    }
复制代码

在进入set方法之前,系统会将你传进去的参数与原型上的默认参数进行合并,不清楚的可以看一下_.defaults方法

复制代码
_.defaults = function(obj) {
        each(slice.call(arguments, 1), function(source) {
            if (source) {
                for (var prop in source) {
                    console.log(obj[prop]);
                    if (obj[prop] === void 0) obj[prop] = source[prop];
                }
            }
        });
        return obj;
    }
复制代码

这里我们又有了一个新的判断方法,记得在原型上的默认参数,都是unknow和0,作者这样写也可以判断,大家学习一下。经过这个过滤,留下的基本都是我们自定义的参数了。

进入set方法。

复制代码
set: function(key, val, options) {
            var attr, attrs, unset, changes, silent, changing, prev, current;
            if (key == null) return this;
            // Handle both `"key", value` and `{key: value}` -style arguments.
            if (typeof key === 'object') {
                attrs = key;
                options = val;
            } else {
                (attrs = {})[key] = val;
            }

            options || (options = {});

            // Run validation.
            // 执行自定义的validation
            if (!this._validate(attrs, options)) return false;//validate为false时不能通过

            // Extract attributes and options.
            // 提取属性和选项
            unset           = options.unset;
            silent          = options.silent;
            changes         = [];
            changing        = this._changing;
            this._changing  = true;
            if (!changing) {
                this._previousAttributes = _.clone(this.attributes);
                this.changed = {};
            }
            //current表示当前状态,prev表示上一个状态。
            current = this.attributes, prev = this._previousAttributes;
            // Check for changes of `id`.
            if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];//检测id

            // For each `set` attribute, update or delete the current value.
            for (attr in attrs) {
                val = attrs[attr];
                if (!_.isEqual(current[attr], val)) changes.push(attr);//将变化了属性名加入数组
                if (!_.isEqual(prev[attr], val)) {
                    this.changed[attr] = val;//跟上此状态不相同的,修改为现在的值
                } else {
                    delete this.changed[attr];
                }
                unset ? delete current[attr] : current[attr] = val;//第一次赋完值后,将值放入current中。
            }
            // Trigger all relevant attribute changes.
            // silent配置用于忽略验证规则,并且它不会触发change和error等事件

            if (!silent) {
                if (changes.length) this._pending = true;
                for (var i = 0, l = changes.length; i < l; i++) {
                    //console.log(current[changes[i]]);
                    this.trigger('change:' + changes[i], this, current[changes[i]], options);
                }
            }

            // You might be wondering why there's a `while` loop here. Changes can
            // be recursively nested within `"change"` events.
            // 在所有事件结束后,触发一次change事件
            if (changing) return this;
            if (!silent) {
                while (this._pending) {
                    this._pending = false;
                    this.trigger('change', this, options);
                }
            }
            this._pending = false;
            this._changing = false;
            return this;
        }
复制代码

验证和silent这块,我们在例子上再说。set中很重要的一步就是处理当前状态和上一个状态,保存相应状态。

最后,我们执行

this.changed = {};//将changed(变化的)清空
this.initialize.apply(this, arguments);//实例化时执行

看一下原型上的initialize方法

initialize: function(){
        }

英文注释是:Initialize is an empty function by default. Override it with your own,用到的时候,我们需要重载下。

以上完成了一个Book的实例化。

 

 

1.2   关于model实例读取数据

看个例子:

console.log(javabook.get('name'));
console.log(javabook.get('author'));
console.log(javabook.get('price'));

显示结果:

 

1.2.1  get方法

看一下get方法

get: function(attr) {
            return this.attributes[attr];
        }

留心一下我们会发现,这个this.attributes在哪出现。

复制代码
current = this.attributes, prev = this._previousAttributes;
            // Check for changes of `id`.
            if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];//检测id
            // For each `set` attribute, update or delete the current value.
            for (attr in attrs) {
                val = attrs[attr];
                if (!_.isEqual(current[attr], val)) changes.push(attr);//将变化了属性名加入数组
                if (!_.isEqual(prev[attr], val)) {
                    this.changed[attr] = val;//跟上此状态不相同的,修改为现在的值
                } else {
                    delete this.changed[attr];
                }
                unset ? delete current[attr] : current[attr] = val;//第一次赋完值后,将值放入current中。
            }
复制代码

将this.attributes和current之间是引用传递,当current[attr]的值变化时,this.attributes中的值也发生了变化,刚开始current为一个空对象,它会根据你自定义传入的对象,去复制过来。另外attributes是实例上的属性,所以我们可以这样取值。

console.log(javabook.attributes['name'])
console.log(javabook.attributes['author'])
console.log(javabook.attributes['price'])

其实结果是一样的。

1.2.2  escape()

我们看一下,另外一种取值方法:escape()

escape: function(attr) {
            return _.escape(this.get(attr));
        }

看似做了层过滤,看下_.escape方法

复制代码
_.each(['escape', 'unescape'], function(method) {
        _[method] = function(string) {
            if (string == null) return '';//为空将返回
            return ('' + string).replace(entityRegexes[method], function(match) {//匹配到正则的部分执行function方法,实际上就是将<,>,&,",'等进行转换
                return entityMap[method][match];
            });
        };
    });


var entityRegexes = {
        escape:   new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),//拼正则
        unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
    };

var entityMap = {//需要转换的部分
        escape: {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&quot;',
            "'": '&#x27;'
        }
    };
复制代码

如果需要添加新的转换部分,可以添加到entityMap中。

 

1.3  修改数据

看例子:

复制代码
var javabook = new Book();  
  
// 通过set方法设置模型数据  
javabook.set('name', 'Java7入门经典');  
javabook.set('author', 'Ivor Horton');  
javabook.set('price', 88.50);
// 获取数据并将数据输出到控制台  
var name = javabook.get('name');  
var author = javabook.get('author');  
var price = javabook.get('price');  
  
console.log(name); // 输出Java7入门经典  
console.log(author); // 输出Ivor Horton  
console.log(price); // 输出88.50  
复制代码

原型上的set方法,刚才我们已经看过了,set方法会将我们传入的值与上一次状态的值进行比较,同样也与没赋这次值的当前值进行比较。如果改变了,则将新的值覆盖旧的值,不过this._previousAttributes中保存着model上一次状态的值。

set可以单独一个个赋值,同样也可以一起赋值

//set()方法也允许同时设置多个属性,例如:  
javabook.set({  
    name : 'Java7入门经典',  
    author : 'Ivor Horton',  
    price : 88.50  
}); 

 

1.4 修改数据吗,触发事件

当调用set()方法修改模型中的数据时,会触发一系列事件,我们常常通过监听这些事件,来动态调整界面中数据的显示,我们先来看一个例子:

复制代码
// 定义Book模型类  
var Book = Backbone.Model.extend({  
    defaults : {  
        name : 'unknown',  
        author : 'unknown',  
        price : 0  
    }  
});  
  
// 实例化模型对象  
var javabook = new Book();  
  
// 监听模型"change"事件  
javabook.on('change', function(model) {  
    console.log('change事件被触发');  
});  
// 监听模型"change:name"事件  
javabook.on('change:name', function(model, value) {  
    console.log('change:name事件被触发');  
});  
// 监听模型"change:author"事件  
javabook.on('change:author', function(model, value) {  
    console.log('change:author事件被触发');  
});  
// 通过set()方法设置数据  
javabook.set({  
    name : 'Thinking in Java',  
    author : 'unknown',  
    price : 395.70  
});  
  
// 控制台输出结果:  
// change:name事件被触发  
// change事件被触发  
复制代码

问题在set方法中,一般情况我们不设置slient的情况下,会执行事件,看代码:

复制代码
// silent配置用于忽略验证规则,并且它不会触发change和error等事件

            if (!silent) {
                if (changes.length) this._pending = true;
                for (var i = 0, l = changes.length; i < l; i++) {
                    this.trigger('change:' + changes[i], this, current[changes[i]], options);
                }
            }
复制代码

如果我们修改的数据,changes数组中是会有值的。遍历changes数组,将修改过的属性名找到,执行类似'change:属性名'为名称的事件,这里,告诉我们在页面,如果想通过数据修改触发事件的话,这个事件的命名按照'change'+'属性名'来定义。

另外,set源码中,还有一段:

复制代码
// 在所有事件结束后,触发一次change事件
            if (changing) return this;
            if (!silent) {
                while (this._pending) {
                    this._pending = false;
                    this.trigger('change', this, options);
                }
            }
复制代码

一旦数据不真正修改了,那this._pending将变为true,将默认执行一遍change方法。我们可能监听change方法来判断数据是否被修改(但是你也可以通过获取实例的_pending属性来判断)

除了get方法之外,还有两种方法获取上一次状态的值

previous()

previousAttributes()

先看previous()

previous: function(attr) {
            if (attr == null || !this._previousAttributes) return null;
            return this._previousAttributes[attr];
        }

很简单,this._previousAttributes存放着上一个状态的参数。

previousAttributes()

previousAttributes: function() {
            return _.clone(this._previousAttributes);
        }

克隆一份返回,这样修改不会影响原来的状态值。

 

1.5  数据验证

Backbone模型提供了一套数据验证机制,确保我们在模型中存储的数据都是通过验证的,我们通过下面的例子来说明这套验证机制:

复制代码
var Book = Backbone.Model.extend({
        validate : function(data) {
            if(data.price < 1) {
                return '书籍价格不应低于1元.';
            }
        }
    });

    var javabook = new Book();

    // 监听error事件,当验证失败时触发
    javabook.on('error', function(model, error) {
        console.log(error);
    });
    javabook.set('price', 0);
复制代码

找到set方法中的相应方法:

if (!this._validate(attrs, options)) return false;

大家注意,上述的例子没有作用,为什么?因为this._validate()中传入的两个参数为空,定义Book时传入的validate实际上绑定到Book的原型上。实例化时根本没有传入任何数据。这里源码存在错误,看看我们该如何修改。

先看_validate方法:

复制代码
_validate: function(attrs, options) {
            //if (!options.validate || !this.validate) return true;//这里的this.validate是你自己定义的,所以validate需要定义在model类中
            if(!this.validate || !options.validate) return true//没有验证,直接通过
attrs = _.extend({}, this.attributes, attrs); var error = this.validationError = this.validate(attrs, options) || null; if (!error) return true;//没有报错内容,返回true
this.trigger('invalid', this, error, _.extend(options, {validationError: error})); //如果是false,则表示验证正确,否则则自动执行下面的trigger方法,抛出异常 return false; }
复制代码

因为options没值,所以!options.validate恒为true,这也就是为什么validate不验证的关键。修改下判断为:

if(!this.validate || !options.validate) return true//没有验证,直接通过

 

继续向下,看这段代码:

this.trigger('invalid', this, error, _.extend(options, {validationError: error})); 

如果抛出错误,会执行事件名为invalid的事件,那我们再看看页面的绑定事件名,是error,不相符,导致从this._events中按事件名取事件取不到,导致验证失败。ok,简单修改下页面

// 监听error事件,当验证失败时触发
    javabook.on('invalid', function(model, error) {
        console.log(error);
    });

ok,修改完成,我们再运行一遍,看结果

对于修改validate这块,我只是列举了一种办法,还有很多方法,你选择你喜欢。再说一句,绑定validate还有一种方式:

javabook.set('price', 0, {  
    error : function(model, error) {  
        console.log('自定义错误:' + error);  
    }  
}); 

options将会有值,这样就不会跳出validate判断了。

 

1.6  slient配置

这个东西搁了一段没看,现在我们根据例子来过一遍。上例子:

javabook.set('price', 0, {  
    silent : true  
});

如果传入silent,那将不触发change和change:属性名的事件,但记住,它只在定义它的时候生效,换言之,如下代码:

javabook.set('price', 0, {
        silent : true
    });
    javabook.set('name', 'Thinking in Java');

第一次set不会抛异常,第二次会,为什么,因为传入的参数不一样。第二次没有silent,就可以触发change等事件了。

 

1.7  删除数据

Backbone提供了unset和clear方法,看下API

unset()方法用于删除对象中指定的属性和数据

clear()方法用于删除模型中所有的属性和数据

例子:

复制代码
// 定义Book模型类  
var Book = Backbone.Model.extend();  
// 实例化模型对象  
var javabook = new Book({  
    name : 'Java7入门经典',  
    author : 'Ivor Horton',  
    price : 88.50  
});   
// 输出: Java7入门经典  
console.log(javabook.get('name'));    
// 删除对象name属性  
javabook.unset('name');    
// 输出: undefined  
console.log(javabook.get('name'));  
当我们对模型的name属性执行unset()方法后,模型内部会使用delete关键字将name属性从对象中删除。  
  
clear()方法与unset()方法执行过程类似,但clear()方法会删除模型中的所有数据,例如:  
// 定义Book模型类  
var Book = Backbone.Model.extend();    
// 实例化模型对象  
var javabook = new Book({  
    name : 'Java7入门经典',  
    author : 'Ivor Horton',  
    price : 88.50  
});  
  
// 删除对象name属性  
javabook.clear();  
  
// 以下均输出: undefined  
console.log(javabook.get('name'));  
console.log(javabook.get('author'));  
console.log(javabook.get('price')); 
复制代码

 先从unset开始

javabook.unset('name');

找到相应的unset方法

unset: function(attr, options) {
            return this.set(attr, void 0, _.extend({}, options, {unset: true}));
        }

可以看的很清楚,unset还是用到set方法,注意它传入的{unset:true}的,回到之前的set方法中,去找相应的unset部分,我们会找到这段代码。

unset ? delete current[attr] : current[attr] = val;//第一次赋完值后,将值放入current中。

unset为true之后,delete current[attr],即删除current['name']。表示删除了name属性。

再来看clear方法

clear: function(options) {
            var attrs = {};
            for (var key in this.attributes) attrs[key] = void 0;//将this.attributes中所有的成员清空,这里undefined可能会被重写,所以用void 0代替
            return this.set(attrs, _.extend({}, options, {unset: true}));
        }

 

clear方法依旧是调用set方法,由于成员被清空,再传入set中,删除掉current[attr],释放掉内存。

 

1.8  将模型数据同步到服务器

1.8.1  save

Backbone提供了与服务器数据的无缝连接,我们只需要操作本地Model对象,Backbone就会按照规则自动将数据同步到服务器。如果需要使用Backbone默认的数据同步特性,请确定你的服务器数据接口已经支持了REST架构。具体概念,大家可以看http://blog.csdn.net/eagle_110119/article/details/8842007部分的讲解,或者上网搜些资料看。

数据标识:Backbone中每一个模型对象都有一个唯一标识,默认名称为id,你可以通过idAttribute属性来修改它的名称。

URL: Backbone默认使用PATHINFO的方式来访问服务器接口。

先看例子

复制代码
// 定义Book模型类  
var Book = Backbone.Model.extend({  
    urlRoot : '/service'  
});  
  
// 创建实例  
var javabook = new Book({  
    id : 1001,  
    name : 'Thinking in Java',  
    author : 'Bruce Eckel',  
    price : 395.70  
});  
  
// 保存数据  
javabook.save(); 
复制代码

看一下save方法

复制代码
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);
            // 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;
            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方法,有条件的朋友,可以搭建一个环境,与服务器交互下,效果会更好(目前我也再搞,我使用的是node+mongodb)

在save方法中,我们调用了validate进行了验证。验证不通过,则不允许发送请求。其中的options.success是一个返回成功的回调函数。看一下wrapError

复制代码
var wrapError = function(model, options) {
        var error = options.error;
        options.error = function(resp) {
            if (error) error(model, resp, options);
            model.trigger('error', model, resp, options);
        };
    };
复制代码

这个方法,给options添加了一个error方法,主要是为了防止当ajax请求失败时,捕获错误信息。

其中ajax的请求,主要包含:url,method,async,datatype,success,error等。看一下save如果处理method

method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');//判断id,如果没有则新建,如果有再判断options.patch,改为更新

接着是

xhr = this.sync(method, this, options);

ajax归根结底是通过XMLHttpRequest实例来操作的,来看如何创建一个xhr实例,进入原型的sync方法

sync: function() {
            return Backbone.sync.apply(this, arguments);
        }

看一下Backbone.sync

复制代码
Backbone.sync = function(method, model, options) {
        var type = methodMap[method];
        // Default options, unless specified.
        _.defaults(options || (options = {}), {
            emulateHTTP: Backbone.emulateHTTP,//false
            emulateJSON: Backbone.emulateJSON //false
        });

        // Default JSON-request options.
        // 默认JSON请求选项
        var params = {type: type, dataType: 'json'};

        // Ensure that we have a URL.
        // 查看选项中是否有url,没有则选用实例set时的url
        if (!options.url) {
            params.url = _.result(model, 'url') || urlError();
        }
        // Ensure that we have the appropriate request data.
        if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
            params.contentType = 'application/json';//用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件
            params.data = JSON.stringify(options.attrs || model.toJSON(options));//转成字符串,其中model.toJSON()返回this.attributes里的信息
        }
        // params中包含了发送给服务器的所有信息
        // For older servers, emulate JSON by encoding the request into an HTML-form.
        if (options.emulateJSON) {
            params.contentType = 'application/x-www-form-urlencoded';
            params.data = params.data ? {model: params.data} : {};
        }

        // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
        // And an `X-HTTP-Method-Override` header.
        if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
            params.type = 'POST';
            if (options.emulateJSON) params.data._method = type;
            var beforeSend = options.beforeSend;
            options.beforeSend = function(xhr) {
                xhr.setRequestHeader('X-HTTP-Method-Override', type);
                if (beforeSend) return beforeSend.apply(this, arguments);
            };
        }

        // Don't process data on a non-GET request.
        if (params.type !== 'GET' && !options.emulateJSON) {
            params.processData = false;
        }

        // If we're sending a `PATCH` request, and we're in an old Internet Explorer
        // that still has ActiveX enabled by default, override jQuery to use that
        // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
        if (params.type === 'PATCH' && noXhrPatch) {
            params.xhr = function() {
                return new ActiveXObject("Microsoft.XMLHTTP");
            };
        }
        // Make the request, allowing the user to override any Ajax options.
        var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
        model.trigger('request', model, xhr, options);
        return xhr;
    }
复制代码

其中,整个这个方法中,主要完成的工作,就是填充params,让其包含传到服务器所需要的所有信息,包括头,编码等等。另外在ajax中存在兼容性问题,低版本的IE没有xhr对象,它们有自己的实例对象activeObject。

兼容性判断

var noXhrPatch = typeof window !== 'undefined' && !!window.ActiveXObject && !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);

浏览器不支持xhr,可以使用activeObject

代码最后,调用了Backbone.ajax(_.extend(params,options))

Backbone.ajax = function() {
        return Backbone.$.ajax.apply(Backbone.$, arguments);
    }

再看

Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$;

这里,我们都清楚了,Backbone在与服务器交互时,再发送请求时调用的是第三方的ajax,这里我们使用的是jQuery。最后返回xhr对象。save方法结束。(这里大家可以结合$.ajax()来理解)

例子中还将,可以这样写回调

复制代码
// 将数据保存到服务器  
javabook.save(null, {  
    success : function(model) {  
        // 数据保存成功之后, 修改price属性并重新保存  
        javabook.set({  
            price : 388.00  
        });  
        javabook.save();  
    }  
}); 
复制代码

这样也可以,为什么呢,看源码:

复制代码
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);
            };
复制代码

没有自己写回调,系统帮你写,如果有回调,则使用你的回调,至于回调函数,我们最后看。

还有一个wait参数的配置,看例子

复制代码
// 从将数据保存到服务器  
javabook.save({  
    name : 'Thinking in Java',  
    author : 'Bruce Eckel',  
    price : 395.70  
}, {  
    wait : true  
});
复制代码

例子中的解答是如果我们传递了wait配置为true,那么数据会在被提交到服务器之前进行验证,当服务器没有响应新数据(或响应失败)时,模型中的数据会被还原为修改前的状态。如果没有传递wait配置,那么无论服务器是否保存成功,模型数据均会被修改为最新的状态、或服务器返回的数据。

我们来看回调,里面有几个方法

if (attrs && !options.wait) {
                if (!this.set(attrs, options)) return false;
            } else {
                if (!this._validate(attrs, options)) return false;//验证通过了
            }

如果设置了wait为true,将会进行验证(其实你不传值,也要进行验证。。。)

再看这个方法

parse: function(resp, options) {
            return resp;
        }

对照save里的方法

model.attributes = attributes;
var serverAttrs = model.parse(resp, options);
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);

如果设置了wait,当服务器没有响应时,理论上resp是没有值,serverAttrs的值应该为attrs,这个是原来修改前的值。对于ajax请求失败,失败了服务器一般返回错误信息,数据库里的数据是不会修改原来的状态,所以原来的状态依旧是原来的状态。

其实在我们不设置wait参数时,save方法可以有参数,不需要在此之前使用set,因为他包含了这set方法看例子

javabook.save({
        name: 'helloWorld'
        //,wait: true
        }
    );

运行结果为:

可以看到name被修改了,save完成了set的功能,前提,不要设置wait,wait可以理解为保持原有的参数不变(在ajax没有返回时,或者报错时)

 

很多时候,接口返回的数据是多种多样的,例子上有一种情况

复制代码
{  
    "resultCode" : "0",  
    "error" : "null",  
    "data" : [{  
        "isNew" : "true",  
        "bookId" : "1001",  
        "bookName" : "Thinking in Java(修订版)",  
        "bookAuthor" : "Bruce Eckel",  
        "bookPrice" : "395.70"  
    }]  
} 
复制代码

backbone提供了一个parse方法,不过之前我们已经看过,默认情况,这个方法提供传入两个参数,并返回第一个参数,解析时,自己重写parse方法解析。

 

1.8.2  fetch

fetch()方法用于从服务器接口获取模型的默认数据,常常用于模型的数据恢复,它的参数和原理与save()方法类似,因此你可以很容易理解它。

从fetch代码看起:

复制代码
fetch: function(options) {
            options = options ? _.clone(options) : {};
            if (options.parse === void 0) options.parse = true;
            var model = this;
            var success = options.success;
            options.success = function(resp) {//成功的回调
                if (!model.set(model.parse(resp, options), options)) return false;
                if (success) success(model, resp, options);
                model.trigger('sync', model, resp, options);
            };
            wrapError(this, options);//错误的回调
            return this.sync('read', this, options);
        }
复制代码

基本跟save相似,注意,这里的标识为read。如果需要在回调中绑定响应事件的,可以在页面用on绑定事件,事件名为sync,这样是正确回调后,是可以触发的。

这里,因为没有连接上服务器,所以id的部分没有给出,抱歉。

 

1.8.3  destroy

destroy()方法用于将数据从集合(关于集合我们将在下一章中讨论)和服务器中删除,需要注意的是,该方法并不会清除模型本身的数据,如果需要删除模型中的数据,请手动调用unset()或clear()方法)当你的模型对象从集合和服务器端删除时,只要你不再保持任何对模型对象的引用,那么它会自动从内存中移除。(通常的做法是将引用模型对象的变量或属性设置为null值)

看一下destory

 

复制代码
destroy: function(options) {
            options = options ? _.clone(options) : {};
            var model = this;
            var success = options.success;

            var destroy = function() {  //模型会触发destroy事件,页面需要声明
                model.trigger('destroy', model, model.collection, options);
            };

            options.success = function(resp) { //成功回调
                if (options.wait || model.isNew()) destroy();
                if (success) success(model, resp, options);
                if (!model.isNew()) model.trigger('sync', model, resp, options);
            };

            if (this.isNew()) {//查看id是否是新的。
                options.success();
                return false;
            }
            wrapError(this, options);//错误回调

            var xhr = this.sync('delete', this, options);
            if (!options.wait) destroy();//设置wait之后,将不会触发destory
            return xhr;
        }
复制代码

这里的标识为delete,可以看到,该方法并不会清除模型本身(也就是没有跟this.attributes打交道)。

这里基本过完一遍model。ok,好吧有点长。。

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

 

你可能感兴趣的:(backbone)