三日
1. 如何理解Vue的模板編譯原理
Vue的模板編譯實際就是將模板字符串通過解析、優化和代碼生成等步驟轉換為渲染函數的過程。這個過程中,AST扮演了非常重要的角色,它用樹形結構描述了模板的內容和結構,是編譯過程的核心數據結構;同事,優化步驟可以提高后續渲染的性能,減少不必要的計算和比較;最后生成渲染函數實可以根據Vue實例的數據生成虛擬DOM,并最終渲染。
- 模板解析(Parse):將模板字符串轉換為抽象語法樹(AST);
- 優化(Optimize):靜態優化,標記無需更新的節點,提高后續渲染性能;
- 生成(Generate):將優化后的AST轉換為JS渲染函數;
- 生成渲染函數(Render):將生成的JS字符串轉換為可執行函數,可根據Vue數據生成虛擬DOM;
2. Vue的自定義指令
- 全局注冊
Vue.directive('focus', { // 當被綁定的元素掛載到 DOM 中時…… inserted: function (el) { // 聚焦元素 el.focus() }
})
- 局部注冊
directives: { focus: { // 當被綁定的元素掛載到 DOM 中時…… inserted: function (el) { // 聚焦元素 el.focus() } }
}
自定義指令鉤子
bind
:只調用一次,指令第一次綁定到元素時調用;inserted
:被綁元素插入父節點時調用;update
:所在組件的VNode更新時調用componentUpdated
:指令所在組件的VNode及其子VNode全部更新后調用;unbind
:只調用一次;
注意事項:
- 自定義指令:專注于操作DOM,而不是更改數據或者處理復雜邏輯;
- 當與Vue的響應式系統交互時,要小心使用
updated
鉤子,可能會在一個元素的生命周期內多次調用; - 如果需要在組件卸載時清理DOM操作,可使用
unbind
3. 對Vue的diff算法理解
vue的diff算法,也被稱為“虛擬DOM差異算法”,是Vue實現高效DOM更新機制的核心部分。這個算法用于比較新舊兩個虛擬DOM樹,將差異應用到實際的DOM上,從而避免不必要的DOM操作,提高性能。
- 雙端比較:同層節點比較;
- 深度優先遍歷:先比較當前節點的子節點,再比較兄弟節點;
key
比較:key不同,則直接會創建一個新的DOM節點;- 優化策略:同層比較、先頭后尾、復用策略等;
二日
概念30分
1. Vue2和Vue3的區別
- 響應式:vue2使用的是
Object.defineProperty()
,vue3使用的是proxy
; - vue3全部由ts重構,對ts支持更友好;
- 自定義渲染器
- composition API
- vue3可以存在多個根節點,vue2只能有一個
1.1 Vue3相比較Vue2的優勢
- 性能更好
- 體積更小
- 更好的ts支持
- 更好的代碼組織
- 更好的邏輯抽離
- 更多的新功能
1.2 Vue3 升級了哪些重要功能
- createAPP()
- emits 屬性:在子組件中生命 emits options 父組件的綁定事件;
- 生命周期:使用 setup 整合 beforeCreate 和 created 鉤子, destory 修改為 unmount;
- 多事件處理:在點擊事件中寫入多個處理函數,用逗號分割;
- Fragment:可以存放多個根節點
- 移除.sync
- 異步組件的寫法:需要從Vue 引入
defineAsyncComponent
,使用這個函數包裹import()
引入異步組件; - 移除filter:雙括號中 用 | 分割轉換含義;
- Teleport:主要場景就是把組件的嵌套層級提高;
- Suspense:用來加載異步組件未成功時的一些loading,主要實現原理:具名插槽;
- Composition API
1.3 vue3的響應式原理
vue3的響應式原理:主要基于ES6的Proxy對象來實現的,通過代理data對象來攔截屬性的讀寫操作,進而實現數據的響應性,提供了更強大和靈活的響應式能力。
- Proxy 對象代理:Proxy對象能夠攔截目標對象的各種屬性操作,實現對數據的監聽;
- 響應性處理:通過Proxy代理的對象訪問屬性時,觸發 getter,設置屬性時,觸發setter;
- 多層屬性嵌套與動態屬性監聽:Vue3.x的響應式系統能夠處理多層屬性嵌套的情況,無需手動進行深度監聽。同事,對于動態添加的屬性,也能夠自動進行監聽;
- 數組監聽:由于Proxy對象能夠直接監聽動態添加的屬性,所以能夠自動監聽數組所有增刪改的方法;
- 作為單獨模塊使用:Vue3.x的響應式系統被封裝為一個獨立的模塊,可以單獨使用或者與其他庫繼承,提供了更多的靈活性和可定制型;
- 核心函數與API:reactive、ref、toRefs、effect、computed、watch、watchEffect等;
- track/ trigger機制:Vue3.x使用了底層的track/ trigger機制來收集依賴和觸發更新,當數據發生變化時,trigger函數會遍歷所有收集到的依賴,并觸發它們的更新;
- Reflect對象:內置對象,提供了一系列與 Proxy handlers 相對應的方法。Vue3.x在實現響應式系統時使用了Reflect對象來調用目標對象的方法,以確保操作的正確性。
2. 如何理解ref、toRef、toRefs
- ref:
- 生成值類型的響應式數據
- 可以用于模板和
reactive
- 可以用過
.value
修改值
- toRef:
- 針對一個響應式對象(
reactive
封裝)的prop
- 創建一個
ref
,具有響應式 - 兩者保持引用關系
- 針對一個響應式對象(
- toRefs:
- 將響應式對象(
reactive
封裝)轉換為普通對象 - 對象的每個
prop
都是對應的ref
(不然reactive
響應式直接解構會是去響應式) - 兩者保持引用關系
- 將響應式對象(
2.1 為何ref需要value屬性
- ref是一個對象(不丟失響應式,值類型不能用proxy代理),value儲存之
- 通過
.value
屬性的get
和set
實現響應式 - 用于模板、reactive時,不需要
.value
,其它情況都需要
3. 設計模式
- 工廠模式:傳入參數即可創建實例(eg:虛擬DOM根據參數的不同返回基礎標簽 VNode 和組件 VNode );
- 單例模式:真個程序僅有一個實例(eg:
vuex
和vue-router
的插件注冊方法install
,判斷如果系統存在實例就直接返回掉); - 觀察者模式:響應式數據原理;
- 策略模式:指對象的某個行為,在不同場景中有不同的實現方案(eg:選項的合并策略);
- 代理模式:proxy;
4. 虛擬DOM是什么?
虛擬DOM,即用js模擬一棵dom樹,放在瀏覽器內存中,當需要變更時,虛擬dom使用diff算法進行新舊比較,將變更結果放在隊列中,反應到實際的dom樹上,減少了頁面dom操作。
優點:
- 保證性能下限:虛擬DOM需要適配任何上層API可能產生的操作,所以它屬于普適的,所以性能不是最優的;
- 無需手動操作DOM;
- 跨平臺:虛擬DOM實際上是SJ對象,而DOM與平臺強相關,因此,虛擬DOM可以更方便進行跨平臺操作,比如服務器渲染,weex開發等;
缺點:
- 無法進行極致優化:首次渲染大量DOM時,由于多了一層計算,會比innerHTML插入慢。
5. mixin是什么?vue3用了什么取代了?
mixin是多組件之間的相同代碼邏輯的抽離混入。分為局部混入和全局混入,提供了分發vue組件中的可復用功能。一個mixin對象可以包含任何組件選項。
缺點
- 來源不明確;
- 多mixin可能會造成命名沖突;
- 迷信和組件可能出現多對的多的關系,復雜度比較高;
Vue3使用了 composition API 替代了mixin 的使用
- 代碼提取;
- 代碼復用;
- 命名沖突解決
6. vue-loader的用途
vue-loader 是 vue 文件的加載器,將 template、js、style轉換為js模塊。使用該插件,js可以寫成es6,style樣式可以支持多預防,template可以加 jade 等。
7. computed 和 watch 的使用
- computed:計算屬性,存在數據緩存,能夠優化一定的計算性能,內部實際是一個函數,根據需要做回調計算返回計算結果;
- watch:用于觀察和響應數據變化的特性。無緩存,支持深度監聽。異步或者開銷較大的操作,可以適當使用watch。
8. 單頁面應用和多頁面應用
- 單頁面應用:只有一個主頁面,瀏覽器初始化需要加載所有必須的js、css等文件。用戶體驗好,內容改變不需要重加載整個頁面;
- 多頁面應用:指一個應用多個頁面,頁面跳轉即整頁刷新。不利于SEO,導航不可用,初次加載耗時多,頁面復雜度高。
9. Vue的單項數據流
vue的單項數據流主要是指父組件通過props傳遞數據給子組件,但是子組件不能直接修改該數據的特性。需要修改則需要
$emits
上告。該規則主要是維護數據的可追蹤和可維護性,使組件狀態清晰。
10. vue-router 的路由鉤子函數是什么?調用順序是什么?
路由鉤子函數:導航守衛。允許在路由發生變化前后處理權限驗證、數據預加載等;
10.1 分類:
- 全局守衛:
beforeEach
:全局前置守衛,在路由跳轉前觸發;beforeResolve
:全局解析守衛,在路由開始解析之前觸發,在beforeEach
和組件內的beforeRouterEnter
之后;afterEach
:全局后置守衛,在路由跳轉之后觸發。
- 路由獨享守衛:
beforeEnter
:進入路由前觸發,只在某個路由下有效;
- 組件內的守衛:
beforeRouterEnter
:在渲染組件的對應路由被確認前被調用;beforeRouterUpdate
:在當前路由被改變,但是該組件仍然被復用時調用。例如帶有動態參數的路由,組件的實例已經存在,對這個這一件調用;beforeRouterLeave
:導航離開該組件的對應路由時調用。
10.2 調用順序
beforeEach
→ beforeEnter
→ beforeResolve
→ beforeRouterEnter
→ beforeRouterUpdate
→ afterEach
10.3 vue-router組件復用導致路由參數失效怎么辦
解決辦法:
- 監聽路由變化;
- 使用
:key
屬性阻止復用:會影響性能; - 動態路由匹配
- 使用query代替params:query會附加唉URL的查詢字符串中
使用30分
1. transition標簽的使用
<transition>
組件為元素和組件提供過渡效果,可自定義或者使用內置過渡效果。
使用:
name
屬性:自定義過渡類名:xxx-enter-active
等;<transition>
包裹,使用 CSS 來定義過渡的樣式,如.v-enter-active
、.v-leave-active
等類名。- 過渡模式(
mode
屬性):out-in
表示離開-進入過渡;in-out
表示離開-進入過渡; duration
:過渡持續時間,ms為單位,:duration="{ enter: 500, leave: 800 }"
- JS 鉤子:
@before-enter
等 - 列表過渡:
<transition-group>
,并為其指定tag
屬性,默認span
,即渲染的根元素。
2. Vuex的使用
- 狀態管理:集中式存儲管理應用的所有組件的狀態,輕松實現跨組件的狀態共享;
- 可預測的狀態變化:主要通過
mutation
實現,可追蹤可預測; - 結構化和模塊化;
- Vuex特別使用于單頁應用開發中大型項目的狀態管理;
一日
概念30分
1. Vue2的響應式原理
Vue2是采用數據劫持結合觀察者(訂閱-發布者)模式的方式,通過
Object.defineProperty()
來劫持各個屬性的setter
、getter
、dep
,以及Watcher實現依賴收集和派發更新的過程。
- vue將
data
初始化為一個Observe,并對每個數據綁定setter
、getter
,data
中的每個key
,都有獨立的dep
(依賴收集器); compile
解析模板指令,將模板中的變量替換為數據,初始化渲染頁面視圖,并將指令對應節點綁定update()
,向dep
添加監聽;mount
時,實例化一個Watcher,將收集器的目標指向當前Watcher,待屬性dep.notice()
通知,調用自身update()
,觸發compile
的回調;- MVVM作為數據綁定的入口,整合Observer、Compile、Watcher,達到數據變化觸發視圖更新,視圖交互變化觸發數據Model變更的雙向綁定效果。
1.1 $set 的原理
因為響應式數據,,我們給對象和數組本身都增加了
__ob__
屬性,代表的是 Observer 實例。
當給對象新增不存在的屬性 首先會把新的屬性進行響應式跟蹤, 然后會觸發對象__ob__
的dep
收集到的 Watcher 去更新,當修改數組索引時我們調用數組本身的splice
方法去更新數組。
1.2 vue的data為什么必須是一個函數?
主要使用終于js的特性所導致的。在components中,data作為一個函數返回值的形式定義,使組件在復用過程中都是一個新的數據對象,相當于每個組件實例都有自己的私有數據空間。
1.3 vue如何監聽到數據的變化
- 響應式原理:
- 在data中定義一個屬性,vue會遍歷這些屬性并使用
Object.defineProperty()
將其轉換為getter/setter,使其具有相應性質; - 當組件被創建時,vue會編譯模板為虛擬DOM渲染函數,當訪問數據屬性時,則觸發屬性的getter;
- 當數據屬性發生變化時,vue會觸發更新流程,重新運行渲染函數。
- 在data中定義一個屬性,vue會遍歷這些屬性并使用
- 如何監聽數據的變化:
- data:響應變化;
- computed:依賴的數據緩存更新;
- watch:異步更新;
- vuex:mutations、store 常用更新處理。
- vue3中的響應式變化:
- vue3引入了proxy替代
Object.defineProperty()
,從而提供更好的響應式支持,包含數據和Map、Set等原始類型的深度響應性; - Vue3還引入了 Composition API,允許使用更函數式的方法來組織組件邏輯,包括使用
ref
和reactive API
來創建響應式數據。
- vue3引入了proxy替代
2. MVVM的理解
MVVM就是模型+視圖+框架視圖三者實現數據視圖的監聽、通知、更新的雙向綁定的。
3. Vue的生命周期是如何實現的
8個生命周期,分別為4個階段(創建、載入、更新、銷毀)的前后。
vue的生命周期鉤子是Vue框架內置的一組回調函數,是通過Vue內部實現和JS的原型繼承機制來工作的。
new Vue()
實例化初始配置等;- 生命周期鉤子函數注冊;
- 不同階段調用觸發;
- 原型鏈和繼承:Vue組件實際上就是Vue實例,通過Vue的組件系統來創建和管理、訪問。
- 對于異步組件,加載完成后觸發特定的鉤子,動態組件在切換時會根據組件狀態觸發響應的鉤子
3.1 Tips
created
階段,vue實例數據對象data
可使用,$el
還未有;beforeMounte
階段,data
和$el
可使用,但是還是虛擬DOM節點,data.message
還未替換;Destroyed
階段,vue實例解除綁定,但是DOM結構依然存在哦。
3.2 父子組件生命周期執行順序
- 加載渲染:父
beforeCreate
-> 父created
-> 父beforeMount
-> 子beforeCreate
-> 子created
-> 子beforeMount
-> 子mounted
-> 父mounted
- 更新:父
beforeUpdate
-> 子beforeUpdate
-> 子updated
-> 父updated
- 銷毀:父
beforeDestroy
-> 子beforeDestroy
-> 子destroyed
-> 父destroyed
3.3 Vue3生命周期
- Options API
beforeDestory
改為beforeUnmount
;destory
改為unmounted
;- 其它沿用Vue2 生命周期;
- Composition API
setup 相當于整合了beforeCreate 和
created`。其它生命周期分別是卸載 setup中的函數 onBeforeMount()
onMounted()
onBeforeUpdate()
onUpdated()
onBeforeUnmount()
onUnmounted()
** 使用**
- 不建議共有,會引起混亂;
- 小型項目、業務邏輯簡單,用 Options API
- 中大型項目、邏輯復雜,用 Composition API
4. Vue組件封裝
Vue.extend()
:全局注冊;Vue.component()
:局部注冊;
5. Vue組件通訊
props
和$emit
:- 自定義組件通訊:
$on
、$off
、$emit
$refs
$parent
和$child
- vuex
provide
和inject
:常用于組件庫編寫
5.1 $on
和 $emit
的本質
- 基于發布訂閱模式;
$on
用于收集事件依賴;$emit
用于觸發事件,根據傳入的event在vm_events找到對應的事件,并執行invokewithErrorHandling()
(通過handler.apply(context, args)
和handler.call(context)
的形式執行對應的方法);
5.2 事件綁定原理
$on
和$emit
是基于發布訂閱模式的,維護一個事件中心。$on
的時候將事件按名稱存在事件中心里,稱之為訂閱者,然后$emit
將對應的事件進行發布,去執行事件中心里的對應的監聽器
6. $nextTick是什么
$nextTick中的回調是下次DOM更新循環結束之后執行的延遲回調。主要是采用微任務優先的方式調用異步方法去執行nextTick包裝的方法。
7. 宏任務和微任務
宏任務和微任務都是異步任務的分類,在JS引擎中執行方式和優先級不同。
- 宏任務:主線程上執行的代碼塊,包括:主代碼塊、定時器、UI渲染、時間等,這些任務會按照他們在代碼中的順序被添加至宏任務隊列,等待執行;
- 微任務:更小、更輕量級的任務,通常不會阻塞主線程的執行,包括:Promise的then和catch,async中的await、MutationObserve監聽器等。當宏任務執行完畢后,js會立即執行所有已添加到微任務隊列中的任務。
為什么優先解決微任務?
微任務具有更高的執行優先級。在JS事件循環中,每當一個宏任務執行完畢后,js會立即執行所有已添加到微任務隊列中的任務,然后再去執行下一個宏任務。這樣能確保微任務盡快得到處理,從而提高應用程序的響應速度和性能。
此外,有限解決微任務還能避免一些潛在問題。例如:宏任務中執行了異步操作(如定時器或者網絡請求),并且該異步操作的回調函數被添加到了微任務隊列。那么如果這個回調函數依賴于宏任務中的某些數據或者狀態,那么優先解決微任務可以確保這些回調函數能夠在正確的上下文執行。
總之,在編寫代碼時,需要注意避免在宏任務中進行耗時操作,以免影響其它任務的執行。
8. Vuex
全局狀態管理系統,用于多個組件中的數據共享、緩存等(無法持久化,內部核心原理是創造一個全局實例new Vue()
)。
state
:應用狀態的數據結構;getter
:允許組件從Store中獲取數據;mutation
:唯一一個更改store中狀態的方法,必須為同步函數;action
:用于提交mutation
,而不是直接變更狀態,可以包含任意異步操作;module
:多模塊的store;
9. keep-alive
開發中緩存組件使用keep-alive
(內置組件),使用時會緩存不活動的組件實例,使在組件切換過程中將狀態保留;
原理
keep-alive
是一個通用組件,內部定義了一個map
,緩存創建過的組件實例,返回的渲染函數內部會查找是否存在。由于component
的is
屬性是一個響應式數據,因此只要變化,則會執行render
。
使用
// max:限制緩存組件的最大數量
<keep-alive include='' exclude='' max=3></keep-alive>
結合屬性 include
和 exclude
可以明確指定緩存哪些組件或排除緩存指定組件。vue3 中結合 vue-router
時變化較大,之前是 keep-alive
包裹 router-view
,現在需要反過來用 router-view
包裹 keep-alive
。
LRU(Least Recently Used) 算法
LRU,即最近最少使用的緩存機制。以關鍵字key
操作。緩存容量達到上限的時候,它應該在寫入新數據之前刪除最久未使用的數據值,提供新空間;
組件緩存更新
- beforeRouter:進入路由的時候都會執行;
- activated:在
keep-alive
緩存的組件被激活的時候執行(如果需要在每次進入頁面獲取最新的數據,需要在activated
鉤子獲取最新的數據,承擔原來的created
鉤子中獲取數據的任務);
10. history和hash路由實現原理?區別是什么
location.hash
:- 實際就是URL中
#
后面的東西。特點:不會被包含在http請求中,不會重新加載頁面; - 可以為hash的改變添加監聽事件:
window.addEventListener(‘hashchange’, func, false)
; - 每一次hash的改變,均會新增一個瀏覽器的訪問歷史記錄;
- 功能使用:更新視圖但不重新請求頁面;
- 特點:兼容性好,不美觀;
- 實際就是URL中
location.history
- 使用了HTML5 History Interface中新增的
pushState()
和replaceState()
方法。 - 功能:
back()
、forword()
、go()
以及對歷史記錄進行修改; - 功能使用:單頁應用前端路由,更新視圖但不重新請求頁面;
- 特點:美觀,但是刷新頁面會出現404,需要后端進行配置
- 使用了HTML5 History Interface中新增的
使用30分
1. v-for和v-if可以混合使用嗎
可以,但是不建議使用,v-for
的優先級會高于v-if
,即先渲染后判斷,會增加不必要的性能消耗。更好的解決方案是使用computed
處理數據再使用。
2. v-key的使用
Vue在渲染和更新時會用diff算法做最大限度的減少動態元素操作,盡可能實現就地修改、復用相同類型元素。因此,key
的標記作用能夠使判斷更準確、更快速。