MVVM
簡(jiǎn)單地說就是數(shù)據(jù)驅(qū)動(dòng)視圖,視圖改變(事件)也可以改變數(shù)據(jù),就是雙向綁定的概念。
實(shí)現(xiàn)
為了監(jiān)聽數(shù)據(jù)的改變容达,從而響應(yīng)到視圖上,用的是Vue雙向綁定的核心Object.definePrototype(obj,key,{...})
垂券,編寫Observer.js來實(shí)現(xiàn)花盐。若set被觸發(fā),通知所有的訂閱者(dep中存儲(chǔ)的是所有訂閱者實(shí)例對(duì)象)菇爪,且在get中引入Dep.target算芯,僅在添加watcher時(shí)主動(dòng)賦值,防止之后多次添加watcher凳宙,并且get返回當(dāng)前的val值熙揍。
Object.defineProperty(data,key,{
enumerable: true, // 可枚舉,可被Object.value()等遍歷方法所遍歷
configurable: true, // 可再重新定義氏涩,即可被修改届囚,可被刪除, 默認(rèn)為false
get: function(){
// console.log(Dep.target)
// Dep.target現(xiàn)在其實(shí)為null,但是在watcher.js中每次get都將watch自身添加到全局的Dep.target中
// 等到value獲取完畢,再將Dep.target清空
// Dep.target作為閉包,在函數(shù)中保持了dep的存在
// 如果沒有Dep.target,dep會(huì)被清除,在set中就無法通過dep.nptify()來出發(fā)watcher了
// Dep.target && dep.addSub(Dep.target)
if(Dep.target){
dep.addSub(Dep.target) // 添加一個(gè)訂閱者
}
return val
},
set: function(newVal){
console.log('值變化')
if(newVal === val) return
val = newVal
// 通知所有訂閱者
dep.notify()
}
})
訂閱者watcher.js關(guān)聯(lián)著模板編譯,每個(gè)生成的訂閱者都包含一個(gè)修改模板的callback是尖,一旦對(duì)應(yīng)發(fā)布者Observer.js中的set函數(shù)執(zhí)行意系,所有對(duì)應(yīng)訂閱者接收到通知,就會(huì)執(zhí)行該訂閱者被創(chuàng)建時(shí)包含的callback饺汹,修改視圖昔字。
update: function(){
this.run() // 屬性值變化收到通知
},
run: function(){
// 數(shù)據(jù)改變時(shí)
var value = this.vm.data[this.exp] // 取到最新的值 || this.get()
var oldVal = this.value // 存儲(chǔ)老值
if(value !== oldVal){
this.value = value
this.cb.call(this.vm,value,oldVal) // 執(zhí)行compile中的回調(diào),更新視圖
}
}
在Vue中實(shí)現(xiàn)數(shù)據(jù)綁定有兩種途徑,一種是雙大括號(hào){{}}
首繁,一種是v-model
,為了將數(shù)據(jù)綁定到頁(yè)面陨囊,同時(shí)為了給包含這兩種情況的模板添加訂閱者弦疮,編寫compile.js來實(shí)現(xiàn)。compile.js接收MVVM實(shí)例中掛載的根節(jié)點(diǎn)和該實(shí)例對(duì)象蜘醋。先將所有節(jié)點(diǎn)剪切到fragment文檔片段中胁塞,再通過遍歷所有節(jié)點(diǎn)的方式,碰到包含數(shù)據(jù)綁定的節(jié)點(diǎn),就創(chuàng)建一個(gè)新的watcher啸罢,并包含改變視圖的callback從而與watcher.js關(guān)聯(lián)编检,碰到其他類似事件綁定的節(jié)點(diǎn),則給其綁定事件監(jiān)聽器扰才,從而實(shí)現(xiàn)v-on的事件綁定效果允懂。另外使用文檔片段的好處是避免了頁(yè)面的頻繁的回流重繪,文檔節(jié)點(diǎn)使用完畢后返回頁(yè)面只需渲染一次即可衩匣。
核心代碼
init(){
if(this.el){
this.fragment = this.nodeToFragment(this.el)
this.compileElement(this.fragment);
this.el.appendChild(this.fragment);
}
},
// 節(jié)點(diǎn)全部轉(zhuǎn)為文檔片段
nodeToFragment(el){
// 創(chuàng)建空的文檔片段
var fragment = document.createDocumentFragment()
var child = el.firstChild
while(child){
// 子節(jié)點(diǎn)推入文檔片段,appendChild會(huì)有剪切的效果@僮堋!@拍蟆I佟!
fragment.appendChild(child)
child = el.firstChild
}
return fragment
},
compileElement(el){
// 創(chuàng)建好的文檔片段拿過來編譯
var childNodes = el.childNodes
var self = this
// dom數(shù)組不是真正的數(shù)組柄延,沒有遍歷方法
// 多層嵌套slice處理效率過低導(dǎo)致執(zhí)行失效
// [].slice.call(childNodes).forEach(function(node){})
// 也好像不是蚀浆??搜吧?市俊?在控制臺(tái)測(cè)試了一下都運(yùn)行的飛快啊...
Array.prototype.forEach.call(childNodes,function(node){
// 處理{{}}的正則
var reg = /\{\{(.*)\}\}/
var text = node.textContent
if(self.isElementNode(node)){
self.compile(node)
}else if(self.isTextNode(node) && reg.test(text)){
// 檢測(cè)到雙括號(hào)
self.compileText(node,reg.exec(text)[1])
}
// 遞歸編譯,編譯所有節(jié)點(diǎn)
if(node.childNodes && node.childNodes.length){
self.compileElement(node)
}
})
}