又快到年底了,有很多小夥伴可能會選擇跳槽或者升職或者考核,尤其重要的是面試,在面試過程中面試官總會問一些基礎性的內容來考察我們的專業知識的掌握程度,再決定是否錄用。
前言
現在對於前端來說,問的最多的可能是JavaScript
基礎,除此之外問的更多的就是目前的主流框架相關的一些知識點。目前主流的框架Vue、React、Angular
,國內的網際網路情況來講,目前還是Vue
居多。
就目前來看越來越多的面試,和之前的面試不太一樣了,兩三年前問的更多的是對於主流框架的應用,目前更多的是對於底層的一些問題更多一些。針對這些Vue
相關面試題做一下總結並記錄。希望可以幫到更多的小夥伴。
基礎面試題
基礎面試題即Vue
應用和使用方面的面試題,這一部分大多數還是面向於初級工程師,相對來說要簡單一些。
問:Vue生命週期鉤子都有哪些?
答:Vue
生命週期一共有10
個:
- beforeCreate - 在例項初始化之前呼叫,在此生命週期中是不存在
this
的,因為Vue
例項還沒有建立。 - created - 例項初始化之後呼叫
- beforeMount - 在掛載開始之前被呼叫,如果是在伺服器端渲染時不被呼叫,由於元素還沒有渲染,在此宣告週期無法獲取到
DOM
元素。 - mounted - 在掛載後被呼叫,是可以獲取元素,並進行操作
- beforeUpdate - 檢視層資料更新前呼叫,在虛擬
DOM
更新之前,可以獲取到DOM
j節點,但是此節點為更新之前的DOM
節點 - updated - 檢視層資料更新後呼叫,此處獲取到的
DOM
節點是更新之後DOM
- beforeDestroy - 例項銷燬之前呼叫,在被銷燬的元件中進行呼叫
- destroyed - 例項銷燬後呼叫
beforeDestroy
和destroyed
生命週期階段可以在此階段,進行`Vuex
資料重置,取消事件監聽等操作,提升效能。
以上是比較常用的生命週期鉤子函式,還有兩個比較特殊的生命週期鉤子,分別是activated
和deactivated
,這兩個生命週期只有在元件上使用了keep-alive
之後才會被執行。
- activated - 例項被啟用時使用,用於重複啟用一個例項的時候,該生命週期於會在
mounted
之後執行。 - deactivated - 例項沒有被啟用時
問:父元件和子元件生命週期鉤子函式執行順序?
答:
Vue
的父元件和子元件生命週期鉤子函式執行順序可以歸類為以下4部分:
載入渲染過程
- 父 beforeCreate
- 父 created
- 父 beforeMount
- 子 beforeCreate
- 子 created
- 子 beforeMount
- 子 mounted
- 父 mounted
子元件更新過程
- 父 beforeUpdate
- 子 beforeUpdate
- 子 updated
- 父 updated
父元件更新過程
- 父 beforeUpdate
- 父 updated
銷燬過程
- 父 beforeDestroy
- 子 beforeDestroy
- 子 destroyed
- 父 destroyed
問:在哪個生命週期內呼叫非同步請求?
答:
可以在鉤子函式created
、beforeMount
、mounted
中進行呼叫,因為在這三個鉤子函式中,data
已經建立,可以將服務端端返回的資料進行賦值。推薦在 created 鉤子函式中呼叫非同步請求,因為在created
鉤子函式中呼叫非同步請求,因為能更快獲取到服務端資料,減少頁面loading
時間,ssr
不支援beforeMount
、mounted
鉤子函式,所以放在created
中有助於一致性。
問:父元件可以監聽到子元件的生命週期嗎?
答:
可以的,有兩種方法可以可以做到,第一種則是使用$emit
自定義事件進行事件監聽,當子元件執行到某個生命週期時用$emit
通知即可。第二種使用@hook:生命週期名稱
,@hook
方法不僅僅是可以監聽mounted
,其它的生命週期事件,例如:created
,updated
等都可以監聽。
問:keep-alive是做什麼的?
答:keep-alive
是Vue
內建的一個元件,可以使被包含的元件保留狀態,避免重新渲染 ,其有以下特性:
- 一般結合路由和動態元件一起使用,用於快取元件
- 提供
include
和exclude
屬性,兩者都支援字串或正規表示式,include
表示只有名稱匹配的元件會被快取,exclude
表示任何名稱匹配的元件都不會被快取 ,其中exclude
的優先順序比include
高 - 對應兩個鉤子函式
activated
和deactivated
,當元件被啟用時,觸發鉤子函式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狀態管理
Vuex
是Vue
的核心外掛、用於任意元件的任意通訊,需注意重新整理後有可能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
是監控值的變化,值發生變化時會執行對應的回撥函式,computed
和watch
都是基於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
屬性,修改其他第三方元件的樣式。使用sass
或less
的樣式穿透/deep/
。
問:v-show與v-if 有什麼區別?
答:
v-if
是真正的條件渲染,因為它會確保在切換過程中條件塊內的事件監聽器和子元件適當地被銷燬和重建,v-if
是惰性的:如果在初始渲染時條件為假,則什麼也不做——直到條件第一次變為真時,才會開始渲染條件塊。v-show
就簡單得多——不管初始條件是什麼,元素總是會被渲染,並且只是簡單地基於CSS
的display
屬性進行切換。所以,v-if
適用於在執行時很少改變條件,不需要頻繁切換條件的場景,v-show
則適用於需要非常頻繁切換條件的場景。
問:當給data中的物件或陣列資料發生變更資料沒有檢視沒有響應是什麼原因?
答:
因為在Vue
在初始化時,會對data
中的資料使用object.defineproperty
對資料進行挾持,通過get/set
完成資料響應的操作,由於新增的屬性,在Vue
在初始化時該屬性不存在於data
中,所以該屬性沒有挾持到無法執行get/set
。Vue
提供過this.$set
方法可以對新增加的資料挾持完成資料相應。
問:SPA和SSR有什麼區別
答:
SPA
應用是現在最常用的開發形式即單頁面應用,頁面整體式javaScript
渲染出來的,稱之為客戶端渲染CSR
。SPA
渲染過程由客戶端訪問URL
傳送請求到服務端,返回HTML
結構(但是SPA
的返回的HTML
結構是非常的小的,只有一個基本的結構)。客戶端接收到返回結果之後,在客戶端開始渲染HTML
,渲染時執行對應javaScript
最後通過JavaScript
渲染成HTML
,渲染完成之後再次向服務端傳送資料請求,注意這裡時資料請求,服務端返回json
格式資料。客戶端接收資料,然後完成最終渲染。
SSR
渲染流程是這樣的,客戶端傳送URL
請求到服務端,服務端讀取對應的URL
的模板資訊,在服務端做出HTML
和資料的渲染,渲染完成之後返回HTML
結構,客戶端這時拿到的之後首屏頁面的HTML
結構。所以使用者在瀏覽首屏的時候速度會很快,因為客戶端不需要再次傳送ajax
請求。並不是做了SSR
我們的頁面就不屬於SPA
應用了,它仍然是一個獨立的SPA
應用。
SSR
是處於CSR
與SPA
應用之間的一個折中的方案,在渲染首屏的時候在服務端做出了渲染,注意僅僅是首屏,其他頁面還是需要在客戶端渲染的,在服務端接收到請求之後並且渲染出首屏頁面,會攜帶著剩餘的路由資訊預留給客戶端去渲染其他路由的頁面。
SSR優點:
- 更好的SEO,搜尋引擎爬蟲爬取工具可以直接檢視完全渲染的頁面
- 更寬的內容達到時間(
time-to-content
),當權請求頁面的時候,服務端渲染完資料之後,把渲染好的頁面直接傳送給瀏覽器,並進行渲染。瀏覽器只需要解析html
不需要去解析javaScript
。
SSR缺點:
- 開發條件受限,
Vue
元件的某些生命週期鉤子函式不能使用 - 開發環境基於
Node.js
- 會造成服務端更多的負載。在
Node.js
中渲染完整的應用程式,顯然會比僅僅提供靜態檔案server
更加佔用CPU
資源,因此如果你在預料在高流量下使用,請準備響應的服務負載,並明智的採用快取策略
問:vue-router有幾種模式?
答:
vue-router
公共三種模式:hash
、history
和abstract
。
hash模式:
hash
值是url
中#
及後面的部分hash
值改變不會引起頁面重新整理hash
值的改變會觸發hashchange
事件
history模式:
- 利用
history.pushState
來完成url
跳轉而無需重新整理頁面 - 需要後臺配置支援,如果
URL
匹配不到任何靜態資源,伺服器應該返回應用依賴的index.html
頁面
abstract模式:
- 適用於所有
JavaScript
環境,例如伺服器端使用Node.js
- 沒有瀏覽器
API
,路由器將自動被強制進入此模式 - 這個歷史記錄的主要目的是處理
SSR
,通過呼叫router.push
或router.replace
將該位置替換為啟動位置
問:$route和$router的區別?
答:
$router
通過Vue.use(VueRouter)
和Vue
建構函式得到一個router
的例項物件,這個物件中是一個全域性的物件,他包含了所有的路由,包含了許多關鍵的路由資訊,還有路由的跳轉方法,鉤子函式等。
$route
$route
是一個跳轉的路由物件,每一個路由都會有一個$route
物件,是一個區域性的物件,可以獲取對應的name
,path
,params
,query
等。路由物件的屬性是隻讀的,裡面的屬性是immutable
(不可變)的,不過可以使用watch
監測路由的變化。
問:vue-router守衛都有哪些?
答:
vue-router
守衛主分為三類守衛分別是:全域性守衛、路由守衛和元件守衛
全域性守衛
全域性守衛字面量理解就是定義在全域性鉤子函式,當路由觸發跳轉操作時則會觸發對應的鉤子函式,全域性守衛有三種:
- beforeEach:它在每次導航路由跳轉前觸發
- beforeResolve: 所有
元件內守衛
和非同步路由元件
被解析之後觸發 - afterEach:路由跳轉完成後觸發
beforeEach
全域性前置守衛接收三個引數:
- to: 即將要進入的目標路由物件
- from: 當前導航正要離開的路由物件
- next: 一定要呼叫該方法不然會阻塞路由
重點說一下next
引數,next
引數是一個函式,可以不新增,但是一旦新增,則必須呼叫一次,否則路由跳轉等會停止。next
引數只有在beforeEach
和beforeResolve
兩個路由守衛中存在,afterEach
因為已經跳轉到了目標路由則沒有提供next
方法。如果直接執行next
函式則會直接接入to
所對應的目標路由中。next(false)
則會終端當前路由當行,返回form
路由對應的路由中。在next
中傳入跳轉方案(如:next('/') 或者 next({ path:'/'})
),則會跳轉到對應的地址中。next(error)
則導航會終止,且該錯誤會被傳遞給router.onError()
註冊過的回撥。beforeEach
可以定義多個,會根據建立順序呼叫,在所有守衛完成之前導航一直處於等待中。
路由守衛
路由守衛只有一個beforeEnter
,需要在路由配置上定義beforeEnter
守衛,此守衛只在進入路由時觸發,在beforeEach
之後緊隨執行,不會在params
、query
或hash
改變時觸發,beforeEnter
路由守衛的引數是to
、from
、next
,同beforeEach
一樣。
元件守衛
元件守衛則是需要定義在元件中的,元件守衛石油三種:
- beforeRouteEnter:路由進入元件之前呼叫,該鉤子在全域性守衛
beforeEach
和路由守衛beforeEnter
之後,全域性beforeResolve
和全域性afterEach
之前呼叫,該守衛內訪問不到元件的例項,也就是this
為undefined
,也就是他在beforeCreate
生命週期前觸發 - beforeRouteUpdate:在當前路由改變,但是該元件被複用時呼叫,舉例來說,對於一個帶有動態引數的路徑
/foo/:id
,在/foo/1
和/foo/2
之間跳轉的時候,由於會渲染同樣的Foo
元件,因此元件例項會被複用。而這個鉤子就會在這個情況下被呼叫。可以訪問到元件例項this
- 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
query
和param
傳參只是有略微的不同,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
提供的狀態管理模式,集中式存貯管理應用的所有元件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。中有五大核心屬性:
- state:
state
主要用於儲存資料和儲存狀態,在根例項中註冊store
以後,用this.$store.state
來訪問到state
中所儲存的資料。存放資料方式為響應式,vue
元件從store
中讀取資料,如資料發生變化,元件也會對應的更新。 - mutation:更改
Vuex
的store
中的狀態的唯一方法是提交mutation
。 - action:該方法與
mutation
類似,唯一不同的是在action
所執行的是非同步方法。 - getter:可以認為是
store
的計算屬性,它的返回值會根據它的依賴被快取起來,且只有當它的依賴值發生了改變才會被重新計算。 - module:將
store
分割成模組,每個模組都具有state
、mutation
、action
、getter
甚至是巢狀子模組。
當元件進行資料修改的時候我們需要呼叫dispatch
來觸發actions
裡面的方法。actions
裡面的每個方法中都會有一個commit
方法,當方法執行的時候會通過commit
來觸發mutations
裡面的方法進行資料的修改。mutations
裡面的每個函式都會有一個state
引數,這樣就可以在mutations
裡面進行state
的資料修改,當資料修改完畢後,會傳導給頁面。頁面的資料也會發生改變。
由於傳參的方法對於多層巢狀的元件將會非常繁瑣,並且對於兄弟元件間的狀態傳遞雖然也可以通過其他的方法進行引數傳遞,可能仍會感到無力。我們經常會採用父子元件直接引用或者通過事件來變更和同步狀態的多份拷貝。以上的這些模式非常脆弱,通常會導致程式碼無法維護。所以我們需要把元件的共享狀態抽取出來,以一個全域性單例模式管理。在這種模式下,我們的元件樹構成了一個巨大的檢視
,不管在樹的哪個位置,任何元件都能獲取狀態或者觸發行為!另外,通過定義和隔離狀態管理中的各種概念並強制遵守一定的規則,我們的程式碼將會變得更結構化且易維護。
底層面試題
這一部分面試題相比上面要相對難一些,可能直接上升了一個等級,可能需要對Vue
相關API
有足夠的瞭解,並且閱讀過相關原始碼的下才能更好的理解。
問:Vue的兩個核心是什麼?
答:分別是:資料驅動和元件系統
資料驅動
Vue
資料觀測原理在技術實現上,利用的是ES5
的Object.defineProperty
和儲存器屬性:getter
和setter
可稱為基於依賴收集的觀測機制,這些getter
和setter
對使用者來說是不可見的,但是在內部它們讓Vue
能夠追蹤依賴,在property
被訪問和修改時通知變更。核心是VM
,即ViewModel
,保證資料和檢視的一致性。每一個指令都會有一個對應的用來觀測資料的物件,叫做watcher
,每個元件例項都對應一個watcher
例項,它會在元件渲染的過程中把接觸
過的資料property
記錄為依賴。getter
的時候我們會收集依賴,依賴收集就是訂閱資料變化watcher
的收集,依賴收集的目的是當響應式資料發生變化時,能夠通知相應的訂閱者去處理相關的邏輯。setter
的時候會觸發依賴更新,之後當依賴項的setter
觸發時,會通知watcher
,從而使它關聯的元件重新渲染。
元件系統
元件系統是Vue
的另一個重要概念,因為它是一種抽象,允許我們使用小型、獨立和通常可複用的元件構建大型應用。仔細想想,幾乎任意型別的應用介面都可以抽象為一個元件樹,Vue
更希望構建的頁面是由各個元件構成而非html
,當開發專案時把整個應用程式劃分為元件,以使開發更易管理與維護。
元件系統中包含了幾個很重要的部分:
- template:模板宣告瞭資料和最終展現給使用者的DOM之間的對映關係。
- data:一個元件的初始資料狀態。對於可複用的元件來說,這通常是私有的狀態
- props:元件之間通過引數來進行資料的傳遞和共享
- methods:對資料的改動操作一般都在元件的方法內進行
- lifecycle hooks:一個元件會觸發多個生命週期鉤子函式
- assets:Vue.js當中將使用者自定義的指令、過濾器、元件等統稱為資源
問:如何理解MVVM?
答:
MVVM
主要由三個部分組成:Model
、View
、ViewModel
- Model:代表資料模型,也可以在Model中定義資料修改和業務邏輯
- View:代表UI元件,它負責將資料模型轉化成UI展現出來
- ViewModel:代表同步View和Model的物件
在MVVM
中View
和Model
沒有直接的關係,全部通過ViewModel
進行互動。ViewModel
負責把Model
的資料同步到View
顯示出來,還負責把View
的修改同步回Model
。MVVM
本質就是基於運算元據來操作檢視進而操作DOM
,藉助MVVM
無需直接操作DOM
,開發者只需完成包含宣告繫結的檢視模板,編寫ViewModel
中有業務,使得View
完全實現自動化。
問:Vue初始化做了什麼?
答:
Vue
在初始化時會接收使用者所傳入的options
即所需要的引數,之後則會建立Vue
的例項,並且為該例項定義一個唯一的_uid
的標識,用於區分Vue
的例項,把Vue
例項標記成Vue
避免被觀察者觀測到。
完成例項建立的初始化工作之後,需要對使用者選項和系統預設的選項進行合併,首先處理的的是元件的配置內容,把傳入的option
與其建構函式本身進行合併,這裡蠶蛹的策略是預設配置和傳入配置的配置進行合併。如果是內部元件(子元件)例項化,且動態的options
合併會很慢,需要對options
的一些特殊引數進行處理。如果是根元件,將全域性配置選項合併到根元件的配置上,其實就是一個選項合併。
接下來則是初始化核心部分,首先初始化Vue
例項生命週期相關的屬性,定義了比如:root
、parent
、children
、refs
,接著初始化自定義元件事件的監聽,若存在父監聽事件,則新增到該例項上,然後初始化render
渲染所需的slots、渲染函式等。其實就兩件事
- 插槽的處理、
- $createElm 也就是render函式中的h的宣告
接下來則是執行beforeCreate
鉤子函式,在這裡就能看出一個元件在建立前和後分別做了哪些初始化。
執行完beforeCreate
鉤子函式之後,初始化注入資料,隔代傳參時先inject
。作為一個元件,在要給後輩元件提供資料之前,需要先把祖輩傳下來的資料注入進來。對props
、methods
、data
、computed
、watch
進行初始化,包括響應式的處理,在把祖輩傳下來的資料注入進來以後再初始化provide
。
最後呼叫created
鉤子函式,初始化完成,可以執行掛載了,掛載到對應DOM
元素上。如果元件建構函式設定了el
選項,會自動掛載,所以就不用再手動呼叫$mount
去掛載。
問:如何理解Vue的響應式資料?
答:
Vue
中實現了一個definedReactive
方法,方法內部借用Object.definedProperty()
給每一個屬性都新增了get/set
的屬性。definedReactive
只能監控到最外層的物件,對於內層的物件需要遞迴劫持資料。陣列則是重寫的7個push
、pop
、shift
、unshift
、reverse
、sort
、splice
來給陣列做資料攔截,因為這幾個方法會改變原陣列。物件新增或者刪除的屬性無法被set
監聽到只有物件本身存在的屬性修改才會被劫持。或使用Vue
提供的$set()
實現監聽,在defineReactive
中存有一個Dep
類,這個用來收集渲染的Watcher
,Watcher
的主要工作則使用用來更新檢視。
問: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
的封裝,template
在vue
原始碼裡面會變轉化為render
函式:
- 將template模板字串轉換成ast語法樹(parser 解析器),這裡使用了大量的正則來匹配標籤的名稱,屬性,文字等。
- 對AST進行靜態節點static標記,主要用來做虛擬DOM的渲染優化(optimize優化器),這裡會遍歷出所有的子節點也做靜態標記
- 使用AST語法樹 重新生成render函式程式碼字串code。
問:Vue生命週期鉤子實現原理?
答:
Vue
中的生命週期鉤子只是一個回撥函式,在建立元件例項化的過程中會呼叫對應的鉤子執行。使用Vue.mixin({})
混入的鉤子或生命週期中定義了多個函式,Vue
內部會呼叫mergeHook()
對鉤子進行合併放入到佇列中依次執行。
問:Vue.mixin使用場景和原理?
答:
Vue.mixin
主要用於抽離一個公共的業務邏輯實現複用。其內部執行時會呼叫mergeOptions()
方法採用策略模式針對不同的屬性合併。混入的資料和元件的資料有衝突就採用元件本身的。Vue.mixin({})
存在一些缺陷,導致混入的屬性名和元件屬性名發生命名衝突,資料依賴的來源問題。
問:$nextTick實現原理?
答:
vm.$nextTick(cb)
是一個非同步的方法為了相容性做了很多降級處理依次有promise.then,MutationObserver
,setImmediate
,setTimeout
。在資料修改後不會馬上更新檢視,而是經過set
方法notify
通知Watcher
更新,將需要更新的Watcher
放入到一個非同步佇列中,nexTick
的回撥函式就放在Watcher
的後面,等待主執行緒中同步程式碼執行借宿然後依次清空佇列中,所以vm.nextTick(callback)
是在dom
更新結束後執行的。
問:vue-router實現原理?
答:vue-router
最常用的模式有兩種分別是hash
模式和history
模式:
hash模式
hash
模式是vue-router
的預設路由模式,它的標誌是在域名之後帶有一個#
,通過window.location.hash
獲取到當前url
的hash
。·hash·模式下通過hashchange
方法可以監聽url
中hash
的變化。hash
模式的特點是相容性更好,並且hash
的變化會在瀏覽器的history
中增加一條記錄,可以實現瀏覽器的前進和後退功能。
history模式
history
模式是另一種前端路由模式,它基於HTML5
的history
物件,通過location.pathname
獲取到當前url的路由地址。history
模式下,通過pushState
和replaceState
方法可以修改url
地址,結合popstate
方法監聽url
中路由的變化。
問:vuex實現原理?
答:
Vuex
在初始化時,在全域性儲存了Vue
的例項,在install
函式中,首先會判斷是否已經呼叫了Vue.use(Vuex)
,然後呼叫applyMixin
方法進行初始化的一些操作,applyMixin
方法只做了一件事情,就是將所有的例項上掛載一個$store
物件,在使用vuex
的時候,會將store
掛載在根元件之上。在第一次呼叫vuexInit
函式時,options.store
就是根選項的store
,因此會判斷其型別是不是function
,若是則執行函式並將結果賦值給根例項的$store
中,否則直接賦值。
Vuex
的state
狀態是響應式,是藉助vue
的data
是響應式,將state
存入vue
例項元件的data
中,Vuex
的getters
則是藉助vue
的計算屬性computed
實現資料實時監聽。
結束語
以上面試題是筆者在面試過程中面試官們問到的做了一些調研學習和整理,可能會有些許的錯誤,大家可以在下面評論指出錯誤,大家共同學習進步。如果有哪些方面沒有覆蓋到的地方大家也可以評論告訴我,後面回補齊。