本版本對循環綁定做了巨大改進,感謝@soom, @limodou, @ztz, @Gaubee 提供的大量測試文件。
- fix scanNodes, 在循環綁定(ms-each)掃描元素節點時必須 nextTick,否則舊式IE會忙碌不過來。
- fix ms-css ,舊式IE style[name] = value, 當value為NaN ,不帶單位或不是數值什么會拋異常,需要try catch。
- 舊式IE下有些元素的innerHTML是只讀的, 因此不能一律使用innerHTML,並且有些元素的生成,如script標簽是不會執行,為此我引入新的parseHTML模塊來處理此事。
- fix AMD 加載因為手誤進錯分支的BUG
- fix scanExpr bug, 它在IE10有時會多生成一個綁定對象,異致不渲染錯誤。
- 重構Collection內部對象與ms-each綁定,引入“事務”的概念,讓其插入節點時更加智能高效。
我們看最后一條,我們可以類似純JS操作為內存操作,DOM操作為IO操作,執行一萬次前者所需的時間可能還比不上一次后者的。DOM操作的開銷就是這么大。有的DOM操作還會引起reflow,這危害更大。因此明智的做法就是將要操作的節點移出DOM樹。更好的辦法是,此多個DOM操作合成一個,全部在文檔碎片中搞完才插入DOM樹。
我們看下面的注解:
<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方法,它里面就默認使用事件進行處理。
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中的數組
如果有更好的算法,請多多指教。
經過這次大重構后,avalon在API上基本沒有變化了,未來的v0.9就是fix BUG然后發布正式版。
迷你MVVM框架在github的倉庫https://github.com/RubyLouvre/avalon
官網地址http://rubylouvre.github.io/mvvm/
大家可以加入QQ群:79641290進行討論,此群為技術群,禁水!
