天天看點

Backbone入門指南(五):Collection (資料模型集合)

8. Collection (資料模型集合)

  如果将一個Model對象比喻成資料庫中的一條記錄,那麼Collection就是一張資料表。它表示為一個模型集合類,用于存儲和管理一系列相同類型的模型對象。

8.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]);      

  在這個例子中,我們定義了模型類Book和集合類BookList,然後建立了3個模型對象,并将它們放到一個集合對象中。(你可以在控制台輸出books.models屬性,用來檢視集合中包含的模型對象清單)

  我們為了建立3個Book模型對象,對Book類顯式執行個體化了3次,其實Model本身已經提供了更簡單的方法來複制一個模型,例如:

var book1 = new Book({
    name : 'Effective Java中文版(第2版)'
});

var book2 = book1.clone();
book2.set('name', 'JAVA核心技術卷II:進階特性(原書第8版)');

var book3 = book1.clone();
book3.set('name', '精通Hibernate:Java對象持久化技術詳解(第2版)');      

  在段代碼中,我們使用模型的clone()方法來複制一個和目前對象相同(包括資料)的新對象,這可以簡化我們建立模型的流程。

  在執行個體化集合對象時,除了可以向構造函數中添加已經建立好的模型清單(就像上面的例子那樣),我們還可以直接傳遞模型資料,集合會自動将這些資料轉換為模型對象,例如:

// 定義模型類
var Book = Backbone.Model.extend({
    defaults : {
        name : ''
    }
});

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

var models = [{
    name : 'Effective Java中文版(第2版)'
}, {
    name : 'JAVA核心技術卷II:進階特性(原書第8版)'
}, {
    name : '精通Hibernate:Java對象持久化技術詳解(第2版)'
}];

// 建立集合對象
var books = new BookList(models);      

  運作這個例子,并在控制台輸出books.models屬性,你可以看到集合中存儲的是Book模型類的執行個體,而并非我們在models數組中聲明的原始資料。

  這是因為我們在聲明BookList集合類的時候,就已經設定了model屬性,該屬性指向集合中存儲的模型對象的構造函數,當我們傳遞原始資料時,集合會自動建立model中定義的模型類,并将原始資料傳遞給它。

  (如果你在定義集合類的時候沒有設定model,那麼集合會預設将原始資料轉換為Backbone.Model類的執行個體)

  我們之是以要使用extend來繼承Backbone.Collection類,是因為我們希望定義一個自己的集合類,并向其中擴充更多的自定義方法。如果你的集合類僅僅是用于簡單地存儲和管理模型對象,且Backbone.Collection類所提供的方法已經可以滿足你的要求,那麼你可以直接執行個體化一個Backbone.Collection,同時也可以像上面一樣實作原始資料和模型對象的自動轉換,例如:

// 定義模型類
var Book = Backbone.Model.extend({
    defaults : {
        name : ''
    }
});

var models = [{
    name : 'Effective Java中文版(第2版)'
}, {
    name : 'JAVA核心技術卷II:進階特性(原書第8版)'
}, {
    name : '精通Hibernate:Java對象持久化技術詳解(第2版)'
}];

// 建立集合對象
var books = new Backbone.Collection(models, {
    model : Book
});      

  在本例中,我們沒有通過extend定義自己的集合類,而是直接執行個體化Collection類。我們依然傳入了原始資料,但同時我們在構造函數的第2個參數(配置對象)中設定了model屬性,Collection通過它就能知道要将原始資料轉換為哪個模型類的執行個體。

8.2 向集合中添加模型

  集合提供了3個方法允許我們動态地向集合中動态插入模型:

  • add():向集合中的指定位置插入模型,如果沒有指定位置,預設追加到集合尾部
  • push():将模型追加到集合尾部(與add方法的實作相同)
  • unshift():将模型插入到集合頭部

  這些方法很容易了解,但我們還是通過一個例子來說明:

// 定義模型類
var Book = Backbone.Model.extend({
    defaults : {
        name : '',
        price : 0
    }
});

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

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);      

  在例子中,我們通過3個方法向集合中添加了多個模型,最後,我們在控制台輸出了集合中的模型清單。請仔細觀察和分析我們調用的方法,以及最終清單中模型的排列順序:

  這些方法的作用和上面介紹的一樣,用于将模型添加到集合中不同的位置,但當我們設定了at配置之後,它們就變得完全一樣了,因為它們會忽略自身的規則,将模型插入到at所指向的位置。

  當資料被成功添加到集合中時,集合會觸發add事件,執行所有監聽add事件的方法。除非我們在調用add()方法時設定了silent配置項,則會忽略事件的觸發。

8.3 操作集合中的模型

  在Underscore中,提供了許多對對象和數組集合進行操作的方法,這些方法已經被Backbone添加到Collection類的原型中。這意味着你可以使用Underscore中的集合方法來操作Collection集合中的資料,例如each()、map()、find()等方法。

  但有一些方法我們還是要單獨介紹它們,因為Collection對這些方法進行了重寫,它們和Underscore中的同名方法不完全相同。(這也是為什麼我要在上面單獨介紹模型的添加方法)

  删除模型:

  集合類提供了3個方法用于從集合中移除模型對象,分别是:

  • remove():從集合中移除一個或多個指定的模型對象
  • pop():移除集合尾部的一個模型對象
  • shift():移除集合頭部的一個模型對象

  這些方法與添加的方法是對應的,而且當模型被移除成功後,會觸發集合對象的remove事件,除非你在移除時使用了silent配置。

  這些方法很容易了解,但還是讓我們通過一個簡單的例子來說明:

// 定義模型類
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()方法移除了集合中第2個模型,調用pop()方法移除了最後一個模型,調用shift()方法移除了第一個模型。最後我們在控制台輸出集合中剩下的模型清單,請檢視控制台輸出結果,它和你想象的結果是一緻的。

  在集合中查找模型:

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

  • get():根據模型的唯一辨別(id)查找模型對象
  • getByCid():根據模型的cid查找模型對象
  • at():查找集合中指定位置的模型對象
  • where():根據資料對集合的模型進行篩選

  前面前面一片介紹資料模型時我們提到,每個模型對象都有一個唯一辨別(id),它與資料庫中記錄的id保持同步。實際上,每個模型對象内部還會自動建立一個cid,它用來辨別每一個模型(請注意将id和cid區分開,它們沒有任何關系)。

  集合對象提供了兩個方法用于根據id和cid來查找模型對象,分别是get()方法和getByCid()方法,例如:

// 定義模型類
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);      

  本例中,我們從集合中根據模型的id和cid查找出2個模型對象,但實際開發中我們不會直接在代碼中寫出模型的id和cid。

  • id應該是從伺服器接口進行同步擷取到的。
  • cid應該是在之前已經記錄下某個模型的cid,再根據它從集合中查找的。

  at()方法根據我們給定的索引,從集合中查找對應位置的模型,我們在上面的例子中追加以下代碼:

// 根據索引查找模型對象
var book3 = books.at(1);

// 在控制台輸出模型
console.dir(book3);

  最後,我們還可以通過where()方法,實作相對複雜的查找規則,例如:
// 根據price從集合中查找模型
var book4 = books.where({
    price : 51.80
});

// 在控制台輸出模型
console.dir(book4);      

  請檢視控制台輸出的結果:where()方法用于給定一個或多個資料,查找并傳回集合中比對資料的模型。該方法傳回一個數組,是以能夠包含一個或多個結果。

  當我們調用get()、getByCid()和at()方法沒有找到到比對對象時,會傳回undefined,而where()方法在沒有找到比對對象時會傳回一個空數組。你可以使用Underscore中的isEmpty()方法檢查傳回值是否為空,因為它能檢查到空數組和空對象。

8.4 自動排序

  我們常常使用數組的sort()方法對元素進行排序,Underscore也提供了sortBy()方法實作更為複雜的集合排序。但在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);      

  這個例子和我們前面介紹添加方法時的例子相同,但集合中存儲的模型順序卻不一樣,因為我們在建立集合對象時設定了comparator方法。我們不需要手動調用該方法,因為它會在新模型被添加到集合中時自動被調用,并按照方法中定義的規則對集合中的資料進行重新排序。

  comparator方法接收兩個參數,表示臨近的兩個模型對象,你需要通過傳回值表示它們的排序規則,這和JavaScript中原生的sort()方法是一樣的。

  當我們設定了comparator方法後,所有關于元素位置的方法和參數都會失效,例如push()、unshift()方法和at參數等。

  需要注意的是:comparator方法在很多時候都是非常有用的(例如顯示動态資料清單時),因為它能保證我們擷取到的資料始終都是按規則排列的,但在集合中的資料量太多時,它可能會耗費很多的資源和事件來實時確定資料的排序規則。這時,你可以手動調用集合對象的sort()方法在需要的進行手動排序。

8.5 從伺服器擷取集合資料

  • Collection也提供了兩個與伺服器進行互動的方法:
  • fetch():用于從伺服器接口擷取集合的初始化資料,覆寫或追加到集合清單中
  • create():在集合中建立一個新的模型,并将其同步到伺服器

  我們先來看一個fetch()方法例子:

// 定義模型類
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);
    }
});      

  我們前面讨論過模型的資料同步,需要與伺服器同步資料,需要設定一個url或urlPath指定伺服器接口位址。同步集合資料時也不例外,本例中我們設定伺服器接口位址為/service,接口傳回資料為:

[{
    "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"
}]      

  我們在執行個體化集合對象之後,調用fetch()與伺服器接口進行資料同步,并在同步成功後将集合中的資料清單輸出在控制台。請運作例子中的代碼,你能看到控制台中輸出的結果,它包含5個模型對象,正是我們伺服器接口傳回的這些資料。

  在調用fetch()方法同步集合資料時,預設會以覆寫的方式進行,這意味着集合在同步之前的資料将丢失。我們可以在調用fetch()方法時傳遞add參數來通知集合進行添加,而不是覆寫,例如:

