Backbone.js是前端的MVC框架,它經過提供模型Models、集合Collection、視圖Veiew賦予了Web應用程序分層結構。從源碼中能夠知道,Backbone主要分了如下幾個模塊:javascript
(function(root) { Backbone.Events //自定義事件機制 Backbone.Model //模型 Backbone.Collection //模型集合 Backbone.Router //路由配置器 Backbone.View //視圖 Backbone.sync //向服務器同步數據方法 })(this)
本身主要閱讀了Events、Model、Collection、sync這幾個模塊,因此對這幾個模塊進行介紹。前端
//Backbone的事件對象 var Events = Backbone.Events = { //事件訂閱函數 //name:事件名 //callback:事件回調函數對象 //context:事件上下文 on: function(name, callback, context) { //eventsApi的做用請看下方eventsApi方法的註釋 if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this; //_events對象用於存儲各個事件的回調函數對象列表 //_events對象中的屬性名爲事件名稱,而屬性值則爲一個保護函數對象的對象數組 this._events || (this._events = {}); var events = this._events[name] || (this._events[name] = []); //將包含回調函數的對象添加到指定事件的回調函數列表,即註冊事件 events.push({callback: callback, context: context, ctx: context || this}); return this; }, //取消事件訂閱 off: function(name, callback, context) { var retain, ev, events, names, i, l, j, k; if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; //當name,callback,context都沒指定時,取消訂閱全部事件 if (!name && !callback && !context) { this._events = void 0; return this; } //未指定name時,則取全部的事件name names = name ? [name] : _.keys(this._events); //對每一個包含回調函數的對象進行篩選,不符合指定參數條件的進行保留 for (i = 0, l = names.length; i < l; i++) { name = names[i]; if (events = this._events[name]) { this._events[name] = retain = []; if (callback || context) { for (j = 0, k = events.length; j < k; j++) { ev = events[j]; if ((callback && callback !== ev.callback && callback !== ev.callback._callback) || (context && context !== ev.context)) { //保留 retain.push(ev); } } } if (!retain.length) delete this._events[name]; } } return this; }, //觸發事件 //name: 觸發的事件名 trigger: function(name) { if (!this._events) return this; //參數列表,不包含name var args = slice.call(arguments, 1); if (!eventsApi(this, 'trigger', name, args)) return this; var events = this._events[name]; var allEvents = this._events.all; //觸發事件,即調用相應的回調函數 if (events) triggerEvents(events, args); //任何事件觸發時,all事件都會被觸發 if (allEvents) triggerEvents(allEvents, arguments); return this; } }; //該函數的主要做用: //當指定事件名爲object對象時,將object對象中key做爲事件名 //將obejct中的value做爲回調函數對象,而後遞歸調用on、off、trigger //當指定的事件名爲string,但包含空格時,將string按空格切割,再依次遞歸調用 //該函數須要對應的看它是如何被調用的,直接看是比較難明白的 //當時我就看了很久沒明白,函數名取爲eventsApi對我一點幫助也沒用。。 var eventsApi = function(obj, action, name, rest) { if (!name) return true; //當指定的事件名爲object時 if (typeof name === 'object') { for (var key in name) { obj[action].apply(obj, [key, name[key]].concat(rest)); } return false; } // 當指定的事件名包含空格時 //eventSplitter = /\s+/; if (eventSplitter.test(name)) { var names = name.split(eventSplitter); for (var i = 0, l = names.length; i < l; i++) { obj[action].apply(obj, [names[i]].concat(rest)); } return false; } return true; }; //調用事件回調函數的函數 //多是爲了性能問題,才使用了switch,而不是直接使用default中的代碼 //但我不太明白,這樣爲何會提升效率,但願高人解答 var triggerEvents = function(events, args) { var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; switch (args.length) { case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); } };
上面的代碼就爲Events的核心部分,咱們能夠從中得知:java
在Backbone中Model是一個構造函數ajax
var Model = Backbone.Model = function(attributes, options) { var attrs = attributes || {}; options || (options = {}); this.cid = _.uniqueId('c'); //存儲相應model所應具備的屬性 this.attributes = {}; if (options.collection) this.collection = options.collection; //解析attrs,默認直接返回attrs if (options.parse) attrs = this.parse(attrs, options) || {}; attrs = _.defaults({}, attrs, _.result(this, 'defaults')); //設置model相應的屬性 this.set(attrs, options); this.changed = {}; //用於初始化model的函數,須要使用者本身指定 this.initialize.apply(this, arguments); };
隨後咱們能夠看到Model函數的原型擴展,須要注意的是Events對象被拓展到了Model的原型中,這樣model對象也就有了事件機制:sql
//將Events和指定對象擴展至Model的原型中 _.extend(Model.prototype, Events, { //該方法用於向服務端同步數據(增、刪、改) //該方法默認調用的是Backbone.sync方法(ajax) //咱們能夠經過替換Backbone.sync來使用咱們本身的sync方法,好比mongodb,這樣backbone也能 //用於Node.js後端 sync: function() { return Backbone.sync.apply(this, arguments); }, //獲取model的屬性值 get: function(attr) { return this.attributes[attr]; }, //設置model的屬性,當屬性值發生變化時,觸發'change'事件 //該方法爲Model的核心 set: function(key, val, options) { var attr, attrs, unset, changes, silent, changing, prev, current; if (key == null) return this; //讓set方法能夠這樣調用set({key: value ....}, options); if (typeof key === 'object') { attrs = key; options = val; } else { (attrs = {})[key] = val; } options || (options = {}); //驗證設置的屬性是否符合要求,_validate方法內部將會調用validate方法 //validate方法須要model使用者本身指定 //當設置的屬性不符合要求時,直接返回false if (!this._validate(attrs, options)) return false; //表示應當刪除屬性,而不是設置屬性 unset = options.unset; //當silent爲true時,不觸發change事件 silent = options.silent; //變化的屬性列表 changes = []; //表示是否在變化中 //這裏我仍是有點疑惑 changing = this._changing; this._changing = true; if (!changing) { //表示變化前的屬性值 this._previousAttributes = _.clone(this.attributes); //存儲改變了的屬性和其屬性值 this.changed = {}; } current = this.attributes, prev = this._previousAttributes; if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; //設置model屬性值 for (attr in attrs) { val = attrs[attr]; //當設置的值與當前的model對象的屬性值不一樣時,將要設置的屬性的key加入changes列表中 if (!_.isEqual(current[attr], val)) changes.push(attr); //this.changed存儲改變了的屬性和其屬性值 if (!_.isEqual(prev[attr], val)) { this.changed[attr] = val; } else { delete this.changed[attr]; } unset ? delete current[attr] : current[attr] = val; } //觸發change事件 if (!silent) { if (changes.length) this._pending = options; //觸發'change:變動的屬性名'事件 for (var i = 0, l = changes.length; i < l; i++) { this.trigger('change:' + changes[i], this, current[changes[i]], options); } } if (changing) return this; if (!silent) { //觸發'change'事件,這裏使用while,是由於change事件也有可能會調用set方法 //因此須要遞歸的調用 while (this._pending) { options = this._pending; this._pending = false; this.trigger('change', this, options); } } this._pending = false; this._changing = false; return this; } });
在這裏我列出來的3個方法:mongodb
首先是sync方法,它用於與服務器端同步,model中的create、update、fetch、destory方法都是經過調用它來跟服務端 交付,backbone默認實現的sync就是經過ajax與服務端交付,因此我認爲,若是咱們將sync替換爲直接與sqlite、mongodb交 付,這樣backbone的Model也能用於服務器端了。json
第二個是get方法:這個方法很簡單,獲取model的屬性值(model的屬性值是存在於model.attributes對象中)c#
第三個是set方法:該方法是model的核心,它用於設置model的屬性值,首先調用this.validate方法(使用者需指定)驗證屬性 值是否符合業務要求,以後對屬性值一一設置,對於改變了的屬性值觸發'change:key'事件(沒有指定silent)。最後再觸發change事 件。後端
set: function(models, options) { //other code..... var add = options.add, merge = options.merge, remove = options.remove; var order = !sortable && add && remove ? [] : false; //迭代參數models,對於其中每一個model進行相應的操做 for (i = 0, l = models.length; i < l; i++) { attrs = models[i] || {}; if (attrs instanceof Model) { id = model = attrs; } else { id = attrs[targetModel.prototype.idAttribute || 'id']; } //若是集合中已經存在該對象,則是進行刪除或者修改操做 if (existing = this.get(id)) { //進行刪除操做,記錄下須要刪除的對象 if (remove) modelMap[existing.cid] = true; //進行修改操做 if (merge) { attrs = attrs === model ? model.attributes : attrs; if (options.parse) attrs = existing.parse(attrs, options); existing.set(attrs, options); if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true; } models[i] = existing; //不然對集合進行添加操做,記錄下應該被添加的對象 } else if (add) { model = models[i] = this._prepareModel(attrs, options); if (!model) continue; toAdd.push(model); this._addReference(model, options); } if (order) order.push(existing || model); } //根據以前的記錄下的應刪除的對象,刪除集合中相應的對象 if (remove) { for (i = 0, l = this.length; i < l; ++i) { if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model); } //刪除集合中相應的對象,並觸發remove事件 if (toRemove.length) this.remove(toRemove, options); } //進行添加操做 if (toAdd.length || (order && order.length)) { if (sortable) sort = true; this.length += toAdd.length; //添加到指定位置,默認添加到末尾 if (at != null) { for (i = 0, l = toAdd.length; i < l; i++) { this.models.splice(at + i, 0, toAdd[i]); } //說實話,我不太明白這段代碼 } else { if (order) this.models.length = 0; var orderedModels = order || toAdd; for (i = 0, l = orderedModels.length; i < l; i++) { this.models.push(orderedModels[i]); } } } //當進行了添加或修改操做,而且能夠排序時,則對集合進行排序 if (sort) this.sort({silent: true}); if (!options.silent) { for (i = 0, l = toAdd.length; i < l; i++) { //觸發add事件 (model = toAdd[i]).trigger('add', model, this, options); } //觸發排序事件 if (sort || (order && order.length)) this.trigger('sort', this, options); } return singular ? models[0] : models; }
我列出了set方法中的大部分代碼,它根據指定的參數進行添加 刪除 修改操做,並進行排序。而我我的感受這樣不是太好,由於一個方法作了太多的事情,有點array.splice的味道,使得整個方法的代碼十分冗長,也變得不易理解。。。我比較菜,看這個看了很久。。設計模式
Collection雖然提供了set,但它仍是提供了add(內部調用set)、reset(內部調用set)、remove方法。Collection還跟Model同樣提供了sync方法,用於和服務端同步數據。
最後,Collection還提供了underscore.js庫對集合的操做方法,它們都是調用underscore庫實現的方法。
sync是Backbone用於同步服務端數據的方法,它的默認實現:
Backbone.sync = function(method, model, options) { var type = methodMap[method]; //some init code.... //默認使用json格式 var params = {type: type, dataType: 'json'}; if (!options.url) { params.url = _.result(model, 'url') || urlError(); } //將請求的數據類型設置爲json //將對象格式化爲json數據 if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { params.contentType = 'application/json'; params.data = JSON.stringify(options.attrs || model.toJSON(options)); } //some for old server code.... //and some for ie8 code // ajax請求 var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); model.trigger('request', model, xhr, options); return xhr; }
上面就是它的主要代碼,我將一部分有關兼容性的代碼給移除了。
由於本身接觸js不久,就想去看看一個優秀的js項目是如何寫的,因此就選擇了backbone這個相對比較輕量級的框架。固然,由於本身水平有 限,加上寫的js代碼也很少,不能很好領悟backbone的設計思想,也不能很好的指出backbone有什麼不足的地方,但我仍是有一些收穫:
1.學到了js中的一些使用技巧,好比使用||操做符 model || model = {},還有如何利用參數在代碼中實現相似重載的行爲(js函數自己沒有重載)
2.對this變量的綁定有了更好的理解
3.相對於c#而言,js是一門弱類型的動態語言,因此對一個對象的擴展要靈活多
4.在c#中,若是我須要去提升模塊的可擴展性,我可能要利用接口利用多態去實現,但js則就輕鬆的多,我只需暴露一個屬性接口便可,由於我能夠輕 鬆的替換他,就像Backbone.sync同樣,但帶來的缺點就是若是你的sync方法並不符合設計,你只會在運行時發現錯誤,而不是編譯時