【再探backbone 01】模型-Model

前言

點保存時候不注意發出來了,有須要的朋友將就看吧,還在更新......javascript

幾個月前學習了一下backbone,這段時間也用了下,感受以前對backbone的學習非常基礎,前幾天有個園友問我如何將路由的#改成其餘css

我其實想說這個不能亂改,又怕不熟悉誤人子弟,因此今天咱們來一塊兒從新學習下他,看看會不會帶來不同的感受html

我在博客園nuysoft的博客看到了backbone的分析,惋惜沒有寫完,不失爲一個遺憾,但願做者堅持下去,水平高得貢獻出來喲(@nuysoft)前端

而後,網上backbone基礎用法的學習文章不少,感受就nuysoft的深刻,只不過可能是點一下有點惋惜,再次但願做者堅持下去......java

Web應用愈來愈關注前端,如今一個服務器端可能要對付五個前端,前端的業務邏輯複雜,各類問題層出不窮,現實對javascript程序的重用性、健壯性提出了更高的要求jquery

要求提升了,可是並不會給你更多的時間,反而爲了搶佔移動市場份額而拉快開發速度,如今的前端不可謂不難git

PS:若是你的公司是互聯網公司且不重視前端的話,你能夠來咱們公司啊......github

Backbone是一個基於MVC模式的架構,自己強依賴與underscore,因此上個星期咱們初略的學習了下underscore,有了一個大概印象web

非強制依賴於jquery/zepto,而後require是一個很好的基友,建議不要放過ajax

backbone據我使用來看,有幾個優勢:

① 模板引擎避免js中嵌入過多html代碼,這是一種結構數據分離的體現,可是他要歸功於underscore了

而後他的優勢我用的時候就沒有了......

以上說法其實有點坑爹,咱們爲了減小backbone的size,因此對backbone作了刪除,最後只用到了其中的view(事件處理),控制器咱們本身實現了

因此,我應該還未學習到backbone的精華,好了,前面扯多了,咱們正式開始學習吧,這裏附上以前的學習博客:

http://www.cnblogs.com/yexiaochai/p/3219402.html

http://www.cnblogs.com/yexiaochai/p/3221081.html

例子參考:http://www.ibm.com/developerworks/cn/web/wa-backbonejs/

簡單例子

咱們今天首先作一個簡單的例子,而後經過例子去讀backbone的源碼,明天再總體進行學習,這個例子固然就是咱們偉大的官方例子了......

第一步,頁面結構

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4   <meta charset="utf-8">
 5   <title>Backbone.js Todos</title>
 6   <link rel="stylesheet" href="todos.css" />
 7 </head>
 8 <body>
 9   <div id="todoapp">
10     <header>
11       <h1>
12         Todos</h1>
13       <input id="new-todo" type="text" placeholder="What needs to be done?">
14     </header>
15     <section id="main">
16       <input id="toggle-all" type="checkbox">
17       <label for="toggle-all">
18         Mark all as complete</label>
19       <ul id="todo-list">
20       </ul>
21     </section>
22     <footer>
23       <a id="clear-completed">Clear completed</a>
24       <div id="todo-count">
25       </div>
26     </footer>
27   </div>
28   <div id="instructions">
29     Double-click to edit a todo.
30   </div>
31   <div id="credits">
32     Created by
33     <br />
34     <a href="http://jgn.me/">J&eacute;r&ocirc;me Gravel-Niquet</a>.
35     <br />
36     Rewritten by: <a href="http://addyosmani.github.com/todomvc">TodoMVC</a>.
37   </div>
38   <script src="../../test/vendor/json2.js"></script>
39   <script src="../../test/vendor/jquery.js"></script>
40   <script src="../../test/vendor/underscore.js"></script>
41   <script src="../../backbone.js"></script>
42   <script src="../backbone.localStorage.js"></script>
43   <script src="todos.js"></script>
44   <!-- Templates -->
45   <script type="text/template" id="item-template">
46     <div class="view">
47       <input class="toggle" type="checkbox" <%= done ? 'checked="checked"' : '' %> />
48       <label><%- title %></label>
49       <a class="destroy"></a>
50     </div>
51     <input class="edit" type="text" value="<%- title %>" />
52   </script>
53   <script type="text/template" id="stats-template">
54     <% if (done) { %>
55       <a id="clear-completed">Clear <%= done %> completed <%= done == 1 ? 'item' : 'items' %></a>
56     <% } %>
57     <div class="todo-count"><b><%= remaining %></b> <%= remaining == 1 ? 'item' : 'items' %> left</div>
58   </script>
59 </body>
60 </html>
View Code

頁面結構比較簡單,其實就只有一個文本框,而後下面有一個用於顯示的列表,固然頁面中有咱們用到的模板

第二步,定義model

