我的MVVM架構 v3發布!

來源:互聯網
上載者:User

人們總是愛探求完美的東西,醫學界追求長生不死之藥,鍊金術師追求賢者之石,物理學家追求永動機……編程界也有自己的追求,完美架構什麼的,什麼從MVC,到MVP,到MVVM……當然MVC,MVP,MVVM有他們不同的情境,但MVVM在微軟試水後已被證實為介面開發最好的方案了。於前端而言,一直糾纏於模板與組件的優劣。其實答案大家都知道,web page用模板, web app用組件,問題是如何將它們統合起來。頁面之所以能互動,是因為它存在狀態。因此核心問題是如何將這兩者管轄的狀態統合在一起。狀態的由來有兩個,直接從模型得到,比如後端傳送過來的JSON與XML,或比較悲劇地把PHP序列化的欄位還原出來,第二種是在程式中控制流程程時不生的中間量。我們可以這些後端資料或中間量整成一個資料來源,或作為模板的填充資料,或作為組件的傳參最後成為它的屬性。MVVM的出現就很好解決這問題,資料是資料,最好作為貧血模型而存在,對這些資料的操作以及基於這些操作的操作獨立成另一個東西,ViewModel。View是設計師與頁面重構師的陣地。於是我們能實現並行開發。View與ViewModel的連結是資料繫結,ko稱之為聲明式綁定。綁定可以分解成綁定器與相應的參數,它們調用ViewModel的東西,ViewModel操作Model。綁定器通常是非常薄的一層,很少被人提起,但意義重大。它實現了資料再加工與事件綁定(WPF稱之為命令),從而讓資料可視,操作可用!整個流程是非常清晰的,得到Model,抽象出ViewModel,在View中聲明綁定,就完了。覺得架構提供的功能不足,就添加自定綁定器,在ViewModel添加命令(它可以作為事件回調,或資料的過濾器,驗證函式,格式函數)。格式是固定的,像後端那梓,哪個頁面應該由哪個action負責都有規可循。這正是我們前端夢寐以來的究極解決方案。

但思路是有,實現起來不輕鬆,因此前端的MVVM架構也林林總總,每個月都冒出一兩個出來。開源的世界類似於免費的世界,很容易引起馬太效應,強者愈強,弱者愈弱,很易產生壟斷。jQuery的一枝獨秀也證明了這一點。MVVM架構的混亂說明還沒有出現足夠稱得上強大的東西。現在比較拉風的是knouckoutjs,emberjs,angularjs,backbone(它也有資料繫結外掛程式讓它改裝成真正的MVVM)。個人是比較看好knouckoutjs,畢竟是由MVVM的發源地微軟的人搞的,是最正統的派系。但它的綁定也一直被人詬病,太複雜難用。後端的WPF由微軟的強大工具撐著,因此人們覺得不怎麼。但一旦要你們手寫這些綁定時就慘了,加之前端經過jQuery那極簡主義的DSL式API洗禮後,很多人無法接受這樣複雜的用法。emberjs與angularjs對IE6支援不佳,因此在大陸沒有銷路,加之提供的API太多了,對應的概念也多,學習曲線陡峭。backbone是太笨重,沒有幹什麼活,卻要寫一大堆代碼,與jQuery反向而行。

我的MVVM架構avalon v3兩個重要借鑒者為knouckoutjs與rivetsjs。從knouckoutjs得到它的雙向依賴鏈的架構,從學習到消化經曆兩個版本,v2的實現完全原創。從rivetsjs得到它的聲明式綁定的API設計,但實現完全是自己的。v3對v2的雙向依賴鏈的架構進行一些改進,只要是重新命名,讓這些概念更讓人接受。

avalon v3的雙向繫結鏈架構圖

    // ViewModel              架構              View    //屬性訪問器  ┓    //組合訪問器 ┫→→→綁定器 ←←← DOM訪問器 ←←← 資料繫結    //集合訪問器 ┫    //命令       ┛

