本版本對循環綁定作了巨大改進,感謝@soom, @limodou, @ztz, @Gaubee 提供的大量測試文件。css
咱們看最後一條,咱們能夠相似純JS操做爲內存操做,DOM操做爲IO操做,執行一萬次前者所需的時間可能還比不上一次後者的。DOM操做的開銷就是這麼大。有的DOM操做還會引發reflow,這危害更大。所以明智的作法就是將要操做的節點移出DOM樹。更好的辦法是,此多個DOM操做合成一個,所有在文檔碎片中搞完才插入DOM樹。html
咱們看下面的註解:git
<div ms-controller="box"> <div ms-each-el="array" id="aaa"> <p>{{$index}}----{{el}}</p> </div> </div> avalon.define("box", function(vm) { vm.array = [1, 2, 3, 4, 5] }) 實現過程 當掃描到div#aaa 將div#aaa的全部子節點複製一份到文檔碎片vTemplate 執行begin命令,將vTemplate複製一個空的文檔碎片vTransation( cloneNode(false) ), 設置全局變量flagTransation = true; 開始循環數組 執行insert命令 將vTemplate複製一個文檔碎片vEl( cloneNode(true) ), 將對應的子VM與它進行掃描 此時它的內容應爲 <p>0 --- 1</p> 將vEl appendChild到 vTemplate ..... 重複執行array.length次 執行commit命令,將vTemplate append到div#aaa節點中, 設置全局變量flagTransation = false 從新排列全部$index
在數組有關添加元素的push, unshift, splice這三個方法中,都調用了add方法,它裏面就默認使用事件進行處理。github
array._splice = array.splice array.add = function(arr, insertPos) { insertPos = typeof insertPos === "number" ? insertPos : this.length; notifySubscribers(this, "begin") for (var i = 0, n = arr.length; i < n; i++) { var el = convert(arr[i]) var pos = insertPos + i this._splice(pos, 0, el) notifySubscribers(this, "insert", pos, el) } notifySubscribers(this, "commit", insertPos) if (!this.stopFireLength) { return dynamic.length = this.length } }
notifySubscribers會向上通知updateListView方法,而後讓它執行相關的DOM操做算法
case "begin": list.vTransation = data.vTemplate.cloneNode(false) flagTransation = true case "insert": //將子視圖插入到文檔碎片中 var tmodel = createVModel(pos, el, list, data.args) var tview = data.vTemplate.cloneNode(true) tmodel.$view = tview vmodels = [tmodel].concat(vmodels) tmodels.splice(pos, 0, tmodel) scanNodes(tview, vmodels); data.group = ~~tview.childNodes.length //記錄每一個模板一共有多少子節點 list.vTransation.appendChild(tview) break case "commit": pos = ~~pos //獲得插入位置 IE6-10要求insertBefore的第2個參數爲節點或null,不能爲undefined var insertNode = parent.childNodes[ data.group * pos] || null parent.insertBefore(list.vTransation, insertNode) flagTransation = false resetItemIndex(tmodels) break
嘛,不過此次改動太大了,有關Collection與bindingHandlers["each"]的代碼都幾乎改清光。另外一個值得一提的是VM數組在腓序時,與視圖的同步。這裏涉及如何讓一個數組基於另外一個數組進行排序,個人解決方式以下:數組
var aaa = [1, 2, 3, 4, 5, 1] var bbb = [{v: 2}, {v: 3}, {v: 1}, {v: 1}, {v: 5}, {v: 4}] var swapTime = 0 var isEqual = Object.is || function(x, y) {//主要用於處理NaN 與 NaN 比較 if (x === y) { return x !== 0 || 1 / x === 1 / y; } return x !== x && y !== y; }; for (var i = 0, n = bbb.length; i < n; i++) { var a = aaa[i]; var b = bbb[i] var b = b && b.v ? b.v : b if (!isEqual(a, b)) { console.log(++swapTime) var index = getIndex(a, bbb, i); var el = bbb.splice(index, 1) bbb.splice(i, 0, el[0]) } } function getIndex(a, bbb, start) { for (var i = start, n = bbb.length; i < n; i++) { var b = bbb[i]; var check = b && b.v ? b.v : b if (isEqual(a, check)) { return i } } } console.log(JSON.stringify(bbb)) //在框架中,aaa爲數據模型M中的數組,bbb爲視圖模型VM中的數組
若是有更好的算法,請多多指教。ruby
通過此次大重構後,avalon在API上基本沒有變化了,將來的v0.9就是fix BUG而後發佈正式版。app
迷你MVVM框架在github的倉庫https://github.com/RubyLouvre/avalon框架
官網地址http://rubylouvre.github.io/mvvm/mvvm
你們能夠加入QQ羣:79641290進行討論,此羣爲技術羣,禁水!