avalon2的vm是一個很是重要的東西,其設計原型最初脫胎於knockout.js,但到avalon1.6中,終於尋得本身的方案,更精簡,更易用,更魔幻。javascript
vm是一種特殊的數據結構,看起來像普通對象,但它大部分屬性都被重寫了,從而實現「操做數據即操做視圖」的效果。咱們在定義vm時,通常須要定義$id,其次是其餘業務數據屬性,它們都是來自後端的數據表。在1.4,1.5中,還有一個叫$skipArray的數組,用於方置一些只用同步一次視圖的屬性名,這是爲了提升性能。由於將普通屬性轉換能同步視圖的特殊屬性,咱們通常稱之爲監控屬性(knockoutjs是這麼叫的),其真正術語叫訪問器屬性。此外1.4與knockout同樣,能定義計算屬性,但2.0已經廢掉,這裏就不詳述了!html
$id vm的ID名,用於ms-controllerjava
$skipArray, 數組, 用於指定那些屬性不用轉換監控屬性, 這個在定義時指定, 生成後的vm並不存在。react
var vm = avalon.define({ $id: 'test', a: 11, b: 22 }) vm.$watch('a', function(newValue, oldValue){ }) console.log(vm)
打開控制檯,咱們還會發現vm多出一些特殊屬性,它們都是以$開頭的git
$events, 用於放咱們的$watch回調github
$fire, 用於觸發某一個屬性的全部回調chrome
$watch, 用於監聽某個屬性的變化,當它變化時,將對應回調依次執行後端
$hashcode, $id可能有重複,但$hashcode不會重複數組
$track, 這是一個字符串,裏面包括vm的全部屬性名(除了那些內置的$開頭屬性),以;;隔開(這用於內部對象轉換的)ruby
$model, 返回純淨的JS對象
$element, 同名的ms-controller元素節點,這是應社區的要求,怎麼經過vm獲得元素
$render, 靈感來自react的render方法,用於生成對應的虛擬DOM樹
$accessors, 儲存全部監控屬性的定義,這在avalon.modern及avalon.next不存在,avalon.modern能夠經過 Object.getOwnPropertyDescriptor獲得訪問器屬性的定義,而avalon.next是使用Proxy實現vm,徹底沒有這方面的必要。
一般咱們把avalon.define建立的vm叫頂層vm,內部使用masterFactory生成。
若是一個vm的屬性 也是一個對象,那麼它也會轉換爲vm,叫子級vm,或子vm,內部使用slaveFactory生成。
var vm = avalon.define({ $id: 'test', a: 11, b: { c: 22 } }) console.log(vm.b)
vm.b就是一個子vm,它與頂層vm有些區別,首先其$id爲頂層vm的$id加上其屬性名構成, 即"test.b"。它少了一些系統屬性,如$element, $render, $watch, $fire, $events(這個在avalon.next存在),能夠說是一個輕量的vm。它的數據發生改動時,它不會本身處理$watch回調,而是交由頂層的vm來處理,由於全部回調都放在頂層vm的$events上。
var vm = avalon.define({ $id: 'test', a: 11, arr: [{b:1},{b:2},{b:3}] }) console.log(vm.arr)
若是vm的子級屬性是一個數組,那麼與1.4同樣,轉換爲監控數組。監控數組就是一個push, unshift, splice, pop, shift, sort, reverse等方法被重寫的數組。它在內部是由arrayFactory方法生成的。
若是監控數組的每一個元素是一個對象,那麼它們會轉換爲頂層vm, 由masterFactory生成,它們的$id名都叫作test.arr.*。這時大家明白$hashcode的用處了吧(如去重,排序)。
在avalon2,還提供了一個工廠來合併兩個vm
<!DOCTYPE html> <html> <head> <title>TODO supply a title</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="./dist/avalon.js"></script> <script> var vm1 = avalon.define({ $id: "test", a: 111 }) vm1.$watch('a', function(){ console.log('vm1.a change') }) var vm2 = avalon.define({ $id: 'test2', b: 222 }) vm2.$watch('b', function(){ console.log('vm2.b change') }) var vm3 = avalon.mediatorFactory(vm1,vm2) //這個回調其實放在vm1.$events中 vm3.$watch('a', function(){ console.log('vm3.a change') }) //這個回調其實放在vm2.$events中 vm3.$watch('b', function(){ console.log('vm3.b change') }) console.log('------') vm3.a = 22 vm3.b = 44 </script> <style> .ms-controller{ display:none; } </style> </head> <body> <div ms-controller="test"> <input ms-duplex="@a" /> <p>{{@a}}</p> </div> </body> </html>
在chrome控制檯中依次打印以下:
有人可能不理解爲何輸出6次,咱們先忽視調試信息。
首先前兩個是vm3.a的值發生改變,由111變成22, 因爲vm3.a實際上與vm1.a是同一個東西,所以都觸發了。
其次中間兩個是vm3.b的值發生變化,由222變成44,因爲vm3.b實際上與vm2.b是同一個東西,所以都觸發了。
最後是ms-duplex要將input.value同步爲vm1,a這時爲數字的22,但到了元素上,變成字符串的22, 因而又觸了兩下!
avalon.mediatorFactory是一個重要的方法,是實現ms-controller套嵌的關鍵,你們有興趣的話能夠看看其源碼。
頂層vm | masterFactory | 供用戶操做與保存回調與同步視圖 |
子vm | slaveFactory | 承載更多用戶數據 |
監控數組 | arrayFactory | 承載更多用戶數據 |
內部vm | mediatorFactory | 容納多個vm的數據與回調,並做爲參數傳入$render方法,生成新的虛擬DOM樹 |
與vm做用域相關的有三個指令,ms-skip, ms-controller, ms-important。
ms-skip,讓vm的做用域進不到此元素內部,那麼裏面的{{}}就不會被替換了。
ms-controller, 讓此vm的做用域進入此元素內部,而且若是它上方已經有ms-controller,那麼它們所指向的vm會進行合併。合併方式使用mediatorFactory實現。
ms-important, 讓此vm的做用域進入此元素內部,而且屏蔽上方的ms-controller或ms-important的vm的影響。
<!DOCTYPE html> <html> <head> <title>TODO supply a title</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="../dist/avalon.js"></script> <script> var vm = avalon.define({ $id: "test", aaa: 111, ddd: 444 }) var vm2 = avalon.define({ $id: "test2", ddd: 555 }) var vm3 = avalon.define({ $id: "test3", aaa: 333 }) </script> </head> <body ms-controller="test"> <p>{{@aaa}}</p> <div ms-controller="test2"> {{@aaa}}::{{@ddd}} </div> <div ms-important="test3"> {{@aaa}}::{{@ddd}} </div> </body> </html>
最後請你們點星加贊!