迷你MVVM框架 avalonjs 學習教程3、綁定屬性與掃描機制


在MVVM框架中,你都會看到頁面定了許多奇怪的屬性,比如knockout的data-☆,angular的ng-☆,avalon的ms-☆,此外還有一些只寫文本節點上的雙花括號,它們統稱為指令。ms-☆由於定義在元素節點上,是一個特性節點(Attribute),因此稱為綁定屬性。 雙花括號稱之為插值表達式,意即這里在插入ViewModel對應的屬性,或通過加減乘除后得到的結果。

綁定屬性與插值表達式對於MVVM是非常重要的東西,它們是實現雙向綁定的重要一環,我們通過它來操作DOM。在angular里面,還可以自定義標簽,不過自定義標簽在IE6-8下存在兼容性問題,因此被avalon拋棄了。knockout還有一些特殊的注釋節點做指令,這在UI,OL,LI標簽間會引發各種渲染BUG,這得需要大量代碼來修復,因此avalon也不歡迎它。avalon使用最可靠的特性節點與文本節點的內容做指令,使其兼容性最好。

avalon的許多指令從是knockout抄過來的,如ms-visible, ms-click, ms-if, ms-with, ms-each……angular興起后,也抄來了它的ms-repeat, ms-include,ms-include-src,{{}}。但ms-on-*這種接第三個參數的設計是從rivetsjs抄來的。

先說綁定屬性,它的名字是分為三部分。由於它是特性節點,根據HTML規范,它只能是全小寫,就算有大寫,瀏覽器也會將它轉換成小寫,這個大家要注意了。每一部分是由“-”隔開,最開始是前綴,avalon的前綴是ms,意即mass, 彌撒,記念我之前的框架mass Framework。正因為搞了mass Framework,我獲得了像jQuery那樣強大的DOM處理能力,掌握大量瀏覽器私有方法與屬性,成噸的黑魔法與飛線方案。換言之,技術是需要積累,每個框架都存在傳承關系。

 <body AAA="aaa"> 
      test
  </body>

enter image description here enter image description here 第二個是指令的名字,如if、visible、on、text、html、include……從它們的名字,大家也能一窺端睨。如if表示是否它輸出到頁面,visible表示是否可見,on是綁定事件,text是原樣輸出,html是轉換為HTML標簽再輸出,include表示加載子模板到當前位置。avalon就是根據它轉換為不同的視圖刷新函數。

第三個是指令的參數,比如 我們可以ms-on-click,ms-on-keyup,ms-duplex-radio,ms-duplex-bool,ms-duplex-text。此外,還有一個特殊的東西,如ms-click-1,ms-click-2,ms-click-3表示可以為某一個元素綁定N個點擊事件。這些我們以后會慢慢詳述。而綁定屬性的值,則復雜多了,不過具體來說,表示事件的要求對應一個回調的名字,后面可帶小括號可不帶;表示字符串屬性(ms-title, ms-value,ms-src, ms-href, ms-alt)的,里面可以添加插值表達式;與類名相關的ms-class, ms-hover, ms-active可以跟一個冒號,通過它之后表達式計算是否添加移除對應的類名。下面的avalon的綁定屬性的族譜—— enter image description here

