天天看點

vue key重複_12道vue高頻原理面試題,你能答出幾道?

vue key重複_12道vue高頻原理面試題,你能答出幾道?

前言

本文分享 12 道 vue 高頻原理面試題,覆寫了 vue 核心實作原理,其實一個架構的實作原理一篇文章是不可能說完的,希望通過這 12 道問題,讓讀者對自己的 Vue 掌握程度有一定的認識(B 數),進而彌補自己的不足,更好的掌握 Vue ❤️

1. Vue 響應式原理

vue key重複_12道vue高頻原理面試題,你能答出幾道?

核心實作類:

Observer : 它的作用是給對象的屬性添加 getter 和 setter,用于依賴收集和派發更新

Dep : 用于收集目前響應式對象的依賴關系,每個響應式對象包括子對象都擁有一個 Dep 執行個體(裡面 subs 是 Watcher 執行個體數組),當資料有變更時,會通過 dep.notify()通知各個 watcher。

Watcher : 觀察者對象 , 執行個體分為渲染 watcher (render watcher),計算屬性 watcher (computed watcher),偵聽器 watcher(user watcher)三種

Watcher 和 Dep 的關系

watcher 中執行個體化了 dep 并向 dep.subs 中添加了訂閱者,dep 通過 notify 周遊了 dep.subs 通知每個 watcher 更新。

依賴收集

  1. initState 時,對 computed 屬性初始化時,觸發 computed watcher 依賴收集
  2. initState 時,對偵聽屬性初始化時,觸發 user watcher 依賴收集
  3. render()的過程,觸發 render watcher 依賴收集
  4. re-render 時,vm.render()再次執行,會移除所有 subs 中的 watcer 的訂閱,重新指派。

派發更新

  1. 元件中對響應的資料進行了修改,觸發 setter 的邏輯
  2. 調用 dep.notify()
  3. 周遊所有的 subs(Watcher 執行個體),調用每一個 watcher 的 update 方法。

原理

當建立 Vue 執行個體時,vue 會周遊 data 選項的屬性,利用 Object.defineProperty 為屬性添加 getter 和 setter 對資料的讀取進行劫持(getter 用來依賴收集,setter 用來派發更新),并且在内部追蹤依賴,在屬性被通路和修改時通知變化。

每個元件執行個體會有相應的 watcher 執行個體,會在元件渲染的過程中記錄依賴的所有資料屬性(進行依賴收集,還有 computed watcher,user watcher 執行個體),之後依賴項被改動時,setter 方法會通知依賴與此 data 的 watcher 執行個體重新計算(派發更新),進而使它關聯的元件重新渲染。

一句話總結:

vue.js 采用資料劫持結合釋出-訂閱模式,通過 Object.defineproperty 來劫持各個屬性的 setter,getter,在資料變動時釋出消息給訂閱者,觸發響應的監聽回調

2. computed 的實作原理

computed 本質是一個惰性求值的觀察者。

computed 内部實作了一個惰性的 watcher,也就是 computed watcher,computed watcher 不會立刻求值,同時持有一個 dep 執行個體。

其内部通過 this.dirty 屬性标記計算屬性是否需要重新求值。

當 computed 的依賴狀态發生改變時,就會通知這個惰性的 watcher,

computed watcher 通過 this.dep.subs.length 判斷有沒有訂閱者,

有的話,會重新計算,然後對比新舊值,如果變化了,會重新渲染。 (

Vue 想確定不僅僅是計算屬性依賴的值發生變化,而是當計算屬性最終計算的值發生變化時才會觸發渲染 watcher 重新渲染,本質上是一種優化。

)

沒有的話,僅僅把 this.dirty = true。 (

當計算屬性依賴于其他資料時,屬性并不會立即重新計算,隻有之後其他地方需要讀取屬性的時候,它才會真正計算,即具備 lazy(懶計算)特性。

)

3. computed 和 watch 有什麼差別及運用場景?

差別

computed 計算屬性 : 依賴其它屬性值,并且 computed 的值有緩存,隻有它依賴的屬性值發生改變,下一次擷取 computed 的值時才會重新計算 computed 的值。

watch 偵聽器 : 更多的是「觀察」的作用,

無緩存性

,類似于某些資料的監聽回調,每當監聽的資料變化時都會執行回調進行後續操作。

運用場景

運用場景:

當我們需要進行數值計算,并且依賴于其它資料時,應該使用 computed,因為可以利用 computed 的緩存特性,避免每次擷取值時,都要重新計算。

