avalon2學習教程15指令總結


avalon的指令在上一節已經全部介紹完畢,當然有的語焉不詳,如ms-js。本節主要總結我對這方面的思考與探索。

MVVM的成功很大一語分是來自於其指令,或叫綁定。讓操作視圖的功能交由形形式式的指令來代勞。VM,成了一個大管家。它只一個反射體。我們對它的操作,直接影響到視圖。因此俗稱“操作數據即操作視圖”!至於它是怎么影響視圖,avalon視其版本的不同,也有不同的解法。如果拋開avalon,縱觀世上所有MVVM框架,大抵有如下幾種方式

  1. 函數wrapper:將原數據對象重新改造,所有屬性都變成一個函數,有參數時就是賦值,進行視圖同步與回調派發,沒有參數時就取值,進行依賴收集。如knockout.js。
  2. 上帝getter,setter: 將原數據對象重新包裝,但對數據的操作必須經過統一的set,get方法。在set方法進行視圖同步與回調派發,沒有參數時進行依賴收集。如reactive.js。如果放松要求,react也是這種方式,它使用setState進行視圖同步。但它們依賴收集的過程。
  3. 函數編譯及臟檢測:將VM放到一個函數體內,取toString重新編譯,內部是第一種方式。如angular.
  4. Object.defineProperty屬性劫持:前三種的用戶體驗非常糟,於是有了這種方式,最初我是從emberjs中學來的。avalon,vue及其他后來的MVVM框架都是使用這種方式。在賦值時執行內部的setter方法,進行視圖同步與回調派發,在取值時執行內部的getter方法,進行依賴收集。
  5. Object.observe對象監控: 這個API很短命,因此沒幾個MVVM框架用上它。由於無法進行依賴收集,需要別辟蹊徑!
  6. Proxy對象監控:Object.observe對用戶的行為監控是很弱的,並且是異步的,不夠友好。於是有了這個新寵物。但這其實也不算新寵,firefox4就存在了。它能對數據賦值,取值,遍歷,刪除等各種行為都能監控到,了解Object.defineProperty 不能監控新屬性的難題。在avalon2 中,就有一分支使用它實現。avalon2一共使用了VBScript, Object.defineProperty, Proxy實現vm。並且它沒有進行依賴數據,而是將整個視圖編譯成一個大函數,每次數據變動,都重新執行這個函數,產生虛擬DOM,前后虛擬DOM進行diff,最后全量更新。這思路從react.js學來的。

上面說了,既然使用編譯整個視圖成模板函數這一手段,我們就盡量讓這函數輕量化。位於這視圖上的所有指設也要簡化,方便在對應位置上代入VM中的屬性。在avalon1及其他MVVM框架,都是使用動態依賴收集方式來推斷指令中的某個單詞是否為vm中的某個屬性,這性能耗損比較嚴重。在avalon2直接讓用戶在屬性名前加上 @ 符號,人工優化這步驟了。此外,指令屬性值的設計原則也很明確,就是方便轉換一個函數,返回對象或對象數組(如ms-attr,ms-css,ms-widget,ms-effect,ms-class,ms-hover,ms-active,ms-for),少量的返回布爾或字符串等直接可用於JS 語句的字面量。除了ms-for與過濾器,沒有其他特殊語法。

圖片描述
除了ms-duplex,ms-on,原則上不再出來ms-xxx-yyy這樣的指令。ms-后面只跟一個單詞就夠了,不用再加-及其他單詞。

各種指令的優先級如下:
ms-for, ms-widget, ms-effect, ms-if…………其他指令(按指令名的charCodeAt排序)…………ms-duplex!

ms-duplex是最后,因此再不用擔心它與其他指令沖突的問題。

指令的更新時機有兩個,一個是位於此標簽之間的所有孩子執行之前,一個是位於此標簽之間的所有孩子執行之后。亦即change, afterChagne列隊。有興趣的話,可以閱覽這里的源碼

想自定義指令,可以使用avalon.directive方法,第一個為指令名,第二個是定義體,里面至少有parse, diff, update三個方法。自己參看css指令,編寫指令吧。

//css指令

var update = require('./_update')

avalon.directive('css', {
    parse: function(cur, pre, binding) {
       cur[binding.name] = avalon.parseExpr(binding)
    },
    diff: function (copy, src, name) {
        var a = copy[name]
        var p = src[name]
        if (Object(a) === a) {
            
            a = a.$model || a//安全的遍歷VBscript
            if (Array.isArray(a)) {//轉換成對象
                a = avalon.mix.apply({}, a)
            }
            if (typeof p !== 'object') {//如果一開始為空
                src.changeStyle = src[name] = a
            } else {
                var patch = {}
                var hasChange = false
                for (var i in a) {//diff差異點
                    if (a[i] !== p[i]) {
                        hasChange = true
                        patch[i] = a[i]
                    }
                }
                if (hasChange) {
                    src[name] = a
                    src.changeStyle = patch
                }
            }
            if (src.changeStyle) {
                update(src, this.update)
            }
        }
        delete copy[name]//釋放內存
    },
    update: function (dom, vdom) {
        var change = vdom.changeStyle
        var wrap = avalon(dom)
        for (var name in change) {
            wrap.css(name, change[name])
        }
        delete vdom.changeStyle
    }
})


里面的parse(cur, pre, binding)方法是用於創建虛擬DOM, cur是通過vm.$render方法生成的新虛擬節點,pre是之前的虛擬節點,binding是當前指令抽象生成的綁定對象。

里面的diff(copy, src, name)方法是用來比較前后兩個虛擬DOM。copy是新虛擬節點,src是之前的虛擬DOM,name為指令的名字。當你用各種方式比較出這兩個虛擬DOM有差異,那你就可以使用require('./_update')這個方法執行更新,更新方式為指令的update方法。

2.1.0后,刷新機制有點改動,兩個節點比較出差異后立即更新真實DOM, 不像過去那樣全部比較再全量更新。

里面的update(dom,vnode,parent)方法是用來更新真實元素的。

最后你可以在avalon.directives對象中指到所有指令的定義。你也可以在vm.$element.vtree中看到你生成的虛擬DOM樹。

既然avalon的指令已經全部介紹完了,因此大家現在可以直接使用avalon2了!


免責聲明!

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



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