從這圖,我們也可以看出,綁定屬性間也是存在繼承關系的,或者准確地說,一些綁定屬性是從某個綁定屬性衍生出來的。最明顯的是ms-click、ms-keyup、ms-mousedown都是從ms-on-☆衍生出來。它們在框架內部是調用同一方法。

 var events = oneObject("animationend,blur,change,input,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit")
   if (events[type]) {
    param = type
    type = "on"
} else if (type === "enabled") {//吃掉ms-enabled綁定,用ms-disabled代替
    type = "disabled"
    value = "!(" + value + ")"
}
//吃掉以下幾個綁定,用ms-attr-*綁定代替
if (type === "checked" || type === "selected" || type === "disabled" || type === "readonly") {
    param = type
    type = "attr"
    elem.removeAttribute(name)
    name = "ms-attr-" + param
    elem.setAttribute(name, value)
    match = [name]
    msData[name] = value
}
if (typeof bindingHandlers[type] === "function") {
    var binding = {
        type: type,
        param: param,
        element: elem,
        name: match[0],
        value: value,
        priority: type in priorityMap ? priorityMap[type] : type.charCodeAt(0) * 10 + (Number(param) || 0)
}              

我們細看源碼,就會發現,一個綁定屬性會在內部轉換為一個叫binding的對象,其屬性名的第二個部分,如if, visible變成它的type,第三部分變成param,綁定屬性所在的元素節點就是它的element,原屬性值就是value,原屬性名則是name,此外還有優先級priority。這就涉及avalon另一個重要的機制了——掃描機制

絕對大多數MVVM框架是沒有自己的選擇器引擎的,那么它怎么找到要處理的元素呢?辦法就在於框架在DOMReady之時,對DOM樹進行全盤掃描,把特殊的標簽,如它有特殊的tagName,有綁定屬性,有插值表達式的元素,全部保存起來。avalon里就有一個scan方法,它有兩個可選參數,一個是元素節點,另一個是ViewModel,也可以是ViewModel數組。順序是從上到下掃描,頁面必須要有ms-controller, ms-important綁定屬性。如果沒有這兩個綁定屬性,就算你寫了ms-visible,ms-text,它們也不會生效,因為它們找不到自己的作用域對象(ViewModel)。

掃描機制一般是從body元素開始,逐等下級,它會跳過script, style, noscript, textarea這幾個元素的內部。而script、noscript、textarea我通常稱之為模板元素,因為它們在avalon都用於存放子模板。

一個元素可以綁定多個指令,這些指令存在優先級,其中ms-skip的優移動級最高(0),其次是ms-important(1),再次是ms-controller(2),它們決定掃描引擎是否繼續往下掃描,因此排最前面。

其他元素可以從內部一個哈希里查到,越小的優先級越高。

var priorityMap = {
    "if": 10,
    "repeat": 90,
    "widget": 110,
    "each": 1400,
    "with": 1500,
    "duplex": 2000,
    "on": 3000
}

可以看到排第4的是if,排第5的是repeat,排第5的是widget,那么它們與each之間的其他屬性呢?ms-attr,ms-visible, ms-data的優先級是怎么計算出來的呢?就要用到下面這條公式了:

type.charCodeAt(0) * 10 + (Number(param) || 0)

比如ms-data-index,它的type為data,取其第一個字母d,再取其UniCode值,計算得到100, 再乘以10, 得1000,然后取其第三個參數index強制轉數字,得到NaN,后跟短路或於是變成0,最后相加,等到1000。這就是它的優先級了。

由於屬性名只能是小寫,因此就是z開頭,最大也只能是1220上下。最后計算得到它們的移動后順序為:

ms-skip(0) --> ms-important(1) --> ms-controller(2) --> ms-if(10) --> ms-repeat(90) -->ms-if-loop(110) --> ms-attr(970) ...--> ms-each(1400)-->ms-with(1500)-->ms-duplex(2000)-->ms-duplex(3000)墊后

有關掃描順序更詳細的介紹可見這里,不過用戶不必在意它們,框架這樣巧妙的設計已經保證你為同一個元素綁定N個指令,它們都能相安無事地正常執行。

最后我們看一下綁定屬性能為我們做什么吧,這里有一個圖,展示了它們的所有功能。下一章節起來我們就根據它逐個擊破了。

enter image description here

<!DOCTYPE html>
<html>
    <head>
        <title>ms-duplex</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 
        <script src="avalon.js" ></script>
        <script>
            var model = avalon.define("model", function(vm) {
                vm.textModel = "text";
                vm.passwordModel = "password"
                vm.radioModel = true;
                vm.checkRadio = true;
                vm.selectModel = "bbb";
                vm.checkboxModel = ["aaa", "bbb"];
                vm.checkboxText = vm.checkboxModel.join(",")
                vm.sex = "1"

            });
            var qinerg = avalon.define("qinerg", function(vm) {
                vm.sex = "";
                vm.lang = []
                vm.langtext = ""
            });
            qinerg.lang.$watch("length", function() {
                qinerg.langtext = qinerg.lang.join(",")
            })
            model.checkboxModel.$watch("length", function() {
                model.checkboxText = model.checkboxModel.join(",")
            })
            var dynamic = avalon.define("dynamic", function(vm) {
                vm.langtext = ""
                vm.array = ["aaa", "bbb", "ccc"]
                vm.lang = []
            })

            dynamic.lang.$watch("length", function() {
                dynamic.langtext = dynamic.lang.join(",")
            })

        </script>
        <style>
            .parent{
                height:50px;
                width:100%;
                overflow:hidden;
            }
            .visible{
                width:400px;
                height: 50px;
                background: red;   
                float:left;
            }
            .if{
                width:400px;
                height: 50px;
                background: blueviolet;  
                float:left;
            }
            fieldset{
                background:#d2d2d2;
            }

        </style>
    </head>
    <body >
        <div ms-controller="model">
            <h3 style="text-align: center">ms-duplex</h3>
            <input ms-duplex="textModel" ms-data-duplex-observe="radioModel"/>
            <input ms-duplex="passwordModel" type="password"/>

            <input type="radio" ms-duplex="radioModel"> <input type="checkbox" ms-duplex-radio="checkRadio">
            <select ms-duplex="selectModel">
                <option value="aaa" selected>aaa</option>
                <option value="bbb">bbb</option>
                <option value="ccc">ccc</option>
            </select>
            <input ms-duplex="checkboxModel" type="checkbox" value="aaa" />
            <input ms-duplex="checkboxModel" type="checkbox" value="bbb" />
            <input ms-duplex="checkboxModel" type="checkbox" value="ccc" />
            <input ms-duplex-text="sex" type="radio" value="1"/>
            <input ms-duplex-text="sex" type="radio" value="2"/>
            <input ms-duplex-text="sex" type="radio" value="3"/>
            {{sex}}
            <p>text, password, textarea要求對應的VM屬性為字符串,
                select、checkbox為字符串數組,radio為布爾,
                我們可以通過ms-duplex-radio讓checkbox對應一個布爾,
                ms-duplex-text讓radio對應一個字符串 </p>
            <div class="parent">
                <div ms-visible="radioModel" class="visible">
                    <div>ms-visible這個區域是受到radioModel控制</div>
                    <div>data-duplex-observe為{{radioModel}}</div>
                </div>
                <div ms-if="checkRadio" class="if">
                    <div> ms-if這個區域是受到checkRadio控制</div>
                </div>
            </div>
            <fieldset>
                <legend>textModel</legend>
                <p>{{textModel}}</p>
            </fieldset>
            <fieldset>
                <legend>passwordModel</legend>
                <p>{{passwordModel}}</p>
            </fieldset>
            <fieldset>
                <legend>radioModel</legend>
                <p>{{radioModel}}</p>
            </fieldset>
            <fieldset>
                <legend>selectModel</legend>
                <p>{{selectModel}}</p>
            </fieldset>
            <fieldset>
                <legend>checkboxModel</legend>
                <p>{{checkboxText}}</p>
            </fieldset>


        </div>
        <div ms-controller='qinerg' >
            <p><input type="radio" ms-duplex-text="sex" value="man">男
                <input type="radio" ms-duplex-text="sex" value="woman">女</p>
            <p>
                <input type="checkbox" ms-duplex="lang" value="#C">#C
                <input type="checkbox" ms-duplex="lang" value="java">java
                <input type="checkbox" ms-duplex="lang" value="ruby">ruby
            </p>
            <P>{{sex}}                   {{langtext}}</P>
        </div>
        <fieldset ms-controller="dynamic">
            <div  ms-each="array">
                <div><input type="checkbox" ms-duplex="lang" ms-value="{{el}}">{{el}}</div>
            </div>
            <div>{{langtext}}</div>
        </fieldset>

    </body>
</html>

enter image description here


免責聲明!

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



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