當我們需要在資料變化時執行異步或開銷較大的操作時,應該使用 watch,使用 watch 選項允許我們執行異步操作 ( 通路一個 API ),限制我們執行該操作的頻率,并在我們得到最終結果前,設定中間狀态。這些都是計算屬性無法做到的。

4. 為什麼在 Vue3.0 采用了 Proxy,抛棄了 Object.defineProperty?

Object.defineProperty 本身有一定的監控到數組下标變化的能力,但是在 Vue 中,從性能/體驗的成本效益考慮,尤大大就棄用了這個特性(Vue 為什麼不能檢測數組變動 )。為了解決這個問題,經過 vue 内部處理後可以使用以下幾種方法來監聽數組
push
           

由于隻針對了以上 7 種方法進行了 hack 處理,是以其他數組的屬性也是檢測不到的,還是具有一定的局限性。

Object.defineProperty 隻能劫持對象的屬性,是以我們需要對每個對象的每個屬性進行周遊。Vue 2.x 裡,是通過 遞歸 + 周遊 data 對象來實作對資料的監控的,如果屬性值也是對象那麼需要深度周遊,顯然如果能劫持一個完整的對象是才是更好的選擇。

Proxy 可以劫持整個對象,并傳回一個新的對象。Proxy 不僅可以代理對象,還可以代理數組。還可以代理動态增加的屬性。

5. Vue 中的 key 到底有什麼用?

key 是給每一個 vnode 的唯一 id,依靠 key,我們的 diff 操作可以更準确、更快速 (對于簡單清單頁渲染來說 diff 節點也更快,但會産生一些隐藏的副作用,比如可能不會産生過渡效果,或者在某些節點有綁定資料(表單)狀态,會出現狀态錯位。)

diff 算法的過程中,先會進行新舊節點的首尾交叉對比,當無法比對的時候會用新節點的 key 與舊節點進行比對,進而找到相應舊節點.

更準确 : 因為帶 key 就不是就地複用了,在 sameNode 函數 a.key === b.key 對比中可以避免就地複用的情況。是以會更加準确,如果不加 key,會導緻之前節點的狀态被保留下來,會産生一系列的 bug。

更快速 : key 的唯一性可以被 Map 資料結構充分利用,相比于周遊查找的時間複雜度 O(n),Map 的時間複雜度僅僅為 O(1),源碼如下:

function 
           

6. 談一談 nextTick 的原理

JS 運作機制

JS 執行是單線程的,它是基于事件循環的。事件循環大緻分為以下幾個步驟:

  1. 所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)。
  2. 主線程之外,還存在一個"任務隊列"(task queue)。隻要異步任務有了運作結果,就在"任務隊列"之中放置一個事件。
  3. 一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",看看裡面有哪些事件。那些對應的異步任務,于是結束等待狀态,進入執行棧,開始執行。
  4. 主線程不斷重複上面的第三步。
vue key重複_12道vue高頻原理面試題,你能答出幾道?

主線程的執行過程就是一個 tick,而所有的異步結果都是通過 “任務隊列” 來排程。 消息隊列中存放的是一個個的任務(task)。 規範中規定 task 分為兩大類,分别是 macro task 和 micro task,并且每個 macro task 結束後,都要清空所有的 micro task。

for 
           

在浏覽器環境中 :

常見的 macro task 有

setTimeout、MessageChannel、postMessage、setImmediate

常見的 micro task 有

MutationObsever 和 Promise.then

異步更新隊列

可能你還沒有注意到,Vue 在更新 DOM 時是

異步

執行的。隻要偵聽到資料變化,Vue 将開啟一個隊列,并緩沖在同一事件循環中發生的所有資料變更。

如果同一個 watcher 被多次觸發,隻會被推入到隊列中一次。這種在緩沖時去除重複資料對于避免不必要的計算和 DOM 操作是非常重要的。

然後,在下一個的事件循環“tick”中,Vue 重新整理隊列并執行實際 (已去重的) 工作。

Vue 在内部對異步隊列嘗試使用原生的 Promise.then、MutationObserver 和 setImmediate,如果執行環境不支援,則會采用 setTimeout(fn, 0) 代替。

在 vue2.5 的源碼中,macrotask 降級的方案依次是:setImmediate、MessageChannel、setTimeout

vue 的 nextTick 方法的實作原理:

  1. vue 用異步隊列的方式來控制 DOM 更新和 nextTick 回調先後執行
  2. microtask 因為其高優先級特性,能確定隊列中的微任務在一次事件循環前被執行完畢
  3. 考慮相容問題,vue 做了 microtask 向 macrotask 的降級方案