ViewModel是一個由訪問器與命令組成的對象。訪問器即accessor, 取義自ruby的attr_accessor,是attr_writer, attr_reader的結合,用於對某個資料進行讀寫操作。比如Model中有個aaa屬性,ViewModel就會對應產生一個叫aaa的函數,我們可以傳參修改這個aaa的值,也可以從它那時得到aaa的值。之所以這麼大週摺,是因為IE9才支援用Object.defineProperty描述對象的屬性的訪問機制,它是否可遍曆啊,可配置啊,讀取時應該返回什麼,寫入時會進行什麼處理。如果aaa屬性與bbb屬性有關聯,我們可以在訪問aaa時修改bbb,直接obj.aaa = "xxx"就行了。但為了兼并IE6,我們唯有obj.aaa("xxx")。emberjs就是基於Object.defineProperty構建它的雙向繫結鏈,因此對IE9-支援不好。

//IE9+ FF4+, safari5+, opera11+, chrome5(IE8隻支援DOM)var obj = {}, aValue = 0;Object.defineProperty(obj, "aaa", {    get : function(){        return aValue;    },    set : function(newValue){         obj.bbb += newValue        aValue = newValue;     },    enumerable : true,    configurable : true})Object.defineProperty(obj, "bbb", {    value :10,    writable : true,    enumerable : true,    configurable : true});                console.log(obj.aaa)//0console.log(obj.bbb)//10obj.aaa = 7console.log(obj.aaa)//7console.log(obj.bbb)//17//IE6+var aValue = 0, bValue = 10;var obj = {    aaa: function(newValue){        if(arguments.length){            bValue += newValue            aValue = newValue;                     }        console.log("xxxxxxxxx")        return aValue    },    bbb: function(newValue){        if(arguments.length){            bValue = newValue;         }                  return bValue    }}    console.log(obj.aaa())//0console.log(obj.bbb())//10obj.aaa(7)console.log(obj.aaa())//7console.log(obj.bbb())//17

訪問器又分四種,存在於ViewModel中的有三種。最簡單的是屬性訪問器,它是對Model中某一個屬性進行操作,相當於ko的監控屬性。如果一個欄位由模型中的兩個屬性,或兩個以上,或要對這屬性進行一下加工才產生它的值呢,這就要用到組合訪問器,相當於ko的依賴監控屬性,或emberjs中的computed。像程式中許多表示狀態的中間量都可以抽象成一個組合訪問器。組合訪問器換言之,對已有的東西重新組合而成的屬性的監控函數。集合訪問器,是Model中的數組進行監控,如果它發生排序增刪,它會通知雙向依賴鏈的兩端來重新整理自身。集合訪問器是個特殊的數組,它的方法都被重寫了,雖然用法一樣,但調用了它們會同步到對應的節點地區上!

ViewModel中還存在一種叫命令的東西,打個比方,綁定器相當於MVC中的action,命令相當於helpers。它只是一個普通的函數,架構不會再對它加工。架構對命令與訪問器的區分是,訪問器是用$type 與 "$"+(new Date - 0)這兩個屬性。說得可能有點複雜,比如有個對象var model = {aaa:1, bbb:1},然後$.ViewModel( obj )就得到它對應的ViewModel了。

接著我們看綁定部分。要實現事件綁定。knouckoutjs實現如下:

<div>    <div data-bind="event: { mouseover: enableDetails, mouseout: disableDetails }">        Mouse over me    </div>    <div data-bind="visible: detailsEnabled">        Details    </div></div> <script type="text/javascript">    var viewModel = {        detailsEnabled: ko.observable(false),        enableDetails: function() {            this.detailsEnabled(true);        },        disableDetails: function() {            this.detailsEnabled(false);        }    };    ko.applyBindings(viewModel);</script>

