vue算是很通俗的源碼分析(面試裝逼大法)

1.請你說下數(shù)據(jù)綁定(數(shù)據(jù)綁定原理):

image
  • 首先通過一次渲染操作觸發(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é)點的時候,只是文本的替換。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容