backbone庫學習-model

backbone庫的結構:html

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

本文全部例子來自於http://blog.csdn.net/eagle_110119/article/details/8842007java

 

1.1  先看model塊的結構node

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庫中找到相對應的方法。ajax

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

model上的方法很是多,咱們先從實例化開始,先上例子mongodb

//定義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方法json

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,好吧有點長。。

內容很少,時間恰好,以上是個人一點讀碼體會,若有錯誤,請指出,你們共通學習。

相關文章
相關標籤/搜索