avalon大家可能不熟悉,但是Knockout估計或多或少聽過用過,那么說說KO的幾個概念
- 監控屬性(Observables)和依賴跟蹤(Dependency tracking)
- 聲明式綁定(Declarative bindings)
- 模板(Templating)
本章主要提到 監控屬於 與 依賴跟蹤(后改名叫計算屬性)
監控顧名思義,監聽着你設定目標的變化,換句話說能夠通知訂閱者它的改變以及自動探測到相關的依賴。
計算屬性,就是依賴監控屬性變化而自動調用處理更新
KO的一個例子
如果你已經有了監控屬性firstName和lastName,你想顯示全稱怎么辦? 這就需要用到依賴監控屬性了 – 這些函數是一個或多個監控屬性, 如果他們的依賴對象改變,他們會自動跟着改變。
例如,下面的view model,
var viewModel = { firstName: ko.observable('Bob'), lastName: ko.observable('Smith') };
… 你可以添加一個依賴監控屬性來返回姓名全稱:
viewModel.fullName = ko.dependentObservable(function () { return this.firstName() + " " + this.lastName(); }, viewModel);
並且綁定到UI的元素上,例如:
The name is <span data-bind="text: fullName"></span>
… 不管firstName還是lastName改變,全稱fullName都會自動更新(不管誰改變,執行函數都會調用一次,不管改變成什么,他的值都會更新到UI或者其他依賴監控屬性上)
OK
KO是怎么實現雙向機制的呢?
- 通過轉換VM中所有要監聽的東西為函數,然后執行它們,得到某一時刻中,一共有多少函數被執行,將它們放到棧中,最底的就是最先被執行的,它上面的就是此函數所依賴的函數,從而得到依賴關系。
- 然后設計一個觀察者模式,從上面的依賴檢測中,將依賴函數作為被依賴者(最先執行的那個的)的訂閱者,以后我們對被依賴者進行賦值時,就會通先訂閱者更新自身,從而形成一個雙向綁定鏈。
- knockout會將視圖中的綁定屬性進行轉換,分解出求值函數與視圖刷新函數,視圖刷新函數依賴於求值函數,而求值函數亦依賴於我們VM中的某些屬性(這時,它們都轉換為函數),在第一次掃描時,它們會加入對應屬性的訂閱者列隊中, 從而VM中的某個屬性改變,就會自動刷新視圖、
豬腳登場
avalon實現雙向綁定跟ko的實現其實大同小異,但是ko的實現異常的復雜,avalon則清晰很多
上列子,然后分析
HTML結構
<div id='box' ms-controller="box"> <div style=" background: #a9ea00;" ms-css-width="w" ms-css-height="h" ms-click="click"></div> <p>{{ w }} x {{ h }}</p> <p>W: <input type="text" ms-model="w" data-event="change"/></p> <p>H: <input type="text" ms-model="h" /></p> </div>
JS
avalon.define("box", function(vm) { vm.w = 100; vm.h = 100; vm.click = function() { vm.w = parseFloat(vm.w) + 10; vm.h = parseFloat(vm.h) + 10; } }) avalon.scan(document.getElementById('box')
就是官網提供的一個DEMO http://rubylouvre.github.io/mvvm/、
分析HTML結構:
- ms-controller="box" 作用域范圍
- ms-css-width="w" css樣式綁定寬度
- ms-css-height="h" css樣式綁定高度
- ms-click="click" 綁定click處理事件
- {{ w }} x {{ h }} 插值表達式 數據填充
- ms-model="w" 改名叫ms-duplex 雙向綁定
JS處理中:
- avalon.define 構建一個view model視圖模型 box就是作用域范圍
- vm.w = 100; 定義一個監控屬性寬,默認值是100
- vm.h = 100; 定義一個監控屬性高,默認值是100
- vm.click 定義一個click處理方法
- avalon.scan 掃描節點指定綁定
view model視圖模型的構建在以前已經講過了,這里主要講下雙向綁定的構建及處理的原理
1.構建VM的時候,對監控屬性進程轉化處理,生成一個監控對象
監控對象是通過Object.defineProperty轉換過的處理函數,所以在setter,getter時候會調用轉化的處理函數,這個用戶是不可見的
賦值時處理 setter
var old = value; if (valueType === "array" || valueType === "object") { //監控數組 if (value && value.$id) { updateViewModel(value, neo, Array.isArray(neo)) } else if (Array.isArray(neo)) { value = Collection(neo) value._add(neo) } else { value = modelFactory(neo, neo) } } else { value = neo } accessor.value = value; model[name] = value && value.$id ? value.$model : value; //值變化了,通知頂層改變 notifySubscribers(accessor); vmodel.$fire && vmodel.$fire(name, value, old)
取值時處理 getter
collectSubscribers(accessor); //收集視圖函數 return value
操作時
vm.w = 100 setter,就會默認調用賦值處理函數
vm.w 同樣,getter 調用取值函數
這樣方式,比ko的this.firstName() + " " + this.lastName(); 友愛多了,因為KO轉換的是處理函數,必須要函數調用。。。別提多變扭
關鍵點:
collectSubscribers //收集視圖函數
notifySubscribers //值變化了,通知頂層改變
這個2個方法,用來處理依賴關系的
實現的流程:
預處理過程:
- 生成監控屬性,其中監控屬性subscribers用來收集依賴處理的回調
- 掃描DOM節點上的對應的屬性編碼 比如,ms-css-width="w"
- 根據css類型找到對應bindingHandlers處理句柄函數
- 通過parseExpr分解出求值函數
- 通過watchView函數生成視圖更新函數
- 視圖更新函數updateView加入到當前對應監控屬性的subscribers隊列中,形成依賴關系
交互時:
- 用戶點擊某個通過ms-click="click” 綁定事件的元素(當value變化時改變model的值)視圖通知模型
- 執行預先生成updateModel函數,通過執行’取值時處理’通知notifySubscribers(accessor)
- 執行accessor中subscribers的所有依賴函數,從而更新所有依賴
畫了張圖。。
本文只是很簡單的說了下監控屬性大概的邏輯,還有計算屬性的處理,轉化,調用,之后會分解
評價:實現非常巧妙,而且代碼寫的清晰易懂