目錄
Vue面試題如何做到淺入深出村長的話下面開會v-if和v-for哪個優先順序更高?
如果兩個同時出現,應該怎麼優化得到更好的效能?
Vue元件data為什麼必須是個函式而Vue的根例項則沒有此限制?
你知道vue中key的作用和工作原理嗎?說說你對它的理解。
你怎麼理解vue中的diff演算法?
談一談對vue元件化的理解?
談一談對vue設計原則的理解?
談談你對MVC、MVP和MVVM的理解?
你了解哪些Vue效能優化方法?
你對Vue3.0的新特性有沒有了解?
總結髮言
Vue面試題如何做到淺入深出
村長的話
同學們好,我是開課吧Young村長。關於Vue框架部分會涉及一些高頻面試題,大多數看似非常初級,在官方文件就能檢視的純記憶性質的面試題,比如:
vue元件之間如何通訊?
vue生命週期有哪些?
這類面試題在文件中就能找到答案,網路上面找到的答案也是千篇一律。大家只要使用過vue並且稍加記憶即可流暢作答,但如果你也這樣回答,就算自己真的比其他競爭者更有實力,也很難脫穎而出,更別提獲得高薪offer了。
事實真是這樣嗎?
答案當然是否定的。以下這篇文章提煉於開課吧全棧課程的訓練營,我們的問題並不多,但是旨在將回答問題的方式、思路和層次提升一個層級。如果你真的學會了這些方法,在絕大多數情況下都會有舉一反三的效果,可以說拿下offer如探囊取物一般簡單!
下面開會
v-if和v-for哪個優先順序更高?如果兩個同時出現,應該怎麼優化得到更好的效能?
原始碼中找答案compiler/codegen/index.js
<p v-for="item in items" v-if="condition">
做個測試如下
兩者同級時,渲染函式如下:
(function anonymous() {with(this){return _c('div',{attrs:{"id":"demo"}},[_c('h1',[_v("v-for和v-if誰的優先順序高?應該如何正確使用避免效能問題?")]),_v(" "),_l((children),function(child){return (isFolder)?_c('p',[_v(_s(child.title))]):_e()})],2)}})
_l包含了isFolder的條件判斷
兩者不同級時,渲染函式如下
(function anonymous() {with(this){return _c('div',{attrs:{"id":"demo"}},[_c('h1',[_v("v-for和v-if誰的優先順序高?應該如何正確使用避免效能問題?")]),_v(" "),(isFolder)?_l((children),function(child){return _c('p',[_v(_s(child.title))])}):_e()],2)}})
先判斷了條件再看是否執行_l
結論:
顯然v-for優先於v-if被解析(把你是怎麼知道的告訴面試官)
如果同時出現,每次渲染都會先執行迴圈再判斷條件,無論如何迴圈都不可避免,浪費了效能
要避免出現這種情況,則在外層巢狀template,在這一層進行v-if判斷,然後在內部進行v-for迴圈
如果條件出現在迴圈內部,可通過計算屬性提前過濾掉那些不需要顯示的項
Vue元件data為什麼必須是個函式而Vue的根例項則沒有此限制?
原始碼中找答案:src\\core\\instance\\state.js - initData()
函式每次執行都會返回全新data物件例項
測試程式碼如下
<!DOCTYPE html><html><head> <title>Vue事件處理</title></head><body> <div> <h1>vue元件data為什麼必須是個函式? </h1> <comp></comp> <comp></comp> </div> <script src="../../dist/vue.js"></script> <script> Vue.component('comp', { template:'<div @click="counter++">{{counter}}</div>', data: {counter: 0} }) // 建立例項 const app = new Vue({ el: '#demo', }); </script></body></html>
程式甚至無法通過vue檢測
結論
Vue元件可能存在多個例項,如果使用物件形式定義data,則會導致它們共用一個data物件,那麼狀態變更將會影響所有元件例項,這是不合理的;採用函式形式定義,在initData時會將其作為工廠函式返回全新data物件,有效規避多例項之間狀態汙染問題。而在Vue根例項建立過程中則不存在該限制,也是因為根例項只能有一個,不需要擔心這種情況。
你知道vue中key的作用和工作原理嗎?說說你對它的理解。
原始碼中找答案:src\\core\\vdom\\patch.js - updateChildren()
測試程式碼如下
<!DOCTYPE html><html><head> <title>03-key的作用及原理?</title></head><body> <div> <p v-for="item in items" :key="item">{{item}}</p> </div> <script src="../../dist/vue.js"></script> <script> // 建立例項 const app = new Vue({ el: '#demo', data: { items: ['a', 'b', 'c', 'd', 'e'] }, mounted () { setTimeout(() => { this.items.splice(2, 0, 'f') }, 2000); }, }); </script></body></html>
上面案例重現的是以下過程
不使用key
如果使用key
// 首次迴圈patch AA B C D EA B F C D E// 第2次迴圈patch BB C D EB F C D E// 第3次迴圈patch EC D EF C D E// 第4次迴圈patch DC DF C D// 第5次迴圈patch CC F C// oldCh全部處理結束,newCh中剩下的F,建立F並插入到C前面
結論
key的作用主要是為了高效的更新虛擬DOM,其原理是vue在patch過程中通過key可以精準判斷兩個節點是否是同一個,從而避免頻繁更新不同元素,使得整個patch過程更加高效,減少DOM操作量,提高效能。
另外,若不設定key還可能在列表更新時引發一些隱蔽的bug
vue中在使用相同標籤名元素的過渡切換時,也會使用到key屬性,其目的也是為了讓vue可以區分它們,否則vue只會替換其內部屬性而不會觸發過渡效果。
你怎麼理解vue中的diff演算法?
原始碼分析1:必要性,lifecycle.js - mountComponent()
元件中可能存在很多個data中的key使用
原始碼分析2:執行方式,patch.js - patchVnode()
patchVnode是diff發生的地方,整體策略:深度優先,同層比較
原始碼分析3:高效性,patch.js - updateChildren()
測試程式碼:
<!DOCTYPE html><html><head> <title>Vue原始碼剖析</title> <script src="../../dist/vue.js"></script></head><body> <div> <h1>虛擬DOM</h1> <p>{{foo}}</p> </div> <script> // 建立例項 const app = new Vue({ el: '#demo', data: { foo: 'foo' }, mounted() { setTimeout(() => { this.foo = 'fooooo' }, 1000); } }); </script></body></html>
總結
1.diff演算法是虛擬DOM技術的必然產物:通過新舊虛擬DOM作對比(即diff),將變化的地方更新在真實DOM上;另外,也需要diff高效的執行對比過程,從而降低時間複雜度為O(n)。
2.vue 2.x中為了降低Watcher粒度,每個元件只有一個Watcher與之對應,只有引入diff才能精確找到發生變化的地方。
3.vue中diff執行的時刻是元件例項執行其更新函式時,它會比對上一次渲染結果oldVnode和新的渲染結果newVnode,此過程稱為patch。
4.diff過程整體遵循深度優先、同層比較的策略;兩個節點之間比較會根據它們是否擁有子節點或者文字節點做不同操作;比較兩組子節點是演算法的重點,首先假設頭尾節點可能相同做4次比對嘗試,如果沒有找到相同節點才按照通用方式遍歷查詢,查詢結束再按情況處理剩下的節點;藉助key通常可以非常精確找到相同節點,因此整個patch過程非常高效。
談一談對vue元件化的理解?
回答總體思路:
元件化定義、優點、使用場景和注意事項等方面展開陳述,同時要強調vue中元件化的一些特點。
原始碼分析1:元件定義
// 元件定義Vue.component('comp', { template: '<div>this is a component</div>'})
元件定義,src\\core\\global-api\\assets.js
<template> <div> this is a component </div></template>
vue-loader會編譯template為render函式,最終匯出的依然是元件配置物件。
原始碼分析2:元件化優點
lifecycle.js - mountComponent()
元件、Watcher、渲染函式和更新函式之間的關係
原始碼分析3:元件化實現
建構函式,src\\core\\global-api\\extend.js
例項化及掛載,src\\core\\vdom\\patch.js - createElm()
總結
元件是獨立和可複用的程式碼組織單元。元件系統是 Vue 核心特性之一,它使開發者使用小型、獨立和通常可複用的元件構建大型應用;
元件化開發能大幅提高應用開發效率、測試性、複用性等;
元件使用按分類有:頁面元件、業務元件、通用元件;
vue的元件是基於配置的,我們通常編寫的元件是元件配置而非元件,框架後續會生成其建構函式,它們基於VueComponent,擴充套件於Vue;
vue中常見元件化技術有:屬性prop,自定義事件,插槽等,它們主要用於元件通訊、擴充套件等;
合理的劃分元件,有助於提升應用效能;
元件應該是高內聚、低耦合的;
遵循單向資料流的原則。
談一談對vue設計原則的理解?
在vue的官網上寫著大大的定義和特點:
漸進式JavaScript框架
易用、靈活和高效
所以闡述此題的整體思路按照這個展開即可。
漸進式JavaScript框架:
與其它大型框架不同的是,Vue 被設計為可以自底向上逐層應用。Vue 的核心庫只關注檢視層,不僅易於上手,還便於與第三方庫或既有專案整合。另一方面,當與現代化的工具鏈以及各種支援類庫結合使用時,Vue 也完全能夠為複雜的單頁應用提供驅動。
易用性
vue提供資料響應式、宣告式模板語法和基於配置的元件系統等核心特性。這些使我們只需要關注應用的核心業務即可,只要會寫js、html和css就能輕鬆編寫vue應用。
靈活性
漸進式框架的最大優點就是靈活性,如果應用足夠小,我們可能僅需要vue核心特性即可完成功能;隨著應用規模不斷擴大,我們才可能逐漸引入路由、狀態管理、vue-cli等庫和工具,不管是應用體積還是學習難度都是一個逐漸增加的平和曲線。
高效性
超快的虛擬 DOM 和 diff 演算法使我們的應用擁有最佳的效能表現。
追求高效的過程還在繼續,vue3中引入Proxy對資料響應式改進以及編譯器中對於靜態內容編譯的改進都會讓vue更加高效。
談談你對MVC、MVP和MVVM的理解?
答題思路:此題涉及知識點很多,很難說清、說透,因為mvc、mvp這些我們前端程式設計師自己甚至都沒用過。但是恰恰反映了前端這些年從無到有,從有到優的變遷過程,因此沿此思路回答將十分清楚。
Web1.0時代
在web1.0時代,並沒有前端的概念。開發一個web應用多數採用ASP.NET/Java/PHP編寫,專案通常由多個aspx/jsp/php檔案構成,每個檔案中同時包含了HTML、CSS、JavaScript、C#/Java/PHP程式碼,系統整體架構可能是這樣子的:
這種架構的好處是簡單快捷,但是,缺點也非常明顯:JSP程式碼難以維護
為了讓開發更加便捷,程式碼更易維護,前後端職責更清晰。便衍生出MVC開發模式和框架,前端展示以模板的形式出現。典型的框架就是Spring、Structs、Hibernate。整體框架如圖所示:
使用這種分層架構,職責清晰,程式碼易維護。但這裡的MVC僅限於後端,前後端形成了一定的分離,前端只完成了後端開發中的view層。
但是,同樣的這種模式存在著一些:
前端頁面開發效率不高
前後端職責不清
web 2.0時代
自從Gmail的出現,ajax技術開始風靡全球。有了ajax之後,前後端的職責就更加清晰了。因為前端可以通過Ajax與後端進行資料互動,因此,整體的架構圖也變化成了下面這幅圖:
通過ajax與後臺伺服器進行資料交換,前端開發人員,只需要開發頁面這部分內容,資料可由後臺進行提供。而且ajax可以使得頁面實現部分重新整理,減少了服務端負載和流量消耗,使用者體驗也更佳。這時,才開始有專職的前端工程師。同時前端的類庫也慢慢的開始發展,最著名的就是jQuery了。
當然,此架構也存在問題:缺乏可行的開發模式承載更復雜的業務需求,頁面內容都雜糅在一起,一旦應用規模增大,就會導致難以維護了。因此,前端的MVC也隨之而來。
前後端分離後的架構演變——MVC、MVP和MVVM
MVC
前端的MVC與後端類似,具備著View、Controller和Model。
Model:負責儲存應用資料,與後端資料進行同步
Controller:負責業務邏輯,根據使用者行為對Model資料進行修改
View:負責檢視展示,將model中的資料可視化出來。
三者形成了一個如圖所示的模型:
這樣的模型,在理論上是可行的。但往往在實際開發中,並不會這樣操作。因為開發過程並不靈活。例如,一個小小的事件操作,都必須經過這樣的一個流程,那麼開發就不再便捷了。
在實際場景中,我們往往會看到另一種模式,如圖:
這種模式在開發中更加的靈活,backbone.js框架就是這種的模式。
但是,這種靈活可能導致嚴重的問題:
資料流混亂。如下圖:
View比較龐大,而Controller比較單薄:由於很多的開發者都會在view中寫一些邏輯程式碼,逐漸的就導致view中的內容越來越龐大,而controller變得越來越單薄。
既然有缺陷,就會有變革。前端的變化中,似乎少了MVP的這種模式,是因為AngularJS早早地將MVVM框架模式帶入了前端。MVP模式雖然前端開發並不常見,但是在安卓等原生開發中,開發者還是會考慮到它。
MVP
MVP與MVC很接近,P指的是Presenter,presenter可以理解為一箇中間人,它負責著View和Model之間的資料流動,防止View和Model之間直接交流。我們可以看一下圖示:
我們可以通過看到,presenter負責和Model進行雙向互動,還和View進行雙向互動。這種互動方式,相對於MVC來說少了一些靈活,VIew變成了被動檢視,並且本身變得很小。雖然它分離了View和Model。但是應用逐漸變大之後,導致presenter的體積增大,難以維護。要解決這個問題,或許可以從MVVM的思想中找到答案。
MVVM
首先,何為MVVM呢?MVVM可以分解成(Model-View-VIewModel)。ViewModel可以理解為在presenter基礎上的進階版。如圖所示:
ViewModel通過實現一套資料響應式機制自動響應Model中資料變化;
同時Viewmodel會實現一套更新策略自動將資料變化轉換為檢視更新;
通過事件監聽響應View中使用者互動修改Model中資料。
這樣在ViewModel中就減少了大量DOM操作程式碼。
MVVM在保持View和Model鬆耦合的同時,還減少了維護它們關係的程式碼,使使用者專注於業務邏輯,兼顧開發效率和可維護性。
總結
這三者都是框架模式,它們設計的目標都是為了解決Model和View的耦合問題。
MVC模式出現較早主要應用在後端,如Spring MVC、ASP.NET MVC等,在前端領域的早期也有應用,如Backbone.js。它的優點是分層清晰,缺點是資料流混亂,靈活性帶來的維護性問題。
MVP模式在是MVC的進化形式,Presenter作為中間層負責MV通訊,解決了兩者耦合問題,但P層過於臃腫會導致維護問題。
MVVM模式在前端領域有廣泛應用,它不僅解決MV耦合問題,還同時解決了維護兩者對映關係的大量繁雜程式碼和DOM操作程式碼,在提高開發效率、可讀性同時還保持了優越的效能表現。
你了解哪些Vue效能優化方法?
答題思路:根據題目描述,這裡主要探討Vue程式碼層面的優化
路由懶載入
const router = new VueRouter({ routes: [ { path: '/foo', component: () => import('./Foo.vue') } ]})
keep-alive快取頁面
<template> <div> <keep-alive> <router-view/> </keep-alive> </div></template>
使用v-show複用DOM
v-for 遍歷避免同時使用 v-if
<template> <ul> <li v-for="user in activeUsers" :key="user.id"> {{ user.name }} </li> </ul></template><script> export default { computed: { activeUsers: function () { return this.users.filter(function (user) { return user.isActive }) } } }</script>
長列表效能優化
如果列表是純粹的資料展示,不會有任何改變,就不需要做響應化
export default { data: () => ({ users: [] }), async created() { const users = await axios.get("/api/users"); this.users = Object.freeze(users); }};
如果是大資料長列表,可採用虛擬滾動,只渲染少部分割槽域的內容
<recycle-scroller :items="items" :item-size="24"> <template v-slot="{ item }"> <FetchItemView :item="item" @vote="voteItem(item)" /> </template></recycle-scroller>
參考vue-virtual-scroller、vue-virtual-scroll-list
事件的銷燬
Vue 元件銷燬時,會自動解綁它的全部指令及事件監聽器,但是僅限於元件本身的事件。
created() { this.timer = setInterval(this.refresh, 2000)},beforeDestroy() { clearInterval(this.timer)}
圖片懶載入
對於圖片過多的頁面,為了加速頁面載入速度,所以很多時候我們需要將頁面內未出現在可視區域內的圖片先不做載入, 等到滾動到可視區域後再去載入。
<img v-lazy="/static/img/1.png">
參考專案:vue-lazyload
第三方外掛按需引入
像element-ui這樣的第三方元件庫可以按需引入避免體積太大。
import Vue from 'vue';import { Button, Select } from 'element-ui'; Vue.use(Button) Vue.use(Select)
無狀態的元件標記為函式式元件
<template functional> <div> <div v-if="props.value"></div> <section v-else></section> </div></template><script>export default { props: ['value']}</script>
子元件分割
<template> <div> <ChildComp/> </div></template><script>export default { components: { ChildComp: { methods: { heavy () { /* 耗時任務 */ } }, render (h) { return h('div', this.heavy()) } } }}</script>
變數本地化
<template> <div :style="{ opacity: start / 300 }"> {{ result }} </div></template><script>import { heavy } from '@/utils'export default { props: ['start'], computed: { base () { return 42 }, result () { const base = this.base // 不要頻繁引用this.base let result = this.start for (let i = 0; i < 1000; i++) { result += heavy(base) } return result } }}</script>
SSR
你對Vue3.0的新特性有沒有了解?
根據尤大的PPT總結,Vue3.0改進主要在以下幾點:
更快
虛擬DOM重寫
優化slots的生成
靜態樹提升
靜態屬性提升
基於Proxy的響應式系統
更小:通過搖樹優化核心庫體積
更容易維護:TypeScript + 模組化
更加友好
跨平臺:編譯器核心和執行時核心與平臺無關,使得Vue更容易與任何平臺(Web、Android、iOS)一起使用
更容易使用
改進的TypeScript支援,編輯器能提供強有力的型別檢查和錯誤及警告
更好的除錯支援
獨立的響應化模組
Composition API
虛擬 DOM 重寫
期待更多的編譯時提示來減少執行時開銷,使用更有效的程式碼來建立虛擬節點。
元件快速路徑+單個呼叫+子節點型別檢測
跳過不必要的條件分支
JS引擎更容易優化
優化slots生成
vue3中可以單獨重新渲染父級和子級
確保例項正確的跟蹤依賴關係
避免不必要的父子元件重新渲染
靜態樹提升(Static Tree Hoisting)
使用靜態樹提升,這意味著 Vue 3 的編譯器將能夠檢測到什麼是靜態的,然後將其提升,從而降低了渲染成本。
跳過修補整棵樹,從而降低渲染成本
即使多次出現也能正常工作
靜態屬性提升 使用靜態屬性提升,Vue 3打補丁時將跳過這些屬性不會改變的節點。
基於 Proxy 的資料響應式
Vue 2的響應式系統使用 Object.defineProperty 的getter 和 setter。Vue 3 將使用 ES2015 Proxy 作為其觀察機制,這將會帶來如下變化:
元件例項初始化的速度提高100%
使用Proxy節省以前一半的記憶體開銷,加快速度,但是存在低瀏覽器版本的不相容
為了繼續支援 IE11,Vue 3 將釋出一個支援舊觀察者機制和新 Proxy 版本的構建
高可維護性
Vue 3 將帶來更可維護的原始碼。它不僅會使用 TypeScript,而且許多包被解耦,更加模組化。
總結髮言
同學們,看了上面面試題解答,不知道大家有沒有一些收穫。村長這裡還要多囉嗦兩句,大家千萬不要只背答案,更要學會答題思路和學習方法,這樣不管將來遇上什麼問題,大家都能做到舉一反三。
往大了說,提升內力才是最重要的目標,將來不管使用什麼語言、框架,你都將輕鬆駕馭、信手拈來。