沐鳴主管:_Vue3.0面試題

前言

昨晚做了一個夢,夢見自己到了一家大廠面試,面試官走近房間,坐了下來:是楊溜溜吧?國際慣例,先來個自我介紹吧。

於是我巴拉巴拉開始了長達兩分鐘的自我介紹,與此同時,面試官邊聽邊看我的簡歷,邊看邊皺眉,結束后問:看你之前的項目經常用到vue,對vue熟悉嗎?

我嘴角一笑,心裏暗喜:幸好有專門看Vue的面試題,看來這次穩了。於是謙虛又裝逼的回答:還行吧,您隨便問。

於是面試官看我口氣那麼大,心想:喲嚯,來了一個裝逼的,勞資今天就只問Vue。

來,先介紹一下Vue的響應式系統

Vue為MVVM框架,當數據模型data變化時,頁面視圖會得到響應更新,其原理對data的getter/setter方法進行攔截(Object.defineProperty或者Proxy),利用發布訂閱的設計模式,在getter方法中進行訂閱,在setter方法中發布通知,讓所有訂閱者完成響應。

在響應式系統中,Vue會為數據模型data的每一個屬性新建一個訂閱中心作為發布者,而監聽器watch、計算屬性computed、視圖渲染template/render三個角色同時作為訂閱者,對於監聽器watch,會直接訂閱觀察監聽的屬性,對於計算屬性computed和視圖渲染template/render,如果內部執行獲取了data的某個屬性,就會執行該屬性的getter方法,然後自動完成對該屬性的訂閱,當屬性被修改時,就會執行該屬性的setter方法,從而完成該屬性的發布通知,通知所有訂閱者進行更新。

computed與watch的區別

計算屬性computed和監聽器watch都可以觀察屬性的變化從而做出響應,不同的是:

計算屬性computed更多是作為緩存功能的觀察者,它可以將一個或者多個data的屬性進行複雜的計算生成一個新的值,提供給渲染函數使用,當依賴的屬性變化時,computed不會立即重新計算生成新的值,而是先標記為臟數據,當下次computed被獲取時候,才會進行重新計算並返回。

而監聽器watch並不具備緩存性,監聽器watch提供一個監聽函數,當監聽的屬性發生變化時,會立即執行該函數。

介紹一下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空殼。組件已被拆解,數據綁定被卸除,監聽被移出,子實例也統統被銷毀。

為什麼組件的data必須是一個函數

一個組件可能在很多地方使用,也就是會創建很多個實例,如果data是一個對象的話,對象是引用類型,一個實例修改了data會影響到其他實例,所以data必須使用函數,為每一個實例創建一個屬於自己的data,使其同一個組件的不同實例互不影響。

組件之間是怎麼通信的

  • 父子組件通信

父組件 -> 子組件:prop

子組件 -> 父組件:$on/$emit

獲取組件實例:使用$parent/$children,$refs.xxx,獲取到實例后直接獲取屬性數據或調用組件方法

  • 兄弟組件通信

Event Bus:每一個Vue實例都是一個Event Bus,都支持$on/$emit,可以為兄弟組件的實例之間new一個Vue實例,作為Event Bus進行通信。

Vuex:將狀態和方法提取到Vuex,完成共享

  • 跨級組件通信

使用provide/inject

Event Bus:同兄弟組件Event Bus通信

Vuex:將狀態和方法提取到Vuex,完成共享

Vue事件綁定原理說一下

每一個Vue實例都是一個Event Bus,當子組件被創建的時候,父組件將事件傳遞給子組件,子組件初始化的時候是有$on方法將事件註冊到內部,在需要的時候使用$emit觸發函數,而對於原生native事件,使用addEventListener綁定到真實的DOM元素上。

slot是什麼?有什麼作用?原理是什麼?

slot又名插槽,是Vue的內容分發機制,組件內部的模板引擎使用slot元素作為承載分發內容的出口。插槽slot是子組件的一個模板標籤元素,而這一個標籤元素是否显示,以及怎麼显示是由父組件決定的。

slot又分三類,默認插槽,具名插槽和作用域插槽。

  • 默認插槽:又名匿名查抄,當slot沒有指定name屬性值的時候一個默認显示插槽,一個組件內只有有一個匿名插槽。
  • 具名插槽:帶有具體名字的插槽,也就是帶有name屬性的slot,一個組件可以出現多個具名插槽。
  • 作用域插槽:默認插槽、具名插槽的一個變體,可以是匿名插槽,也可以是具名插槽,該插槽的不同點是在子組件渲染作用域插槽時,可以將子組件內部的數據傳遞給父組件,讓父組件根據子組件的傳遞過來的數據決定如何渲染該插槽。

實現原理:當子組件vm實例化時,獲取到父組件傳入的slot標籤的內容,存放在vm.$slot中,默認插槽為vm.$slot.default,具名插槽為vm.$slot.xxx,xxx 為插槽名,當組件執行渲染函數時候,遇到slot標籤,使用$slot中的內容進行替換,此時可以為插槽傳遞數據,若存在數據,則可稱該插槽為作用域插槽。

Vue模板渲染的原理是什麼?

vue中的模板template無法被瀏覽器解析並渲染,因為這不屬於瀏覽器的標準,不是正確的html語法,所有需要將template轉化成一個JavaScript函數,這樣瀏覽器就可以執行這一個函數並渲染出對應的html元素,就可以讓視圖跑起來了,這一個轉化的過程,就成為模板編譯。

模板編譯又分三個階段,解析parse,優化optimize,生成generate,最終生成可執行函數render。

  • parse階段:使用大量的正則表達式對template字符串進行解析,將標籤、指令、屬性等轉化為抽象語法樹AST。
  • optimize階段:遍歷AST,找到其中的一些靜態節點並進行標記,方便在頁面重渲染的時候進行diff比較時,直接跳過這一些靜態節點,優化runtime的性能。
  • generate階段:將最終的AST轉化為render函数字符串。

template預編譯是什麼?

對於 Vue 組件來說,模板編譯只會在組件實例化的時候編譯一次,生成渲染函數之後在也不會進行編譯。因此,編譯對組件的 runtime 是一種性能損耗。

而模板編譯的目的僅僅是將template轉化為render function,這個過程,正好可以在項目構建的過程中完成,這樣可以讓實際組件在 runtime 時直接跳過模板渲染,進而提升性能,這個在項目構建的編譯template的過程,就是預編譯。

那template和jsx的有什麼分別?

對於 runtime 來說,只需要保證組件存在 render 函數即可,而我們有了預編譯之後,我們只需要保證構建過程中生成 render 函數就可以。

在 webpack 中,我們使用vue-loader編譯.vue文件,內部依賴的vue-template-compiler模塊,在 webpack 構建過程中,將template預編譯成 render 函數。

與 react 類似,在添加了jsx的語法糖解析器babel-plugin-transform-vue-jsx之後,就可以直接手寫render函數。

所以,template和jsx的都是render的一種表現形式,不同的是:

JSX相對於template而言,具有更高的靈活性,在複雜的組件中,更具有優勢,而 template 雖然顯得有些獃滯。但是 template 在代碼結構上更符合視圖與邏輯分離的習慣,更簡單、更直觀、更好維護。

說一下什麼是Virtual DOM

Virtual DOM 是 DOM 節點在 JavaScript 中的一種抽象數據結構,之所以需要虛擬DOM,是因為瀏覽器中操作DOM的代價比較昂貴,頻繁操作DOM會產生性能問題。虛擬DOM的作用是在每一次響應式數據發生變化引起頁面重渲染時,Vue對比更新前後的虛擬DOM,匹配找出盡可能少的需要更新的真實DOM,從而達到提升性能的目的。

介紹一下Vue中的Diff算法

在新老虛擬DOM對比時

  • 首先,對比節點本身,判斷是否為同一節點,如果不為相同節點,則刪除該節點重新創建節點進行替換
  • 如果為相同節點,進行patchVnode,判斷如何對該節點的子節點進行處理,先判斷一方有子節點一方沒有子節點的情況(如果新的children沒有子節點,將舊的子節點移除)
  • 比較如果都有子節點,則進行updateChildren,判斷如何對這些新老節點的子節點進行操作(diff核心)。
  • 匹配時,找到相同的子節點,遞歸比較子節點

在diff中,只對同層的子節點進行比較,放棄跨級的節點比較,使得時間複雜從O(n^3)降低值O(n),也就是說,只有當新舊children都為多個子節點時才需要用核心的Diff算法進行同層級比較。

key屬性的作用是什麼

在對節點進行diff的過程中,判斷是否為相同節點的一個很重要的條件是key是否相等,如果是相同節點,則會盡可能的復用原有的DOM節點。所以key屬性是提供給框架在diff的時候使用的,而非開發者。

說說Vue2.0和Vue3.0有什麼區別

  1. 重構響應式系統,使用Proxy替換Object.defineProperty,使用Proxy優勢:
  • 可直接監聽數組類型的數據變化
  • 監聽的目標為對象本身,不需要像Object.defineProperty一樣遍歷每個屬性,有一定的性能提升
  • 可攔截apply、ownKeys、has等13種方法,而Object.defineProperty不行
  • 直接實現對象屬性的新增/刪除
  1. 新增Composition API,更好的邏輯復用和代碼組織
  2. 重構 Virtual DOM
  • 模板編譯時的優化,將一些靜態節點編譯成常量
  • slot優化,將slot編譯為lazy函數,將slot的渲染的決定權交給子組件
  • 模板中內聯事件的提取並重用(原本每次渲染都重新生成內聯函數)
  1. 代碼結構調整,更便於Tree shaking,使得體積更小
  2. 使用Typescript替換Flow

