本文將深入介紹一下avalon的運作機理及與jQuery的區別。
許多人都知道MVVM是MVC的一個變體,但那些MVC是在后端的,包括微軟的WPF,這意味着這個V與我們前端接觸到的V差別很大。后端的V就是使用各種模板拼湊成一個靜態頁面給前端。而前端的V在JSer的眼中就是一個巨大的DOM樹,要考慮加載時間,渲染順序,瀏覽器對HTML的容錯與修復,瀏覽器的默認事件,新圖片與節點增刪移動引起的reflow,是活生生的。前端的V就是一個DOM的世界,而后端就是字符串!
M,模型,一個數據體,用於填允我們的頁面,通常PHP交給我們時數據已經填好了,想改變,就要再發出請求,然后通過jQuery尋找節點,修改它的innHTML與innerText。當然DOM操作是非常繁鎖的,什么透明度,背景,位置等CSS屬性我們都需要通過操作類名來修改。我們有時忙乎於這些細節,而把我們業務混在其中了。因此調試時,業務好了,但樣式壞了,樣好修好,這時務業又爆開了……
在MVVM中,M只是一個過客,它與其他表示業務狀態的東西融入VM(ViewModel)中。ViewModel是一個狀態的集合,當然還拖家帶口監控着大量的回調。狀態聽起來是個深奧的概念,其實就是一個開關。比如if(aaa){}語句中的aaa,就是表示true與false,switch(bbb){}語句,它表示有多個值的狀態,就像有人幼年,童年,少年,青年,中年,老年這幾個階段,它們都指向同一個東西。jQuery是什么管理狀態的呢?比如我做個切換卡,點一下,這個面板顯示,其他面板隱藏,它里面就使用了大量的hasClass來檢測元素有沒有某個表示顯示的類名,有就隱藏,其他關閉。
$(button).click(function(){ var index = $(this).index(); $(panel).eq(index).show().siblings().hidden() })
而在MVVM中,只要妥善綁定好視圖,直接像 vm.activated = 1,1 為你要顯示的按鈕,這樣切換卡就切換過去,包括你點的按鈕上的類名。你可以被動地在事件回調修改VM中的屬性,也可以直接對VM中屬性,無論哪一種,基本上很少看到DOM代碼。
<div ms-controller="tabs" > <div ms-each-tab="tabs" class="triggers"> <a href="###" class="trigger" ms-class-selected="activated === $index" ms-click="activate">{{tab.title}}</a> </div> <div ms-each-tab="tabs" > <div class="panel" ms-visible="activated === $index">{{tab.panel}}</div> </div > </div >
avalon.ready(function() { avalon.define("tabs", function(a) { a.tabs = [ //tabs上的title, panel來源自后端的數據庫,如果PHPer幫你把整個頁面搞定,這個就不需要 { //只要下面的activated 與 activate title: "aaaa", panel: "aaaa panel" }, { title: "bbbb", panel: "ffffffffffffffffffff" }, { title: "cccc", panel: "cccc panel" } ] a.activated = 0 //這個是我們的務業產生的變量, a.activate = function(){ a.activated = this.$scope.$index;//這是我們的業務,就是改一下activated 的值。 } }) avalon.scan(); })
在MVVM中,數據是核心。而jQuery則以DOM為核心。而DOM只是HTML在JS的世界的抽象,是一個很易變的東西。因此如果業務代碼遍歷選擇器表達式會非常難維護。但不可否認,jQuery是操作DOM的王者,讓我們操作DOM順手拈來。但如果不讓你操作DOM,不是更好嗎?就像jQuery不讓你用getElementById,getElementsByTagName, querySelecterAll,大家都不知道里面有多少坑,短短幾個字母$(expr)是背后sizzle選擇器引擎1700行的實現!!!!jQuery其實是在用戶代碼與原生API中提供一層厚厚的粘合層,因此摸起來光溜溜。在MVVM中,DOM操作基本是水下運作了。由於VM與V之間的雙向綁定,操作了VM中的數據(當然只能是監控屬性),就會同步到DOM,我們透過DOM事件監控用戶對DOM的改動,也會同步到VM。DOM隱形了,就像軟件公司,到處跑出來活動的是業務員與不寫代碼的經理老總,程序員全部關起來加班!雖然這比喻有點殘酷,但這正體現了各司其職的威力。能說會道去拉風投接單子沒什么不妥,喜歡呆在電腦前的就讓他呆吧。jQuery的世界就是一個混亂的公司,全能的程序員什么都做。
為了各司其職,必須有良好的分層。MVVM划分三層,M,VM,V,M是原始數據,用於轉換為VM,VM管理狀態與綁定回調,V通過綁定得到VM的狀態與回調,渲染頁面,綁定事件,切換類名,什么臟活都攬了——但用戶只需要聲明。avalon與其他前端MVVM框架最大的不同是,VM是用ecma262v5的新API, Object.defineProperties生成的一個充滿訪問器的對象,這樣的對象,能通過用戶對它的屬性的讀寫,觸發定義時的getter, setter函數。getter, setter對rubyer, pythoner, C#er應該很熟悉,我就不展開了。舊式IE,avalon利用VBScript的類實例,它也存在其他語言的訪問器。不過,VBS對象不像JS對象那樣隨意添加新屬性,刪除已有屬性,因此我們就無法監后添加的新屬性。Object.defineProperties也一樣,它能處理的屬性也只是它定義時的屬性,想監控后來的,需要再調用一次Object.defineProperties。盡管如此,也比其他MVVM框架魔幻多了。
如果監控屬性不夠用,可以用監控數組,計算屬性與$watch, $fire方法。
比如下面一個監控數組fruits :
var model = avalon.define("form", [], function(vm) { vm.fruits = [{name: "xxx"}, {name: "yyy"}, {name: "ooo"}, {name: "ppp"}] vm.numbers = [1, 2, 3, 4] });
被改造成如下樣子,我們可以在chrome的控制台看到它的正身:

首先這個數組的sort, splice, push, shift, reverse, pop, unshift等方法被重寫,並添加了contains, ensure, remove, removeAll, removeAt, clear, upeate, set等方法, 還有表示它是個監控數組的isCollection, $id等屬性。它內部四個對象也轉換子ViewModel,name轉換為監控屬性,每個name在內部都有個set, get方法。我們只需要aaa.name = "xxx"; var d = aaa.name就能調用它們。而不像knockout那樣全部轉為真正的函數。
為了能互相同步,要寫你綁定在頁面上的字段為監控屬性,如果它是一個復雜數據的某個子屬性的子屬性,那么至少也保持最后那個是監控屬性,否則無法同步
var model = avalon.define("test", [], function(vm) { vm.fruits = [{name: "xxx"}, {name: "yyy"}, {name: "ooo"}, {name: "ppp"}] vm.numbers = [1, 2, 3, 4] vm.lang = "ruby" }); //在這里,我們可以監聽當中的name 即{{ fruits[0].name }}, 但無法監控numbers中的元素,因為數字無法作為Object.defineProperty的第一個參數傳入,因此轉換不了。 // Object.defineProperty(object, propertyname, descriptor);//obj必須為對象
根據這思路,整個轉換過程為:
Object.defineProperty(vm, "fruits", {set:fn, get: fn})//事實上,avalon是使用Object.defineProperties同時處理! Object.defineProperty(vm, "numbers ", {set:fn, get: fn}) Object.defineProperty(vm, "lang", {set:fn, get: fn}) Object.defineProperty(fruits[0], "name", {set:fn, get: fn}) Object.defineProperty(fruits[1], "name", {set:fn, get: fn}) Object.defineProperty(fruits[2], "name", {set:fn, get: fn}) Object.defineProperty(fruits[3], "name", {set:fn, get: fn})
比如像下面這樣的復雜結構,avalon就有點為不從心了。因此不要直接把Model的數據整個扔到ViewModel中,你懶,avalon也懶——它只是監聽對象屬性的變化,數組長度與位置的變化。不會監聽純字符數組與純字母數組。即便對於angular,google擁有這么多算法帝,它也要求你使用$watch來處理,而不是直接放在VM中就一了百了。emberjs, knockout就更嗆了。要用好ViewModel必須對原始數據進行加工。
var model = avalon.define("class", [], function(vm) { vm.data = { rows: [ { "d": [ "PO00000078", "ABC" ] }, { "d": [ "PO00000079", "DEF" ] } ] } })
如果一定要監聽,只能使用each綁定,你這個對象在層次結構上有多少個數組,就應該用多少次each綁定。比如這個對象,就有rows, d這個數組,需要用上兩次。
<ul ms-each-row="rows" ms-controller="class"> <li ms-each-td="row.d"> {{td}} </li> </ul>
生成的結構如下:

不過真正強大的是各種綁定,幫你打包所有DOM操作。可詳看這里的API說明。
MVVM是前端未來的發展方向,微軟有knockout, winjs等MVVM框架; 著名的.Net組件開發公司 Telerik 推出了一套基於 jQuery的MVVM UI庫kendoui; 谷歌組織開發angular;jQuery, rails,Sproutecore,Merb,Handlebars這幾個著名框架的核心成員,超級大牛Yehuda Katz推出了emberjs!
外國有文章介紹,使用了AngularJS(MVVM)代替(Backbone),代碼減少了一半。
http://www.localytics.com/blog/2013/angularjs-at-localytics/

現在園子里有不少人在用knockout與angular,在ruby china中,最愛玩新技術的rubyer最近也密集提到它們。毫無疑問,這是非常有吸引力,極具生產力的東西。
avalon作為現在最迷你的MVVM框架,易用性也堪稱一絕,絕對可以一試。