隨着前端應用日趨複雜,現在如angular,vue的mvvm框架,基於virtual dom的react等前端庫基本成爲了各個公司的首選。而以當初最流行的頭號大哥backbone爲表明的mvc庫基本退出了歷史舞臺。前端
在現現在人人都說mvvm/react多好,backbone多差的時代。筆者看別人文章,看的時候老是感受好像有點道理,看完以後如耳邊風通常左耳朵進,右耳朵出。vue
so,痛定思痛以後,筆者定了個小目標,實現了份簡易版的backbone庫。以設計,實現的角度來對比其它類型庫的差別。react
ok,廢話很少說,上正菜。git
MVC即將前端應用抽象爲Model,View,Control三大模塊。View爲用戶視圖,經過瀏覽器事件接受用戶輸入。Model爲數據模型,他能夠隨時和後端同步數據。Control則是具體實現View派發的事件,計算並改變Model的數據。es6
UI能夠被抽象爲模版+數據,隨着用戶不斷的觸發瀏覽器提供的各類事件,交互不斷的進行,Control接受了View指令改變着Model的數據,而View則隨着Model的改變作出響應,最終展示在用戶面前。github
本篇文章的思路來自於backbone,並屏棄了耦合的後端操做。早期MVC並無對Control作嚴格的劃分,也許是數據的改變計算並不那麼複雜,因此Control功能在View的事件內完成了,也就是說View模塊裏面耦合了Control的功能。web
但近幾年flux的action,store的出現,View調用action,具體數據變化計算則在store內部實現,也算是把Control功能從View內部抽象出來了吧。後端
爲對象提供對事件的處理和回調,內部實現了觀察者(訂閱者)模式,如view訂閱了model的變化,model變化以後則通知view。數組
on函數經過event名,在object上綁定callback函數,將回調函數存儲在數組裏。瀏覽器
off函數移除在object上綁定的callback函數
經過event名移除指定callback。如object.off("change", onChange)
經過event名移除全部callback。如object.off("change")
移除全部指定callback。如object.off(null, onChange);
移除全部指定context的callback。如object.off(null, null, context);
清空object全部的callback。如object.off()
trigger函數經過event名,找到object對應的數組,並觸發全部數組內回調函數。
其全部方法應該支持相似on(name,callback),on('name1 name2 name3',callback), on({name1:callback1,name2:callback2})
這時候則能夠抽象內部公用方法。經過遞歸的方式,on({name1:callback1,name2:callback2})類型的和on('name1 name2 name3',callback)類型,最終轉化爲最基本的on(name,callback)類型。核心代碼以下:
this.eventsApi = function (iteratee, name, callback, context) { let event; if (name && typeof name === 'object') { Object.keys(name).forEach(key=> { event = this.eventsApi(key, name[key], context); }) } else if (SEPARATE.test(name)) { var keys = name.split(SEPARATE); keys.forEach(key=> { event = iteratee.call(this,key, name[key], context); }); } else { event = iteratee.call(this,name, callback, context); } return event; };
無狀態,實例化的時候能夠對應多個model實例,並以觀察者的身份觀察這些model的變化,經過這些model數據,加上指定的模版渲染dom,展現UI。
銷燬的時候註銷掉全部model的觀察,取消與相關model之間的關聯。
實例化的時候經過事件委託註冊瀏覽器事件
_ensureElement,確保View有一層dom包裹,若是this.el這個dom不存在,則經過id,className,tagName建立一個dom並賦值於this.el。
listenTo,將model與view實例關聯起來,並收集關聯model,存儲於listenTo數組內,內部實現則是調用model的on函數
stopListening,view銷燬前調用,經過listenTo數組找到關聯model,並取消view與這些model之間的觀察者關係。
$,將dom的查找定位在 this.$el下
delegateEvents,事件委託,以{'click #toggle-all': 'choose'}爲例,爲在this.el子節點的id等於toggle-all的dom註冊click事件choose函數。核心代碼以下:
delegateEvents: function (events) {
var $el = this.$el; Object.keys(events).forEach(item=> { var arr = item.split(' '); if (arr.length === 2) { var event = arr[0]; var dom = arr[1]; $el.on(event + '.delegateEvents' + this.$id, dom, this[events[item]].bind(this)); } }) },
undelegateEvents,註銷掉經過delegateEvents註冊的dom事件
Model在backbone裏被抽象爲object類型的Model和array類型的Collection
承載着應用的狀態,能夠隨時和後端保持同步。
內部實現了對數據變化的監聽,一旦發生變化則通知觀察者View發生變化。
監聽數據的變化,對model的修改,刪除以後調用對應的trigger函數,通知訂閱了model變化的view。
set函數,改變model數據,並觸發change事件
set: function (obj) { this._changing = true; this.changed = obj; this._previousAttributes = Object.assign({}, this.attributes); this.attributes = Object.assign({}, this.attributes, obj); const keys = []; Object.keys(obj).forEach(key=> { keys.push(key); this.trigger('change:' + key, this); }, this); if (keys.length > 0) { this.trigger('change', this); } this._changing = false; },
destroy函數觸發destroy事件
destroy: function () { this.stopListening(); this.trigger('destroy', this); },
提供數組類型models的push,unshift,pop,shift,remove,reset等功能。push,unshift實際調用add函數,pop,shift實際調用remove函數。
add函數支持任意索引插入指定數組,觸發add事件。核心的代碼以下:
export const splice = (array, insert, at)=> { at = Math.max(0, Math.min(array.length, at)); let len = insert.length; let tail = []; for (let i = at; i < array.length; i++) { tail.push(array[i]); } for (let i = 0; i < tail.length; i++) { array[i + at + len] = tail[i]; } for (let i = 0; i < len; i++) { array[i + at] = insert[i]; } return array; };
remove函數支持刪除指定model,觸發update事件。
_addReference,調用add方法新增model時,經過觀察者模式增長該model與collection之間的關聯,model的變化通知collection。核心代碼以下:
_addReference: function (model) { model.on('all',this._onModelEvent,this); }
_removeReference,調用remove,reset移除model時,取消該model與collection關聯。核心代碼以下:
_removeReference: function(model) { if (this === model.collection) delete model.collection; model.off('all', this._onModelEvent, this); }
生產環境下須要在保留原生View,Model類的功能狀況下作一些業務拓展,這時候須要用到類的繼承。
雖然es6支持extend繼承,但這邊我仍是手寫了一份。思路則是返回一個構造函數,該函數的原型爲新的實例對象props,而props的原型對象則是父函數的原型(有點拗口,本身看代碼理解)。
核心代碼以下:
export const extend = function (props) { var parent = this; var child = function () { parent.apply(this, arguments); }; child.prototype = Object.assign(Object.create(parent.prototype), props, { constructor: child }); return child; };
整篇文章基本是圍繞着以下2點
view-model,collection-model的觀察者模式的實現展開,期間view,model的銷燬則取消與之有關聯對象的關係,如view銷燬時,註銷掉與之關聯的model的回調函數。
監聽數據變化,並通知觀察者做出響應,如model變化後觸發trigger('change')
好了,文章草草寫到這了,多謝各位看官,以上也是純我的觀點,有問題歡迎各位web前端mvc設計指教。