avalon是國內最強大的MVVM框架,沒有之一,雖然淘寶KISSY團隊也搞了兩個MVVM框架,但都無疾而終。其他的MVVM框架都沒幾個。也只有外國人與像我這樣閑的架構師才有時間鑽研這東西。我很早之前就預言,MVVM是前端的終極解決方案。我之前在盛大無線做盛大通行證就深有體會,一個業務邏輯對應十來個不同的界面,分層架構是必不可少的。因此雙向綁定作為解葯,結合很早就流行的MVC框架,衍生出MVVM這神器。
但這么牛叉的東西,為什么現在才流行起來呢?要不是谷歌振臂高呼,這個一直縮在flex, wps世界的MVVM就根本不可能在前端冒頭。要知道,微軟也搞了knockout, winjs等MVVM框架。原因之一,這東西非常難做。早些年,JS沒有后端語言那種監聽對象屬性變動的高級特性,屬性的變動如何同步視圖,這需要非常巧妙的依賴收集機制,綁定(或叫指令)需要把一個編譯器把VM的屬性分離出來,這也不是一般人能搞出來的。加之,前端本來就沒有幾個是專科出來的人,都是半路出家的,寫編譯器與玩轉jQuery不是同一個概念。knockout沒有依賴什么高級特性,但用戶體驗太差,因此也沒有流行起來。angular的缺點與優點也非常明顯,幸好google比較大牌。
但牆的內外畢竟是兩個世界,這也是avalon存在的理由。avalon最早是模擬knockout為了解決盛大通行證這樣多界面的東東而研發出來的。為了不像knockout那樣別扭,它是使用IE8的Object.defineProperty劫持用戶對數據的操作,從而實現對視圖的同步。這種設計也比后來的angular的臟檢測優秀許多。但Object.defineProperty是缺陷的,兼容性不好,早期的標准瀏覽器需要用_defineGetter_, _defineSetter_, IE6,IE7,IE8(因為IE8的Object.defineProperty也是有缺陷的)需要用VBScript,為了弄懂VBScript,我還特意入了一本90塊錢的書。但這不是全部,兼容IE6是非常痛苦的,需要寫大量額外的代碼,因此存在avalon.js與avalon.modern.js兩個版本。
avalon.js的兼容性是最好的,支持IE6及非常老的標准瀏覽器。這里的標准瀏覽器特指W3C陣營中的safari, opera, firefox, chrome。avalon.js在最近幾個月的升級中,還對IE的VML,W3C方的SVG進行各種兼容處理。要知道,就是瀏覽器自身的API,也有各種問題。從這個層面來看,avalon.js的兼容能力比jQuery強多了。並且它可以與jQuery和平共處,享用其強大的AJAX,動畫, Deferred等功能。加之,avalon現在擁有全職的團隊幫它打造UI庫(OniUI),大家就不用自己去拼湊各種插件了。
<!DOCTYPE html> <html> <head> <title>TODO supply a title</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> <script src="avalon.js"> </script> </head> <body> <div>TODO write content</div> </body> </html>
上面就是一個范本,如果想引入avalon.modern.js,就是把上面script標簽的src改一改。
avalon.modern.js之前是叫做avalon.mobile.js,是打算用在移動端的,里面是用了許多高級API,因此性能比avalon.js高許多。由於也不算兼容舊式IE(avalon.modern.js是支持IE10及以上的新瀏覽器),許多兼容邏輯也刪掉了,因此體積少了許多,大概少了1000行代碼。
如果你想做移動端開發呢,這要用到觸屏事件,avalon的倉庫有一個mobile.js,你可以直接將它的源碼 拷貝到avalon.modern.js里最后一個花括號的前面,或者這樣引入:
<!DOCTYPE html> <html> <head> <title>TODO supply a title</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> <script src="avalon.modern.js"></script> <script src="mobile.js"></script> </head> <body> <div>TODO write content</div> </body> </html>
我建議使用拷貝方式,方便以后我們通過合並方式,把所有業務邏輯也統統合並成一個文件。
avalon.js本身是自帶加載器,它是符合AMD規范,因此它可以用requirejs項目的rjs進行合並。如果大家不想用avalon.js的自帶加載器,可以在緊接着的 script標簽里將它禁用。
<!DOCTYPE html> <html> <head> <title>TODO supply a title</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> <script src="avalon.modern.js"></script><!--不兼容IE6到8,也不玩移動端就用這個--> <script> avalon.config({ loader: false }) </script> </head> <body> <div>TODO write content</div> </body> </html>
或者直接在源碼里改,我在公司里就是直接改源碼:
avalon.ready = function(fn) { if (innerRequire) { innerRequire("ready!", fn) } else if (fireReady === noop) { fn(avalon) } else { readyList.push(fn) } } avalon.config({ loader: false })
或者直接在源碼里AMD加載器這個模塊刪掉,這樣可以減少300行代碼
/********************************************************************* * AMD加載器 * **********************************************************************/ var innerRequire var modules = avalon.modules = { "ready!": { exports: avalon }, "avalon": { exports: avalon, state: 2 } } …… …… innerRequire.checkDeps = checkDeps } /********************************************************************* * DOMReady * **********************************************************************/
如果你也用require.js,那么avalon自帶的DOMReady模塊也可以省掉。這時,你們可以引用avalon.shim.js。此JS是基於avalon.js改造而來,你也可以模仿一下改造avalon.mobile.js。
如果你只支持最新的chrome瀏覽器,比如chrome36,那么你可以使用基於Promise, Object.observe 高級API冶造的avalon.observe.js,它使用全新的編譯器與監聽機制,其性能是目前所有MVVM框架之首!
最后我們結合require.js, domReady.js,text.js,css.js,jQuery.js做一個簡單的項目吧。
我們建立一個新項目,結構如下:
其中modules文件是放置不同的業務模塊,可能不同的模塊由不同的人來說,每個人管好自己的js、html、 css,因此我們才需要requirejs的text、css插件。vendor是放置第三方JS庫、CSS庫什么的,main.js為入口文件,特意與index.html放在醒目的位置。
注意,我們需要禁用avalon自帶的加載器。
index.html的內容如下:
<!DOCTYPE html> <html> <head> <title>第一個avalon項目</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> <script src="vendor/require/require.js" data-main="main.js"></script> <style> .ms-controller{ visibility: hidden; } </style> </head> <body ms-controller="root"> <div>{{header | html}}</div> <div ms-include-src="page"></div> <div>{{footer}}</div> </body> </html>
里面有許多奇怪的屬性,不要慌,這是avalon的綁定屬性,后面的章節我們慢慢講。之於requirejs的用法,自己到官網看。
然后是main.js,它大體分為三大塊:
require.config({//第一塊,配置 baseUrl: '', paths: { jquery: 'vendor/jquery/jquery-2.1.1', avalon: "vendor/avalon/avalon",//必須修改源碼,禁用自帶加載器,或直接刪提AMD加載器模塊 text: 'vendor/require/text', domReady: 'vendor/require/domReady', css: 'vendor/require/css.js' }, priority: ['text', 'css'], shim: { jquery: { exports: "jQuery" }, avalon: { exports: "avalon" } } }); require(['avalon', "domReady!"], function() {//第二塊,添加根VM(處理共用部分) avalon.log("加載avalon完畢,開始構建根VM與加載其他模塊") avalon.templateCache.empty = " " avalon.define({ $id: "root", header: "這是根模塊,用於放置其他模塊都共用的東西,比如<b>用戶名</b>什么的", footer: "頁腳消息", page: "empty" }) avalon.scan(document.body) require(['./modules/aaa/aaa'], function() {//第三塊,加載其他模塊 avalon.log("加載其他完畢") }); });
然后每一個模塊里都有其JS文件與模板文件(CSS的引入以后再說)
aaa.html
<div ms-controller="aaa"> <input ms-duplex="username"/>{{username}} </div>
aaa.js
define(["avalon", "text!./aaa.html"], function(avalon, aaa) { avalon.templateCache.aaa = aaa avalon.define({ $id: "aaa", username: "司徒正美" }) avalon.vmodels.root.page = "aaa" })
然后大家運行服務器,就能看到效果(推薦用netBeans,可以直接右鍵運行)
注意,這不是一個簡單的玩具級helloworld!這是一個工業級的項目的種子原型,以后我們所有項目都可以根據它進行改造,最后用rjs進行合並壓縮!
最近附上本章節的源碼!