avalon v3參考了rivetsjs的綁定文法,實現如下:

        <div>            <div data-on-mouseover="enableDetails" data-on-mouseout="disableDetails" >                Mouse over me            </div>            <div data-display="detailsEnabled">                Details            </div>        </div>        <script type="text/javascript">            require("avalon,ready", function($) {                var VM = $.MVVM.convert({                    detailsEnabled: false,                    enableDetails: function() {                        VM.detailsEnabled(true);                    },                    disableDetails: function() {                        VM.detailsEnabled(false);                    }                });                $.MVVM.render(VM)            })        </script>

要實現迴圈綁定,knouckoutjs實現如下

<table>    <thead>        <tr><th>First name</th><th>Last name</th></tr>    </thead>    <tbody data-bind="foreach: people">        <tr>            <td data-bind="text: firstName"></td>            <td data-bind="text: lastName"></td>        </tr>    </tbody></table> <script type="text/javascript">    ko.applyBindings({        people: [            { firstName: 'Bert', lastName: 'Bertington' },            { firstName: 'Charles', lastName: 'Charlesforth' },            { firstName: 'Denise', lastName: 'Dentiste' }        ]    });</script>

avalon v3實現如下:(data-each-[item]-[index],item, index是可選,名字任取,只要符合變數命名規則就行)

<script type="text/javascript">    require("avalon,ready", function($) {        $.MVVM.render({            people: [                { firstName: 'Bert', lastName: 'Bertington' },                { firstName: 'Charles', lastName: 'Charlesforth' },                { firstName: 'Denise', lastName: 'Dentiste' }            ]        });    })</script> <table>    <thead>        <tr><th>First name</th><th>Last name</th></tr>    </thead>    <tbody data-each-p="people">        <tr>            <td data-text="p.firstName"></td>            <td data-text="p.lastName"></td>        </tr>    </tbody></table>

avalon v3的優勢在於,它完全DSL,我們可以通過點號來尋找VM中某一個可用的訪問器或命令,作為資料繫結的值。而且實現起來很簡單,不需要像knouckoutjs那樣編寫複雜的JSON編譯器。複雜的東西就難維護,不易升級。有關資料繫結以後我與一系列教程介紹它的。

在資料繫結中,我們藉助於一種特殊的屬性來指引MVVM幹活,格式為data-binding-[param]-[param]。以“-”斷開,第二個字串為綁定器的名字,剩餘的為它的參數。比如事件綁定,data-on-click。

在avalon v3中,它提供了以下預設綁定器,可以通過$.ViewModel.bindings訪問到。v3彌合了v2的傷口,完美支援事件綁定與事件代理。

  • data-text
  • data-html
  • data-class
  • data-css-[class]
  • data-attr
  • data-value
  • data-display
  • data-on-[event]
  • data-enable
  • data-disable
  • data-options
  • data-each-[item]-[index]
  • data-with-[value]-[key]
  • data-if
  • data-unless

avalon v3會將這個屬性的名字分解成綁定器與其他參數,再將它的值得到VM中對應的訪問器與命令,最後把它們構建成一個叫DOM訪問器的東西,作為雙向繫結鏈的頂層,專門與DOM打交道。

在jQuery時代,ID是我們命中元素最可靠的基點,以此為起點八爪魚般處理周遭的節點。行為層上,我們通過事件綁定,幾乎可以用根據代理一切事件。但jQuery是函數式編程,狀態如果在連續在多個回調中使用時,它就要寫在回調外面。當然我們可以緩衝於某個節點上(data),在另一個回調中通過選取器得到那個節點再重新data出來。但整體上,jQuery代碼都是以事件分割成一段段,中間夾雜著一些中間量與處理函數。它們是否能很好工作完全看編程人員的技術水平了。在MVVM中,資料繫結與元素是一體的,因此絕沒有偏差。處理互動上,事件以命令的新身份登場,
回調被集合管理於VM,狀態也被收籠於VM中,我們不再為如何組織代碼傷腦筋,所有都有章而循,新手接力也易上手。MVVM減少對選取器的依賴,將資料與操作綁定在堅固的支點上。

連結地址

過幾天寫些教程,介紹如何用。完!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.