var books = new BookList();
books.add({
    id : 1000,
    name : 'Thinking in Java',
    price : 395.70
});

books.fetch({
    add: true,
    success : function(collection, resp) {
        // 同步成功後在控制台輸出集合中的模型清單
        console.dir(collection.models);
    }
});      

  我們修改了例子中的代碼,在執行個體化集合對象後,我們向集合中添加了一條資料,然後從伺服器同步了5條資料(請注意在調用fetch()方法時我們設定了add參數為true)。在控制台輸出的結果中,之前的資料并沒有被覆寫掉。

  資料在成功同步到集合中之後,會觸發reset事件,我們可以通過監聽該事件進而進行下一步操作(比如将集合中的資料顯示到頁面中)。

  集合的資料同步與模型的資料同步有許多相似之處(例如你可以重載parse()方法來對伺服器傳回的資料進行解析,使其能順利被添加到集合中),這裡就不再重複讨論。

  集合提供的另一個create()方法,是根據集合的model所指向的模型類,建立一個模型對象,并把該對象添加到集合中,最後将資料同步到伺服器接口。

  我們通過例子來說明create()方法的使用:

var books = new BookList();
// 建立一個模型
books.create({
    name : 'Thinking in Java',
    price : 395.70
}, {
    success : function(model, resp) {
        // 添加成功後, 在控制台輸出集合中的模型清單
        console.dir(books.models);
    }
});      

  請将這段代碼替換到前面的例子中,并檢視運作效果。

  (通過抓包我們能看到Request Method為POST,如果建立的模型中包含id,則Request   Method為PUT,這與我們之前講模型的save()方法是相同的。)

  集合對象預設會先将模型添加到集合中,再送出到伺服器接口,無論接口傳回是否成功,建立的模型對象都會被添加到集合中。我們可以通過傳遞wait配置,來控制隻有在伺服器傳回成功之後(響應狀态碼為200),才将模型對象添加到集合中。

  在Backbone内部,create()方法是通過add()方法将新建立的模型添加到集合中的,是以我們一般通過監聽add事件,來對新模型進行下一步操作。

8.6 将資料批量同步到伺服器

  上一節我們讨論過,Backbone中集合提供了資料同步和建立的方法與伺服器進行互動,但實際上這可能并不能滿足我們的需求。例如:當我們需要對資料進行批量地添加、修改和删除操作時,就需要在Collection的基礎上擴充自己的方法。

  在下面的例子中,我擴充了對集合中的模型資料批量同步的方法:

// 定義模型類
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();      

  來分析這個例子:

  我們定義了BookList集合類,并擴充了createAll()、updateAll()和deleteAll()方法(稍後我們再讨論這3些方法的作用)。

  然後我們執行個體化了一個BookList對象books,并監聽了reset事件,reset事件會在從伺服器同步資料成功之後被觸發。

  接着我們調用fetch()方法從伺服器接口擷取預設資料(預設資料跟前面例子中傳回的資料一緻),擷取成功後,reset事件的監聽函數将被執行。

  我們可以在reset事件的監聽函數中做許多的事情(例如寫入業務邏輯),但這裡為了更直覺地示範,我直接調用books的自定義方法進行批量同步。

  首先調用的是createAll()方法,它将把目前集合中的所有資料同步到伺服器接口,以Request   Method為POST方式告訴伺服器接口建立并儲存這些資料。

  在createAll()方法中,我們調用Backbone.sync()方法發送異步請求,請注意sync()方法的第2個參數,它是一個模型或集合對象,當操作為create或update時,在sync()方法内部會調用該對象的toJSON()方法,并将toJSON()方法的傳回值作為Request   Payload請求資料發送到伺服器接口。

  你可以通過抓包并分析請求資訊,來更好地了解它。

  (toJSON()方法預設會傳回模型或集合的資料對象,你可以通過重載該方法來自定義需要發送的請求資料)

  我們假設集合中的資料已經被使用者批量修改過,是以我們通過updateAll()方法将最新的資料送出到伺服器接口。

  updateAll()方法與createAll()方法幾乎相同,它們的差別在于updateAll()方法在修改資料時傳遞給sync()方法的操作名為update而不是create,而發送給伺服器的Request Method為PUT而不是POST。

  最後我們通過deleteAll()方法,通知伺服器删除集合中的模型,并從本地集合對象中删除這些資料。

  deleteAll()方法與createAll()和updateAll()方法有些不同,因為deleteAll()方法發送給伺服器的Request   Method為DELETE方式,這種方式下不能直接調用toJSON()方法将資料發送給接口,是以我們需要手動組裝和發送資料。

  在本例中,我們定義了getIds()方法用于将集合中的所有模型的id連接配接起來,在deleteAll()方法中,我們調用sync()方法發送了Request Method為DELETE的請求,并在URL中将集合中的所有模型id傳遞給接口進行删除。

繼續閱讀