1.請你說下數(shù)據(jù)綁定(數(shù)據(jù)綁定原理):
- 首先通過一次渲染操作觸發(fā)Data的getter(這里保證只有視圖中需要被用到的data才會觸發(fā)getter)進(jìn)行依賴收集校哎,這時候其實Watcher與data可以看成一種被綁定的狀態(tài)(實際上是data的閉包中有一個Deps訂閱者,在修改的時候會通知所有的Watcher觀察者)榜轿,在data發(fā)生變化的時候會觸發(fā)它的setter,setter通知Watcher,Watcher進(jìn)行回調(diào)通知組件重新渲染的函數(shù),之后根據(jù)diff算法來決定是否發(fā)生視圖的更新招刹。
初始化data(個人簡化了的initData)
function initData (vm: Component) {
/*得到data數(shù)據(jù)*/
let data = vm.$options.data
/*遍歷data,和props對象*/
const keys = Object.keys(data)
const props = vm.$options.props
let i = keys.length
//遍歷data中的數(shù)據(jù)沥匈,且props和data沒有key沒有沖突
while (i--) {
if (!(props && hasOwn(props, keys[i]))) {
proxy(vm, `_data`, keys[i])
}
/*這里是我們前面講過的代理蔗喂,將data上面的屬性代理到了vm實例上*/
}
}
/*從這里開始我們要observe了忘渔,開始對數(shù)據(jù)進(jìn)行綁定*/
observe(data, true /* asRootData */)
}
initData函數(shù)主要做了兩件事:
- _data上面的數(shù)據(jù)代理到vm實例上
- 通過observe將所有數(shù)據(jù)變成observable
proxy
/*添加代理*/
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
- 通過proxy函數(shù)將data上面的數(shù)據(jù)代理到vm上高帖,這樣就可以用app.text代替app._data.text了
Observe
Vue的響應(yīng)式數(shù)據(jù)都會有一個ob的屬性作為標(biāo)記,里面存放了該屬性的觀察器畦粮,也就是Observer的實例散址,防止重復(fù)綁定。
Observer為數(shù)據(jù)加上響應(yīng)式屬性進(jìn)行雙向綁定宣赔。如果是對象則進(jìn)行深度遍歷预麸,為每一個子對象都綁定上方法,如果是數(shù)組則為每一個成員都綁定上方法儒将。
Watcher
- Watcher是一個觀察者對象吏祸。依賴收集以后Watcher對象會被保存在Deps中,數(shù)據(jù)變動的時候會由Deps通知Watcher實例钩蚊,然后由Watcher實例回調(diào)cb進(jìn)行視圖的更新贡翘。
Dep
- Dep是一個發(fā)布者,可以訂閱多個觀察者砰逻,依賴收集之后Deps中會存在一個或多個Watcher對象鸣驱,在數(shù)據(jù)變更的時候通知所有的Watcher。
defineReactive
- defineReactive的作用是通過Object.defineProperty為數(shù)據(jù)定義上getter\setter方法蝠咆,進(jìn)行依賴收集后閉包中的Deps會存放Watcher對象踊东。觸發(fā)setter改變數(shù)據(jù)的時候會通知Deps訂閱者通知所有的Watcher觀察者對象進(jìn)行試圖的更新北滥。
2.小伙子,說下虛擬dom:
咳咳闸翅,一般來說再芋,我們要修改試圖的話需要直接操作dom執(zhí)行各種事件才行,是應(yīng)用一大就會變得難以維護(hù)缎脾。
vnode就是把真實dom都抽象成一顆以js對象構(gòu)成的抽象樹祝闻,在修改抽象樹的數(shù)據(jù)后將抽象樹轉(zhuǎn)換成真實dom重繪到頁面上去。
遗菠,當(dāng)某個抽象樹的某個數(shù)據(jù)被修改的時候联喘,set方法會讓閉包中的Dep調(diào)用notify通知所有訂閱者Watcher,Watcher通過get方法執(zhí)行vm._update(vm._render(),hydrating)
經(jīng)過diff算法只需要修改抽象樹修改了的部分即可辙纬,相對于一大片的HTML修改豁遭,大大提高了性能。
Vue使用了這樣的抽象節(jié)點VNode贺拣,它是對真實DOM的一層抽象蓖谢,所以它不依賴某個平臺,比如weex
3.那diff算法你知道怎么運作的嗎譬涡?
diff算法在patch方法內(nèi)闪幽,path將通過新老Vnode節(jié)點的對比,根據(jù)兩者的比較結(jié)果進(jìn)行最小單位地修改視圖涡匀,而不是將整個視圖根據(jù)Vnode重繪盯腌。
diff算法是通過同層的樹節(jié)點進(jìn)行比較而非對樹進(jìn)行逐層搜索遍歷的方式,所以時間復(fù)雜度是O(n)陨瘩,是非常高效的算法腕够。
這張圖代表舊的VNode與新VNode進(jìn)行patch的過程,他們只是在同層級的VNode之間進(jìn)行比較得到變化(第二張圖中相同顏色的方塊代表互相進(jìn)行比較的VNode節(jié)點)
4.使用v-for進(jìn)行列表渲染的時候舌劳,加:key的效果是什么帚湘,為什么會這樣?
效果是更高效的更新虛擬dom甚淡。
數(shù)據(jù)更新后大诸,新老Vnode如果是同一節(jié)點,就會直接修改現(xiàn)有的節(jié)點贯卦,否則就是創(chuàng)建新的dom资柔,移除舊的dom
-
判斷兩個Vnode節(jié)點是否是同一節(jié)點,需要滿足:
- ==key相同==
- tag(當(dāng)前節(jié)點的標(biāo)簽名)相同
- isComment(是否為注釋節(jié)點)相同
- 是否data(當(dāng)前節(jié)點對應(yīng)的對象脸侥,包含了具體的一些數(shù)據(jù)信息建邓,是一個VNodeData類型,可以參考VNodeData類型中的數(shù)據(jù)信息)都有定義
- 當(dāng)標(biāo)簽是<input>的時候睁枕,type必須相同
所以官边,這就是為什么盡可能在使用 v-for 時提供key
延伸: 更新虛擬dom的規(guī)則是這樣:
1.如果新舊VNode都是靜態(tài)的沸手,同時它們的key相同(代表同一節(jié)點),并且新的VNode是clone或者是標(biāo)記了once(標(biāo)記v-once屬性注簿,只渲染一次)契吉,那么只需要替換elm以及componentInstance即可。
2.新老節(jié)點均有children子節(jié)點诡渴,則對子節(jié)點進(jìn)行diff操作捐晶,調(diào)用updateChildren,這個updateChildren也是diff的核心妄辩。
3.如果老節(jié)點沒有子節(jié)點而新節(jié)點存在子節(jié)點惑灵,先清空老節(jié)點DOM的文本內(nèi)容,然后為當(dāng)前DOM節(jié)點加入子節(jié)點眼耀。
4.當(dāng)新節(jié)點沒有子節(jié)點而老節(jié)點有子節(jié)點的時候英支,則移除該DOM節(jié)點的所有子節(jié)點。
5.當(dāng)新老節(jié)點都無子節(jié)點的時候哮伟,只是文本的替換干花。