在這個大前端時代,各路MV*
框架如雨後春筍搬涌現出來,在infoQ上有一篇
12種JavaScript MVC框架之比較,勝出的是Ember.js
,固然這只是
Gordon L. Hempton的一家之言(Ember.js確實有其強大之處),關於孰強孰弱,你們確定有本身心中的No.1。關於到底有多少種前端MVC
框架,愚安我確定是不知道的,除了上面提到的12種之外,還有不少國內國外的MV*
框架,你們造輪子的熱情也無比高漲,各類demo活躍在
各大技術社區。一時間有句調侃的話——前端MV*哪家強,不服寫個TodoList
,這裏有一個目前主流的MV*框架寫的Todolist的example,叫作
Helping you select an MV* framework你們能夠稍做了解。javascript
咱們知道,MV*框架的優點在於,在結構上其能夠組織良好的結構化、模塊化代碼;在邏輯上,實現如下功能:前端
在諸多的此類框架中,筆者真正在生產環節使用過的聊聊無幾,如,Angular,Backbone,Ember,React,其他的我就不敢多言了。
其中,React.js
使用的是一種叫作virtual dom
的概念,讓我眼前一亮。Angular.js
採用一種預編譯技術,將dom中的元素與Controler的scope
結合起來,而後採起髒輪訓的方式監聽兩者的變化,實現模型數據與dom間的雙向綁定,實時更新。而Ember.js
做爲Ruby on Rails
框架開發團隊的
又一力做,其野心能夠從其類庫的強大看出,單純的Ember.js文件就有足足141kb,並且Ember在視圖層提供了數據綁定的功能,能夠輕鬆實現
頁面數據與模型的數據綁定。java
而今天愚安要說的Backbone.js
相比以上三者,就顯的弱小多了,其文件大小隻有18kb(無依賴未壓縮)。爲了單純的實現一個MVC結構,Backbone
並無像其餘框架那樣,花大力氣加強本身的工具類庫。其在操做dom和ajax上徹底依賴jQuery
,在工具類上徹底依賴underscore
。ajax
正是由於如此,Backbone的結構十分簡潔清晰,易於擴展,因此Backbone得開源社區十分活躍,插件數量在全部MV*
框架中鶴立雞羣。因此,加上註釋也只有1700
餘行的Backbone是一個純淨的MVC框架。瀏覽器
打開Backbone的官網,咱們發現構成Backbone的模塊只有Events
,Model
,Collection
,Router
,History
,Sync
,
View
,noConflict
幾部分組成。前端框架
摺疊後的Backbone關鍵代碼以下:mvc
Backbone.VERSION = '1.1.2';//版本 Backbone.$ = $; //出讓對Backbone命名空間的全部權 Backbone.noConflict = function() { }; //Events事件 var Events = Backbone.Events = { }; _.extend(Backbone, Events); //Model模型 var Model = Backbone.Model = function(attributes, options) { }; _.extend(Model.prototype, Events, { }); //Collection集合 var Collection = Backbone.Collection = function(models, options) { }; _.extend(Collection.prototype, Events, { }); //View視圖 var View = Backbone.View = function(options) { }; _.extend(View.prototype, Events, { }); //sync同步方法 Backbone.sync = function(method, model, options) { }; //貼出只是爲了佐證Backbone的ajax是使用jQuery的ajax,而不是像Angular.js那樣實現本身的$http Backbone.ajax = function() { return Backbone.$.ajax.apply(Backbone.$, arguments); }; //Router路由 var Router = Backbone.Router = function(options) { }; _.extend(Router.prototype, Events, { }); //History瀏覽歷史(window.history) var History = Backbone.History = function() { }; _.extend(History.prototype, Events, { }); Backbone.history = new History; //在underscore基礎上實現的關鍵性的繼承方法,這個也很關鍵 var extend = function(protoProps, staticProps) { }); Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
不得不認可Backbone的代碼真的很是簡潔清晰。app
經過上面的簡單摺疊代碼,咱們能夠看出,不論是Model
,Collection
,Router
,History
,History
,甚至是Backbone自己,
都或經過原型鏈或直接繼承了Backboe.Events
。這也是在Backbone的代碼編寫順序上Backboe.Events
會放在最前面的緣由。框架
那麼Backboe.Events
到底作了些什麼呢?仍是貼代碼最有說服力:dom
var Events = Backbone.Events = { //綁定一個事件到`callback`回調函數上。經過 `"all"`能夠綁定這個回調函數到全部事件上 on: function(name, callback, context) { }, //綁定一個僅會被觸發一次的事件。在這個事件的回調函數被調用一次以後,這個回調函數將被移除 once: function(name, callback, context) { }, //移除一個或多個的事件回調 off: function(name, callback, context) { }, //觸發一個或多個事件,調用對應的回調函數。 trigger: function(name) { }, //`on`和`once`的控制反轉版本。告訴當前對象去監聽另外一個對象的事件 listenTo: function(obj, name, callback) { }, listenToOnce: function(obj, name, callback) { }, //告訴當前對象中止對指定對象的指定事件的監聽,或中止全部監聽 stopListening: function(obj, name, callback) { } };
基於這樣的一個Events對象的實現,Backbone能夠輕鬆實現了不少功能,如在Model.set(key,value)
時觸發一個change
事件,視圖層在撲捉到
這個事件的時候,對dom作出相應的更新,這樣就實現了Model層到View層的綁定。例如:
var View = Backbone.View.extend({ initialize:function(){ this.listenTo(this.model,'change:name',this.onNameChange); }, onNameChange:function(){ this.$('.name').text(this.model.get('name')); } template: '<span class="name"></span>' }); var m = new Backbone.Model({name:'Jack'}); var v = new View({model:m}); m.set('name','John');
那麼,這裏的set爲何會觸發change事件呢?具體實現我仍是貼一下源碼和本身的中文註釋:
Backbone.Model.prototype.set = function (key, val, options) { var attr, attrs, unset, changes, silent, changing, prev, current; if (key == null) return this; //格式化參數 if (typeof key === 'object') { attrs = key; options = val; } else { (attrs = {})[key] = val; } options || (options = {}); //執行當前對象的驗證方法 if (!this._validate(attrs, options)) return false; //提取屬性和可選項 unset = options.unset; silent = options.silent; changes = []; changing = this._changing; this._changing = true; //標記當前Model是否改變,並記錄改變的屬性及其變化先後的值 if (!changing) { this._previousAttributes = _.clone(this.attributes); this.changed = {}; } current = this.attributes, prev = this._previousAttributes; //若改變的屬性爲id,則同時改變當前對象的id if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; //遍歷`set`的屬性,更新或刪除對應屬性的當前值 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; } //若非沉默更新(傳參時options.silent=true),觸發change:attr事件 //attr爲各個對應被set的屬性的key,並傳當前值到回調函數 if (!silent) { if (changes.length) this._pending = options; for (var i = 0, l = changes.length; i < l; i++) { this.trigger('change:' + changes[i], this, current[changes[i]], options); } } //change能夠遞歸嵌套到change事件中 if (changing) return this; if (!silent) { while (this._pending) { options = this._pending; this._pending = false; this.trigger('change', this, options); } } this._pending = false; this._changing = false; return this; }
因此若想,監聽到Model的屬性變化,改變Model的屬性值時,必須採用Model.set()
方法,而不能簡單的使用Model.attributes[key] = value
。
其實這個是一個兼容的作法,咱們知道Backbone對低版本瀏覽器的支持很是好,若是不考慮這些的話,徹底可使用更高級的API
Object.observe(this.attributes, function(changes){ if (!silent) { if (changes.length) this._pending = options; for (var i = 0, l = changes.length; i < l; i++) { this.trigger('change:' + changes[i].name, this, current[changes[i].name], options); } }.bind(this));
固然,這只是愚安個人一點意淫,沒有什麼實際意義。
實際上,Backbone內部事件除了change
之外還有不少,這裏簡單列舉一下:
上面咱們已經知道基於強大的Backbone.Events
,咱們能夠輕鬆的實現model到view的綁定,反之呢?
Backbone沒有相似Angular.js的預編譯機制,也沒有View-Model
的概念,從View到Model的綁定依賴於原生DOM事件的監聽,完整雙向綁定如:
var View = Backbone.View.extend({ initialize:function(){ this.listenTo(this.model,'change:name',this.onNameChange); }, onNameChange:function(){ this.$('.name').text(this.model.get('name')); } template: '<input type="text" class="name-input"><span class="name"></span>', events: {'input .name-input': '_changeName'}, _changeName: function(e){ var value = e.currentTarget.value; this.model.set('name', value); return false; } }); var m = new Backbone.Model({name:'Jack'}); var v = new View({model:m}); m.set('name','John');
須要注意的是View層的events字典,其實就是DOM事件,而不是Backbone.Events
。並且,純淨的Backbone這裏用的是jQuery的jQuery
的on
方法
進行綁定的。
好的,愚安又貼了不少源碼,和一些本身對文檔的不成翻譯,有沒有乾貨,見仁見智了。另外,本人是很是推薦剛接觸前端框架的童鞋,以Backbone
作爲開始的。就像學習PHP的MVC框架,我很是推薦以codeigniter
做爲開始的,沒有強大的封裝,但有着最基本純淨的MVC思想。
注:本文是以Backbone的1.1.2版本爲基礎的
引用: