「面試題」20+Vue面試題整理

童歐巴發表於2020-06-04

6841583658006_.pic.jpg

從鏡片的厚度和黃黑相見的格子襯衫我察覺到,面前坐著的這位面試官應該是來者不善。

我像以往一樣,準備花3分鐘的時間進行自我介紹。在此期間,為了避免尷尬,我盯著面試官的眉毛中間,不過面試官明顯對我的經歷不是很感興趣。他在1分半的時候打斷了我。

你覺得自己最擅長的技術棧是什麼?

Vue吧,我很喜歡尤大,最近剛釋出了Vue的首部紀錄片,真的很好看。

0.那你能講一講MVVM嗎?

MVVM是Model-View-ViewModel縮寫,也就是把MVC中的Controller演變成ViewModel。Model層代表資料模型,View代表UI元件,ViewModel是View和Model層的橋樑,資料會繫結到viewModel層並自動將資料渲染到頁面中,檢視變化的時候會通知viewModel層更新資料。

1.簡單說一下Vue2.x響應式資料原理

Vue在初始化資料時,會使用Object.defineProperty重新定義data中的所有屬性,當頁面使用對應屬性時,首先會進行依賴收集(收集當前元件的watcher)如果屬性發生變化會通知相關依賴進行更新操作(釋出訂閱)。

2.那你知道Vue3.x響應式資料原理嗎?

(還好我有看,這個難不倒我)

Vue3.x改用Proxy替代Object.defineProperty。因為Proxy可以直接監聽物件和陣列的變化,並且有多達13種攔截方法。並且作為新標準將受到瀏覽器廠商重點持續的效能優化。

Proxy只會代理物件的第一層,那麼Vue3又是怎樣處理這個問題的呢?

(很簡單啊)

判斷當前Reflect.get的返回值是否為Object,如果是則再通過reactive方法做代理,
這樣就實現了深度觀測。

監測陣列的時候可能觸發多次get/set,那麼如何防止觸發多次呢?

我們可以判斷key是否為當前被代理物件target自身屬性,也可以判斷舊值與新值是否相等,只有滿足以上兩個條件之一時,才有可能執行trigger。

面試官抬起了頭。心裡暗想

(這小子還行,比上兩個強,應該是多多少少看過Vue3的原始碼了)

3.再說一下vue2.x中如何監測陣列變化

使用了函式劫持的方式,重寫了陣列的方法,Vue將data中的陣列進行了原型鏈重寫,指向了自己定義的陣列原型方法。這樣當呼叫陣列api時,可以通知依賴更新。如果陣列中包含著引用型別,會對陣列中的引用型別再次遞迴遍歷進行監控。這樣就實現了監測陣列變化。

(能問到這的面試官都比較注重深度,這些常規操作要記牢)

(原型鏈的細節可以參考我的另一篇專欄)

一文帶你徹底搞懂JavaScript原型鏈

4.nextTick知道嗎,實現原理是什麼?

在下次 DOM 更新迴圈結束之後執行延遲迴調。nextTick主要使用了巨集任務和微任務。根據執行環境分別嘗試採用

  • Promise
  • MutationObserver
  • setImmediate
  • 如果以上都不行則採用setTimeout

定義了一個非同步方法,多次呼叫nextTick會將方法存入佇列中,通過這個非同步方法清空當前佇列。

(關於巨集任務和微任務以及事件迴圈可以參考我的另兩篇專欄)

(看到這你就會發現,其實問框架最終還是考驗你的原生JavaScript功底)

瀏覽器中JavaScript的事件迴圈

Node.js事件迴圈

5.說一下Vue的生命週期

beforeCreate是new Vue()之後觸發的第一個鉤子,在當前階段data、methods、computed以及watch上的資料和方法都不能被訪問。

created在例項建立完成後發生,當前階段已經完成了資料觀測,也就是可以使用資料,更改資料,在這裡更改資料不會觸發updated函式。可以做一些初始資料的獲取,在當前階段無法與Dom進行互動,如果非要想,可以通過vm.$nextTick來訪問Dom。

beforeMount發生在掛載之前,在這之前template模板已匯入渲染函式編譯。而當前階段虛擬Dom已經建立完成,即將開始渲染。在此時也可以對資料進行更改,不會觸發updated。

mounted在掛載完成後發生,在當前階段,真實的Dom掛載完畢,資料完成雙向繫結,可以訪問到Dom節點,使用$refs屬性對Dom進行操作。

beforeUpdate發生在更新之前,也就是響應式資料發生更新,虛擬dom重新渲染之前被觸發,你可以在當前階段進行更改資料,不會造成重渲染。

