【再探backbone 02】集合-Collection

前言

昨天咱們一塊兒學習了backbone的model,我我的對backbone的熟悉程度提升了,可是也發現一個嚴重的問題!!!html

我平時壓根沒有用到model這塊的東西,事實上我只用到了view,因此昨天學習的效果其實不佳,比起上次對underscore的熟悉,對zepto的熟悉,甚至對fastclick的熟悉前端

學習效率打了折扣,並且一些地方不明不白,因此,我今天決定將速度放慢,咱們學習collection時候先作小例子,爭取覆蓋關鍵點,而後再從源碼學習,因而開始吧json

【再探backbone 01】模型-Model服務器

RequireJS與Backbone簡單整合app

初探Backbone dom

集合-Collection

實例解析

首先回到咱們熟悉的官方例子,其中關於集合的敘述很少,官方的例子流程以下:函數

① 定義模型Model

這個model很簡單,基本沒有幹什麼有意義的事情,驗證的事情都沒有幹,說到這裏有一點須要注意:post

咱們在實際項目中,不管是使用backbone的model或者本身實現了model
都必定要提供validate驗證機制,或者驗證方法,這個可讓咱們的程序更加穩健!

舉個例子來講,咱們的程序中model會讀取來自服務器端的數據,正常狀況下是這樣的:學習

{
data1: [{name: '葉小釵', ......},],
data2: ......
}

可是若是有一天咱們的服務器出錯了,他會返回錯誤的數據,或者空數據,而前端的代碼沒有作容錯處理的話,這個數據就會被寫入localstoragefetch

{
data1: {},
data2: {}
}

這個json對象固然不爲空,由於咱們model的機制是爲空時候會拉取數據,不爲空則直接讀取localstorage數據,可是事實上,在業務邏輯上這個數據是爲空的!!!

因此,咱們的程序表現上就是列表爲空,讀不到數據了,這能夠引起很很差的問題

可是若是咱們這個model有一個validate方法用於驗證model的正確性的話,就能夠避免這個問題了!

以咱們官方的例子來講,他這個Todo的model事實上應該驗證title爲空的狀況,但他沒有這麼作,他將數據的驗證放到了view裏面

1 createOnEnter: function (e) {
2   if (e.keyCode != 13) return;
3   if (!this.input.val()) return;
4 
5   Todos.create({ title: this.input.val() });
6   this.input.val('');
7 },

可是若是咱們導入model數據的方式不止一種,或者要批量導入時候,view要面臨的問題就相對複雜一點了,總而言之Todo模型應該具備validate驗證函數

validate函數不返回數據就是驗證成功,不然失敗是不會執行set方法的,因此,我這裏將view中的驗證去掉,將驗證寫入model,咱們先走一次流程:

1 AppView會觸發createOnEnter事件,原本這裏有一個爲空驗證,被我破壞掉了

1 createOnEnter: function (e) {
2   if (e.keyCode != 13) return;
3 //    if (this.input.val() == '') return;
4 
5   Todos.create({ title: this.input.val() });
6   this.input.val('');
7 },

2 執行集合的create方法

 1 // 向集合中添加並建立一個模型, 同時將該模型保存到服務器
 2 // 若是是經過數據對象來建立模型, 須要在集合中聲明model屬性對應的模型類
 3 // 若是在options中聲明瞭wait屬性, 則會在服務器建立成功後再將模型添加到集合, 不然先將模型添加到集合, 再保存到服務器(不管保存是否成功)
 4 create: function (model, options) {
 5   var coll = this;
 6   // 定義options對象
 7   options = options ? _.clone(options) : {};
 8   // 經過_prepareModel獲取模型類的實例
 9   model = this._prepareModel(model, options);
10   // 模型建立失敗
11   if (!model)
12     return false;
13   // 若是沒有聲明wait屬性, 則經過add方法將模型添加到集合中
14   if (!options.wait)
15     coll.add(model, options);
16   // success存儲保存到服務器成功以後的自定義回調函數(經過options.success聲明)
17   var success = options.success;
18   // 監聽模型數據保存成功後的回調函數
19   options.success = function (nextModel, resp, xhr) {
20     // 若是聲明瞭wait屬性, 則在只有在服務器保存成功後纔會將模型添加到集合中
21     if (options.wait)
22       coll.add(nextModel, options);
23     // 若是聲明瞭自定義成功回調, 則執行自定義函數, 不然將默認觸發模型的sync事件
24     if (success) {
25       success(nextModel, resp);
26     } else {
27       nextModel.trigger('sync', model, resp, options);
28     }
29   };
30   // 調用模型的save方法, 將模型數據保存到服務器
31   model.save(null, options);
32   return model;
33 },

