天天看點

【再探backbone 02】集合-Collection

前言

昨天我們一起學習了backbone的model,我個人對backbone的熟悉程度提高了,但是也發現一個嚴重的問題!!!

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

學習效率打了折扣,而且一些地方不明不白,是以,我今天決定将速度放慢,我們學習collection時候先做小例子,争取覆寫關鍵點,然後再從源碼學習,于是開始吧

【再探backbone 01】模型-Model

RequireJS與Backbone簡單整合

初探Backbone 

集合-Collection

執行個體解析

首先回到我們熟悉的官方例子,其中關于集合的叙述不多,官方的例子流程如下:

① 定義模型Model

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

我們在實際項目中,無論是使用backbone的model或者自己實作了model

都一定要提供validate驗證機制,或者驗證方法,這個可以讓我們的程式更加穩健!

舉個例子來說,我們的程式中model會讀取來自伺服器端的資料,正常情況下是這樣的:

{

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

data2: ......

}

但是如果有一天我們的伺服器出錯了,他會傳回錯誤的資料,或者空資料,而前端的代碼沒有做容錯處理的話,這個資料就會被寫入localstorage

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;

5   Todos.create({ title: this.input.val() });

6   this.input.val('');

7 },

但是如果我們導入model資料的方式不止一種,或者要批量導入時候,view要面臨的問題就相對複雜一點了,總而言之Todo模型應該具有validate驗證函數

validate函數不傳回資料就是驗證成功,否則失敗是不會執行set方法的,是以,我這裡将view中的驗證去掉,将驗證寫入model,我們先走一次流程:

1 AppView會觸發createOnEnter事件,本來這裡有一個為空驗證,被我破壞掉了

3 //    if (this.input.val() == '') return;

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

},

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

結語

這篇部落格暫時到這裡,我發現沒有好的例子學習效率确實不高,下來先寫一個例子,再根據例子學習吧

您可以考慮給小钗發個小額微信紅包以資鼓勵 

本文轉自葉小钗部落格園部落格,原文連結:http://www.cnblogs.com/yexiaochai/p/3485951.html,如需轉載請自行聯系原作者

繼續閱讀