為什麼要新增Composition API,它能解決什麼問題

Vue2.0中,隨着功能的增加,組件變得越來越複雜,越來越難維護,而難以維護的根本原因是Vue的API設計迫使開發者使用watch,computed,methods選項組織代碼,而不是實際的業務邏輯。

另外Vue2.0缺少一種較為簡潔的低成本的機制來完成邏輯復用,雖然可以minxis完成邏輯復用,但是當mixin變多的時候,會使得難以找到對應的data、computed或者method來源於哪個mixin,使得類型推斷難以進行。

所以Composition API的出現,主要是也是為了解決Option API帶來的問題,第一個是代碼組織問題,Compostion API可以讓開發者根據業務邏輯組織自己的代碼,讓代碼具備更好的可讀性和可擴展性,也就是說當下一個開發者接觸這一段不是他自己寫的代碼時,他可以更好的利用代碼的組織反推出實際的業務邏輯,或者根據業務邏輯更好的理解代碼。

第二個是實現代碼的邏輯提取與復用,當然mixin也可以實現邏輯提取與復用,但是像前面所說的,多個mixin作用在同一個組件時,很難看出property是來源於哪個mixin,來源不清楚,另外,多個mixin的property存在變量命名衝突的風險。而Composition API剛好解決了這兩個問題。

都說Composition API與react Hook很像,說說區別

從React Hook的實現角度看,React Hook是根據useState調用的順序來確定下一次重渲染時的state是來源於哪個useState,所以出現了以下限制

  • 不能在循環、條件、嵌套函數中調用Hook
  • 必須確保總是在你的React函數的頂層調用Hook
  • useEffect、useMemo等函數必須手動確定依賴關係

而Composition API是基於Vue的響應式系統實現的,與React Hook的相比

  • 聲明在setup函數內,一次組件實例化只調用一次setup,而React Hook每次重渲染都需要調用Hook,使得React的GC比Vue更有壓力,性能也相對於Vue來說也較慢
  • Compositon API的調用不需要顧慮調用順序,也可以在循環、條件、嵌套函數中使用
  • 響應式系統自動實現了依賴收集,進而組件的部分的性能優化由Vue內部自己完成,而React Hook需要手動傳入依賴,而且必須必須保證依賴的順序,讓useEffect、useMemo等函數正確的捕獲依賴變量,否則會由於依賴不正確使得組件性能下降。

雖然Compositon API看起來比React Hook好用,但是其設計思想也是借鑒React Hook的。

SSR有了解嗎?原理是什麼?

在客戶端請求服務器的時候,服務器到數據庫中獲取到相關的數據,並且在服務器內部將Vue組件渲染成HTML,並且將數據、HTML一併返回給客戶端,這個在服務器將數據和組件轉化為HTML的過程,叫做服務端渲染SSR。

而當客戶端拿到服務器渲染的HTML和數據之後,由於數據已經有了,客戶端不需要再一次請求數據,而只需要將數據同步到組件或者Vuex內部即可。除了數據意外,HTML也結構已經有了,客戶端在渲染組件的時候,也只需要將HTML的DOM節點映射到Virtual DOM即可,不需要重新創建DOM節點,這個將數據和HTML同步的過程,又叫做客戶端激活。

使用SSR的好處:

  • 有利於seo:其實就是有利於爬蟲來爬你的頁面,因為部分頁面爬蟲是不支持執行JavaScript的,這種不支持執行JavaScript的爬蟲抓取到的非SSR的頁面會是一個空的HTML頁面,而有了SSR以後,這些爬蟲就可以獲取到完整的HTML結構的數據,進而收錄到搜索引擎中。

  • 白屏時間更短:相對於客戶端渲染,服務端渲染在瀏覽器請求URL之後已經得到了一個帶有數據的HTML文本,瀏覽器只需要解析HTML,直接構建DOM樹就可以。而客戶端渲染,需要先得到一個空的HTML頁面,這個時候頁面已經進入白屏,之後還需要經過加載並執行 JavaScript、請求後端服務器獲取數據、JavaScript 渲染頁面幾個過程才可以看到最後的頁面。特別是在複雜應用中,由於需要加載 JavaScript 腳本,越是複雜的應用,需要加載的 JavaScript 腳本就越多、越大,這會導致應用的首屏加載時間非常長,進而降低了體驗感。

更多詳情查看徹底理解服務端渲染 – SSR原理

結束

面試官點了點頭,嗯呢,這小伙還可以,懂得還挺多,可以弄進來寫業務。

我也暗自竊喜,幸虧沒問到我不會的,然後我坐那傻笑,笑着笑着,突然聽到我的鬧鈴響了,然後,我夢醒了。

然後,新的搬磚的一天又開始了。

原文來自:https://github.com/yacan8/blog/issues/31

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/10049