最新前端面試題,最新vue面試題總結(jié)

簡述 MVVM 框架
Model:代表數(shù)據(jù)模型,也可以在 Model 中定義數(shù)據(jù)修改和操作的業(yè)務(wù)邏輯。
View: 代表 UI 組件,它負(fù)責(zé)將數(shù)據(jù)模型轉(zhuǎn)化成 UI 展現(xiàn)出來。
ViewModel: 監(jiān)聽數(shù)據(jù)模型的改變和控制視圖行為、處理用戶交互,簡單理解就是一個(gè)同步 View 和 Model 的對象,連接 Model 和 View。
vue2 和 vue3 區(qū)別
寫法上的區(qū)別:vue2 使用的是
options(選項(xiàng))Api
,vue3 的是composition Api
(當(dāng)然 vue3 也兼容composition api
)。options Api
中methods,compute,data
等 api 都是分散的。而composition api
中的代碼是根據(jù)邏輯功能來組織的,我們可以將一個(gè)功能所定義的methods,compute,data
等 api 會放在一起,讓我們可以更靈活地組合組件邏輯。vue2 將響應(yīng)式數(shù)據(jù)放到 data 函數(shù)中,而 vue3 則是使用
ref
和reactive
將數(shù)據(jù)聲明為響應(yīng)式響應(yīng)式實(shí)現(xiàn)方式:vue2 中是通過
Object.defineProperty
對數(shù)據(jù)劫持實(shí)現(xiàn)的,vue3 中則是使用Proxy
對數(shù)據(jù)代理實(shí)現(xiàn)的。生命周期區(qū)別:vue3 中將
beforeCreate
和created
合并到了setup
函數(shù)中根節(jié)點(diǎn): vue3 組件允許多個(gè)根節(jié)點(diǎn),而 vue2 只允許一個(gè)
內(nèi)置組件: vue3 新增了傳送組件
Teleport
和異步依賴處理組件Suspense
v-if 和 v-show
v-if
表示一個(gè) dom 元素是否被創(chuàng)建,而v-show
則是控制這個(gè) dom 元素的display
屬性是否為none
一般在頻繁切換狀態(tài)的地方使用
v-show
,v-if
則更適合條件不經(jīng)常改變的場景,因?yàn)樗袚Q開銷相對較大
v-for 和 v-if 優(yōu)先級
開發(fā)過程中一般不建議同時(shí)將 v-for 和 v-if 放在一個(gè)標(biāo)簽中使用
Vue2 中 v-for 的優(yōu)先級會更高,所以會先執(zhí)行循環(huán),再進(jìn)行 v-if 判斷,所以這樣就會導(dǎo)致無論需不需展示這個(gè)元素,都會先遍歷整個(gè)列表
在 Vue3 中 v-if 的優(yōu)先級會更高,但是當(dāng)我們遍歷一個(gè)數(shù)組的時(shí)候,根據(jù)數(shù)組中的某個(gè)元素進(jìn)行 v-if 判斷的時(shí)候就會報(bào)錯(cuò),因?yàn)?v-if 會先執(zhí)行此時(shí)還沒有拿到這個(gè)數(shù)組。所以 Vue3 中會報(bào)錯(cuò)
介紹 Vue 插槽用法
插槽slot
可以理解為占坑,當(dāng)使用一個(gè)組件的時(shí)候,在組件標(biāo)簽里的對應(yīng)的內(nèi)容就會替換掉這個(gè)組件中的slot
標(biāo)簽。
插槽分為默認(rèn)插槽
,具名插槽
,作用域插槽
。
默認(rèn)插槽子組件中用slot
標(biāo)簽來確定渲染位置,父組件使用它時(shí)直接在子組件的標(biāo)簽內(nèi)寫入內(nèi)容即可
//子組件
<template>
? ?<slot />
</template>
//父組件
<Child>
?<div>默認(rèn)插槽</div>
</Child>
具名插槽
顧名思義就是具有名字的插槽,子組件中可以用name
熟悉對slot
命名,父組件在使用的時(shí)候通過template
中的v-slot:name
或者#name
來定義這個(gè)插槽中的內(nèi)容
//子組件
<template>
?<slot name="content"></slot>
</template>
//父組件
<Child>
? ?<template v-slot:content>具名插槽內(nèi)容</template>
</Child>
作用域插槽
子組件中的slot
可以通過類似組件屬性傳遞的方式將子組件的值傳遞給父組件中這個(gè)子組件的插槽內(nèi)容中
(子組件標(biāo)簽內(nèi)),在父組件使用子組件的時(shí)候要用v-slot
的值進(jìn)行接收這些參數(shù),默認(rèn)插槽可以將其直接寫在子組件標(biāo)簽上,具名插槽則寫在template
上。而傳過來的值只能在子組件標(biāo)簽中或者template
標(biāo)簽中使用。所以在父組件作用域中獲取到了子組件作用域中的變量,可以認(rèn)為作用域插槽延伸了子組件數(shù)據(jù)的作用范圍,因此叫做作用域插槽
過濾器的作用,如何實(shí)現(xiàn)一個(gè)過濾器
過濾器是用來過濾數(shù)據(jù)的,在 Vue 中使用 filters 來過濾數(shù)據(jù),filters 不會修改數(shù)據(jù),而是過濾數(shù)據(jù),改變用戶看到的輸出
使用場景:
需要格式化數(shù)據(jù)的情況,比如需要處理時(shí)間、價(jià)格等數(shù)據(jù)格式的輸出 / 顯示。
比如后端返回一個(gè) 年月日的日期字符串,前端需要展示為 多少天前 的數(shù)據(jù)格式,此時(shí)就可以用 fliters 過濾器來處理數(shù)據(jù)。
過濾器是一個(gè)函數(shù),它會把表達(dá)式中的值始終當(dāng)作函數(shù)的第一個(gè)參數(shù)。過濾器用在插值表達(dá)式 {{ }} 和 v-bind 表達(dá)式 中,然后放在操作符“ | ”后面進(jìn)行指示
<li>商品價(jià)格:{{item.price | filterPrice}}</li>
filters: { filterPrice (price) { return price ? ('¥' + price) : '--' } }
注意 vue3 已經(jīng)移除了過濾器
常見的事件修飾符及其作用
.stop:等同于 JavaScript 中的 event.stopPropagation() ,防止事件冒泡;
.prevent :等同于 JavaScript 中的 event.preventDefault() ,防止執(zhí)行預(yù)設(shè)的行為(如果事件可取消,則取消該事件,而不停止事件的進(jìn)一步傳播);
.capture :將事件改成捕獲模式,由外到內(nèi)觸發(fā)
.self :只會觸發(fā)自己范圍內(nèi)的事件,不包含子元素;
.once :只會觸發(fā)一次。
v-model 如何實(shí)現(xiàn)的
v-model 其實(shí)是一個(gè)語法糖,比如
<input v-model="message" />
等同于
<input
? :value="message"
? @input="message=$event.target.value"
>
Vue2 中給 data 中的對象屬性添加一個(gè)新的屬性時(shí)會發(fā)生什么?如何解決?
Vue2 中對象添加新屬性綁定的視圖不會更新,因?yàn)?Vue2 中 Object.defineProperty 劫持不到 data 對象中新增的屬性,可以使用this.$set(this.obj, 'b', 'obj.b')
解決
$set()方法相當(dāng)于手動的去把 obj.b 處理成一個(gè)響應(yīng)式的屬性,此時(shí)視圖也會跟著改變了。
Vue 插件用法
面試一般會問你如何寫一個(gè) vue 插件,所以沒寫過 vue 插件的最好去親自體驗(yàn)一下
回答:
vue
實(shí)例會有一個(gè)use
函數(shù),它接受的是一個(gè)帶有install
函數(shù)的對象和一個(gè)可選的選項(xiàng)對象,當(dāng)我們使用 vue.use(plugin)
或者app.use(plugin)
會調(diào)用我們插件的install
屬性的函數(shù),并且將當(dāng)前組件的實(shí)例傳進(jìn)來.所以在插件中就可以對這個(gè)實(shí)例進(jìn)行一些操作來實(shí)現(xiàn)我們插件的功能
Vue 自定義指令
vue 官方提供了 v-text、v-for、v-model、v-if 等常用的指令。除此之外 vue 還允許開發(fā)者自定義指令。面試經(jīng)常會問什么是自定義指令?你用自定義指令做過哪些功能?
回答 1:什么是自定義指令?
自定義指令包含局部指令和全局指令,在模板中使用指令前必須先使用
directives
選項(xiàng)注冊。局部指令指在某個(gè)組件中注冊,而全局則是將指令注冊到全局,通常在 main.js 中注冊。自定義指令由一個(gè)包含類似組件生命周期鉤子的對象來定義。它的生命周期鉤子包含
created
,beforeMount
,mounted
,beforeUpdate
,updated
,beforeUnmount
,unmounted
,常用的鉤子為
mounted
和 ?updated
,它接受el
,binding
等參數(shù).binding
參數(shù)的值一般包含綁定到這個(gè)元素上的信息,比如下面這個(gè)指令
<div v-example:foo.bar="baz">
它的 binding 會是這個(gè)對象
{
? ?arg: 'foo',
? ?modifiers: { bar: true },
? ?value: /* `baz` 的值 */,
? ?oldValue: /* 上一次更新時(shí) `baz` 的值 */
}
回答 2:你用自定義指令做過哪些功能?
數(shù)據(jù)埋點(diǎn),通過綁定自定義事件傳入點(diǎn)擊當(dāng)前元素需要埋點(diǎn)的事件名,在指令中監(jiān)聽當(dāng)前元素的點(diǎn)擊事件后調(diào)用后臺接口將事件名傳入
權(quán)限控制,通過綁定自定義事件傳入控制當(dāng)前元素的權(quán)限字段,在指令中獲取到當(dāng)前元素根據(jù)權(quán)限字段來控制該元素的狀態(tài)(顯示,隱藏等)
computed 和 watch
computed是計(jì)算屬性,依賴其它屬性值,用于解決模板中放入過多的邏輯會讓模板過重且難以維護(hù)的問題.watch是偵聽器,當(dāng)我們需要根據(jù)一個(gè)屬性的變化而做出一些處理的時(shí)候,可以使用watch來對這個(gè)屬性進(jìn)行監(jiān)聽
computed具有緩存的特點(diǎn),即當(dāng)它所依賴的屬性發(fā)生改變的時(shí)候它才會重新執(zhí)行內(nèi)部邏輯.如下代碼
<template>
? ?<div>{{ addSum }}</div>
? ?<div>{{ addSum }}</div>
? ?<div>{{ addSum }}</div>
</template>
<script setup>
import { computed, ref, watch } from "vue";
const a = ref(1)
const b = ref(2)
let addSum = computed(() => {
? ?console.log('內(nèi)部邏輯執(zhí)行')
? ?return a.value + b.value
})
</script>
頁面多次使用addSum
,但是只會打印一次"內(nèi)部邏輯執(zhí)行"
watch在頁面首次加載的時(shí)候默認(rèn)不會執(zhí)行,需要設(shè)置
immediate:true
首次才會執(zhí)行監(jiān)聽watch默認(rèn)只監(jiān)聽一層數(shù)據(jù),不監(jiān)聽多層數(shù)據(jù)里屬性的變化,需要設(shè)置
deep:true
才會進(jìn)行深度監(jiān)聽
vue 生命周期
Vue2(選項(xiàng)式 API)Vue3(setup)描述beforeCreate-實(shí)例創(chuàng)建前created-實(shí)例創(chuàng)建后beforeMountonBeforeMountDOM 掛載前調(diào)用mountedonMountedDOM 掛載完成調(diào)用beforeUpdateonBeforeUpdate數(shù)據(jù)更新之前被調(diào)用updatedonUpdated數(shù)據(jù)更新之后被調(diào)用beforeDestroyonBeforeUnmount組件銷毀前調(diào)用destroyedonUnmounted組件銷毀完成調(diào)用
vue 父子組件生命周期執(zhí)行順序
這個(gè)相對于上一個(gè)問題稍微復(fù)雜一點(diǎn),可以試著理解記憶或者直接記住吧
渲染過程
graph TD
父beforeCreate --> 父created --> 父beforeMount --> 子beforeCreate --> 子created --> 子beforeMount --> 子mounted ?--> 父mounted
更新過程
graph TD
父beforeUpdate --> 子beforeUpdate --> 子updated --> 父updated
銷毀過程
graph TD
父beforeDestroy --> 子beforeDestroy --> 子destroyed --> 父destroyed
注意如果子組件是異步組件的話它們的執(zhí)行順序會發(fā)生改變,會先執(zhí)行完父組件的生命周期然后再執(zhí)行子組件的生命周期
vue 導(dǎo)航(路由守衛(wèi))
路由守衛(wèi)分為全局路由守衛(wèi),路由獨(dú)享守衛(wèi),組件路由守衛(wèi)
全局路由守衛(wèi)
beforeEach
,接收to、from、next
三個(gè)參數(shù),每個(gè)路由跳轉(zhuǎn)前都會觸發(fā),登錄驗(yàn)證時(shí)用的比較多beforeResolve
,和beforeEach
類似,區(qū)別是在導(dǎo)航被確認(rèn)之前,同時(shí)在所有組件內(nèi)守衛(wèi)和異步路由組件被解析之后調(diào)用afterEach,在路由跳轉(zhuǎn)完成后調(diào)用,接收 to、from 兩個(gè)參數(shù)
路由獨(dú)享守衛(wèi)
beforeEnter
,一般配置在路由配置文件中(router/index.js),對進(jìn)入某個(gè)路由之前進(jìn)行相關(guān)操作
組件路由守衛(wèi)
接收
to、from、next
三個(gè)參數(shù)
beforeRouteEnter
,進(jìn)入該組件之前調(diào)用,無法獲取到 vue 實(shí)例beforeRouteUpdate
,在當(dāng)前路由改變,但是該組件被復(fù)用時(shí)調(diào)用beforeRouteLeave
, 在離開當(dāng)前組件時(shí)調(diào)用
Vue-Router 的懶加載如何實(shí)現(xiàn)
使用箭頭函數(shù)+import 動態(tài)加載
const router = new VueRouter({
?routes: [{ path: "/list", component: () => import("@/components/list.vue") }],
});
路由的 hash 和 history 模式的區(qū)別
Vue-Router 有兩種模式:hash 模式和 history 模式。默認(rèn)的路由模式是 hash 模式。
hash 模式
簡介: hash 模式是開發(fā)中默認(rèn)的模式,它的 URL 帶著一個(gè)#,例如:www.abc.com/#/vue,它的hash值就是#/vue。
特點(diǎn):hash 值會出現(xiàn)在 URL 里面,但是不會出現(xiàn)在 HTTP 請求中,對后端完全沒有影響。所以改變 hash 值,不會重新加載頁面。這種模式的瀏覽器支持度很好,低版本的 IE 瀏覽器也支持這種模式。hash 路由被稱為是前端路由,已經(jīng)成為 SPA(單頁面應(yīng)用)的標(biāo)配。
原理: hash 模式的主要原理就是 onhashchange()事件:
window.onhashchange = function (event) {
?console.log(event.oldURL, event.newURL);
?let hash = location.hash.slice(1);
};
history 模式
簡介: history 模式的 URL 中沒有#,它使用的是傳統(tǒng)的路由分發(fā)模式,即用戶在輸入一個(gè) URL 時(shí),服務(wù)器會接收這個(gè)請求,并解析這個(gè) URL,然后做出相應(yīng)的邏輯處理。 特點(diǎn): 當(dāng)使用 history 模式時(shí),URL 就像這樣:abc.com/user/id。相比 hash 模式更加好看。但是,history 模式需要后臺配置支持。如果后臺沒有正確配置,訪問時(shí)會返回 404。 API: history api 可以分為兩大部分,切換歷史狀態(tài)和修改歷史狀態(tài):
修改歷史狀態(tài):包括了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法,這兩個(gè)方法應(yīng)用于瀏覽器的歷史記錄棧,提供了對歷史記錄進(jìn)行修改的功能。只是當(dāng)他們進(jìn)行修改時(shí),雖然修改了 url,但瀏覽器不會立即向后端發(fā)送請求。如果要做到改變 url 但又不刷新頁面的效果,就需要前端用上這兩個(gè) API。
切換歷史狀態(tài): 包括 forward()、back()、go()三個(gè)方法,對應(yīng)瀏覽器的前進(jìn),后退,跳轉(zhuǎn)操作。
雖然 history 模式丟棄了丑陋的#。但是,它也有自己的缺點(diǎn),就是在刷新頁面的時(shí)候,如果沒有相應(yīng)的路由或資源,就會刷出 404 來。
如果想要切換到 history 模式,需要后端進(jìn)行一些配置:如果 URL 匹配不到任何靜態(tài)資源,則應(yīng)該返回同一個(gè) ?index.html 頁面,這個(gè)頁面就是你 app 依賴的根頁面
Apache
<IfModule mod_rewrite.c>
?RewriteEngine On
?RewriteBase /
?RewriteRule ^index\.html$ - [L]
?RewriteCond %{REQUEST_FILENAME} !-f
?RewriteCond %{REQUEST_FILENAME} !-d
?RewriteRule . /index.html [L]
</IfModule>
nginx
location / {
?try_files $uri $uri/ /index.html;
}
nexttick 原理
關(guān)于 nextTick 會問到它的用法,然后是它的原理,然后還可能問到 JS 的時(shí)間循環(huán)機(jī)制。
問題 1:vue 中的 nextTick 是干什么用的?
這個(gè)其實(shí)比較簡單,用過都知道它是干嘛的,vue 官方的解釋是:
在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)。在修改數(shù)據(jù)之后立即使用這個(gè)方法,獲取更新后的 DOM。
這是什么意思呢,其實(shí) vue 中修改 data 不會立刻觸發(fā) dom 更新;而是把需要更新的 Watcher 加入到 queueWatcher 隊(duì)列中,然后在合適的時(shí)機(jī)在 nextTick 中調(diào)用這些 Watcher 的更新函數(shù)進(jìn)行 dom 更新,所以在 data 剛被修改的時(shí)候,我們是獲取不到更新后的 dom 的,這時(shí)候便需要調(diào)用 nextTick 函數(shù)在它的回調(diào)函數(shù)中獲取到變化后的 dom
問題 2:nextTick 原理
nextTick 原理是借助瀏覽器事件循環(huán)來完成的,因?yàn)槊看问录h(huán)之間都有一次視圖渲染,nextTick 盡量在視圖渲染之前完成 dom 更新,所以 nextTick 優(yōu)先使用的是 promise(微任務(wù))實(shí)現(xiàn)
每次執(zhí)行 nextTick 時(shí)會將傳入的回調(diào)函數(shù)放入一個(gè)隊(duì)列中(callbacks 數(shù)組),然后當(dāng)在本次事件循環(huán)的同步代碼執(zhí)行完畢后開啟一個(gè)微任務(wù)(promise 或者 MutationObserver)去依次執(zhí)行這個(gè) callbacks 中的回調(diào)函數(shù)。
但是當(dāng)瀏覽器不支持 promise 的時(shí)候在 vue2 中會進(jìn)行進(jìn)行降級處理,依次使用
setImmediate
、setTimeout
開啟一個(gè)宏任務(wù)執(zhí)行 callbacks當(dāng)一個(gè) data 數(shù)據(jù)更新時(shí)對應(yīng)的 watcher 便會調(diào)用一次 nextTick,將它對應(yīng)的 dom 更新操作作為回調(diào)函數(shù)放入 callbacks 中,所以當(dāng)我們想獲取這個(gè) data 更新后的 dom 需要在其值變化后也調(diào)用 nextTick 將回調(diào)函數(shù)傳入排在上個(gè)更新 dom 的回調(diào)函數(shù)后面,所以我們可以在這個(gè) nextTick 的回調(diào)函數(shù)中獲取到更新后的 data
Vue 組件傳參
這里我大概歸納了一下 vue2 和 vue3 的傳參方式
方式Vue2Vue3父傳子propsprops子傳父$emitemits父傳子$attrsattrs子傳父$listeners無(合并到 attrs 方式)父傳子provide/injectprovide/inject子組件訪問父組件$parent無父組件訪問子組件$children無父組件訪問子組件$refexpose&ref兄弟組件傳值EventBusmitt
Vuex
Vuex 是 Vue 中的全局狀態(tài)管理框架,它可以管理應(yīng)用的所有組件的狀態(tài)。并不是每個(gè)項(xiàng)目都需要引入 Vuex 的,當(dāng)我們的項(xiàng)目有很多個(gè)頁面,并且這些頁面共享著多個(gè)數(shù)據(jù)狀態(tài),此時(shí)我們可以引入 Vuex。
Vuex 有三個(gè)核心的概念,
state
,mutations
,actions
,其中state
為存放數(shù)據(jù)的地方,mutations
中的函數(shù)作用則是用來修改state
,actions
中一般是用了處理一些異步操作的函數(shù)。Vuex 除了上面三個(gè)概念還有
getters
,moudles
,getters
就像 Vue 中的計(jì)算屬性computed
一樣用來描述依賴響應(yīng)式狀態(tài) state 中的復(fù)雜邏輯。moudles
則是可以將 store 分割成模塊(module),每個(gè)模塊都擁有自己的state
,mutations
,actions
等,在大型應(yīng)用中經(jīng)常用到場景:當(dāng)我們異步獲取結(jié)果并賦值給 state 的時(shí)候,比如數(shù)據(jù)請求,我們可以在
actions
中進(jìn)行數(shù)據(jù)請求,拿到結(jié)果通過它的dispatch
方法調(diào)用mutations
中修改state
的函數(shù),從而將結(jié)果賦值給了state
Pinia
pinia
其實(shí)就是 Vuex5,它和 Vuex 的主要區(qū)別有以下幾點(diǎn)
Pinia 使用更簡單,更符合開發(fā)者的開發(fā)習(xí)慣
pinia
中沒有了mutations
,狀態(tài)state
的修改可以直接進(jìn)行修改,或者在actions
中修改,或者使用它的$patch
方法進(jìn)行修改pinia
中沒有了modules
,如果想使用多個(gè) store,直接使用defineStore
定義多個(gè) store 傳入不同的 id 即可
篇幅有限 評論回復(fù)學(xué)習(xí) 獲取