3 這裏的model須要經過_prepareModel進行處理

 1 // 將模型添加到集合中以前的一些準備工做
 2 // 包括將數據實例化爲一個模型對象, 和將集合引用到模型的collection屬性
 3 _prepareModel: function (model, options) {
 4   options || (options = {});
 5   // 檢查model是不是一個模型對象(即Model類的實例)
 6   if (!(model instanceof Model)) {
 7     // 傳入的model是模型數據對象, 而並不是模型對象
 8     // 將數據做爲參數傳遞給Model, 以建立一個新的模型對象
 9     var attrs = model;
10     // 設置模型引用的集合
11     options.collection = this;
12     // 將數據轉化爲模型
13     model = new this.model(attrs, options);
14     // 對模型中的數據進行驗證
15     if (!model._validate(model.attributes, options))
16       model = false;
17   } else if (!model.collection) {
18     // 若是傳入的是一個模型對象但沒有創建與集合的引用, 則設置模型的collection屬性爲當前集合
19     model.collection = this;
20   }
21   return model;
22 },

他在13行進行了model實例化,可是並無執行咱們的validate驗證,接下來即是執行了一次validate驗證,若是驗證不經過就完蛋

這裏比較煩的是,他這裏沒有走咱們的驗證流程,由於在第一個判斷就直接跳出來了,而save時候驗證了數據有效性,因此數據沒有寫往localstorage可是頁面上卻多了一條記錄

這裏的處理,我實際上是認爲有問題的,這裏未驗證實際上是由於初始化時候不須要驗證,可是咱們傳一個參數就會發生驗證

Todos.create({ title: this.input.val() }, {validate: true});

在這裏,咱們扯得有點遠了,稍微回顧了下昨天學習的model的知識,如今繼續向後面走

② 定義collection

有了Todo模型後,因而便定義了一個集合,而且對其進行了實例化

這個例子做爲demo沒什麼問題,可是通常項目中,list不可能像這樣全局化,可能是與view進行關聯,view之間以localstorage進行數據通訊

因此跳往 b view時候數據早在a view時候已經準備好,由於我也沒有使用backbone的collection進行項目開發,這裏不太能看清意圖,可是在view裏面進行實例化比較靠譜

好比在APPView的initialize中進行實例化,而構造函數以require的方式進行引入

backbone的集合自己方法多源於underscore的集合方法:

 1 var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
 2   'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
 3   'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
 4   'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
 5   'tail', 'drop', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf',
 6   'isEmpty', 'chain'];
 7 
 8 // Mix in each Underscore method as a proxy to `Collection#models`.
 9 _.each(methods, function (method) {
10   Collection.prototype[method] = function () {
11     var args = slice.call(arguments);
12     args.unshift(this.models);
13     return _[method].apply(_, args);
14   };
15 });

這裏依然有幾點須要注意:

1 由於這裏未與服務器發生交互,用於存儲數據的就是localstorage

localStorage: new Backbone.LocalStorage("todos-backbone"),

持久層咱們通常會本身實現,與服務器的交互也不會使用backbone自己的,因此model與持久層通訊這塊能夠選擇放棄

2 這裏TodoList提供了幾個方法多用於篩選數據,自己底層是調用underscore的方法篩選/排序本身內部models

可是model裏面竟然使用了集合中的nextOrder方法獲取順序標識,這裏不知道各位怎麼看,我反正以爲怪怪的.....

1 return {
2   title: "empty todo...",
3   order: Todos.nextOrder(),
4   done: false
5 };

model裏面的這個order我我的以爲十分很差,有可能我這個模型還得用於其它集合,而其它集合未必是Todos,可是這裏寫死了

並且其它集合未必具備nextOrder這個方法,因此官方這個例子真的就只是一個demo啊......

list方面自己沒什麼難度,根據咱們這兩天的學習能夠作一個總結:

Backbone Model
用於封裝數據對象,而且提供數據改變時候的change事件以及數據正確性驗證的validate方法
總而言之,這個model就是用於數據操做,而且數據改變時候能夠通知到view

Backbone Collection
用於封裝Model對象,其中提供了不少處理model的方法,好比排序分組什麼的

固然,這個只是第一階段的認識,更多的瞭解根據學習的深刻會逐步展開,並且我感受要深刻學習Backbone仍是得好好的用一用

③ 綁定事件

從程序能夠看出Todos(實例化的TodoList)只用於了AppView,而這裏的AppView有點擔當了控制器的意思,

在實例化AppView時,便爲Todos註冊了三大事件,咱們Todolist繼承了Events對象,各位不要忘了哦

1 this.listenTo(Todos, 'add', this.addOne);
2 this.listenTo(Todos, 'reset', this.addAll);
3 this.listenTo(Todos, 'all', this.render);

初始化結束後會執行fetch函數得到數據,數據獲取結束便又會調用model set方法裝入數據,而且觸發model的change事件,而致使TodoView的render觸發

而後AppView獲取TodoView的dom結構後將其dom結構展現到頁面便可

this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);

因而咱們實例解析便基本結束了,接下來看看源碼相關

構造函數

backbone中的集合時模型的有序組合,咱們能夠在集合上綁定change事件,從而當集合中的模型發生變化時得到通知

集合也能夠註冊事件,從服務器端獲得更新,集合中的模型觸發的任何事件均可以在集合上直接觸發,咱們能夠監聽集合中模型的變化

首先依然來看看他的初始化的構造函數:

1 var Collection = Backbone.Collection = function (models, options) {
2   options || (options = {});
3   if (options.url) this.url = options.url;
4   if (options.model) this.model = options.model;
5   if (options.comparator !== void 0) this.comparator = options.comparator;
6   this._reset();
7   this.initialize.apply(this, arguments);
8   if (models) this.reset(models, _.extend({ silent: true }, options));
9 };

前幾行沒什麼好說的,其中實例化時會重置集合內部的狀態,第一次定義即爲初始化,第二次爲重置

1 _reset: function () {
2   this.length = 0;
3   this.models = [];
4   this._byId = {};
5 },

讓後調用初始化方法initialize,若是這裏設置了多個models的話,會執行reset方法,若是指定了models數據, 則調用reset方法將數據添加到集合中

首次調用時設置了silent參數, 所以不會觸發"reset"事件

 1 reset: function (models, options) {
 2   options || (options = {});
 3   for (var i = 0, l = this.models.length; i < l; i++) {
 4     this._removeReference(this.models[i]);
 5   }
 6   options.previousModels = this.models;
 7   this._reset();
 8   this.add(models, _.extend({ silent: true }, options));
 9   if (!options.silent) this.trigger('reset', this, options);
10   return this;
11 },

reset用於替換集合中全部模型數據,該操做將刪除集合中當前的數據和狀態,從新設置models,models通常爲二維數據對象

這裏會刪除原model與集合的映射關係,可是並無刪除model自己

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

其實從官網的代碼來看,構造函數裏面的東西基本沒有用到,官網的代碼的集合充其量就是一個容器,因此要學習這塊還須要更好的例子

結語

這篇博客暫時到這裏,我發現沒有好的例子學習效率確實不高,下來先寫一個例子,再根據例子學習吧

相關文章
相關標籤/搜索