前端高頻面試題之Vue高級篇(含詳細代碼)
1、說一下 Vue.js 的響應式原理
1.1 Vue2 響應式原理
核心原理就是通過 Object.defineProperty 對對象屬性進行劫持,重新定義對象的 getter 和 setter,在 getter 取值時收集依賴,在 setter 修改值時觸發(fā)依賴更新,更新頁面。
Vue2 對數(shù)組和對象做了兩種不同方式的處理。
監(jiān)聽對象變化:
針對對象來說,Vue 會循環(huán)遍歷對象的每一個屬性,用 defineReactive 重新定義 getter 和 setter。
function defineReactive(target,key,value){
observer(value);
Object.defineProperty(target,key,{ ?v
get(){
// ... 收集依賴邏輯
return value;
},
set(newValue){
if (value !== newValue) {
value = newValue;
observer(newValue) // 把新設置的值包裝成響應式
}
// ...觸發(fā)依賴更新邏輯
}
})
}
function observer(data) {
if(typeof data !== 'object'){
return data
}
for(let key in data){
defineReactive(data,key,data[key]);
}
}
監(jiān)聽數(shù)組變化:
我們都知道,數(shù)組其實也是對象,同樣可以用 Object.defineProperty 劫持數(shù)組的每一項,但如果數(shù)組有100萬項,那就要調(diào)用 Object.defineProperty 一百萬次,這樣的話性能太低了。鑒于平時我們操作數(shù)組大都是采用數(shù)組提供的原生方法,于是 Vue 對數(shù)組重寫原型鏈,在調(diào)用7個能改變自身的原生方法(push,pop,shift,unshift,splice,sort,reverse)時,通知頁面進行刷新,具體實現(xiàn)過程如下:
// 先拿到數(shù)組的原型
const oldArrayProtoMethods = Array.prototype
// 用Object.create創(chuàng)建一個以oldArrayProtoMethods為原型的對象
const arrayMethods = Object.create(oldArrayProtoMethods)
const methods = [
'push',
'pop',
'shift',
'unshift',
'sort',
'reverse',
'splice'
]
methods.forEach(method => {
// 給arrayMethods定義7個方法
arrayMethods[method] = function (...args){
// 先找到數(shù)組對應的原生方法進行調(diào)用
const result = oldArrayProtoMethods[method].apply(this, args)
// 聲明inserted,用來保存數(shù)組新增的數(shù)據(jù)
let inserted
// __ob__是Observer類實例的一個屬性,data中的每個對象都是一個Observer類的實例
const ob = this.__ob__
switch(method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
default:
break
}
// 比如有新增的數(shù)據(jù),新增數(shù)據(jù)也要被定義為響應式
if(inserted) ob.observeArray(inserted)
// 通知頁面更新
ob.dep.notify()
return result
}
})
Object.defineProperty的缺點:
- 無法監(jiān)聽新增屬性和刪除屬性的變化,需要通過
$set、$delete實現(xiàn)。 - 監(jiān)測數(shù)組的索引性能太低,故而直接通過數(shù)組索引改值無法觸發(fā)響應式。
- 初始化時需要一次性遞歸調(diào)用,性能較差。
1.2 Vue3 的響應式改進
Vue3 采用 Proxy + Reflect 配合實現(xiàn)響應式。能解決上述 Object.defineProperty 的所有缺陷,唯一缺點就是兼容性沒有 Object.defineProperty 好。
let handler = {
get(target, key) {
if (typeof target[key] === "object") {
return new Proxy(target[key], handler);
}
return Reflect.get(target, key);
},
set(target, key, value) {
let oldValue = target[key];
if (oldValue !== value) {
return Reflect.set(target, key, value);
}
return true;
},
};
let proxy = new Proxy(obj, handler);
2、介紹一下 Vue 中的 diff 算法?
Vue 的 diff 算法是平級比較,不考慮跨級比較的情況。內(nèi)部采用深度遞歸的方式 + 雙指針的方式進行比較。
比較過程:
- 先比較是否是相同節(jié)點。
- 相同節(jié)點比較屬性,并復用老節(jié)點。
- 比較兒子節(jié)點,考慮老節(jié)點和新節(jié)點兒子的情況。
- 優(yōu)化比較:頭頭、尾尾、頭尾、尾頭。
- 比對查找進行復用。
Vue3 在這個比較過程的基礎上增加了最長遞增子序列實現(xiàn)diff算法。
- 找出不需要移動的現(xiàn)有節(jié)點。
- 只對需要移動的節(jié)點進行操作。
- 最小化 DOM 操作次數(shù)。
3、Vue 的模板編譯原理是什么?
Vue 中的模板編譯就是把我們寫的 template 轉換為渲染函數(shù)(render function) 的過程,它主要經(jīng)歷3個步驟:
- 解析(Parse):將 template 模板轉換成 ast 抽象語法樹。
- 優(yōu)化(Optimize):對靜態(tài)節(jié)點做靜態(tài)標記,減少 diff 過程中的比對。
- 生成(Generate):重新生成代碼,將 ast 抽象語法數(shù)轉化成可執(zhí)行的渲染函數(shù)代碼。
3.1 解析階段
<div id="app">
<p>{{ message }}</p>
</div>- 用 HTML 解析器將模板解析為 AST。
- AST中用 js 對象描述模板,里面包含了元素類型、屬性、子節(jié)點等信息。
- 解析指令(
v-for、v-if)和事件(@click)、插值表達式{{}}等 vue 語法。
3.2 優(yōu)化階段
- 遍歷上一步生成的 ast,標記靜態(tài)節(jié)點,比如用
v-once的節(jié)點,以及沒有用到響應式數(shù)據(jù)的節(jié)點。 - 標記靜態(tài)根節(jié)點,避免不必要的渲染。
3.3 代碼生成階段
vue2 解析結果:
function render() {
with(this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('p', [_v(_s(message))])])
}
}
_c: 是 createElement 的別名,用于創(chuàng)建 VNode。_v: 創(chuàng)建文本 VNode。_s: 是 toString 的別名,用于將值轉換為字符串。
vue3 解析結果:
import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", { id: "app" }, [
_createElementVNode("p", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
]))
}
_openBlock: 開啟一個"block"區(qū)域,用于收集動態(tài)子節(jié)點。_createElementBlock: 創(chuàng)建一個塊級虛擬 DOM 節(jié)點。_createElementVNode: 創(chuàng)建一個普通虛擬 DOM 節(jié)點。_toDisplayString: 將響應式數(shù)據(jù) _ctx.message 轉換為顯示字符串,或者處理 null/undefined 等值,確保它們能正確渲染為空白字符串。
vue2在線編譯:template-explorer.vuejs.org/。
vue3在線編譯:v2.template-explorer.vuejs.org/。
運行時+編譯(runtime-compiler) vs 僅運行時(runtime-only):
- 完整版(運行時+編譯):
- 包含編譯模塊,可以寫 template 模版。
- 體積較大(~30kb)。
- 僅運行時版本
- 需要在打包時使用
vue-loader進行編譯。 - 體積較?。ā?0kb)。
- 需要在打包時使用
平時開發(fā)項目推薦使用僅運行時(runtime-only)版本。
編譯后的特點:
- 虛擬DOM:渲染函數(shù)生成的是虛擬DOM節(jié)點(VNode)。
- 響應式綁定:渲染函數(shù)中的變量會自動建立依賴關系。
- 性能優(yōu)化:通過靜態(tài)節(jié)點標記減少不必要的更新。
4、v-show 和 v-if 的原理
簡單來說,v-if 內(nèi)部是通過一個三元表達式來實現(xiàn)的,而 v-show 則是通過控制 DOM 元素的 display 屬性來實現(xiàn)的。
v-if 源碼:
function genIfConditions (
conditions: ASTIfConditions,
state: CodegenState,
altGen?: Function,
altEmpty?: string
): string {
if (!conditions.length) {
return altEmpty || '_e()'
}
const condition = conditions.shift()
if (condition.exp) { // 如果有表達式
return `(${condition.exp})?${ // 將表達式作為條件拼接成元素
genTernaryExp(condition.block)
}:${
genIfConditions(conditions, state, altGen, altEmpty)
}`
} else {
return `${genTernaryExp(condition.block)}` // 沒有表達式直接生成元素 像v-else
}
// v-if with v-once should generate code like (a)?_m(0):_m(1)
function genTernaryExp (el) {
return altGen
? altGen(el, state)
: el.once
? genOnce(el, state)
: genElement(el, state)
}
}
v-show 源碼:
{
bind (el: any, { value }: VNodeDirective, vnode: VNodeWithData) {
const originalDisplay = el.__vOriginalDisplay =
el.style.display === 'none' ? '' : el.style.display // 獲取原始顯示值
el.style.display = value ? originalDisplay : 'none' // 根據(jù)屬性控制顯示或者隱藏
}
}
5、v-if 和 v-for 哪個優(yōu)先級更高?為什么?
- vue2 中
v-for的優(yōu)先級比v-if高,它們作用于一個節(jié)點上會導致先循環(huán)后對每一項進行判斷,浪費性能。 - vue3 中
v-if的優(yōu)先級比v-for高,這就會導致v-if中的條件無法訪問v-for作用域名中定義的變量別名。
<li v-for="item in arr" v-if="item.visible">
{{ item}}
</li>以上代碼在 vue3 的編譯結果如下:
import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, createCommentVNode as _createCommentVNode } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_ctx.item.visible)
? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(_ctx.arr, (item) => {
return (_openBlock(), _createElementBlock("li", null, _toDisplayString(item), 1 /* TEXT */))
}), 256 /* UNKEYED_FRAGMENT */))
: _createCommentVNode("v-if", true)
}
可以看出 vue3 在編譯時會先判斷 v-if,然后再走 v-for 的循環(huán),所以在 v-if 中自然就無法訪問 v-for 作用域名中定義的變量別名。
這樣的寫法在 vue3 中會拋出一個警告??,[Vue warn]: Property "item" was accessed during render but is not defined on instance,導致渲染失敗。
以上代碼在 vue2 還不能直接編譯,因為 vue2 的組件需要一個根節(jié)點,所以我們在外層加一個 div:
<div>
<li v-for="item in arr" v-if="item.visible">
{{ item}}
</li>
</div>其編譯結果如下:
function render() {
with(this) {
return _c('div', _l((arr), function (item) {
return (item.visible) ? _c('li', [_v("\n " + _s(item) + "\n ")]) :
_e()
}), 0)
}
}
很明顯是先循環(huán) arr,然后每一項再用 item.visible 去判斷的,也印證了在 vue2 中, v-for 的優(yōu)先級高于 v-if。
所以不管是
vue2還是vue3,都不推薦同時使用v-if和v-for,更好的方案是采用計算屬性,或者在外層再包裹一個容器元素,將v-if作用在容器元素上。
6、nextTick 的原理是什么?
6.1 Vue2 的 nextTick:
- 首選微任務:
- Promise.resolve().then(flushCallbacks):最常見,使用 Promise 創(chuàng)建微任務。
- MutationObserver:如果 Promise 不可用,創(chuàng)建一個文本節(jié)點,修改其內(nèi)容觸發(fā) MutationObserver 的觀察器回調(diào)。
- 回退宏任務:
- setImmediate:如果環(huán)境支持 setImmediate,比如 node 環(huán)境,則會優(yōu)先使用 setImmediate 。
- setTimeout(flushCallbacks, 0):最后使用定時器。
這里體現(xiàn)了優(yōu)雅降級的思想。
6.2 Vue3 的 nextTick:
- 由于 Vue3 不再考慮 promise 的兼容性,所以 nextTick 的實現(xiàn)原理就是 promise.then 方法。
7、Vue.set 方法是如何實現(xiàn)的?
Vue2的實現(xiàn):在 Vue 2 中,Vue.set 的實現(xiàn)主要位于 src/core/observer/index.js 中:
export function set (target: Array | Object, key: any, val: any): any {
// 1.如果是數(shù)組 Vue.set(array,1,100); 調(diào)用我們重寫的splice方法 (這樣可以更新視圖)
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
// 2.如果是對象本身的屬性,則直接添加即可
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
// 3.如果是響應式的也不需要將其定義成響應式屬性
if (!ob) {
target[key] = val
return val
}
// 4.將屬性定義成響應式的
defineReactive(ob.value, key, val)
// 5.通知視圖更新
ob.dep.notify()
return val
}
Vue3 中 set 方法已經(jīng)被移除,因為 proxy 天然彌補 vue2 響應式的缺陷。
8、Vue.use 是干什么的?原理是什么?
Vue.use 是用來使用插件的,我們可以在插件中擴展全局組件、指令、原型方法等。
Vue.use 源碼:
Vue.use = function (plugin: Function | Object) {
// 插件不能重復的加載
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
const args = toArray(arguments, 1)
args.unshift(this) // install方法的第一個參數(shù)是Vue的構造函數(shù),其他參數(shù)是Vue.use中除了第一個參數(shù)的其他參數(shù)
if (typeof plugin.install === 'function') { // 調(diào)用插件的install方法
plugin.install.apply(plugin, args) Vue.install = function(Vue,args){}
} else if (typeof plugin === 'function') { // 插件本身是一個函數(shù),直接讓函數(shù)執(zhí)行
plugin.apply(null, args)
}
installedPlugins.push(plugin) // 緩存插件
return this
}
9、介紹下 Vue 中的 mixin,Vue3 為何不再推薦使用它?
mixin 是 Vue 2 中一種復用組件邏輯的方式,允許將可復用的配置(data、methods、computed、lifecycle hooks 等)抽離成一個對象,然后通過 mixins: [] 合并到組件中。支持全局注入和局部注入。
- 作用:抽離公共的業(yè)務邏輯
- 原理:類似“對象的繼承”,當組件初始化時會調(diào)用
mergeOptions方法進行合并,采用策略模式針對不同的屬性進行合并。如果混入的數(shù)據(jù)和本身組件中的數(shù)據(jù)沖突,會采用“就近原則”以組件的數(shù)據(jù)為準。
mixin 的優(yōu)點:
- 復用邏輯(如表單驗證、權限判斷)。
- 全局注入(如日志、埋點)。
- 減少重復代碼。
mixin 中有很多缺陷:
- 命名沖突問題:mixin 中的變量、函數(shù)名可能會與組件中的重名。
- 依賴問題:
- 數(shù)據(jù)來源問題:
vue3 不再推薦使用它的理由如下:
| 問題 | 說明 |
|---|---|
| 1. 隱式依賴 & 數(shù)據(jù)來源不明確 | 組件行為來自多個 mixin,難以追蹤 data、methods 是從哪里來的。 |
| 2. 命名沖突 | 多個 mixin 可能定義同名 data、methods,合并規(guī)則復雜(同名 methods 后者覆蓋前者,data 合并為對象,同名 Key 后者覆蓋前者)。 |
| 3. 調(diào)試和維護困難 | 父組件無法知道子組件內(nèi)部有哪些 mixin 注入的屬性,排查 bug 和調(diào)試困難。 |
| 4. 不利于 Tree-shaking | 打包時難以移除未使用的 mixin 代碼。 |
| 5. 與 Composition API 理念沖突 | Mixin 是“橫切關注點”,而 Composition API 強調(diào)顯式、可組合的邏輯。 |
Vue 3 推薦替代方案:Composition API + 可復用函數(shù)(Composables)。
| 特性 | Mixin | Composables |
|---|---|---|
| 數(shù)據(jù)來源明確 | 隱式 | 顯式(import) |
| 是否有命名沖突問題 | 有 | 無 |
| 邏輯封裝 | 全局污染 | 按需引入 |
| Tree-shaking 支持 | 差 | 好 |
| TypeScript 支持 | 差 | 好 |
對于全局混入(Global Mixin),Vue3 雖然提供了 app.mixin(),但不推薦,推薦使用:
app.config.globalProperties。app.provide在頂層提供數(shù)據(jù),組件通過inject方法消費數(shù)據(jù)。
10、介紹下 Vue.js 中的函數(shù)式組件、異步組件和遞歸組件
10.1 函數(shù)式組件(Functional Components)
函數(shù)式組件是一種輕量級、無狀態(tài)的組件形式。它們很像純函數(shù):接收 props,返回 虛擬 DOM(vnode)。函數(shù)式組件在渲染過程中不會創(chuàng)建組件實例 (也就是說,沒有 this),也不會觸發(fā)常規(guī)的組件生命周期鉤子。沒有響應式系統(tǒng)、生命周期和實例的開銷,函數(shù)式組件自然在渲染上更加高效和快速。
總而言之,函數(shù)式組件有無狀態(tài)、無this、無生命周期、性能更高等特點。
使用場景:適合簡單、靜態(tài)的 UI 元素,如列表項或包裝組件。
在 Vue 2 中,通過 functional: true 聲明;在 Vue 3 中,函數(shù)式組件更簡單,直接返回渲染函數(shù)。
Vue2 示例:
<template functional>
<div>{{ props.msg }}</div>
</template>
或者 js 形式:
export default {
functional: true,
props: ['msg'],
render(h, { props }) {
return h('div', props.msg);
}
};
Vue3 示例:
<script setup>
import { h } from 'vue';
const FunctionalComp = (props) => h('div', props.msg);
</script>
<template>
<FunctionalComp msg="Hello Functional" />
</template>
10.2 異步組件(Async Components)
異步組件是一種懶加載(Lazy Loading)機制,用于按需加載組件代碼,優(yōu)化初始加載時間和性能。Vue 會動態(tài)導入組件,只有在使用時才下載和渲染,常用于路由或大型組件。
特點:
- 通過
import()動態(tài)加載,返回 Promise。 - 支持加載中(loading)、錯誤(error)和超時(timeout)處理。
- 在 Vue 3 中,使用
defineAsyncComponent更規(guī)范,支持與<Suspense>結合(Vue 3 獨有,用于統(tǒng)一處理異步)。
Vue2 示例:
<script>
export default {
components: {
AsyncComp: () => import('./AsyncComp.vue')
}
};
</script>
<template>
<AsyncComp />
</template>
Vue3 示例:
<script setup>
import { defineAsyncComponent } from 'vue';
const AsyncComp = defineAsyncComponent(() => import('./AsyncComp.vue'));
</script>
<template>
<Suspense>
<template #default>
<AsyncComp />
</template>
<template #fallback>加載中...</template>
</Suspense>
</template>10.3 遞歸組件(Recursive Components)
遞歸組件是指組件內(nèi)部調(diào)用自身,用于處理樹形或嵌套數(shù)據(jù)結構,如菜單、樹視圖或評論回復。Vue 支持組件自引用,但需注意避免無限循環(huán)(通過條件終止遞歸)。
特點:
- 組件需有名稱(
name選項),才能自引用。 - 常結合
v-for和 props 傳遞數(shù)據(jù)。
Vue 2 示例:
<template>
<ul>
<li v-for="item in tree" :key="item.id">
{{ item.name }}
<Tree v-if="item.children" :tree="item.children" />
</li>
</ul>
</template>
<script>
export default {
name: 'Tree', // 必須有 name
props: ['tree']
};
</script>
Vue 3 示例:
<script setup>
import { defineAsyncComponent } from 'vue'; // 可選:異步加載避免循環(huán)
const Tree = defineAsyncComponent(() => import('./Tree.vue')); // 自引用
defineProps(['tree']);
</script>
<template>
<ul>
<li v-for="item in tree" :key="item.id">
{{ item.name }}
<Tree v-if="item.children" :tree="item.children" />
</li>
</ul>
</template>
11、Vue.js 中的 vue-loader 是什么?
Vue-loader 是一個專為 Vue.js 設計的 Webpack loader(加載器),其主要作用是將 Vue 的單文件組件(Single-File Components,簡稱 SFC,即 .vue 文件)轉換為可執(zhí)行的 JavaScript 模塊。 它允許開發(fā)者以一種結構化的方式編寫組件,將模板(template)、腳本(script)和樣式(style)封裝在同一個文件中,便于管理和維護。
核心功能:
- 解析 SFC 文件:Vue-loader 會自動處理
.vue文件中的三個部分:- template 部分:編譯為 Vue 的渲染函數(shù)(render function)。
- script 部分:提取為組件的 JavaScript 邏輯,支持 ES 模塊和 TypeScript。
- style部分:處理 CSS,支持預處理器(如 Sass、Less)并可選地應用 scoped(作用域樣式)或 CSS Modules。
- 熱重載(Hot Module Replacement,HMR):在開發(fā)模式下,支持組件的熱更新,無需刷新頁面即可看到變化,提高開發(fā)效率。
- 自定義塊(Custom Blocks):支持擴展,如添加
<docs>或其他自定義標簽,用于文檔生成或其他工具集成。 - 預處理器支持:無縫集成 Babel、PostCSS 等工具鏈。
12、Vue.extend 方法的作用?
Vue.extend方法可以作為基礎 Vue 構造器,創(chuàng)建一個“子類”。參數(shù)是一個包含組件選項的對象。
Vue2 示例:
var dialog = Vue.extend({
template: "<div>{{hello}} {{world}}</div>",
data: function () {
return {
hello: "hello",
world: "world",
};
},
});
// 創(chuàng)建 dialog 實例,并手動掛載到一個元素上。
new dialog().$mount("#app");
注意:在
Vue.extend中的data必須是一個函數(shù),要不然會報錯。
Vue3 示例:
Vue3 中不在使用 Vue.extend 方法,而是采用render方法進行手動渲染。
<!-- Modal.vue -->
<template>
<div class="modal">這是一個彈窗</div>
</template>
<script>
export default {
name: 'Modal',
}
</script>
<template>
<div id="box"></div>
</template>
<script setup>
import { h, render, createApp, onMounted } from 'vue'
import Modal from './Modal.vue'
onMounted(() => {
render(h(Modal), document.getElementById('box'));
})
</script>
13、keep-alive 的原理
<keep-alive> 是 Vue.js 的內(nèi)置組件,它的功能是在多個組件間動態(tài)切換時緩存被移除的組件實例,避免重復渲染和狀態(tài)丟失,提高性能。它是一個抽象組件(abstract: true),不會渲染到 DOM,也不會出現(xiàn)在組件樹中,而是通過插槽(slots)和自定義 render 函數(shù)實現(xiàn)緩存邏輯。
核心實現(xiàn)機制:
- 抽象組件與 Render 函數(shù):
<keep-alive>通過 render 函數(shù)處理包裹的內(nèi)容(通常是動態(tài)組件,如<component>或 v-if 切換的組件)。- 在 render 中,它從插槽獲取子組件的 VNode(虛擬節(jié)點)。如果子組件有 key(推薦使用),則用 key 作為緩存標識;否則用組件的 tag 或 cid(組件 ID)。
- 如果組件已緩存,直接返回緩存的 VNode(設置 vnode.componentInstance.keepAlive = true 以標記緩存狀態(tài));否則,渲染新實例并存入緩存。
- 緩存存儲:
- 內(nèi)部使用一個對象(this.cache)作為緩存 Map,以 key 為鍵,值為 VNode 對象(包含組件實例)。
- 當組件首次渲染時,存入緩存;切換回時,從緩存取出,避免重新創(chuàng)建實例和執(zhí)行 mounted 鉤子。
- LRU 緩存算法:
- 支持 max 屬性設置最大緩存數(shù)量(Vue 2.5+)。
- 使用 Least Recently Used(最近最少使用)算法:當緩存超出 max 時,刪除最久未訪問的組件(通過 this.keys 數(shù)組跟蹤訪問順序)。
- 訪問組件時,將其 key 移到數(shù)組末尾(最近使用);超出時,刪除數(shù)組開頭的 key,并銷毀對應實例(調(diào)用 $destroy)。
- 過濾規(guī)則(include/exclude):
- 通過 include(白名單)和 exclude(黑名單)屬性決定哪些組件緩存,支持字符串、正則、數(shù)組或函數(shù)。
- 在 created 鉤子中,監(jiān)聽這些 prop 的變化,并調(diào)用 pruneCache 更新緩存(移除不匹配的組件)。
- 生命周期鉤子:
- 緩存組件不會觸發(fā) destroyed/unmounted,而是使用 activated(激活時)和 deactivated(失活時)鉤子。
- 這允許開發(fā)者在切換時管理狀態(tài)(如暫停定時器),而非完全銷毀。
注意事項:
- 只緩存一個直接子組件(插槽內(nèi)容),不支持多個。
- Vue 3 中原理類似,但優(yōu)化了 VNode 處理和 Composition API 支持。
- 潛在問題:緩存過多導致內(nèi)存占用;需手動清理資源(如在 deactivated 中停止監(jiān)聽)。
14、Vue.js 中使用了哪些設計模式?
1. 觀察者模式 (Observer Pattern)
- 描述:Vue 的響應式系統(tǒng)使用觀察者模式,通過 Proxy (Vue 3) 或 Object.defineProperty (Vue 2) 攔截對象屬性的 get/set 操作。當數(shù)據(jù)變化時,通知訂閱者(Watcher)更新視圖。
- 應用:在 reactive() 函數(shù)中,返回 Proxy 對象,get 陷阱用于依賴收集 (track),set 陷阱用于觸發(fā)更新 (trigger)。
- 優(yōu)勢:實現(xiàn)了細粒度的變更檢測,避免全局重渲染。
2. 發(fā)布-訂閱模式 (Publish-Subscribe Pattern)
- 描述:Vue 的事件系統(tǒng)和響應式通知機制采用 Pub-Sub 模式。數(shù)據(jù)變化時發(fā)布事件,訂閱者(如組件渲染函數(shù))接收并響應。
- 應用:在響應式系統(tǒng)中,trigger() 函數(shù)檢索訂閱者效果并調(diào)用它們;事件 API 如 emit和emit 和 emit和on 也基于此。
- 優(yōu)勢:解耦了數(shù)據(jù)生產(chǎn)者和消費者,支持異步更新。
3. 代理模式 (Proxy Pattern)
描述:Vue 3 的響應式系統(tǒng)直接使用 ES6 Proxy 作為代理層,攔截對象操作,實現(xiàn)透明的響應式。 應用:reactive() 返回 Proxy 對象,代理目標對象的訪問和修改。 優(yōu)勢:比 Vue 2 的 defineProperty 更強大,支持數(shù)組和 Map/Set 等類型。
4. 策略模式 (Strategy Pattern)
- 描述:Vue 的虛擬 DOM diff 算法使用不同策略(如 key-based diff 或簡單 patch)來優(yōu)化更新。
- 應用:在渲染過程中,根據(jù)節(jié)點類型選擇 diff 策略。
- 優(yōu)勢:最小化 DOM 操作,提高渲染效率。
5. 單例模式(Singleton Pattern)
- 描述:整個程序中有且僅有一個實例。
- 應用:vuex 的 store 和插件。
- 優(yōu)勢:全局唯一、節(jié)約資源、便于管理。
6. 工廠模式(Factory Pattern)
- 描述:提供了一種創(chuàng)建對象的方式,使得創(chuàng)建對象的過程與使用對象的過程分離。
- 應用:Vue2 中的組件創(chuàng)建,傳入?yún)?shù)給
createComponentInstance就可以創(chuàng)建實例。 - 優(yōu)勢:解藕,易于維護。
15、Vue.js 應用中常見的內(nèi)存泄漏來源有哪些?
- 未清理的事件監(jiān)聽器、定時器、動畫
<script setup>
import { onMounted, onUnmounted } from 'vue';
let timer = null;
let controller = null;
let raf = null;
onMounted(() => {
// 定時器
timer = setInterval(() => {}, 1000);
// 動畫
raf = requestAnimationFrame(() => {});
// 事件監(jiān)聽
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
clearInterval(timer);
cancelAnimationFrame(this.raf);
window.removeEventListener('resize', handleResize);
});
</script>
- 未移除的第三方庫實例
<script setup>
import { onMounted, onUnmounted } from 'vue';
let chart = null;
onMounted(() => {
chart = echarts.init(this.$refs.chart);
});
onUnmounted(() => {
chart?.dispose();
});
</script>
- 事件總線(Event Bus)未解綁
vue2 用可以用 new Vue 全局創(chuàng)建一個事件總線實例,或者在組件中直接使用 this.$on、this.$emit、this.$off。
vue3 則需要借助第三庫,比如 mitt 來實現(xiàn)事件總線。
<script setup>
import { onMounted, onUnmounted } from 'vue';
import mitt from 'mitt';
// 創(chuàng)建事件總線實例
const emitter = mitt();
onMounted(() => {
emitter.on('update', this.handler);
});
onUnmounted(() => {
emitter.off('update', this.handler);
});
</script>
順便提一下, vue3 為啥去掉 $on、$emit、$off 這些 API,主要有以下原因:
- 設計理念的調(diào)整
Vue 3 更加注重組件間通信的明確性和可維護性。$on 這類事件 API 本質(zhì)上是一種 "發(fā)布 - 訂閱" 模式,容易導致組件間關系模糊(多個組件可能監(jiān)聽同一個事件,難以追蹤事件來源和流向)。Vue 3 推薦使用更明確的通信方式,如:
- 父子組件通過 props 和 emit 通信
- 跨組件通信使用 provide/inject 或 Pinia/Vuex 等狀態(tài)管理庫
- 復雜場景可使用專門的事件總線庫(如 mitt
- 與 Composition API 的適配
Vue 3 主推的 Composition API 強調(diào)邏輯的封裝和復用,而 $on 基于選項式 API 的實例方法,與 Composition API 的函數(shù)式思維不太契合。移除后,開發(fā)者可以更自然地使用響應式變量或第三方事件庫來實現(xiàn)類似功能。
- 減少潛在問題
- $on 容易導致內(nèi)存泄漏(忘記解綁事件)
- 事件名稱可能沖突(全局事件總線尤其明顯)
- 不利于 TypeScript 類型推斷,難以實現(xiàn)類型安全
4. 未清理的 Watcher
Vue 本身不會泄漏內(nèi)存,泄漏幾乎都來自開發(fā)者未清理的副作用。養(yǎng)成“創(chuàng)建即清理”的習慣,使用 beforeDestroy 或者 onUnmounted 集中清理,在使用 keep-alive 的組件中,視情況在 deactivated 鉤子中清理資源。
16、Vue.js 中的性能優(yōu)化手段有哪些?
16.1 數(shù)據(jù)相關
- Vue2 中數(shù)據(jù)層級不易過深,合理設置響應式數(shù)據(jù);
- Vue2 非響應式數(shù)據(jù)可以通過 Object.freeze()方法凍結屬性;
- 合理使用 computed,利用其緩存能力提高性能。
16.2 組件相關
- 控制組件粒度(Vue 采用組件級更新);
- 合適場景可使用函數(shù)式組件(函數(shù)式組件開銷低);
- 采用異步組件(借助構建工具的分包的能力,減少主包體積);
- 在組件卸載或者非激活狀態(tài)及時清除定時器、DOM事件、事件總線、三方庫的實例等。
- v-on 按需監(jiān)聽、使用動態(tài) watch 和及時銷毀 watch。
16.3 渲染相關
- 合理設置 key 屬性;
- v-show 和 v-if 的選取;
- 使用防抖、節(jié)流、分頁、虛擬滾動、時間分片等策略;
- 合理使用 keep-alive 、v-once、v-memo 進行邏輯優(yōu)化。
結語
以上是整理的 Vue 高級的高頻面試題,如有錯誤或者可以優(yōu)化的地方歡迎評論區(qū)指正,后續(xù)還會更新 Vuex 和 Vue-router 相關面試題。
到此這篇關于前端高頻面試題之Vue高級篇的文章就介紹到這了,更多相關前端Vue高頻面試題內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Vue實現(xiàn)hash模式網(wǎng)址方式(就是那種帶#的網(wǎng)址、井號url)
這篇文章主要介紹了Vue實現(xiàn)hash模式網(wǎng)址方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07
Vue前端通過Get和Post方法調(diào)用后臺接口下載文件的應用實例
Vue調(diào)用接口的具體實現(xiàn)方式有多種,這篇文章主要介紹了Vue前端通過Get和Post方法調(diào)用后臺接口下載文件的相關資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2025-06-06

