看過前面三篇文章後,應該會對avalon關於dom的處理有個大致的理念。這裏再理一遍:avalon經過手動觸發scan函數來遍歷dom。而後根據ms-import ms-container ms-include ms-skip
肯定VMODELS的做用域,接下來即是處理用戶代碼並生成相應的函數,經過registerSubscriber
函數,將dom生成的函數以及相應的關聯數據進行註冊。
剩下的,就是其餘模塊的事情了。javascript
如常html
//VMODULE 嚴格意義上,這個不算數據結構,並且,這是object一層的結構,若是object是嵌套式的 // { // a:{ // b:"嵌套" // } // } // 那麼這個比較複雜了 { $id:$string,//define函數的第一個參數,若是是嵌套層的,則隨機 $model:$obj,//define定義中的第二個參數中的vm。若是是嵌套層的,則$model爲當前嵌套層及如下的值 $watch:$fn, $unwatch:$fn, $fire:$fn,//以上三個是Observable對象下的三個方法,不過他們的上下文被修改了。 $skipArray:true|$array,//要忽略的參數 hasOwnProperty:$fn,//重寫的方法,將其做用域定義到$model $accessors:$obj,下文將詳細講解。 $event:$obj,//記錄用戶定義的$watch get ...:$fn, set ...:$fn, ...:...//通過defineProperty處理的model值 } //$accessors, { $modelName:$fn //$fn[subscribers] =["記錄訂閱者"] $vmodel:$obj// 嵌套層時,出現 }
在講解 avalon vmodel以前,必定要對觀察者模式有個清晰的瞭解,觀察者模式又叫訂閱發佈模式(Subscribe/Publish),具體講解參見觀察者模型。
在avalon中,做者用了兩遍觀察者模型,分別解決了model和view的交互和擴展用戶監控model值的問題。具體實現模塊爲依賴收集與觸發和Observable。和java代碼中實現的觀察者不一樣的是,被觀察者會建立一個數組,數組內存放着全部的觀察者,而觀察者則都是函數。
當被觀察者產生改變時,觀察者函數則會被附加上下文環境後,依次執行。java
這裏先上下源碼,用來作輔助講解。數組
function registerSubscriber(data) { Registry[expose] = data //暴光此函數,方便collectSubscribers收集 avalon.openComputedCollect = true var fn = data.evaluator if (fn) { //若是是求值函數 if (data.type === "duplex") { data.handler() } else { data.handler(fn.apply(0, data.args), data.element, data) } } else { //若是是計算屬性的accessor data() } avalon.openComputedCollect = false delete Registry[expose] } function collectSubscribers(accessor) { //收集依賴於這個訪問器的訂閱者 if (Registry[expose]) { var list = accessor[subscribers] list && avalon.Array.ensure(list, Registry[expose])//只有數組不存在此元素才push進去 } }
上面代碼中,accessor
函數的[subscribes]屬性,作了被觀察者存儲觀察者的事情。
爲何registerSubscriber
函數就敢確定在 Registry[expose] = data
和delete Registry[expose]
之間,collectSubscribers
會被執行?
祕密就在於descriptorFactory
函數對Object.defineProperty
函數的應用。咱們先寫一點Object.defineProperty
的簡單例子,具體理解可參考數據屬性和訪問器屬性。數據結構
var a={}; Object.defineProperty(a,"a",{ get:function(){ console.log("get a") return "默認值" }, set:function(val){ console.log("set a") value=val; } }); console.log(a.a); console.log(a.a="new value") console.log(a.a); // get a // 默認值 // set a // new value // get a // 默認值 var memeryValue="默認值";//we need to save the value in some where Object.defineProperty(a,"b",{ get:function(){ console.log("get b") return memeryValue }, set:function(val){ console.log("set b") memeryValue=val; } }); console.log(a.b); console.log(a.b="new value") console.log(a.b); // get b // 默認值 // set b // new value // get b // new value
經過上面的例子,結合觀察者模型咱們可知道,經過對get/set的設定,咱們能夠觀察用戶何時對vmodel裏綁定的值進行賦值或讀取,並作相應的處理(對avalon來說,就是調用accessor
函數)。
至於notifySubscribers
函數,則是調出綁定在accessor函數上的觀察者集合,並執行。app
Observable
是一個對象。它下面定義了三個方法,分別爲$watch
、$unwacth
、$fire
。這三個方法會和$events
會被注入到每個用戶定義的vm中(若是用戶定義的vm是嵌套的,方法會在每一個嵌套層都注入一下)。用戶只須要了解$watch
和$unwacth
用法便可,fire
交給avalon自行處理就行了。$events
用來記錄觀察者和被觀察者關係。框架
注意:"$events"下面的屬性會多出相似這樣的值{"modelValue":undefined},而這個值的來源計算屬性
accessor的真麼一段代碼。dom
//name='abc' var backup = vmodel.$events[name]//當vmodel.$events[name]不存在時,backup會被賦值爲undefined vmodel.$events[name] = [] setter.call(vmodel, newValue) vmodel.$events[name] = backup//這時,{abc:undefined}
這個地方好生糾結,本身稍微改了一下:函數
var backup = vmodel.$events[name] vmodel.$events[name] = [] setter.call(vmodel, newValue) if(backup===undefined) delete vmodel.$events[name] else vmodel.$events[name] = backup
modelFactory
由兩個重要的函數構成 loopModel
和descriptorFactory
。oop
在說loopModel
以前,咱們要先了解下avalon的5種屬性。
model屬性,存放着未被avalon處理用戶定義的屬性集合。你能夠把它當成java的entity,和後臺進行數據交互時,直接和它進行交互,avalon會自動觸發訂閱。
normalProperties 普通屬性,不須要雙向綁定的,例如放在$skipArray
裏的屬性,用戶自定義以$
開頭的,函數以及avalon自定義的一些函數($event, $watch 等)。
accessingProperties 監控屬性,要進行雙向綁定的屬性。
watchProperties 強制要監聽的屬性,以$
開頭的,但又想強制監聽它,例如avalon內部定義的$event等。這個屬性是normalProperties的補充,屬於內部屬性,用戶不會使用到它。
computedProperties 計算屬性,用戶本身自定義的set和get方法。是accessingProperties的補充。
loopModel函數的主要做用是將用戶定義的vm object進行屬性歸類,並生成被觀察者
函數accessor。
做爲被觀察者
accessor,他須要綁定來自dom的觀察者到自身上以及將值大的改變通知到來自dom的觀察(notifySubscribers)和來自用戶自定義的觀察(safeFire)。依據accessingProperties、computedProperties的嵌套的的不一樣特色構造了三種類型的accessor。
咱們撿一個對object嵌套的accessor實現來看看。他除了實現上面的功能外,還須要調用modelFactory
對嵌套的每一層生成VMODULE結構,在這個實現上,爲了儘量的複用現有代碼,犧牲了數據結構,avalon.vmodels
關於$model的記錄有些重複。至於updateWithProxy
、updateVModel
函數以及關於的詳細講解,會在之後補充上。
var isEqual = Object.is || function(v1, v2) { if (v1 === 0 && v2 === 0) { return 1 / v1 === 1 / v2 } else if (v1 !== v1) { return v2 !== v2 } else { return v1 === v2 } }//雖然看不懂,但不妨礙使用
avalon的雙向綁定的基本內容就這麼多了。代碼讀到如今終於有一種可解脫的感受了。感謝司徒正美給咱們帶來如此優秀的代碼,另推薦他的一本書《javascript框架設計》,雖然校驗的不怎麼樣,小bug不斷,但內容絕對豐滿,適合低中級的Jser反覆閱讀(至於高級適不適合,我就不知道了)。