迷你MVVM框架 avalon的魔術


本文將深入介紹一下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框架,易用性也堪稱一絕,絕對可以一試。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM