迷你MVVM框架 avalonjs 學習教程2、模塊化、ViewModel、作用域


一個項目是由許多人分工寫的,因此必須要合理地拆散,於是有了模塊化。體現在工作上,PM通常它這為某某版塊,某某頻道,某某頁面。某一個模塊,必須是包含其固有的數據,樣式,HTML與處理邏輯。在jQuery時代,奉行的是“無侵入式javascript”,頁面雖然是拆成一塊塊,但最后是通過PHP等后端模板合並起來,並且把第一屏的數據直接灌進去,接着是無盡的選擇某些元素進行處理,選擇某些元素進行處理。javascript里面是滿屏的CSS表達式,如果不一一對着HTML頁面,這是無法閱讀的。換言之,jQuery很容易產生readyOnly的代碼。

avalon是引入分層構架,視圖就是視圖,數據就是數據,JS里面是操作數據,不會再操作視圖,涇渭分明。視圖,換言之就是最初做好的那些HTML片段,只需要在里面添加上ms-controller指令(或叫綁定屬性),指定其將要作用的ViewModel的ID,然后在它里面添加其他綁定就行了。數據,特指是ViewModel,avalon是通過define方法定義,目的是實現“操作數據即操作DOM”,從此我們再也用不上什么操作DOM的API,javascript代碼量立即減少了一半以上,條理更清晰,更易維護。

ViewModel的定義是一個重頭戲。在入門教程里,是這樣定義的:

var model = avalon.define("test", function(vm) {
        vm.firstName = "司徒"
        vm.lastName = "正美"
        vm.fullName = {//一個包含set或get的對象會被當成PropertyDescriptor,
            set: function(val) {//里面必須用this指向scope,不能使用scope
                var array = (val || "").split(" ");
                this.firstName = array[0] || "";
                this.lastName = array[1] || "";
            },
            get: function() {
                return this.firstName + " " + this.lastName;
            }
        }
        vm.arr = ["aaa", 'bbb', "ccc", "ddd"]
        vm.selected = ["bbb", "ccc"]
        vm.checkAllbool = vm.arr.length === vm.selected.length
        vm.checkAll = function() {
            if (this.checked) {
                vm.selected = vm.arr
            } else {
                vm.selected.clear()
            }
        }
    })
    model.selected.$watch("length", function(n) {
        model.checkAllbool = n === model.arr.size()
    })

有兩個參數,第一個定義ID,第二個是定義ViewModel本身的數據,它有什么監聽屬性啊,計算屬性啊,一些特殊的指令啊,$watch回調啊用戶需要區分vm與model的區別,有什么需要注意的地方(這些在入門教程都有介紹)。在1.3.3中,添加了現在這種新的定義方式,只要傳入一個對象:

var model = avalon.define({
        $id: "test",
        firstName: "司徒",
        lastName: "正美",
        fullName: {//一個包含set或get的對象會被當成PropertyDescriptor,
            set: function(val) {//里面必須用this指向scope,不能使用scope
                var array = (val || "").split(" ");
                this.firstName = array[0] || "";
                this.lastName = array[1] || "";
            },
            get: function() {
                return this.firstName + " " + this.lastName;
            }
        },
        arr: ["aaa", 'bbb', "ccc", "ddd"],
        selected: ["bbb", "ccc"],
        checkAllbool: false,
        checkAll: function() {
            if (this.checked) {
                model.selected = model.arr
            } else {
                model.selected.clear()
            }
        }
    })
    model.checkAllbool = model.arr.length === model.selected.length
    model.selected.$watch("length", function(n) {
                model.checkAllbool = n === model.arr.size()
    })
  • 監控屬性:就是改變了它會同步視圖的屬性;
  • 非監控屬性:就是改變了它不會同步視圖的屬性,通常是以$開頭,或放在$skipArray數組的屬性(angular到1.3才引入單向數據綁定);
  • 計算屬性:就是一個定義set, get方法的對象,是一種高級的監控屬性;
  • 監控數組:就是一個數組,如果它沒有以$開頭,或名字沒有放到$skipArray數組里,框架就會自動轉換它為監控數組,當用戶調用它的方法時,就會同步視圖通常它是與ms-repeat、ms-each指令配合使用。

