引子:
最近工做挺忙,avalon只能斷斷續續的寫下去了,大概看了下angular的源碼,看到小一半就比較難堅持了,是塊硬骨頭,慢慢啃吧html
不過angular的的文檔中用詞仍是很優雅:json
- HTML編譯器
- 指令
- 編譯
- 連接
- 過濾器
- 注入器
- 控制器
- 管道
等等…看起來以爲老高級,其實avalon也間接的部分實現,原理也不是很複雜數組
avalon版本更新很快,新版的加入了AMD規範的模塊加載器,還修復了不少BUG,不過相信短時間內實現的核心仍是不會變化,因此我依然以如今的版本分析爲主併發
編譯期
- 視圖背後的代碼就是控制器,在mvvm中就是vm視圖模型,它的主要工做就是構造模型,並把模型與回調方法一併發送到視圖,視圖能夠看做做用到模版HTML上的投影
- 因此在編譯階段,咱們的控制器就會把用戶定義的數據模型給構造出來
- avalon中的modelFactory工廠方法構造出的model對象其實就是真正的控制器了,至於構造出來的控制器如何注入到視圖上的,等之後分析到HTML編譯器雙向綁定吧
- model 原本是系統內部定義的一個臨時對象,將控制器和avalon的做用域對象給關聯起來
接上一節app
收集用戶定義的scope在過濾的時候作了2個處理mvvm
callGetters.push(accessor);
callSetters.push(name);
收集控屬性賦監與計算屬性,是爲了在初始化scpoe中的代碼未處理的方法函數
處理監控屬性
//給控屬性賦監值,調用對應監控屬性的set ->accessor方法
callSetters.forEach(function(prop) {
// model.firstName = '司徒' ->調用了 model.firstName->set->accessor方法
model[prop] = scope[prop]; //爲空對象賦值
});
遍歷監控屬性收集器,給初始化的空model對應的方法賦值this
這裏注意各重點,賦值的的時候實際是調用的accessor方法,由於set get給轉換過了spa
accessor 源碼prototype
accessor = function(neo) { //建立監控屬性或數組
//若是有參數
if (arguments.length) {
//用於改變用戶定義的函數內部能訪問正確的是vm模型時,會觸發這個處理須要跳過
if (stopRepeatAssign) {
return; //阻止重複賦值
}
if (value !== neo) {//傳的值與舊的不相等
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, model, name);
} else {
value = modelFactory(neo);
}
} else {
//若是是簡單類型
value = neo;
}
//每次更新都會修正爲新的賦值
json[name] = value && value.$id ? value.$json : value;
//更新依賴,就是當前的操做會觸發與之相關
notifySubscribers(accessor); //通知頂層改變
model.$events && model.$fire(name, value, old);
}
} else {
collectSubscribers(accessor); //收集視圖函數
return value;
}
};
其實源碼註釋很清楚了,咱們概括下執行的流程
- 判斷參數是調用set仍是get方法
- stopRepeatAssign //阻止重複賦值,是這factory.apply(0, deps);重置上下文的時候處理的
- 監控數組處理
- 更新json //收集原始的定義
- notifySubscribers //更新依賴,就是當前的操做會觸發與之相關
- model.$events 觸發訂閱的自定義事件
notifySubscribers 其實就是關鍵的執行點,執行當前做用域所依賴的全部的,這個在雙向綁定的時候就能夠仔細討論了
處理計算屬性:
監控屬性涉及用戶定義的處理,因此要作不少關聯的處理
流程:
- 收集依賴關係
- 處理用於定義的get方法
- 更新json
- 返回定義函數的結果
Publish 對象是將函數曝光到此對象上,方便訪問器收集依賴
fn.nick 就是對應的計算屬性方法名稱,在過濾的時候 accessor.nick = name;附上的
一樣執行了accessor方法,因爲沒有傳遞參數,實際上就是在處理收集依賴關係了
accessor 源碼
accessor = function(neo) { //建立計算屬性
//@第三層做用域
if (arguments.length) {
if (stopRepeatAssign) {
return; //阻止重複賦值
}
if (typeof setter === "function") {
setter.call(model, neo);
}
if (oldArgs !== neo) { //因爲VBS對象不能用Object.prototype.toString來斷定類型,咱們就不作嚴密的檢測
oldArgs = neo;
notifySubscribers(accessor); //通知頂層改變
model.$events && model.$fire(name, neo, value);
}
} else {
if (openComputedCollect || !accessor.locked) {
collectSubscribers(accessor);
}
//解析出get函數,返回新的值
return value = json[name] = getter.call(model); //保存新值到json[name]
}
};
collectSubscribers方法
很明顯的處理,取出開始push到的Publish的處理回調,取出依賴列表,合併
ensure 法只有當前數組不存在此元素時只添加它
全部此時的 subscibers關聯就有值了
最後執行定義的get方法,更新json
注意的一點
這裏又涉及到取值的問題,因此又會關對應的執行各自的accessor
因此這裏會進行一次收集依賴了
在轉換的完畢model後,會給model增長訂閱的特性與一些屬性
- model.$json = json; //純淨的js對象,全部訪問器與viewModel特有的方法屬性都去掉
增長事件訂閱
- model.$events = {}; //VB對象的方法裏的this並不指向自身,須要使用bind處理一下
- model.$watch = Observable.$watch.bind(model);//用於監聽ViewModel中的某屬性變化,它將新值與舊值都傳給回調
- model.$unwatch = Observable.$unwatch.bind(model);//卸載$watch綁定的回調
- model.$fire = Observable.$fire.bind(model); //觸發$watch指定的回調
ViewModel的ID,方便經過avalon.models[$id]訪問
- model.$id = generateID();
判斷是否爲模型中的原始數據
最後返回工廠轉化後的model對象
主方法入口
avalon.define
其實這裏有一種重點
做者再次把定義的模型給執行了一遍,用意呢?
請看
vm.xxx = 1;
vm.fullName = fucntion(){
vm.xxxx
}
在定義的VM中的方法中,若是再次訪問vm.xxx屬性,
這時候內部引用不對了 VM仍是指向原來的普通JS對象,而不是真正的VM因此須要apply一次,改變
那麼有個精妙的思路:
咱們 factory.apply(0, deps); //重置它的上下文
因此把方法執行一次把內部引用換給model
由於轉換了模型關係,因此監控屬性與計算屬性都會有對應的set get操做了,相對應的上下文也變成了vm了
stopRepeatAssign return 阻止了,防止重複賦值
avalon.models[name] = model; 掛到了全局的models中,方面之後使用
下章就開始講HTML編譯器與指令,如何真正開始工做了