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),是非常高效的算法。
- image
這張圖代表舊的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é)點的時候,只是文本的替換。