面試題整理之Vue篇

Aaron發表於2021-11-17

又快到年底了,有很多小夥伴可能會選擇跳槽或者升職或者考核,尤其重要的是面試,在面試過程中面試官總會問一些基礎性的內容來考察我們的專業知識的掌握程度,再決定是否錄用。

前言

現在對於前端來說,問的最多的可能是JavaScript基礎,除此之外問的更多的就是目前的主流框架相關的一些知識點。目前主流的框架Vue、React、Angular,國內的網際網路情況來講,目前還是Vue居多。

就目前來看越來越多的面試,和之前的面試不太一樣了,兩三年前問的更多的是對於主流框架的應用,目前更多的是對於底層的一些問題更多一些。針對這些Vue相關面試題做一下總結並記錄。希望可以幫到更多的小夥伴。

基礎面試題

基礎面試題即Vue應用和使用方面的面試題,這一部分大多數還是面向於初級工程師,相對來說要簡單一些。

問:Vue生命週期鉤子都有哪些?

答:Vue生命週期一共有10個:

  1. beforeCreate - 在例項初始化之前呼叫,在此生命週期中是不存在this的,因為Vue例項還沒有建立。
  2. created - 例項初始化之後呼叫
  3. beforeMount - 在掛載開始之前被呼叫,如果是在伺服器端渲染時不被呼叫,由於元素還沒有渲染,在此宣告週期無法獲取到DOM元素。
  4. mounted - 在掛載後被呼叫,是可以獲取元素,並進行操作
  5. beforeUpdate - 檢視層資料更新前呼叫,在虛擬DOM更新之前,可以獲取到DOMj節點,但是此節點為更新之前的DOM節點
  6. updated - 檢視層資料更新後呼叫,此處獲取到的DOM節點是更新之後DOM
  7. beforeDestroy - 例項銷燬之前呼叫,在被銷燬的元件中進行呼叫
  8. destroyed - 例項銷燬後呼叫

