avalonJS-源碼閱讀(三) VMODEL

avalon dom小結

看過前面三篇文章後,應該會對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] = datadelete 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

avalon Observable

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

avalon modelFactory

modelFactory由兩個重要的函數構成 loopModeldescriptorFactoryoop

loopModel

在說loopModel以前,咱們要先了解下avalon的5種屬性。

  1. model屬性,存放着未被avalon處理用戶定義的屬性集合。你能夠把它當成java的entity,和後臺進行數據交互時,直接和它進行交互,avalon會自動觸發訂閱。

  2. normalProperties 普通屬性,不須要雙向綁定的,例如放在$skipArray裏的屬性,用戶自定義以$開頭的,函數以及avalon自定義的一些函數($event, $watch 等)。

  3. accessingProperties 監控屬性,要進行雙向綁定的屬性。

  4. watchProperties 強制要監聽的屬性,以$開頭的,但又想強制監聽它,例如avalon內部定義的$event等。這個屬性是normalProperties的補充,屬於內部屬性,用戶不會使用到它。

  5. computedProperties 計算屬性,用戶本身自定義的set和get方法。是accessingProperties的補充。

loopModel函數的主要做用是將用戶定義的vm object進行屬性歸類,並生成被觀察者函數accessor。
做爲被觀察者accessor,他須要綁定來自dom的觀察者到自身上以及將值大的改變通知到來自dom的觀察(notifySubscribers)和來自用戶自定義的觀察(safeFire)。依據accessingProperties、computedProperties的嵌套的的不一樣特色構造了三種類型的accessor。
咱們撿一個對object嵌套的accessor實現來看看。他除了實現上面的功能外,還須要調用modelFactory對嵌套的每一層生成VMODULE結構,在這個實現上,爲了儘量的複用現有代碼,犧牲了數據結構,avalon.vmodels關於$model的記錄有些重複。至於updateWithProxyupdateVModel函數以及關於的詳細講解,會在之後補充上。

函數介紹

isEqual
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反覆閱讀(至於高級適不適合,我就不知道了)。