updated發生在更新完成之後,當前階段元件Dom已完成更新。要注意的是避免在此期間更改資料,因為這可能會導致無限迴圈的更新。

beforeDestroy發生在例項銷燬之前,在當前階段例項完全可以被使用,我們可以在這時進行善後收尾工作,比如清除計時器。

destroyed發生在例項銷燬之後,這個時候只剩下了dom空殼。元件已被拆解,資料繫結被卸除,監聽被移出,子例項也統統被銷燬。

(關於Vue的生命週期詳解感興趣的也請移步我的另一篇專欄)

從原始碼解讀Vue生命週期,讓面試官對你刮目相看

6.你的介面請求一般放在哪個生命週期中?

介面請求一般放在mounted中,但需要注意的是服務端渲染時不支援mounted,需要放到created中。

7.再說一下Computed和Watch

Computed本質是一個具備快取的watcher,依賴的屬性發生變化就會更新檢視。
適用於計算比較消耗效能的計算場景。當表示式過於複雜時,在模板中放入過多邏輯會讓模板難以維護,可以將複雜的邏輯放入計算屬性中處理。

Watch沒有快取性,更多的是觀察的作用,可以監聽某些資料執行回撥。當我們需要深度監聽物件中的屬性時,可以開啟deep:true選項,這樣便會對物件中的每一項進行監聽。這樣會帶來效能問題,優化的話可以使用字串形式監聽,如果沒有寫到元件中,不要忘記使用unWatch手動登出哦。

8.說一下v-if和v-show的區別

當條件不成立時,v-if不會渲染DOM元素,v-show操作的是樣式(display),切換當前DOM的顯示和隱藏。

9.元件中的data為什麼是一個函式?

一個元件被複用多次的話,也就會建立多個例項。本質上,這些例項用的都是同一個建構函式。如果data是物件的話,物件屬於引用型別,會影響到所有的例項。所以為了保證元件不同的例項之間data不衝突,data必須是一個函式。

10.說一下v-model的原理

v-model本質就是一個語法糖,可以看成是value + input方法的語法糖。
可以通過model屬性的propevent屬性來進行自定義。原生的v-model,會根據標籤的不同生成不同的事件和屬性。

11.Vue事件繫結原理說一下

原生事件繫結是通過addEventListener繫結給真實元素的,元件事件繫結是通過Vue自定義的$on實現的。

面試官:(這小子基礎還可以,接下來我得上上難度了)

12.Vue模版編譯原理知道嗎,能簡單說一下嗎?

簡單說,Vue的編譯過程就是將template轉化為render函式的過程。會經歷以下階段:

  • 生成AST樹
  • 優化
  • codegen

首先解析模版,生成AST語法樹(一種用JavaScript物件的形式來描述整個模板)。
使用大量的正規表示式對模板進行解析,遇到標籤、文字的時候都會執行對應的鉤子進行相關處理。

Vue的資料是響應式的,但其實模板中並不是所有的資料都是響應式的。有一些資料首次渲染後就不會再變化,對應的DOM也不會變化。那麼優化過程就是深度遍歷AST樹,按照相關條件對樹節點進行標記。這些被標記的節點(靜態節點)我們就可以跳過對它們的比對,對執行時的模板起到很大的優化作用。

編譯的最後一步是將優化後的AST樹轉換為可執行的程式碼

面試官:(精神小夥啊,有點東西,難度提升,不信難不倒你)

13.Vue2.x和Vue3.x渲染器的diff演算法分別說一下

簡單來說,diff演算法有以下過程

  • 同級比較,再比較子節點
  • 先判斷一方有子節點一方沒有子節點的情況(如果新的children沒有子節點,將舊的子節點移除)
  • 比較都有子節點的情況(核心diff)
  • 遞迴比較子節點

正常Diff兩個樹的時間複雜度是O(n^3),但實際情況下我們很少會進行跨層級的移動DOM,所以Vue將Diff進行了優化,從O(n^3) -> O(n),只有當新舊children都為多個子節點時才需要用核心的Diff演算法進行同層級比較。

Vue2的核心Diff演算法採用了雙端比較的演算法,同時從新舊children的兩端開始進行比較,藉助key值找到可複用的節點,再進行相關操作。相比React的Diff演算法,同樣情況下可以減少移動節點次數,減少不必要的效能損耗,更加的優雅。

Vue3.x借鑑了
ivi演算法和 inferno演算法

在建立VNode時就確定其型別,以及在 mount/patch 的過程中採用位運算來判斷一個VNode的型別,在這個基礎之上再配合核心的Diff演算法,使得效能上較Vue2.x有了提升。(實際的實現可以結合Vue3.x原始碼看。)

