大半年前我就說過,MVVM是前端究極的解決方案,因此之后我大多數時間都在折騰avalon,成立了專門的QQ群與感興趣的一起討論。感謝第一批吃螃蟹的人,avalon發展得很快,GITHUB上的貢獻人數達到8人,issues二百多個主題,各種組件也不斷完善。時至今日,我可以自豪地說許多靜待1.0的看客說,它終於來了!
下面是avalon的生態系統:

avalon的核心是avalon.js或avalon.mobile.js(移動端版本,針對手機與平板)。其中avalon.js兼容到IE6,滿足中國的特殊國情。avalon.mobile.js是支持移動端,IE10起支持,它使用了大量高級API,性能遠超avalon.js,並添加了觸屏事件的支持。
然后是三柱臣:mmRouter, mmAnimate, mmRequest,單頁應用最常用的三個組件。
接着是拖放,事件代理,組件鍵,驗證框架,高級定位機制,組合鍵等亂七八糟的,用於錦上添花的
最后是UI庫,比較簡單的控件都做出來,重頭的樹,GRID,多級菜單正在規划中……
說一下我的初衷啊。我最早是做企業級應用,大量的表單,大量的列表,迭代頻繁,雖然JQ已經出來了,但業務邏輯與DOM顯示邏輯混淆在一起,維護起來非常不爽。於是我開發了ejs這個前端模板。前端模板在當前還是一個很新的概念,因此我當時發布在博客園,許多人不解。但比起在JS文件里進行鎖碎的字符串拼接,它就是易讀易維護。QQ空間,淘寶那時也已經使用前端模板了。那是09年的事。之后,前端模板就像雨后春筍地不斷涌現,性能越來越高。
轉輾到盛大創新院后,做圈樂這個視頻項目的瀑布流,與大牛們共事,用的是qwrap,接觸了許多新概念。我的框架由dom Framework更名mass Framework,大量精力耗在選擇器引擎與模塊加載器的開發上。那時requirejs也是剛剛起步, Sizzle也很粗糙,因此這兩個領域都有大量同類型產品。我大量讀庫,實力蒸蒸日上。創新院大裁員,我避風到SDO做盛大通行證,我深切地體會到現行開發模式的掣肘之處。我在創新院還可以用前端模板搞定渲染問題,但這是一對一的。盛大通行證一個邏輯面向不同的視圖,要求務業邏輯與DOM渲染邏輯徹底分離,要不沒法進行下去。當時我只有求助於JAVA的設計模式,各種解耦。像后端spring,早已想到這一點,因此才有IoC容器這樣的東西出來。在處理渲染方面,它們有龐大的標簽庫,這是面向組件開發的拓路者。為了更好的控制標簽的行為,微軟從MVC的基礎上發展起MVVM。MVVM用盡了23種設計模式,讓我們輕松地操作一個叫VM的特殊數據源就能自動同步視圖,這種機制被稱之為綁定。綁定在微軟的體系中分為三種one way, one time, two way。微軟出身的Steve Sanderson搞出了第一個前端MVVM框架knockout。當時,09年,別說MVVM,MVC在前端也是新事物。前端MVC的風頭從jQueryMVC轉到backbone。我的注意力也停留在backbone身上。backbone有着后端JAVA MVC框架的優缺點,分類極細,責任明晰,但這也意味着繁鎖,定義了許多類,但好像又什么也沒干。其中這當中的類可以由框架自動生成。
模塊加載器開發了十多個版本號, 我着手開發UI庫了。這時愈感JQ的不便。UI是一種界面,很早以前人們就發現,干這活是聲明式語言的強項。我們平時熟悉的javascript, C, C++, java則擅長做邏輯。於是才有前端模板這東西,將數據源往模板一套,界面就出來的。但之前的模板都屬於字符串模板,無法對生成的元素節點進行后繼操作。不過,據元素節點的innerHTML的能耐來看,它也不執行里面的script腳本。jquery的html可以打破這桎梏,其實也基於eval處理的。最終,knockout還是走入我的視野。很大一個原因是我一直活躍於博客園,博客園屬於微軟系,微軟這個東西雖然難用一點,但在當時也算是至寶!
knockout是一個偉大的框架,一直很低調。作者是后端的人,很JS用得爐火純青,大量的閉包,加之方法名,變量名是找C#那一套,名字巨長,搞得很難讀懂它的源碼。knockout的思想很簡單,把用戶的操作分成兩部分,賦值與取值。賦值時,它就想辦法同步視圖。取值時,它就試圖取得各種操作的依賴關系。有了依賴關系,就可以構成觀察者模式,讓視圖同步成為可能。為了達到這目的,必須讓框架知道當前進行的是什么操作,操作的作用方是誰。由於當時javascript訪問器屬性還鮮為人知,在舊式IE也有兼容難題。knockout取采的策略是將所有屬性都轉換為函數,根據函數有沒有傳參就可以辨識用戶的行為了。對於數組,它是重寫所有原生方法。avalon0.1-0.3就是追隨knockout的步伐,逐漸了解它的其他概念與實現原理。比如說計算屬性,取值屬性,視圖刷新函數,各種常用的綁定。
我之前談如何寫框架,就提到三個原則:復雜即錯誤,數據結構優於算法,出奇制勝。knockout的源碼太復雜,導致想參與的人參與不進來。為了降低上手難度,你無論是從接口到實現,一定要條理清晰,不能到處是黑魔法與飛線方案。雖然avalon的源碼只有一個文件,里面的條理是非常清晰的,並且刻意使用一些特殊的注釋來隔開各個功能區。API盡可能少,許多重要的接口都不暴露出來,以后就可以隨意折騰,不影響用戶使用了。在厘清knockout的實現機理后,我就是由單純的做輪子,轉為發明新輪子。首先,屬性轉換為函數這一用法必須去掉,太扭曲了。ember的做法是使用兩個上帝set, get方法來接管一切取值賦值操作,比knouckout好多了,但依然不直觀。angular是直接對控制器等函數取toString進行編譯,用法簡單了,但實現難度非常高,加之它其余設計把這易用性泯滅了。於是我轉來轉去,還是求助於訪問器屬性解決了。這過程不是三言兩語說得清的,但從結果來看,就是Object.defineProperties與VBScript類的Set, Get, Let語法。用戶傳入一個對象,然后我轉出另一個跟它非常相似,好像只是添加了幾個屬性的對象,然后用戶對它的屬性進行操作,它就能同步視圖,相當fantasy、 magic!易用性爆表了!
其次是綁定屬性的設計, knockout是使用data-bind對應一個沒有兩端花括號的JSON對象字符串,看似規整划一,但這意味着內部要實現一個parser來轉換。后起之秀的angular則友好多了,但最終我是參照了rivetsjs。緣由它的綁定風格能提供更多信息,更易分解,用戶也很易理解。這樣我就不用耗時實現一個龐大易出錯的parser了。編碼不同於建築,BUG與規模是成比例增長,控制代碼量是avalon在編寫過程的一個重要指標,至少它還沒有超過4000行。
最后根據用戶的反饋,不斷增加新功能,在增加新功能時不斷引入BUG,然后修BUG,改好了,發現性能下降,於是提高性能。性能提高了,用戶又提出新功能,然后……周而復始,不斷與需求與BUG作斗爭。這就像一個普通的產品的開發流程。不同的是,這個產品完全是屬性於你,你有殺生大權,你能完全享受用戶對你的贊譽。但反過來,其實我還是得感謝那些敢吃螃蟹的人,那些提BUG的人,那些pull request的人。正因為,這項目比mass Framework發展得順利多了。它與最初的第一版幾乎沒有一行相同的代碼,盡管我盡量避免API的改動,API最后也有改動了,變得更加精巧。
0.4-0.7是脫胎換骨時期,它一直留在mass Framework項目的內部。0.72獨立出來,可以獨擋一面了。這時不斷改進綁定的實現,一直到0.94才穩定下來。難點在於ms-each, ms-repeat, ms-with, ms-widget(ms-ui), ms-if這五種API。它們的特點都是有節點插入操作。有的是插入了移除,移除了插入, 有的是循環生成一堆相同節點,有的是按照一個模板生成一個控件。由於綁定都有生命周期的概念,它的實現方式直接與它是否在DOM樹掛鈎,但如何是框架自己臨時移出DOM樹呢,於是就引發一系列問題了。幸好最終都解決了。這基本是靠時間磨出來的,今天想不出來,睡一覺明天就想通一點點,后天又解決一點點……
facebook實現一個react的MVVM框架,炒作一個叫反應式編程的概念。其實它的核心就是knockout的弱化版。對我看來,用數學的自變量,因變量來表達它們之間的關系更適合。其中綁定屬性,監控數組的方法就是自變量,這是由用戶直接操作的。計算屬性,求值函數,視圖刷新函數就是因變量,由下層的綁定屬性,數組方法所驅動。0.99起,開始大量性能優化,把求值函數,視圖刷新函數都共享了。但這些東西,用戶根本不需要知道,說不定某天發現更高效的實現方式,它們就不存在了。所有要注意之處,在《入門教程》已經標明。要深入可以看《最佳實踐》。再者看源碼,這些概念只是輔助你看當前版本的源碼。想成為高手,還是從源碼着手。只是想快速完成任務,看我的《入門教程》就夠了。
感謝這個時代,正因為有了IE6才有我們前端,正因為有前端才有我們這些民科外行沾光IT的福利。
迷你MVVM框架在github的倉庫https://github.com/RubyLouvre/avalon
官網地址http://rubylouvre.github.io/mvvm/
有關avalon的最佳實踐或注意點請看這里, 這個我每次發布新版本都可能在這里加東西
1.0的相關改進
- 優化executeBindings, 插值表達式形式的文本綁定不需要removeAttribute
- 優化scanText, 它生成的綁定屬性不需要element元素
- fix duplex 綁定, 從左到右檢測匹配的vmodel
- duplex 綁定添加表單檢證的鈎子
- 優化監控數組的sort, reverse方法
- avalon.fn.css支持設置多個屬性,優化舊式IE下的取寬高
- 重構ms-widget, 過濾中間生成的VM, 支持在配置對象里指定ID
- 重構fixEvent
- fix ms-class BUG
- 優化nextTick
- 為性能考量,弱化avalon.Array.ensure
- 移除data.remove屬性,通過是否存在求值函數evaluator來移除綁定屬性
- 共享所有視圖刷新函數
- clearChild 更名為 clearHTML
- IE67下添加getAttributes方法,直接使用正則抽取綁定屬性
朋友們用avalon做的東西
- 移動應用:讀酷
- chrome插件:飯否客戶端
- 為知筆記
- 金山WPS office 會員中心
- 企業級應用:超博CRM客戶關系管理系統(帳號:crm_ceo 密碼:nncb_ceo)
- uliweb Python框架與avalon的組合示例
- avalon+jQuery實現域名注冊查詢
- 路由器示例
- 邊鋒活動頁
- 記者考試題
未來avalon的發展方向就是三大重型UI組件:樹,GRID,多級菜單。望大家也多多貢獻,一起完善這個生態圈。