而後咱們須要定義咱們備忘錄的model了

 1 // Our basic **Todo** model has `title`, `order`, and `done` attributes.
 2 var Todo = Backbone.Model.extend({
 3 
 4   // Default attributes for the todo item.
 5   defaults: function () {
 6     return {
 7       title: "empty todo...",
 8       order: Todos.nextOrder(),
 9       done: false
10     };
11   },
12   // Toggle the `done` state of this todo item.
13   toggle: function () {
14     this.save({ done: !this.get("done") });
15   }
16 });

這裏須要注意他這種寫法,咱們後面會詳細說明,這裏先簡單來看看這個實例化的結果

咱們看到一個Model實例化後有以上屬性,主要注意點是title與down,事實上咱們可使用model的get/set去操做這些屬性,model的主要工做其實就是維護他的屬性

model維護的屬性可能還會與服務器端發生通訊,通訊時會用到save方法,咱們這裏不予關注

 1 // "name" attribute is set into the model
 2 var team1 = new App.Models.Team({
 3     name : "name1"
 4 });
 5 console.log(team1.get("name")); // prints "name1"
 6 
 7 // "name" attribute is set with a new value
 8 team1.set({
 9     name : "name2"
10 });
11 console.log(team1.get("name")); //prints "name2"

這裏須要注意的一點是,數據變化時候會引起Model的change方法,若是在change方法中,綁定對dom的操做,那麼model變化頁面就會自動發生變化,這就是model這點小九九乾的事情

代碼內部具體幹什麼的咱們暫時無論,繼續往下看,有了model後就會有集合

第三步,集合

 1 var TodoList = Backbone.Collection.extend({
 2   // Reference to this collection's model.
 3   model: Todo,
 4   // Save all of the todo items under the `"todos-backbone"` namespace.
 5   localStorage: new Backbone.LocalStorage("todos-backbone"),
 6   // Filter down the list of all todo items that are finished.
 7   done: function () {
 8     return this.where({ done: true });
 9   },
10   // Filter down the list to only todo items that are still not finished.
11   remaining: function () {
12     return this.without.apply(this, this.done());
13   },
14   // We keep the Todos in sequential order, despite being saved by unordered
15   // GUID in the database. This generates the next order number for new items.
16   nextOrder: function () {
17     if (!this.length) return 1;
18     return this.last().get('order') + 1;
19   },
20   // Todos are sorted by their original insertion order.
21   comparator: 'order'
22 });

這裏對集合與以前定義的Model作了一個映射關係,他這個集合有何做用已經如何發生變化的咱們後面詳細說明

這裏我只能說,這個集合時保存Model的列表,並在內部定義了一些操做model的方法

集合與model息息相關,事實上每一個model內部都有一個collection的映射對象,一旦發生映射,那麼model變化collection內部也會發生變化,這裏的細節咱們後面點說

第四步,TodoView

 1 var TodoView = Backbone.View.extend({
 2 
 3   //... is a list tag.
 4   tagName: "li",
 5 
 6   // Cache the template function for a single item.
 7   template: _.template($('#item-template').html()),
 8 
 9   // The DOM events specific to an item.
10   events: {
11     "click .toggle": "toggleDone",
12     "dblclick .view": "edit",
13     "click a.destroy": "clear",
14     "keypress .edit": "updateOnEnter",
15     "blur .edit": "close"
16   },
17 
18   // The TodoView listens for changes to its model, re-rendering. Since there's
19   // a one-to-one correspondence between a **Todo** and a **TodoView** in this
20   // app, we set a direct reference on the model for convenience.
21   initialize: function () {
22     this.listenTo(this.model, 'change', this.render);
23     this.listenTo(this.model, 'destroy', this.remove);
24   },
25 
26   // Re-render the titles of the todo item.
27   render: function () {
28     this.$el.html(this.template(this.model.toJSON()));
29     this.$el.toggleClass('done', this.model.get('done'));
30     this.input = this.$('.edit');
31     return this;
32   },
33 
34   // Toggle the `"done"` state of the model.
35   toggleDone: function () {
36     this.model.toggle();
37   },
38 
39   // Switch this view into `"editing"` mode, displaying the input field.
40   edit: function () {
41     this.$el.addClass("editing");
42     this.input.focus();
43   },
44 
45   // Close the `"editing"` mode, saving changes to the todo.
46   close: function () {
47     var value = this.input.val();
48     if (!value) {
49       this.clear();
50     } else {
51       this.model.save({ title: value });
52       this.$el.removeClass("editing");
53     }
54   },
55 
56   // If you hit `enter`, we're through editing the item.
57   updateOnEnter: function (e) {
58     if (e.keyCode == 13) this.close();
59   },
60 
61   // Remove the item, destroy the model.
62   clear: function () {
63     this.model.destroy();
64   }
65 
66 });
View Code

熟悉backbone的朋友必定對這段代碼尤爲熟悉(由於咱們只用到了view,model與control所有本身寫的,因此我最熟悉的就是這個......)

上面的代碼會造成一個view,view實例化時會執行initialize中的方法,須要顯示則須要執行render方法(重寫)

render結束後頁面的交互所有放到了events裏面,各位既然用到了backbone,就不要本身隨意以on的形式綁定事件了

在view能夠爲el指定dom結構,新建的view造成的dom就會往裏面裝

總而言之,View的重點是模板引擎與事件綁定,這裏的view不是入口方法,咱們看下一個view

第五步,入口AppView

 1 var AppView = Backbone.View.extend({
 2 
 3   // Instead of generating a new element, bind to the existing skeleton of
 4   // the App already present in the HTML.
 5   el: $("#todoapp"),
 6 
 7   // Our template for the line of statistics at the bottom of the app.
 8   statsTemplate: _.template($('#stats-template').html()),
 9 
10   // Delegated events for creating new items, and clearing completed ones.
11   events: {
12     "keypress #new-todo": "createOnEnter",
13     "click #clear-completed": "clearCompleted",
14     "click #toggle-all": "toggleAllComplete"
15   },
16 
17   // At initialization we bind to the relevant events on the `Todos`
18   // collection, when items are added or changed. Kick things off by
19   // loading any preexisting todos that might be saved in *localStorage*.
20   initialize: function () {
21 
22     this.input = this.$("#new-todo");
23     this.allCheckbox = this.$("#toggle-all")[0];
24 
25     this.listenTo(Todos, 'add', this.addOne);
26     this.listenTo(Todos, 'reset', this.addAll);
27     this.listenTo(Todos, 'all', this.render);
28 
29     this.footer = this.$('footer');
30     this.main = $('#main');
31 
32     Todos.fetch();
33   },
34 
35   // Re-rendering the App just means refreshing the statistics -- the rest
36   // of the app doesn't change.
37   render: function () {
38     var done = Todos.done().length;
39     var remaining = Todos.remaining().length;
40 
41     if (Todos.length) {
42       this.main.show();
43       this.footer.show();
44       this.footer.html(this.statsTemplate({ done: done, remaining: remaining }));
45     } else {
46       this.main.hide();
47       this.footer.hide();
48     }
49 
50     this.allCheckbox.checked = !remaining;
51   },
52 
53   // Add a single todo item to the list by creating a view for it, and
54   // appending its element to the `<ul>`.
55   addOne: function (todo) {
56     var view = new TodoView({ model: todo });
57     this.$("#todo-list").append(view.render().el);
58   },
59 
60   // Add all items in the **Todos** collection at once.
61   addAll: function () {
62     Todos.each(this.addOne, this);
63   },
64 
65   // If you hit return in the main input field, create new **Todo** model,
66   // persisting it to *localStorage*.
67   createOnEnter: function (e) {
68     if (e.keyCode != 13) return;
69     if (!this.input.val()) return;
70 
71     Todos.create({ title: this.input.val() });
72     this.input.val('');
73   },
74 
75   // Clear all done todo items, destroying their models.
76   clearCompleted: function () {
77     _.invoke(Todos.done(), 'destroy');
78     return false;
79   },
80 
81   toggleAllComplete: function () {
82     var done = this.allCheckbox.checked;
83     Todos.each(function (todo) { todo.save({ 'done': done }); });
84   }
85 
86 });
View Code

很遺憾的是,這個代碼沒有用到路由相關的知識,至此就結束了,由於路由相關的知識是單頁應用的一大重點,可是對咱們學習來講夠了

這裏定義了AppView後便實例化了,咱們這裏來詳細讀讀這個入口函數

① 初始化操做

 1   initialize: function () {
 2 
 3     this.input = this.$("#new-todo");
 4     this.allCheckbox = this.$("#toggle-all")[0];
 5 
 6     this.listenTo(Todos, 'add', this.addOne);
 7     this.listenTo(Todos, 'reset', this.addAll);
 8     this.listenTo(Todos, 'all', this.render);
 9 
10     this.footer = this.$('footer');
11     this.main = $('#main');
12 
13     Todos.fetch();
14   },

首先這裏作了初始化操做,在這裏,咱們能夠開開心心定義一些後面會用到的dom,這裏有一個比較有意思的方法:

this.$();//實際上是this.root.find(),這個能夠保證你找到正確的元素

在單頁應用中,id的惟一性收到了吹殘,因此得到元素的方式獲得了便會,以上是一種,不明白以上方法的同窗喜歡用:

this.$el.find();//$el事實上就是根元素

fetch方法用於初始化集合數據,意思是Todos.fetch();執行結束後,集合就被model給填充了(這裏在localstorage中讀取了數據)

Todos填充數據後,便會調用自己的render方法將數據以dom形式呈如今咱們眼前

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