該演算法中還運用了動態規劃的思想求解最長遞迴子序列。

(看到這你還會發現,框架內無處不蘊藏著資料結構和演算法的魅力)

面試官:(可以可以,看來是個苗子,不過自我介紹屬實有些無聊,下一題)

(基操,勿6)

14.再說一下虛擬Dom以及key屬性的作用

由於在瀏覽器中操作DOM是很昂貴的。頻繁的操作DOM,會產生一定的效能問題。這就是虛擬Dom的產生原因

Vue2的Virtual DOM借鑑了開源庫snabbdom的實現。

Virtual DOM本質就是用一個原生的JS物件去描述一個DOM節點。是對真實DOM的一層抽象。(也就是原始碼中的VNode類,它定義在src/core/vdom/vnode.js中。)

VirtualDOM對映到真實DOM要經歷VNode的create、diff、patch等階段。

key的作用是儘可能的複用 DOM 元素。

新舊 children 中的節點只有順序是不同的時候,最佳的操作應該是通過移動元素的位置來達到更新的目的。

需要在新舊 children 的節點中儲存對映關係,以便能夠在舊 children 的節點中找到可複用的節點。key也就是children中節點的唯一標識。

15.keep-alive瞭解嗎

keep-alive可以實現元件快取,當元件切換時不會對當前元件進行解除安裝。

常用的兩個屬性include/exclude,允許元件有條件的進行快取。

兩個生命週期activated/deactivated,用來得知當前元件是否處於活躍狀態。

keep-alive的中還運用了LRU(Least Recently Used)演算法。

(又是資料結構與演算法,原來演算法在前端也有這麼多的應用)

16.Vue中元件生命週期呼叫順序說一下

元件的呼叫順序都是先父後子,渲染完成的順序是先子後父

元件的銷燬操作是先父後子,銷燬完成的順序是先子後父

載入渲染過程

父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount- >子mounted->父mounted

子元件更新過程

父beforeUpdate->子beforeUpdate->子updated->父updated

父元件更新過程

父 beforeUpdate -> 父 updated

銷燬過程

父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

17.Vue2.x元件通訊有哪些方式?

  • 父子元件通訊

    父->子props,子->父 $on、$emit

    獲取父子元件例項 $parent、$children

    Ref 獲取例項的方式呼叫元件的屬性或者方法

    Provide、inject 官方不推薦使用,但是寫元件庫時很常用

  • 兄弟元件通訊

    Event Bus 實現跨元件通訊 Vue.prototype.$bus = new Vue

    Vuex

  • 跨級元件通訊

    Vuex

    $attrs、$listeners

    Provide、inject

18.SSR瞭解嗎?

SSR也就是服務端渲染,也就是將Vue在客戶端把標籤渲染成HTML的工作放在服務端完成,然後再把html直接返回給客戶端

SSR有著更好的SEO、並且首屏載入速度更快等優點。不過它也有一些缺點,比如我們的開發條件會受到限制,伺服器端渲染只支援beforeCreatecreated兩個鉤子,當我們需要一些外部擴充套件庫時需要特殊處理,服務端渲染應用程式也需要處於Node.js的執行環境。還有就是伺服器會有更大的負載需求。

19.你都做過哪些Vue的效能優化?

編碼階段

  • 儘量減少data中的資料,data中的資料都會增加getter和setter,會收集對應的watcher
  • v-if和v-for不能連用
  • 如果需要使用v-for給每項元素繫結事件時使用事件代理
  • SPA 頁面採用keep-alive快取元件
  • 在更多的情況下,使用v-if替代v-show
  • key保證唯一
  • 使用路由懶載入、非同步元件
  • 防抖、節流
  • 第三方模組按需匯入
  • 長列表滾動到可視區域動態載入
  • 圖片懶載入

SEO優化

  • 預渲染
  • 服務端渲染SSR

打包優化

  • 壓縮程式碼
  • Tree Shaking/Scope Hoisting
  • 使用cdn載入第三方模組
  • 多執行緒打包happypack
  • splitChunks抽離公共檔案
  • sourceMap優化

使用者體驗

  • 骨架屏
  • PWA

還可以使用快取(客戶端快取、服務端快取)優化、服務端開啟gzip壓縮等。

(優化是個大工程,會涉及很多方面,這裡申請另開一個專欄)

20.hash路由和history路由實現原理說一下

location.hash的值實際就是URL中#後面的東西。

history實際採用了HTML5中提供的API來實現,主要有history.pushState()history.replaceState()

面試官拿起旁邊已經涼透的咖啡,喝了一口。

(我難道問不倒這小子了麼)

相關文章