7. vue 是如何對數組方法進行變異的 ?

我們先來看看源碼

const 
           

簡單來說,Vue 通過原型攔截的方式重寫了數組的 7 個方法,首先擷取到這個數組的

ob

,也就是它的 Observer 對象,如果有新的值,就調用 observeArray 對新的值進行監聽,然後手動調用 notify,通知 render watcher,執行 update

8. Vue 元件 data 為什麼必須是函數 ?

new Vue()執行個體中,data 可以直接是一個對象,為什麼在 vue 元件中,data 必須是一個函數呢?

因為元件是可以複用的,JS 裡對象是引用關系,如果元件 data 是一個對象,那麼子元件中的 data 屬性值會互相污染,産生副作用。

是以一個元件的 data 選項必須是一個函數,是以每個執行個體可以維護一份被傳回對象的獨立的拷貝。new Vue 的執行個體是不會被複用的,是以不存在以上問題。

9. 談談 Vue 事件機制,手寫$on,$off,$emit,$once

Vue 事件機制 本質上就是 一個 釋出-訂閱 模式的實作。
class 
           

10. 說說 Vue 的渲染過程

vue key重複_12道vue高頻原理面試題,你能答出幾道?
  1. 調用 compile 函數,生成 render 函數字元串 ,編譯過程如下:
  2. parse 函數解析 template,生成 ast(抽象文法樹)
  3. optimize 函數優化靜态節點 (标記不需要每次都更新的内容,diff 算法會直接跳過靜态節點,進而減少比較的過程,優化了 patch 的性能)
  4. generate 函數生成 render 函數字元串
  5. 調用 new Watcher 函數,監聽資料的變化,當資料發生變化時,Render 函數執行生成 vnode 對象
  6. 調用 patch 方法,對比新舊 vnode 對象,通過 DOM diff 算法,添加、修改、删除真正的 DOM 元素

11. 聊聊 keep-alive 的實作原理和緩存政策

export 
           

原理

  1. 擷取 keep-alive 包裹着的第一個子元件對象及其元件名
  2. 根據設定的 include/exclude(如果有)進行條件比對,決定是否緩存。不比對,直接傳回元件執行個體
  3. 根據元件 ID 和 tag 生成緩存 Key,并在緩存對象中查找是否已緩存過該元件執行個體。如果存在,直接取出緩存值并更新該 key 在 this.keys 中的位置( 更新 key 的位置是實作 LRU 置換政策的關鍵 )
  4. 在 this.cache 對象中存儲該元件執行個體并儲存 key 值,之後檢查緩存的執行個體數量是否超過 max 的設定值,超過則根據 LRU 置換政策 删除最近最久未使用的執行個體 (即是下标為 0 的那個 key)
  5. 最後元件執行個體的 keepAlive 屬性設定為 true,這個在渲染和執行被包裹元件的鈎子函數會用到,這裡不細說

LRU 緩存淘汰算法

LRU(Least recently used)算法根據資料的曆史通路記錄來進行淘汰資料,其核心思想是“如果資料最近被通路過,那麼将來被通路的幾率也更高”。

vue key重複_12道vue高頻原理面試題,你能答出幾道?
keep-alive 的實作正是用到了 LRU 政策,将最近通路的元件 push 到 this.keys 最後面,this.keys[0]也就是最久沒被通路的元件,當緩存執行個體超過 max 設定值,删除 this.keys[0]

12. vm.$set()實作原理是什麼?

受現代 JavaScript 的限制 (而且 Object.observe 也已經被廢棄),Vue 無法檢測到對象屬性的添加或删除。

由于 Vue 會在初始化執行個體時對屬性執行 getter/setter 轉化,是以屬性必須在 data 對象上存在才能讓 Vue 将它轉換為響應式的。

對于已經建立的執行個體,Vue 不允許動态添加根級别的響應式屬性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套對象添加響應式屬性。

那麼 Vue 内部是如何解決對象新增屬性不能響應的問題的呢?

export 
           
  1. 如果目标是數組,使用 vue 實作的變異方法 splice 實作響應式
  2. 如果目标是對象,判斷屬性存在,即為響應式,直接指派
  3. 如果 target 本身就不是響應式,直接指派
  4. 如果屬性不是響應式,則調用 defineReactive 方法進行響應式處理

後記

如果你和我一樣喜歡前端,也愛動手折騰,歡迎關注我一起玩耍啊~ ❤️

部落格

我的部落格​github.com

公衆号

前端時刻

http://weixin.qq.com/r/qyi2rrXEOuhFrfHN9325 (二維碼自動識别)

繼續閱讀