這裏的的listenTo事實上是一種自定義事件的寫法,fetch時候觸發了其中的all事件,因此執行了render方法渲染頁面

這裏頁面初始化完成了,固然,一開始咱們列表實際上是空的

② 增長操做

增長操做簡單說一下便可,這裏爲new-todo(文本框)綁定了keypress事件,事件會觸發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 },

這裏愉快的使用集合create方法建立了一個model,固然他觸發本身綁定的add事件,因而執行了

1 addOne: function (todo) {
2   var view = new TodoView({ model: todo });
3   this.$("#todo-list").append(view.render().el);
4 },

這個操做天然不是省油的燈,他自己也是綁定了change事件的,因而高高興興在頁面中新增了一條數據:

 1 initialize: function () {
 2   this.listenTo(this.model, 'change', this.render);
 3   this.listenTo(this.model, 'destroy', this.remove);
 4 },
 5 
 6 // Re-render the titles of the todo item.
 7 render: function () {
 8   this.$el.html(this.template(this.model.toJSON()));
 9   this.$el.toggleClass('done', this.model.get('done'));
10   this.input = this.$('.edit');
11   return this;
12 },

至此整個邏輯基本結束,其它方面我這裏暫時不涉及,這個模式比較好的是,咱們就只須要關注model數據變化便可,頁面上顯示的東西就無論了

PS:說是無論,其實一開始就管完了,只是須要觸發事件便可

至此,咱們基本例子分析結束,咱們下面帶着這個例子去學習下backbone的源碼,這裏又不得不嘆息,這裏未使用路由(控制器)功能

實現繼承-extend

咱們不管定義Model仍是View時候都是這樣乾的:

var Todo = Backbone.Model.extend({})
var TodoList = Backbone.Collection.extend({})
var TodoView = Backbone.View.extend({})

其實這個extend着實令人疑惑,由於在underscore學習時候,咱們知道他是這樣的:

 1 // 將一個或多個對象的屬性(包含原型鏈中的屬性), 複製到obj對象, 若是存在同名屬性則覆蓋
 2 _.extend = function(obj) {
 3     // each循環參數中的一個或多個對象
 4     each(slice.call(arguments, 1), function(source) {
 5         // 將對象中的所有屬性複製或覆蓋到obj對象
 6         for(var prop in source) {
 7             obj[prop] = source[prop];
 8         }
 9     });
10     return obj;
11 };

這個東西和上述實現差了一大截,因而進入源碼一看,事實上咱們看到的應該是inherits方法,可是1.0滅掉了inherits方法,只剩下了extend了

 1 var extend = function(protoProps, staticProps) {
 2   var parent = this;
 3   var child;
 4 
 5   // The constructor function for the new subclass is either defined by you
 6   // (the "constructor" property in your `extend` definition), or defaulted
 7   // by us to simply call the parent's constructor.
 8   if (protoProps && _.has(protoProps, 'constructor')) {
 9     child = protoProps.constructor;
10   } else {
11     child = function(){ return parent.apply(this, arguments); };
12   }
13 
14   // Add static properties to the constructor function, if supplied.
15   _.extend(child, parent, staticProps);
16 
17   // Set the prototype chain to inherit from `parent`, without calling
18   // `parent`'s constructor function.
19   var Surrogate = function(){ this.constructor = child; };
20   Surrogate.prototype = parent.prototype;
21   child.prototype = new Surrogate;
22 
23   // Add prototype properties (instance properties) to the subclass,
24   // if supplied.
25   if (protoProps) _.extend(child.prototype, protoProps);
26 
27   // Set a convenience property in case the parent's prototype is needed
28   // later.
29   child.__super__ = parent.prototype;
30 
31   return child;
32 };
33 
34 // Set up inheritance for the model, collection, router, view and history.
35 Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;

這個傢伙又是如何實現繼承的,咱們這裏詳細來看看,首先咱們將這段代碼分離出來:

 1 var hasOwnProperty = Object.prototype.hasOwnProperty;
 2 var slice = Array.prototype.slice;
 3 var nativeForEach = Array.prototype.forEach;
 4 var _ = {};
 5 
 6 var each = _.each = _.forEach = function (obj, iterator, context) {
 7   if (obj == null)
 8     return;
 9   if (nativeForEach && obj.forEach === nativeForEach) {
10     obj.forEach(iterator, context);
11   } else if (obj.length === +obj.length) {
12     for (var i = 0, l = obj.length; i < l; i++) {
13       if (i in obj && iterator.call(context, obj[i], i, obj) === breaker)
14         return;
15     }
16   } else {
17     for (var key in obj) {
18       if (_.has(obj, key)) {
19         if (iterator.call(context, obj[key], key, obj) === breaker)
20           return;
21       }
22     }
23   }
24 }
25 
26 _.has = function (obj, key) {
27   return hasOwnProperty.call(obj, key);
28 };
29 
30 _.extend = function (obj) {
31   each(slice.call(arguments, 1), function (source) {
32     for (var prop in source) {
33       obj[prop] = source[prop];
34     }
35   });
36   return obj;
37 };
38 
39 var extend = function (protoProps, staticProps) {
40   var parent = this;
41   var child;
42   if (protoProps && _.has(protoProps, 'constructor')) {
43     child = protoProps.constructor;
44   } else {
45     child = function () { return parent.apply(this, arguments); };
46   }
47   _.extend(child, parent, staticProps);
48   var Surrogate = function () { this.constructor = child; };
49   Surrogate.prototype = parent.prototype;
50   child.prototype = new Surrogate;
51   if (protoProps) _.extend(child.prototype, protoProps);
52   child.__super__ = parent.prototype;
53   return child;
54 };
55 
56 
57 var P = function () {
58   this.a = 1;
59   this.b = 2;
60 };
61 
62 P.prototype.aa = function () {
63   alert(this.a);
64 }
65 
66 P.extend = extend;
67 
68 var C = P.extend({
69   c: 3,
70   cc: function () {
71     alert(this.c);
72   }
73 });
74 
75 var s = '';
View Code
1 var c = new C;
2 c.cc();//3
3 c.aa();//1

backbone實現的繼承以最基礎的繼承,由於他只支持一層的繼承,要再多可能這個作法就很差使了,原來inherits其實能夠多層繼承的......

反正,咱們來讀一讀extend代碼

① parent=this

這個代碼實際上是保留當前函數,好比View或者Model,後面的child會繼承他的方法

② protoProps

protoProps其實是原型方法,若是具備constactor屬性,變直接繼承之,不然從新定義一個函數,函數初始化(構造函數)時會調用parent的構造函數

PS:而這裏parent會執行一些初始化操做,而後調用this.initialize.apply(this, arguments); 因此咱們代碼中的initialize必定會執行

③ 複製靜態屬性

而後使用將parent靜態屬性一一收入成本身的

_.extend(child, parent, staticProps);

④ 經典繼承法

1 var Surrogate = function () { this.constructor = child; };
2 Surrogate.prototype = parent.prototype;
3 child.prototype = new Surrogate;
4 if (protoProps) _.extend(child.prototype, protoProps);
5 child.__super__ = parent.prototype;

而後使用此經典的方法實現繼承,最終返回給咱們child,注意其中的_.extend(child.prototype, protoProps);咱們增長的屬性所有給加到了原型鏈上了

至此,backbone的基本繼承,咱們閱讀完畢,如今看到如下代碼要清除發生了什麼事才行

var TodoView = Backbone.View.extend({})

下面咱們來看看backbone的事件機制

事件機制-Events

backbone事件一塊就只放出了三個接口:bind、unbind、trigger

Events 是一個能夠被mix到任意對象的模塊,它擁有讓對象綁定和觸發自定義事件的能力。

事件在被綁定以前是不須要事先聲明的,還能夠攜帶參數。咱們經過一個例子來看:

var object = {};
_.extend(object, Backbone.Events);

object.bind("alert", function(msg) {
  alert("Triggered " + msg);
});

object.trigger("alert", "www.csser.com");

bind用於註冊事件,unbind註銷事件,trigger觸發事件,可是內部的事件一塊遠不止如此

PS:老夫忽然發現我看的中文api是0.5.3的版本!!!fuck me!!! 1.1放出了這麼多的接口,額......

首先咱們來看一看他Events的源碼:

 1 var Events = Backbone.Events = {
 2 
 3   // Bind an event to a `callback` function. Passing `"all"` will bind
 4   // the callback to all events fired.
 5   on: function(name, callback, context) {
 6     if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
 7     this._events || (this._events = {});
 8     var events = this._events[name] || (this._events[name] = []);
 9     events.push({callback: callback, context: context, ctx: context || this});
10     return this;
11   },
12 
13   // Bind an event to only be triggered a single time. After the first time
14   // the callback is invoked, it will be removed.
15   once: function(name, callback, context) {
16     if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
17     var self = this;
18     var once = _.once(function() {
19       self.off(name, once);
20       callback.apply(this, arguments);
21     });
22     once._callback = callback;
23     return this.on(name, once, context);
24   },
25 
26   // Remove one or many callbacks. If `context` is null, removes all
27   // callbacks with that function. If `callback` is null, removes all
28   // callbacks for the event. If `name` is null, removes all bound
29   // callbacks for all events.
30   off: function(name, callback, context) {
31     var retain, ev, events, names, i, l, j, k;
32     if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
33     if (!name && !callback && !context) {
34       this._events = {};
35       return this;
36     }
37 
38     names = name ? [name] : _.keys(this._events);
39     for (i = 0, l = names.length; i < l; i++) {
40       name = names[i];
41       if (events = this._events[name]) {
42         this._events[name] = retain = [];
43         if (callback || context) {
44           for (j = 0, k = events.length; j < k; j++) {
45             ev = events[j];
46             if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
47                 (context && context !== ev.context)) {
48               retain.push(ev);
49             }
50           }
51         }
52         if (!retain.length) delete this._events[name];
53       }
54     }
55 
56     return this;
57   },
58 
59   // Trigger one or many events, firing all bound callbacks. Callbacks are
60   // passed the same arguments as `trigger` is, apart from the event name
61   // (unless you're listening on `"all"`, which will cause your callback to
62   // receive the true name of the event as the first argument).
63   trigger: function(name) {
64     if (!this._events) return this;
65     var args = slice.call(arguments, 1);
66     if (!eventsApi(this, 'trigger', name, args)) return this;
67     var events = this._events[name];
68     var allEvents = this._events.all;
69     if (events) triggerEvents(events, args);
70     if (allEvents) triggerEvents(allEvents, arguments);
71     return this;
72   },
73 
74   // Tell this object to stop listening to either specific events ... or
75   // to every object it's currently listening to.
76   stopListening: function(obj, name, callback) {
77     var listeners = this._listeners;
78     if (!listeners) return this;
79     var deleteListener = !name && !callback;
80     if (typeof name === 'object') callback = this;
81     if (obj) (listeners = {})[obj._listenerId] = obj;
82     for (var id in listeners) {
83       listeners[id].off(name, callback, this);
84       if (deleteListener) delete this._listeners[id];
85     }
86     return this;
87   }
88 
89 };

這裏統一使用了eventApi這個函數:

 1 // Regular expression used to split event strings.
 2 var eventSplitter = /\s+/;
 3 
 4 // Implement fancy features of the Events API such as multiple event
 5 // names `"change blur"` and jQuery-style event maps `{change: action}`
 6 // in terms of the existing API.
 7 var eventsApi = function(obj, action, name, rest) {
 8   if (!name) return true;
 9 
10   // Handle event maps.
11   if (typeof name === 'object') {
12     for (var key in name) {
13       obj[action].apply(obj, [key, name[key]].concat(rest));
14     }
15     return false;
16   }
17 
18   // Handle space separated event names.
19   if (eventSplitter.test(name)) {
20     var names = name.split(eventSplitter);
21     for (var i = 0, l = names.length; i < l; i++) {
22       obj[action].apply(obj, [names[i]].concat(rest));
23     }
24     return false;
25   }
26 
27   return true;
28 };

① 第一個參數爲一個對象,其實指向的是調用者,由於Events對象都是被做爲 繼承/擴展 者使用

② 第二個參數爲你的具體操做(on/off/trigger)

③ name可算是這個對象註冊的這個事件的惟一標識了,註冊事件後後面會用他來讀取

④ 最後是傳入的回調函數,而且帶有做用域

而上述調用點又在其它地方,咱們這裏將上述代碼連起來:

① 首先一個對象繼承了Events對象

var obj = {};
_.extend(obj, Backbone.Events)

② 其次咱們爲他註冊一個alert事件

obj.on('alert', function (msg) {
  alert(msg);
});

此時會調用由Events繼承而來的方法on,而且傳入兩個參數:alert與回調函數,然後會調用eventApi(私有方法)處理這個event對象

PS:此處傳入的那麼不是對象也沒有任何複雜應用因此直接返回true了,像那麼包含「:」,或者包含空格就會作特殊處理,咱們這裏暫時無論

③ 定義對象的events屬性,該屬性用於存儲該對象保存的全部事件

1 on: function(name, callback, context) {
2   if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
3   this._events || (this._events = {});
4   var events = this._events[name] || (this._events[name] = []);
5   events.push({callback: callback, context: context, ctx: context || this});
6   return this;
7 },

處理結束後,這裏便會多出一個對象了:

③ 觸發事件,觸發事件相對比較簡單,能夠選擇傳入參數

obj.trigger("alert", "an event");

觸發事件,固然是調用的trigger方法:

 1 trigger: function(name) {
 2   if (!this._events) return this;
 3   var args = slice.call(arguments, 1);
 4   if (!eventsApi(this, 'trigger', name, args)) return this;
 5   var events = this._events[name];
 6   var allEvents = this._events.all;
 7   if (events) triggerEvents(events, args);
 8   if (allEvents) triggerEvents(allEvents, arguments);
 9   return this;
10 },

這裏會經過name在events屬性中獲取當前對象,調用triggerEvents局部函數調用之,這裏有兩點須要注意:

1 這裏的args是除,name之外傳入的參數
2 這裏會觸發name爲all的事件,不管如何都會觸發,各位這裏要回想起來前面集合的listenerTo方法哦
 1 var triggerEvents = function(events, args) {
 2   var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
 3   switch (args.length) {
 4     case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
 5     case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
 6     case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
 7     case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
 8     default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
 9   }
10 };

而後這裏作了下簡單的處理,高高興興將咱們定義的事件執行了,因而backbone事件機制的第一段講解也結束了,較詳細的講解後面點再來

在此咱們能夠看到,backbone內部的事件機制,其實與javascript事件綁定那塊沒什麼聯繫,只不過是本身內部的實現而已,這裏最後補充一點:

 1 var listenMethods = { listenTo: 'on', listenToOnce: 'once' };
 2 
 3 // Inversion-of-control versions of `on` and `once`. Tell *this* object to
 4 // listen to an event in another object ... keeping track of what it's
 5 // listening to.
 6 _.each(listenMethods, function (implementation, method) {
 7   Events[method] = function (obj, name, callback) {
 8     var listeners = this._listeners || (this._listeners = {});
 9     var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
10     listeners[id] = obj;
11     if (typeof name === 'object') callback = this;
12     obj[implementation](name, callback, this);
13     return this;
14   };
15 });

頁面上調用的listenTo其實就是on方法

模型-Model

構造函數

Model在服務器端來講非常關鍵,記得前幾年老夫還在搞.net最早乾的事情就是建造實體,不知道如今怎麼樣了......

首先看看其繼承源碼:

 1 var Model = Backbone.Model = function (attributes, options) {
 2   var defaults;
 3   var attrs = attributes || {};
 4   options || (options = {});
 5   this.cid = _.uniqueId('c');
 6   this.attributes = {};
 7   _.extend(this, _.pick(options, modelOptions));
 8   if (options.parse) attrs = this.parse(attrs, options) || {};
 9   if (defaults = _.result(this, 'defaults')) {
10     attrs = _.defaults({}, attrs, defaults);
11   }
12   this.set(attrs, options);
13   this.changed = {};
14   this.initialize.apply(this, arguments);
15 };

構造函數中自己沒有幹太多事情:

① 首先爲該model定義了惟一的cid(其中的uniqueId方法內部維護着一個id,這個閉包知識點,各位本身去看吧)

② 初始化model默認的屬性,好比collection就是必須擁有的,若是定義了的話就直接搞給對象

③ parse是爲了兼容不是json數據時候須要作的處理,咱們這裏直接忽略不要json的場景

④ 獲取defaults對象(若是是函數須要返回對象)

⑤ 調用原型鏈中的set方法,將默認的屬性搞到對象中去,set乾的事情比較多,咱們後面點說

反正他產生的結果就是對象默認會多一些屬性值

而後開始調用underscore的exentd擴展對象的原型鏈了(尼瑪,backbone確實強依賴underscore啊,壓根搞不掉)

擴展原型鏈

下面開始擴展Model原型鏈了,其實這樣讀下來,backbone的代碼是頗有調理的,很好讀,咱們這裏撿幾個重要的說(我不知道哪一個重要只能挑我知道的)

① changed

changed屬性記錄了每次調用set方法時, 被改變數據的key集合

② validationError

set model 時候會執行validate方法,若是驗證失敗便會將結果返回該變量

③ idAttribute

每一個模型的惟一標識屬性(默認爲"id", 經過修改idAttribute可自定義id屬性名)

若是在設置數據時包含了id屬性, 則id將會覆蓋模型的id,id用於在Collection集合中查找和標識模型, 與後臺接口通訊時也會以id做爲一條記錄的標識

var Meal = Backbone.Model.extend({
  idAttribute: "_id"
});

var cake = new Meal({ _id: 1, name: "Cake" });
alert("Cake id: " + cake.id);

這裏就將標識符搞到了_id屬性上,可是通常不建議這麼幹,真心不太好......

initialize

這個方法比較關鍵,自己沒有意義,用於子對象複寫,會在實例化時候執行

⑤ get

返回相關屬性的值

set(key, value, options)