當我們將一個對象傳進avalon.define方法,它將返回一個全新的對象,它添加了許多$方法與屬性,並且原來的屬性都變得非常奇怪,在控制台下可以看到它們都對着一個set方法一個get方法。ecma262 v5稱之為訪問器屬性named accessor properties)。當然不同的人有不同的譯法,大家想詳細了解此屬性的特性,可以閱讀以下鏈接,這是avalon能讓你修改屬性就能同步視圖的關鍵!

enter image description here

注意,我們所有定義的VM都存放在avalon.vmodels對象上。打開我們上一節寫的項目,在firebug下輸入avalon.vmodels可以查看到: enter image description here enter image description here

那么為了應用這些ViewModel,我們就需要用到ms-controllerms-importantms-skip這三個指令。ms-controller在頁面上表現為一個特殊的屬性,其屬性值為ViewModel的$id,表示將在此元素或其子孫元素上圈定它的作用域范圍,但如果這些HTML存在它沒有的屬性,它可以向上查找上一級的ViewModel的屬性。換言之,ms-controller可以互相套嵌的。 ms-important的用法與ms-controller差不多,但它不會向上查找。ms-skip注明這塊區域不應用任何的ViewModel的屬性,它里面的任何指令(綁定屬性)都會失效。因為{{}}也算一種指令,而任何指令在被掃描后都會被移除,如果我們想保留某個區域的{{}},就需要用到ms-skip。有關ms-controller, ms-important的詳細用法可見這里

上面的ViewModel再配合一些HTML代碼,就是實現一些用jQuery非常費勁才能實現的功能

<div ms-controller="test">
    <p>First name: <input ms-duplex="firstName" /></p>
    <p>Last name: <input ms-duplex="lastName"  /></p>
    <p>Hello,    <input ms-duplex="fullName"></p>
    <div>{{firstName +" | "+ lastName }}</div>
    <ul>
        <li><input type="checkbox" ms-click="checkAll" ms-checked="checkAllbool"/>全選</li>
        <li ms-repeat="arr" ><input type="checkbox" ms-value="el" ms-duplex="selected"/>{{el}}</li>
    </ul>
</div>

enter image description here

大家可以在這里看到實際運行效果。

再細說一下ViewModel(我們通常也簡稱為VM)的一些屬性。

$id: VM的ID,方便在avalon.vmodels里查找到它,或用在ms-controller、ms-important上。

$events:里面存放着各種回調,它們是通過$watch方法添加的。

$watch:這是一個方法,有兩個參數,第一個是VM中的某一個屬性名,只能這個VM的直接子屬性名,第二個是回調函數,當此屬性發生改變時,就會執行此回調。回調里會依次傳入它的新老屬性值。

$unwatch:移除某個屬性的回調。

$fire:手動觸發此回調。

$accessors:放置與監聽屬性相連動的視圖刷新函數,當我們改變某一屬性時,框架就會在這里找到對應的視圖刷新函數,傳入當前值,實現對視圖的同步。

$123323213:它的格式是$加上一串數字,它是用於放置監控數組的視圖刷新函數,當我們調用監控數組的方法時,框架就此根據當前數組的個數與排列順序,重新渲染對應的區域。它與$accessors一樣,不開放給用戶調用的。

$model:就是ViewModel的凈化版,沒有$XXX屬性,訪問器屬性全部還原為普通屬性,專門用於提交到后台用。當然我們提交后台,還需要用JSON.parse(JSON.stringify(VM.$model))處理一下,將里面的函數干掉。

現在說的還是基本用法,$watch、$unwatch、 $fire其實遠遠比你想象的強大,大家感興趣的話,可以到這里了解其高級用法。

好了,我們把上面的代碼放進上一節,修改aaa.js, aaa.html,感受一下一個復雜的ViewModel的應用吧。 enter image description here

有了ViewModel后,我們的代碼就顯得非常有內聚力,自己知道要作用於視圖的哪一塊區域,並且不用自己操心如此修改DOM,變成單純的數據操作。

而數據操作是需要在頁面定義一些指令(我們稱之為綁定屬性與插值表達式)。現在最簡單的有兩個,{{ prop }}是直接將屬性輸出到頁面,如果它存在尖括號,會原樣輸出,不會轉換為HTML標簽。{{ prop | html}}則相反,比如這個屬性的值為”xxxxerer”,那么里面就真會轉為一個b標簽。其實{{prop}},{{prop|html}}還有另一種寫法, ms-text=”prop”, ms-html=”prop”。有關這些綁定的屬性詳細用法,我們下一節講述。

本章節的代碼可以從這里下載。


免責聲明!

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



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