beforeDestroydestroyed生命週期階段可以在此階段,進行`Vuex資料重置,取消事件監聽等操作,提升效能。

以上是比較常用的生命週期鉤子函式,還有兩個比較特殊的生命週期鉤子,分別是activateddeactivated,這兩個生命週期只有在元件上使用了keep-alive之後才會被執行。

  1. activated - 例項被啟用時使用,用於重複啟用一個例項的時候,該生命週期於會在mounted之後執行。
  2. deactivated - 例項沒有被啟用時

問:父元件和子元件生命週期鉤子函式執行順序?

答:

Vue的父元件和子元件生命週期鉤子函式執行順序可以歸類為以下4部分:

載入渲染過程
  1. 父 beforeCreate
  2. 父 created
  3. 父 beforeMount
  4. 子 beforeCreate
  5. 子 created
  6. 子 beforeMount
  7. 子 mounted
  8. 父 mounted
子元件更新過程
  1. 父 beforeUpdate
  2. 子 beforeUpdate
  3. 子 updated
  4. 父 updated
父元件更新過程
  1. 父 beforeUpdate
  2. 父 updated
銷燬過程
  1. 父 beforeDestroy
  2. 子 beforeDestroy
  3. 子 destroyed
  4. 父 destroyed

問:在哪個生命週期內呼叫非同步請求?

答:

可以在鉤子函式createdbeforeMountmounted中進行呼叫,因為在這三個鉤子函式中,data已經建立,可以將服務端端返回的資料進行賦值。推薦在 created 鉤子函式中呼叫非同步請求,因為在created鉤子函式中呼叫非同步請求,因為能更快獲取到服務端資料,減少頁面loading時間,ssr不支援beforeMountmounted鉤子函式,所以放在created中有助於一致性。

問:父元件可以監聽到子元件的生命週期嗎?

答:

可以的,有兩種方法可以可以做到,第一種則是使用$emit自定義事件進行事件監聽,當子元件執行到某個生命週期時用$emit通知即可。第二種使用@hook:生命週期名稱@hook方法不僅僅是可以監聽mounted,其它的生命週期事件,例如:createdupdated等都可以監聽。

問:keep-alive是做什麼的?

答:keep-aliveVue內建的一個元件,可以使被包含的元件保留狀態,避免重新渲染 ,其有以下特性:

  1. 一般結合路由和動態元件一起使用,用於快取元件
  2. 提供includeexclude屬性,兩者都支援字串或正規表示式, include表示只有名稱匹配的元件會被快取,exclude表示任何名稱匹配的元件都不會被快取 ,其中exclude的優先順序比include
  3. 對應兩個鉤子函式activateddeactivated,當元件被啟用時,觸發鉤子函式activated,當元件被移除時,觸發鉤子函式deactivated

問:元件之間的傳值通訊方式有哪些?

答:元件間通訊方式有7種:

1. props/emit

父元件傳入屬性,子元件通過props接收,可以通過這種方法獲取到父元件傳入的引數。而子元件則是可以通過$emit('時間名',引數)像外暴露一個自定義事件,在父元件中的屬性監聽事件,同時也能獲取子元件傳出來的引數,使用v-on:事件名稱=func或者@事件名稱=func的方式監聽子元件的自定義事件。

還可以通過prop接收函式為引數,子元件呼叫該函式,子元件執行函式時,可以傳入對應的實參,在父元件接收時以形參的形式接收,執行對應的邏輯,可以達到通訊的目的。

2. EventBus

EventBus又稱為事件匯流排,EventBus來作為溝通橋樑可以使任意兩個元件之間通訊,就像是所有元件共用相同的事件中心,可以向該中心註冊傳送事件或接收事件。

如何使用EventBus,使用new Vue的方式獲得Vue例項,實質上EventBus是一個不具備DOM的元件,它具有的僅僅只是它例項方法而已,因此它非常的輕便。

可以把獲取到Vue例項掛載到Vue.prototype中也可以存放到一個單獨的js檔案中匯出,使用哪種取決於業務的具體情況,一般推薦使用第二種方法。

使用EventBus.$emit("事件名稱", 引數);的方式進行事件釋出。使用EventBus.$on("事件名稱", func)的方法對事件進行訂閱。func的引數則是對應事件傳遞過來的引數。

EventBus的原理是把註冊的事件存起來,等觸發事件時再呼叫。定義一個類去處理事件。

3. Vuex狀態管理

VuexVue的核心外掛、用於任意元件的任意通訊,需注意重新整理後有可能Vuex資料會丟失。

建立全域性唯一的狀態管理倉庫store,有同步mutations、非同步actions的方式去管理資料,有快取資料getters,還能分成各個模組modules易於維護

4. ($parent|$root)/$children

這種通訊方式推薦使用,這樣會使兩個元件之間強耦合在一起,對於以後的維護和擴充來說不不是特別的友好。

通過($parent|$root)/$children獲取到對應的元件例項,呼叫元件內部的方法以達到通訊的效果。

5. $ref

通過$ref引用的方式獲取子節點,常用於父元件呼叫子元件的方法,在$refs來儲存當前所有設定了ref屬性的元件的例項,在例項中可以呼叫到元件內部的方法。

6. attrs/listeners

$attrs可以獲取父元件傳進來但沒有通過props接收的屬性,可以使用v-bind="$attrs"的形式繼續向子元件傳遞引數。

$listeners會展開父元件的所有監聽的事件,一個頁面中有兩個元件的點選事件觸發方法是一樣的。可以使用v-on="$listeners"把事件繼續向子元件傳遞事件。

7. provide/inject

父元件中通過provider來提供變數,然後在子元件中通過inject來注入變數。只要在父元件中呼叫了,那麼在這個父元件生效的生命週期內,所有的子元件都可以呼叫inject來注入父元件中的值。

問:watch和computed有什麼區別?

答:

computed是計算屬性在使用時和data物件中的資料屬性是類似的,watch是用於監聽某一個屬性的變化的,computed擅長的是一個資料受多個資料影響,然而watch是用於監聽某一個屬性的變化的。

computed支援快取只有依賴資料發生改變,才會重新進行計算,computed基於它們的響應式依賴進行快取的,也就是基於data中宣告過或者父元件傳遞的props中的資料通過計算得到的值,watch則不支援快取,資料變,直接會觸發相應的操作。watch的函式接收兩個引數,第一個引數是最新的值;第二個引數是輸入之前的值;

computed不支援非同步,computed內有非同步操作時無效,無法監聽資料的變化,watch支援非同步。

computed在使用時,computed屬性值是函式,那麼預設會走get方法,函式的返回值就是屬性的屬性值,在computed中的,屬性都有一個get和一個set方法,當資料變化時,呼叫set方法。watch在使用時,監聽資料必須是data中宣告過或者父元件傳遞過來的props中的資料,當資料變化時,觸發其他操作,函式有兩個引數。immediate(元件載入立即觸發回撥函式執行),deep(深度監聽)為了發現物件內部值的變化,複雜型別的資料時使用。

computed內部就是根據Object.definedProperty()實現的,computed具備快取功能,依賴的值不發生變化,就不會重新計算。watch是監控值的變化,值發生變化時會執行對應的回撥函式,computedwatch都是基於Watcher類來執行的。computed快取功能依靠一個變數 dirty,表示值是不是髒的預設是true,取值後是false,再次取值時dirty還是false直接將還是上一次的取值返回。

問:為什麼data必須是一個工廠函式而不能是一個物件?

答:

因為Vue元件複用原則,由於data是物件為引用型別,當一個元件的資料改變後另一個元件狀態會受到影響,然而通過工廠函式的形式返回物件就不會導致這樣的問題,因為每個工廠函式所返回的物件都是一個全新的物件相對獨立。

問:Vue中style scoped有作用?

答:

vue檔案中的style標籤上,有一個特殊的屬性:scoped。當一個style標籤擁有scoped屬性時,它的CSS樣式就只能作用於當前的元件,也就是說,該樣式只能適用於當前元件元素。通過該屬性,可以使得元件之間的樣式不互相汙染。scoped會在DOM結構及css樣式上加上唯一性的標記data-v-something屬性,即CSS帶屬性選擇器,以此完成類似作用域的選擇方式,從而達到樣式私有化,不汙染全域性的作用。scoped使用雖然方便但是我們需要慎用,因為在我們需要修改公共元件(三方庫或者專案定製的元件)的樣式的時候,scoped往往會造成更多的困難,需要增加額外的複雜度。使用>>>可以穿透scoped屬性,修改其他第三方元件的樣式。使用sassless的樣式穿透/deep/

問:v-show與v-if 有什麼區別?

答:

v-if是真正的條件渲染,因為它會確保在切換過程中條件塊內的事件監聽器和子元件適當地被銷燬和重建,v-if是惰性的:如果在初始渲染時條件為假,則什麼也不做——直到條件第一次變為真時,才會開始渲染條件塊。v-show就簡單得多——不管初始條件是什麼,元素總是會被渲染,並且只是簡單地基於CSSdisplay屬性進行切換。所以,v-if適用於在執行時很少改變條件,不需要頻繁切換條件的場景,v-show則適用於需要非常頻繁切換條件的場景。

問:當給data中的物件或陣列資料發生變更資料沒有檢視沒有響應是什麼原因?

答:

因為在Vue在初始化時,會對data中的資料使用object.defineproperty對資料進行挾持,通過get/set完成資料響應的操作,由於新增的屬性,在Vue在初始化時該屬性不存在於data中,所以該屬性沒有挾持到無法執行get/setVue提供過this.$set方法可以對新增加的資料挾持完成資料相應。

問:SPA和SSR有什麼區別

答:

SPA應用是現在最常用的開發形式即單頁面應用,頁面整體式javaScript渲染出來的,稱之為客戶端渲染CSRSPA渲染過程由客戶端訪問URL傳送請求到服務端,返回HTML結構(但是SPA的返回的HTML結構是非常的小的,只有一個基本的結構)。客戶端接收到返回結果之後,在客戶端開始渲染HTML,渲染時執行對應javaScript最後通過JavaScript渲染成HTML,渲染完成之後再次向服務端傳送資料請求,注意這裡時資料請求,服務端返回json格式資料。客戶端接收資料,然後完成最終渲染。

SSR渲染流程是這樣的,客戶端傳送URL請求到服務端,服務端讀取對應的URL的模板資訊,在服務端做出HTML和資料的渲染,渲染完成之後返回HTML結構,客戶端這時拿到的之後首屏頁面的HTML結構。所以使用者在瀏覽首屏的時候速度會很快,因為客戶端不需要再次傳送ajax請求。並不是做了SSR我們的頁面就不屬於SPA應用了,它仍然是一個獨立的SPA應用。

SSR是處於CSRSPA應用之間的一個折中的方案,在渲染首屏的時候在服務端做出了渲染,注意僅僅是首屏,其他頁面還是需要在客戶端渲染的,在服務端接收到請求之後並且渲染出首屏頁面,會攜帶著剩餘的路由資訊預留給客戶端去渲染其他路由的頁面。

SSR優點:

  1. 更好的SEO,搜尋引擎爬蟲爬取工具可以直接檢視完全渲染的頁面
  2. 更寬的內容達到時間(time-to-content),當權請求頁面的時候,服務端渲染完資料之後,把渲染好的頁面直接傳送給瀏覽器,並進行渲染。瀏覽器只需要解析html不需要去解析javaScript

SSR缺點:

  1. 開發條件受限,Vue元件的某些生命週期鉤子函式不能使用
  2. 開發環境基於Node.js
  3. 會造成服務端更多的負載。在Node.js中渲染完整的應用程式,顯然會比僅僅提供靜態檔案server更加佔用CPU資源,因此如果你在預料在高流量下使用,請準備響應的服務負載,並明智的採用快取策略

問:vue-router有幾種模式?

答:

vue-router公共三種模式:hashhistoryabstract

hash模式:
  1. hash值是url#及後面的部分
  2. hash值改變不會引起頁面重新整理
  3. hash值的改變會觸發hashchange事件
history模式:
  1. 利用history.pushState來完成url跳轉而無需重新整理頁面
  2. 需要後臺配置支援,如果URL匹配不到任何靜態資源,伺服器應該返回應用依賴的index.html頁面
abstract模式:
  1. 適用於所有JavaScript環境,例如伺服器端使用Node.js
  2. 沒有瀏覽器API,路由器將自動被強制進入此模式
  3. 這個歷史記錄的主要目的是處理SSR,通過呼叫router.pushrouter.replace將該位置替換為啟動位置

問:$route和$router的區別?

答:

$router

通過Vue.use(VueRouter)Vue建構函式得到一個router的例項物件,這個物件中是一個全域性的物件,他包含了所有的路由,包含了許多關鍵的路由資訊,還有路由的跳轉方法,鉤子函式等。

$route

$route是一個跳轉的路由物件,每一個路由都會有一個$route物件,是一個區域性的物件,可以獲取對應的namepathparamsquery等。路由物件的屬性是隻讀的,裡面的屬性是immutable(不可變)的,不過可以使用watch監測路由的變化。

問:vue-router守衛都有哪些?

答:

vue-router守衛主分為三類守衛分別是:全域性守衛、路由守衛和元件守衛

全域性守衛

全域性守衛字面量理解就是定義在全域性鉤子函式,當路由觸發跳轉操作時則會觸發對應的鉤子函式,全域性守衛有三種:

  1. beforeEach:它在每次導航路由跳轉前觸發
  2. beforeResolve: 所有元件內守衛非同步路由元件被解析之後觸發
  3. afterEach:路由跳轉完成後觸發

beforeEach全域性前置守衛接收三個引數:

  • to: 即將要進入的目標路由物件
  • from: 當前導航正要離開的路由物件
  • next: 一定要呼叫該方法不然會阻塞路由

重點說一下next引數,next引數是一個函式,可以不新增,但是一旦新增,則必須呼叫一次,否則路由跳轉等會停止。next引數只有在beforeEachbeforeResolve兩個路由守衛中存在,afterEach因為已經跳轉到了目標路由則沒有提供next方法。如果直接執行next函式則會直接接入to所對應的目標路由中。next(false)則會終端當前路由當行,返回form路由對應的路由中。在next中傳入跳轉方案(如:next('/') 或者 next({ path:'/'})),則會跳轉到對應的地址中。next(error)則導航會終止,且該錯誤會被傳遞給router.onError()註冊過的回撥。beforeEach可以定義多個,會根據建立順序呼叫,在所有守衛完成之前導航一直處於等待中。

路由守衛

路由守衛只有一個beforeEnter,需要在路由配置上定義beforeEnter守衛,此守衛只在進入路由時觸發,在beforeEach之後緊隨執行,不會在paramsqueryhash改變時觸發,beforeEnter路由守衛的引數是tofromnext,同beforeEach一樣。

元件守衛

元件守衛則是需要定義在元件中的,元件守衛石油三種:

  1. beforeRouteEnter:路由進入元件之前呼叫,該鉤子在全域性守衛beforeEach和路由守衛beforeEnter之後,全域性beforeResolve和全域性 afterEach之前呼叫,該守衛內訪問不到元件的例項,也就是thisundefined,也就是他在beforeCreate生命週期前觸發
  2. beforeRouteUpdate:在當前路由改變,但是該元件被複用時呼叫,舉例來說,對於一個帶有動態引數的路徑/foo/:id,在/foo/1/foo/2之間跳轉的時候,由於會渲染同樣的Foo元件,因此元件例項會被複用。而這個鉤子就會在這個情況下被呼叫。可以訪問到元件例項 this
  3. beforeRouteLeave:導航離開該元件的對應路由時呼叫,可以訪問元件例項 this

問:vue-router如何做許可權控制?

答:

vue-router許可權控制最常用的方法是在beforeEach需要跳轉的路由進行控制,如果沒有許可權則無法跳轉到路由,一般情況下在路由配置的meta中新增許可權控制欄位,當路由發生跳轉時,會觸發beforeEach全域性路由守衛,在該守衛中對許可權進行鑑別即可完成路由許可權控制。初次之外還有一種方法,則是使用addRoutes方法,動態的向vue-router中新增路由配置,在新增配置之前只在路由中註冊具有許可權的路由資訊即可。

問:vue-router傳參有幾種方式?

答:

vue-router傳參一共有三種方式:

param

param傳參需要在配置路由資訊的是時候,在路由中註明需要接收的引數(如:url/:id),其中的id則是param需要傳遞的引數,需要注意的是當以這種形式傳遞引數的時候,如果沒有傳遞引數是無法正確的匹配到路由的。可以在定義引數前新增?則代表該引數可有可無,無論是否傳遞引數都能正確的匹配到對應的路由。引數後面可以新增正則如:url/?:id(\\d+),以這種形式新增引數可以限制引數的格式。

當使用param的時候,在配置路由資訊的時新增{props:true}配置項,則傳入的param引數,在頁面的中的props中接收到該引數,除了以這種方式接收以外,還可以使用this.$route.params中獲取到引數。傳遞param引數無論使用router-link還是程式設計式導航,如果是傳入的字串形式,則可以在對應的路由地址後面直接拼接引數即可。如果是以物件的形式,在物件中新增params欄位,內部對應的是對應的param引數即可。

query

queryparam傳參只是有略微的不同,query傳參不需要在路由配置中定義有哪些引數,query是可有可無的不會影響到路由地址的匹配。query的傳遞引數使用query物件或者在地址後面以?的形式進行拼接。接收的時候則是在this.$route.query中獲取到傳遞過來的引數。

注:無論是使用param還是query進行路由傳參,當頁面重新整理的時候,都不會導致引數丟失,因為其引數是直接存放於路由地址中

meta

個人不太建議使用meta這種形式傳參,雖然通過操作也是可以完成傳參的目的,但是當頁面重新整理的時候會導致引數的丟失,但是,這種傳參是隱式傳參,使用者是無法得知的。

使用meta傳參可以在路由跳轉之前,在跳轉頁面通過this.$route獲取到當前路由的物件,在路由物件的meta中存入對應的引數。然後使用對應的方法進行路由跳轉。當跳轉完成之後,在目標頁面使用beforeRouteEnter元件鉤子函式,對引數進行接收,雖然beforeRouteEnter無法訪問到this,但是在第三個引數即next中可以傳入一個函式作為引數,這個引數中可以訪問到當前元件的例項,在之後在beforeRouteEnter的第二個引數form中物件中可以讀取到meta中儲存的資料完成傳參。

你對Vuex是如何理解的?

答:

Vuex是為Vue提供的狀態管理模式,集中式存貯管理應用的所有元件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。中有五大核心屬性:

  1. state:state主要用於儲存資料和儲存狀態,在根例項中註冊store以後,用this.$store.state來訪問到state中所儲存的資料。存放資料方式為響應式,vue元件從store中讀取資料,如資料發生變化,元件也會對應的更新。
  2. mutation:更改Vuexstore中的狀態的唯一方法是提交mutation
  3. action:該方法與mutation類似,唯一不同的是在action所執行的是非同步方法。
  4. getter:可以認為是store的計算屬性,它的返回值會根據它的依賴被快取起來,且只有當它的依賴值發生了改變才會被重新計算。
  5. module:將store分割成模組,每個模組都具有statemutationactiongetter甚至是巢狀子模組。

當元件進行資料修改的時候我們需要呼叫dispatch來觸發actions裡面的方法。actions裡面的每個方法中都會有一個commit方法,當方法執行的時候會通過commit來觸發mutations裡面的方法進行資料的修改。mutations裡面的每個函式都會有一個state引數,這樣就可以在mutations裡面進行state的資料修改,當資料修改完畢後,會傳導給頁面。頁面的資料也會發生改變。

由於傳參的方法對於多層巢狀的元件將會非常繁瑣,並且對於兄弟元件間的狀態傳遞雖然也可以通過其他的方法進行引數傳遞,可能仍會感到無力。我們經常會採用父子元件直接引用或者通過事件來變更和同步狀態的多份拷貝。以上的這些模式非常脆弱,通常會導致程式碼無法維護。所以我們需要把元件的共享狀態抽取出來,以一個全域性單例模式管理。在這種模式下,我們的元件樹構成了一個巨大的檢視,不管在樹的哪個位置,任何元件都能獲取狀態或者觸發行為!另外,通過定義和隔離狀態管理中的各種概念並強制遵守一定的規則,我們的程式碼將會變得更結構化且易維護。

底層面試題

這一部分面試題相比上面要相對難一些,可能直接上升了一個等級,可能需要對Vue相關API有足夠的瞭解,並且閱讀過相關原始碼的下才能更好的理解。

問:Vue的兩個核心是什麼?

答:分別是:資料驅動和元件系統

資料驅動

Vue資料觀測原理在技術實現上,利用的是ES5Object.defineProperty和儲存器屬性:gettersetter可稱為基於依賴收集的觀測機制,這些gettersetter對使用者來說是不可見的,但是在內部它們讓Vue能夠追蹤依賴,在property被訪問和修改時通知變更。核心是VM,即ViewModel,保證資料和檢視的一致性。每一個指令都會有一個對應的用來觀測資料的物件,叫做watcher,每個元件例項都對應一個watcher例項,它會在元件渲染的過程中把接觸過的資料property記錄為依賴。getter的時候我們會收集依賴,依賴收集就是訂閱資料變化watcher的收集,依賴收集的目的是當響應式資料發生變化時,能夠通知相應的訂閱者去處理相關的邏輯。setter的時候會觸發依賴更新,之後當依賴項的setter觸發時,會通知watcher,從而使它關聯的元件重新渲染。

元件系統

元件系統是Vue的另一個重要概念,因為它是一種抽象,允許我們使用小型、獨立和通常可複用的元件構建大型應用。仔細想想,幾乎任意型別的應用介面都可以抽象為一個元件樹,Vue更希望構建的頁面是由各個元件構成而非html,當開發專案時把整個應用程式劃分為元件,以使開發更易管理與維護。

元件系統中包含了幾個很重要的部分:

  1. template:模板宣告瞭資料和最終展現給使用者的DOM之間的對映關係。
  2. data:一個元件的初始資料狀態。對於可複用的元件來說,這通常是私有的狀態
  3. props:元件之間通過引數來進行資料的傳遞和共享
  4. methods:對資料的改動操作一般都在元件的方法內進行
  5. lifecycle hooks:一個元件會觸發多個生命週期鉤子函式
  6. assets:Vue.js當中將使用者自定義的指令、過濾器、元件等統稱為資源

問:如何理解MVVM?

答:

MVVM主要由三個部分組成:ModelViewViewModel

  1. Model:代表資料模型,也可以在Model中定義資料修改和業務邏輯
  2. View:代表UI元件,它負責將資料模型轉化成UI展現出來
  3. ViewModel:代表同步View和Model的物件

MVVMViewModel沒有直接的關係,全部通過ViewModel進行互動。ViewModel負責把Model的資料同步到View顯示出來,還負責把View的修改同步回ModelMVVM本質就是基於運算元據來操作檢視進而操作DOM,藉助MVVM無需直接操作DOM,開發者只需完成包含宣告繫結的檢視模板,編寫ViewModel中有業務,使得View完全實現自動化。

問:Vue初始化做了什麼?

答:

Vue在初始化時會接收使用者所傳入的options即所需要的引數,之後則會建立Vue的例項,並且為該例項定義一個唯一的_uid的標識,用於區分Vue的例項,把Vue例項標記成Vue避免被觀察者觀測到。

完成例項建立的初始化工作之後,需要對使用者選項和系統預設的選項進行合併,首先處理的的是元件的配置內容,把傳入的option與其建構函式本身進行合併,這裡蠶蛹的策略是預設配置和傳入配置的配置進行合併。如果是內部元件(子元件)例項化,且動態的options合併會很慢,需要對options的一些特殊引數進行處理。如果是根元件,將全域性配置選項合併到根元件的配置上,其實就是一個選項合併。

接下來則是初始化核心部分,首先初始化Vue例項生命週期相關的屬性,定義了比如:rootparentchildrenrefs,接著初始化自定義元件事件的監聽,若存在父監聽事件,則新增到該例項上,然後初始化render渲染所需的slots、渲染函式等。其實就兩件事

  1. 插槽的處理、
  2. $createElm 也就是render函式中的h的宣告

接下來則是執行beforeCreate鉤子函式,在這裡就能看出一個元件在建立前和後分別做了哪些初始化。

執行完beforeCreate鉤子函式之後,初始化注入資料,隔代傳參時先inject。作為一個元件,在要給後輩元件提供資料之前,需要先把祖輩傳下來的資料注入進來。對propsmethodsdatacomputedwatch進行初始化,包括響應式的處理,在把祖輩傳下來的資料注入進來以後再初始化provide

最後呼叫created鉤子函式,初始化完成,可以執行掛載了,掛載到對應DOM元素上。如果元件建構函式設定了el選項,會自動掛載,所以就不用再手動呼叫$mount去掛載。

問:如何理解Vue的響應式資料?

答:

Vue中實現了一個definedReactive方法,方法內部借用Object.definedProperty()給每一個屬性都新增了get/set的屬性。definedReactive只能監控到最外層的物件,對於內層的物件需要遞迴劫持資料。陣列則是重寫的7個pushpopshiftunshiftreversesortsplice來給陣列做資料攔截,因為這幾個方法會改變原陣列。物件新增或者刪除的屬性無法被set監聽到只有物件本身存在的屬性修改才會被劫持。或使用Vue提供的$set()實現監聽,在defineReactive中存有一個Dep類,這個用來收集渲染的WatcherWatcher的主要工作則使用用來更新檢視。

問:Vue如何進行依賴收集?

答:

Dep是一個用來負責收集Watcher的類,Watcher是一個封裝了渲染檢視邏輯的類,用於派發更新的。需要注意的是Watcher是不能直接更新檢視的還需要結合Vnode經過patch()中的diff演算法才可以生成真正的DOM

然而每一個屬性都有自己的dep屬性,來存放依賴的Watcher,屬性發生變化後會通知Watcher去更新。在使用者獲取(getter)資料時Vue 給每一個屬性都新增了dep屬性來(collect as Dependency)收集Watcher。在使用者setting設定屬性值時dep.notify()通知收集的Watcher重新渲染。Dep依賴收集類其和Watcher類是多對多雙向儲存的關係。每一個屬性都可以有多個Watcher類,因為屬性可能在不同的元件中被使用。同時一個Watcher類也可以對應多個屬性。

每一個屬性可以有多個依賴,比如這個屬性可能使用在computed中,watch中,本身的data屬性中。這些依賴都是使用響應式資料的Dep 來收集的。Watcher是依賴就像一箇中介,能夠被Dep收集也能夠被Dep通知更新。

問:Vue是如何編譯模板的?

答:

編譯是把我們寫的.vue檔案裡的template標籤包含的html標籤變為render函式,可以這麼理解template模板是對render的封裝,templatevue原始碼裡面會變轉化為render函式:

  1. 將template模板字串轉換成ast語法樹(parser 解析器),這裡使用了大量的正則來匹配標籤的名稱,屬性,文字等。
  2. 對AST進行靜態節點static標記,主要用來做虛擬DOM的渲染優化(optimize優化器),這裡會遍歷出所有的子節點也做靜態標記
  3. 使用AST語法樹 重新生成render函式程式碼字串code。

問:Vue生命週期鉤子實現原理?

答:

Vue中的生命週期鉤子只是一個回撥函式,在建立元件例項化的過程中會呼叫對應的鉤子執行。使用Vue.mixin({})混入的鉤子或生命週期中定義了多個函式,Vue內部會呼叫mergeHook()對鉤子進行合併放入到佇列中依次執行。

問:Vue.mixin使用場景和原理?

答:

Vue.mixin主要用於抽離一個公共的業務邏輯實現複用。其內部執行時會呼叫mergeOptions()方法採用策略模式針對不同的屬性合併。混入的資料和元件的資料有衝突就採用元件本身的。Vue.mixin({})存在一些缺陷,導致混入的屬性名和元件屬性名發生命名衝突,資料依賴的來源問題。

問:$nextTick實現原理?

答:

vm.$nextTick(cb)是一個非同步的方法為了相容性做了很多降級處理依次有promise.then,MutationObserversetImmediatesetTimeout。在資料修改後不會馬上更新檢視,而是經過set方法notify通知Watcher更新,將需要更新的Watcher放入到一個非同步佇列中,nexTick的回撥函式就放在Watcher的後面,等待主執行緒中同步程式碼執行借宿然後依次清空佇列中,所以vm.nextTick(callback)是在dom更新結束後執行的。

問:vue-router實現原理?

答:vue-router最常用的模式有兩種分別是hash模式和history模式:

hash模式

hash模式是vue-router的預設路由模式,它的標誌是在域名之後帶有一個#,通過window.location.hash獲取到當前urlhash。·hash·模式下通過hashchange方法可以監聽urlhash的變化。hash模式的特點是相容性更好,並且hash的變化會在瀏覽器的history中增加一條記錄,可以實現瀏覽器的前進和後退功能。

history模式

history模式是另一種前端路由模式,它基於HTML5history物件,通過location.pathname獲取到當前url的路由地址。history模式下,通過pushStatereplaceState方法可以修改url地址,結合popstate方法監聽url中路由的變化。

問:vuex實現原理?

答:

Vuex在初始化時,在全域性儲存了Vue的例項,在install函式中,首先會判斷是否已經呼叫了Vue.use(Vuex),然後呼叫applyMixin方法進行初始化的一些操作,applyMixin方法只做了一件事情,就是將所有的例項上掛載一個$store物件,在使用vuex的時候,會將store掛載在根元件之上。在第一次呼叫vuexInit函式時,options.store就是根選項的store,因此會判斷其型別是不是function,若是則執行函式並將結果賦值給根例項的$store中,否則直接賦值。

Vuexstate狀態是響應式,是藉助vuedata是響應式,將state存入vue例項元件的data中,Vuexgetters則是藉助vue的計算屬性computed實現資料實時監聽。

結束語

以上面試題是筆者在面試過程中面試官們問到的做了一些調研學習和整理,可能會有些許的錯誤,大家可以在下面評論指出錯誤,大家共同學習進步。如果有哪些方面沒有覆蓋到的地方大家也可以評論告訴我,後面回補齊。

相關文章