這個方法很關鍵,咱們這裏來詳細說下

 1 set: function (key, val, options) {
 2   var attr, attrs, unset, changes, silent, changing, prev, current;
 3   if (key == null) return this;
 4 
 5   // Handle both `"key", value` and `{key: value}` -style arguments.
 6   if (typeof key === 'object') {
 7     attrs = key;
 8     options = val;
 9   } else {
10     (attrs = {})[key] = val;
11   }
12 
13   options || (options = {});
14 
15   // Run validation.
16   if (!this._validate(attrs, options)) return false;
17 
18   // Extract attributes and options.
19   unset = options.unset;
20   silent = options.silent;
21   changes = [];
22   changing = this._changing;
23   this._changing = true;
24 
25   if (!changing) {
26     this._previousAttributes = _.clone(this.attributes);
27     this.changed = {};
28   }
29   current = this.attributes, prev = this._previousAttributes;
30 
31   // Check for changes of `id`.
32   if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
33 
34   // For each `set` attribute, update or delete the current value.
35   for (attr in attrs) {
36     val = attrs[attr];
37     if (!_.isEqual(current[attr], val)) changes.push(attr);
38     if (!_.isEqual(prev[attr], val)) {
39       this.changed[attr] = val;
40     } else {
41       delete this.changed[attr];
42     }
43     unset ? delete current[attr] : current[attr] = val;
44   }
45 
46   // Trigger all relevant attribute changes.
47   if (!silent) {
48     if (changes.length) this._pending = true;
49     for (var i = 0, l = changes.length; i < l; i++) {
50       this.trigger('change:' + changes[i], this, current[changes[i]], options);
51     }
52   }
53 
54   // You might be wondering why there's a `while` loop here. Changes can
55   // be recursively nested within `"change"` events.
56   if (changing) return this;
57   if (!silent) {
58     while (this._pending) {
59       this._pending = false;
60       this.trigger('change', this, options);
61     }
62   }
63   this._pending = false;
64   this._changing = false;
65   return this;
66 },
model.set(attributes, [options]) 

這裏第一個參數能夠爲對象或者字符串,最簡單的狀況固然是:

var m = new Model();
m.set('name', '葉小釵');

這樣會開開心心執行個對象就結束,固然也能夠這樣:

m.set({'name': '葉小釵'});

因而,第二個參數的意義就不大了......

{silent: true}的狀況下不會觸發change事件

1 首先,作了簡單的參數檢查,將對象放入了attrs變量

2 其次,執行了一次驗證操做,若是驗證不成立,這裏會直接退出去

3 而後,操做傳入的options(必須是對象)

這裏我有點不太理解:

若是options設置了unset屬性,則將attrs的全部值設置爲undefined

若是options沒有指定silent屬性, 則直接設置changes屬性中當前數據爲已改變狀態

4 進行操做前_previousAttributes會保存改變前的屬性值,這裏有個changing值得注意,他用於檢測一次set觸發時執行才change方法是否結束,沒有結束的話便不能執行

5 遍歷時候若是要設置的屬性與當前值不等,則將該key值壓入changes數組,若是與以前的不等,則在changed對象中賦值(changed記錄了每次set時候改變的鍵值)

若是被相等的話,就將他移除changed對象,若是設置了unset屬性,則須要刪除當前屬性不然就賦值

PS:尼瑪,這裏在幹什麼,我沒搞明白,先放放吧

6 下面若是沒有設置silent,的話會將上面設置的changes中的數據提出來,並觸發相關事件(好比觸發changename事件,可是咱們並未定義)

而後觸發整個model的change事件,這個咱們應該會綁定,最後作一點結尾處理就跳出來了,個人結論就我沒太看懂......後面再看看吧

⑦ unset

刪除屬性

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

看着unset,我忽然好像知道set下面幹了寫什麼事情了......原來他刪除與添加都寫到了一塊兒了

⑧  fetch

聽說是由服務器端獲取數據,而後使用set方法初始化model數據,

 1 fetch: function (options) {
 2   options = options ? _.clone(options) : {};
 3   if (options.parse === void 0) options.parse = true;
 4   var model = this;
 5   var success = options.success;
 6   options.success = function (resp) {
 7     if (!model.set(model.parse(resp, options), options)) return false;
 8     if (success) success(model, resp, options);
 9     model.trigger('sync', model, resp, options);
10   };
11   wrapError(this, options);
12   return this.sync('read', this, options);
13 },
從服務器重置模型狀態。這對模型還沒有填充數據,或者服務器端已有最新狀態的狀況頗有用處。 若是服務器端狀態與當前屬性不一樣,則觸發 "change" 事件。 
選項的散列表參數接受 success 和 error 回調函數, 回調函數中能夠傳入 (model,response) 做爲參數。

這裏具體使用了sync事件由服務器端獲取數據,這個sync實際上封裝了ajax操做,會使用model設置的url,鍵值爲id,因此此處咱們就不關注了

⑨ _validate

1 _validate: function (attrs, options) {
2   if (!options.validate || !this.validate) return true;
3   attrs = _.extend({}, this.attributes, attrs);
4   var error = this.validationError = this.validate(attrs, options) || null;
5   if (!error) return true;
6   this.trigger('invalid', this, error, _.extend(options || {}, { validationError: error }));
7   return false;
8 }

用於驗證屬性的函數,若是爲屬性定義了validate驗證方法,這裏就會被調用,若是調用失敗還會觸發一個事件,Model一塊咱們暫時就結束了,詳細的下面點分析

PS:肚子有點餓,戰鬥力不行了

結語

下次咱們繼續學習集合

相關文章
相關標籤/搜索