avalon之因此能在頁面處理1W個綁定(angular對應的數字是2000),出於兩個重要設計——基於事件驅動的雙向綁定鏈及智能CG回收機制。javascript
avalon的雙向綁定鏈是經過Object.defineProperties及VBScript,將要操做VM屬性變成一種訪問器屬性。訪問器屬性是一種特殊的屬性,須要咱們爲它指定setter、getter方法(固然,這也是框架內部生成的,只有計算屬性能夠作一些干預),當用戶對此屬性進行賦值操做時,就會調用setter方法,對它進行讀取時,就會進行getter方法。咱們經過hack進這兩個方法,作各類各樣的事,如依賴收集及事件廣播、觸發$watch回調,其中最重要一點,就是將關聯在它上面的訂閱數組的對象逐個觸發,從而更新視圖,這也是雙向綁定鏈的原理。好比說html
vm.me = 111 <div ms-text="me"></div>
me就會有一個訂閱數組,裏面放着一個對象,裏面包含如此操做這個div的信息,若是有兩個綁定屬性的值存在me這個變量,這數組就有兩個對象。觸發是用戶修改vm.me 時當即發生,不須要像angular那樣調用$apply或$digest方法,也不像angular那樣將全部綁定對象都檢測一次。angular之因此會卡死,由於頁面一旦綁定對象多,這檢測時間也恐怖了,而且這檢測多是深遍歷對象的屬性進行比較的。而avalon每次只會檢測其一個屬性上對應的小數組,所以檢測壓力會相對少量多。java
因爲綁定屬性會轉換綁對象,而且綁定對象包含要操做的元素節點或文本特色這種慣例的存在,就會引起第二個問題,若是回收這些綁定對象呢?angular雖然爲$scope對象添加了一個$destroy,但沒有針對綁定對象有更精細的操做。avalon在1.36以前在notifySubscribers進行綁定對象的element進行是否在DOM樹的檢測,不在就將綁定對象的全部屬性都置爲null,並從當前訂閱數組移除。node
avalon1.36/1.4引入全新的CG回收機制,頁面上的{{}}插值表達式ms-*屬性,通過掃描後,變成一個個綁定對象,對象包含name、value、type、param、vmodels、priority、args、vmodels、evaluator、handler等屬性與方法,有些綁定對象還會多出template、 group、$repeat、proxies等屬性(1.36前更多,如今都大幅精簡了),所以綁定對象也算一個比較大的JS對象,頁面上的綁定屬性越多,這些綁定對象天然也越多,佔用着大用的內存。若是頁面發生一些移除節點操做,涉及這些綁定屬性原來所在的元素節點,那麼這些綁定對象也應該銷燬,咱們就必須將binding.element 置爲null,纔會方便CG回收。以前是位於notifySubscribers方法裏進行,但它處理的目標是一個很小的數組,不會檢測全部綁定對象,所以總有漏網之魚,這樣積沙成塔,在移動端上就是一個很大的問題,會弄崩手機瀏覽器。git
在1.36/1.4中,內部定義一個全局的$$subscribers數組,全部生成的綁定對象都放到裏面,而後每當我讓VM的屬性發生變化時,就必定會通過notifySubscribers方法,這時對$$subscribers數組的對象進行檢測。而且這檢測也有技巧,爲了減小檢測頻率對瀏覽器形成壓力,每次檢測至少通過333ms纔會進行一次。github
檢測手段,是斷定binding.element是否位於DOM樹上,因爲element多是元素節點,文本節點或註釋節點,IE的原生contains方法也有BUG,因而檢測也是多種多樣的。算法
var $$subscribers = [] function removeSubscribers() { for (var i = $$subscribers.length, obj; obj = $$subscribers[--i]; ) { var data = obj.data var el = data.element var remove = el === null ? 1 : (el.nodeType === 1 ? typeof el.sourceIndex === "number" ? el.sourceIndex === 0 : !root.contains(el) : !avalon.contains(root, el)) if (remove) { //若是它沒有在DOM樹 $$subscribers.splice(i, 1) avalon.Array.remove(obj.list, data) // log("debug: remove " + data.type) obj.data = obj.list = data.evaluator = data.element = data.vmodels = null } } } var beginTime = new Date(), removeID function notifySubscribers(accessor) { //通知依賴於這個訪問器的訂閱者更新自身 var currentTime = new Date() clearTimeout(removeID) if (currentTime - beginTime > 333) { removeSubscribers() beginTime = currentTime } else { removeID = setTimeout(removeSubscribers, 333) } var list = accessor[subscribers] if (list && list.length) { var args = aslice.call(arguments, 1) for (var i = list.length, fn; fn = list[--i]; ) { var el = fn.element if (typeof fn === "function") { fn.apply(0, args) //強制從新計算自身 } else if (fn.$repeat) { fn.handler.apply(fn, args) //處理監控數組的方法 } else if (fn.element) { var fun = fn.evaluator || noop fn.handler(fun.apply(0, fn.args || []), el, fn) } } } }
固然這是兩個要點,爲了提升性能,avalon也像一些大型應用程序那樣,善於利用緩存機制。以前是使用FIFO緩存算法,1.4後改成LRU。數組
<!DOCTYPE html> <html> <head> <title>監控函數</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <script src="avalon.js"></script> <style> .odd{ background: green; } </style> <script type="text/javascript"> var model = avalon.define({ $id: "test", array: [1, 2, 3, 4, 5], more: 10, isOdd: function(el) { return (el + model.more) % 2 === 1 }, add: function() { model.array.push(1) }, remove: function() { model.array.pop() }, change: function(){ model.more = 11 } }) </script> </head> <body ms-controller="test" > <ul> <li ms-repeat="array" ms-class="odd:isOdd(el)">{{el}}</li> </ul> <p><button ms-click="add">add</button><button ms-click="remove">remove</button><button ms-click="change">change</button></p> </body> </html>