身為程式設計師如果你想要找到你心儀的工作,不可避免的就會問到很多八股文,雖然有的和工作沒有半毛錢關係,但是你如果想要通過面試還必須得會。所以我最近開始總結一些面試題,一是為了加強自己的理解能夠找到一份好的工作,二是為了盡可能的幫助更多的小伙伴能夠快速掌握相關知識或者順利通過面試?。
本篇文章介紹了一些vue的常見問題。我盡量將這些問題依難易度,相關聯度排列使其更好的過度與理解。當然這篇文章不只是八股文那麼簡單,工作上也常經常用到這些知識點。 【相關推薦:vuejs影片教學】
vue生命週期
這個問題一般問的不多,但是如果問到了你就必須得答出來
回答
#Vue2(選項式API) |
Vue3(setup) | 描述 |
beforeCreate |
- |
#實例建立前 |
# #created | - | 實例建立後 |
#beforeMount | onBeforeMount | DOM掛載前呼叫 |
mounted | onMounted | DOM掛載完成呼叫 |
beforeUpdate | #onBeforeUpdate | 資料更新之前被呼叫 |
updated | #onUpdated | 資料更新之後被呼叫 |
|
|
|
|
|
|
beforeDestroy
onBeforeUnmount######元件銷毀前呼叫############destroyed######onUnmounted######元件銷毀完成呼叫#############
vue父子元件生命週期
這個相對於上一個問題稍微複雜一點,可以試著理解記憶或直接記住吧
渲染過程
#父親安裝
########### ###############
更新流程
#更新前的子########################## ######## ##孩子更新####################################父親更新### ###### ####################
銷毀過程
#
###################################### #父親被毀### ##### #####################
注意如果子元件是非同步元件的話它們的執行順序會改變,會先執行完父元件的生命週期然後再執行子元件的生命週期
v-if和v-show
這個問題算是非常基本的題了,它也很好理解,面試一般會問這兩個指令的區別是什麼,以及在什麼場景下分別用哪個指令適當回答
v-if
表示一個dom元素是否被創建,而v-show
則是控制這個dom元素的display
屬性是否為none
一般在頻繁切換狀態的地方使用v- show
,v-if
則更適合條件不常改變的場景,因為它切換開銷相對較大
v-for和v-if優先權
這個問題被問到的頻率還是比較高的,雖然它在實際開發上並不會這麼用。
回答
開發過程中一般不建議同時將v-for和v-if放在一個標籤中使用
Vue2中v-for的優先權會更高,所以會先執行循環,再進行v-if判斷,所以這樣就會導致無論需不展示這個元素,都會先遍歷整個列表
在Vue3中v-if的優先權會更高,但是當我們遍歷一個數組的時候,根據數組中的某個元素進行v-if判斷的時候就會報錯,因為v-if會先執行此時還沒有拿到這個陣列。所以Vue3中依然不建議這樣使用
說一下computed和watch
computed和watch實際工作中用的比較多,所以問的也比較多,一般理解了基本都能回答上來
computed是計算屬性,當一個屬性受一個或多個屬性影響的時候可以使用.watch是偵聽器,當我們需要根據一個屬性的變化而做出一些處理的時候,可以使用watch來對這個屬性進行監聽
computed具有快取的特點,即當它所依賴的屬性發生改變的時候它才會重新執行內部邏輯.如下程式碼
##
<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('内部逻辑执行')
return a.value + b.value
})
</script>
登入後複製
頁面多次使用
addSum,但是只會列印一次"內部邏輯執行"
watch在頁面首次載入的時候默認不會執行,需要設定immediate:true首次才會執行監聽
#watch預設只監聽一層資料,不監聽多層資料裡屬性的變化,需要設定deep:true才會進行深度監聽
#vue-router
關於
vue-router能問的問題非常多,例如它的實作原理,路由跳轉,路由守衛等等,所以建議去系統的檢視
vue-router文件
問題1:vue-router是什麼,描述一下它的原理?
Vue Router是Vue官方的路由管理器,有hash和history兩種模式
hash模式是透過監聽
hashchange#事件來實現更新頁面部分內容的操作,url後面會帶有
#號
#history模式則是透過監聽
popstate事件來實現更新頁面部分內容的操作原理和
hash模式差不多,只不過url後面不會出現
#會顯得更美觀。同時會帶來一個問題,因為沒有
#號,所以當使用者刷新頁面時會向伺服器發送請求導致請求資源為404,因此需要對
nginx進行一個配置,需要把所有路由都重定向到根頁面
問題2:路由跳轉方式有哪些?
問題3:說一下路由守衛?
路由守衛分為
全域路由守衛,路由獨享守衛,元件路由守衛
beforeEach,接收
to、from、next三個參數,每個路由跳轉前都會觸發,登入驗證時用的比較多
beforeResolve,和
beforeEach類似,差異是在導航被確認之前,同時在所有元件內守衛和非同步路由元件被解析之後調用
- afterEach,在路由跳轉完成後調用,接收to、from兩個參數
##路由獨享守衛
beforeEnter
,一般設定在路由設定檔中(router/index.js),對進入某個路由前進行相關操作<ul><li>组件路由守卫</li></ul>
<blockquote><p>接收<code>to、from、next
三个参数
beforeRouteEnter
,进入该组件之前调用,无法获取到vue实例
beforeRouteUpdate
,在当前路由改变,但是该组件被复用时调用
beforeRouteLeave
, 在离开当前组件时调用
vue2和vue3区别
当面试问你会用vue3吗,如果你回答会用的话,那么大概率会问vue2和vue3有哪些区别,最近我去面试的时候百分之90都问了这个问题。
回答
写法上的区别:vue2使用的是options(选项)Api
,vue3的是composition Api
(当然vue3也兼容composition api
)。options Api
中methods,compute,data
等api都是分散的。而composition api
中的代码是根据逻辑功能来组织的,我们可以将一个功能所定义的methods,compute,data
等api会放在一起,让我们可以更灵活地组合组件逻辑。
vue2将响应式数据放到data函数中,而vue3则是使用ref
和reactive
将数据声明为响应式
响应式实现方式:vue2中是通过Object.defineProperty
对数据劫持实现的,vue3中则是使用Proxy
对数据代理实现的。
生命周期区别:vue3中将beforeCreate
和created
合并到了setup
函数中
根节点: vue3组件允许多个根节点,而vue2只允许一个
内置组件: vue3新增了传送组件Teleport
和异步依赖处理组件Suspense
vue插件使用
面试一般会问你如何写一个vue插件,所以没写过vue插件的最好去亲自体验一下
回答:
vue
实例会有一个use
函数,它接受的是一个带有install
函数的对象和一个可选的选项对象,当我们使用 vue.use(plugin)
或者app.use(plugin)
会调用我们插件的install
属性的函数,并且将当前组件的实例传进来.所以在插件中就可以对这个实例进行一些操作来实现我们插件的功能
vue插槽
插槽slot
可以理解为占坑,当使用一个组件的时候,在组件标签里的对应的内容就会替换掉这个组件中的slot
标签。
插槽分为默认插槽
,具名插槽
,作用域插槽
。
默认插槽子组件中用slot
标签来确定渲染位置,父组件使用它时直接在子组件的标签内写入内容即可
//子组件
<template>
<slot />
</template>
//父组件
<Child>
<div>默认插槽</div>
</Child>
登入後複製
具名插槽
顾名思义就是具有名字的插槽,子组件中可以用name
熟悉对slot
命名,父组件在使用的时候通过template
中的v-slot:name
或者#name
来定义这个插槽中的内容
//子组件
<template>
<slot name="content"></slot>
</template>
//父组件
<Child>
<template v-slot:content>具名插槽内容</template>
</Child>
登入後複製
作用域插槽
子组件中的slot
可以通过类似组件属性传递的方式将子组件的值传递给父组件中这个子组件的插槽内容中
(子组件标签内),在父组件使用子组件的时候要用v-slot
的值进行接收这些参数,默认插槽可以将其直接写在子组件标签上,具名插槽则写在template
上。而传过来的值只能在子组件标签中或者template
标签中使用。所以在父组件作用域中获取到了子组件作用域中的变量,可以认为作用域插槽延伸了子组件数据的作用范围,因此叫做作用域插槽
如果你想详细理解插槽的作用可以阅读这篇文章Vue3中插槽(slot)用法汇总 - 掘金 (juejin.cn)
组件通信
这里我大概归纳了一下vue2和vue3的传参方式
方式 |
Vue2 |
Vue3 |
父傳子 |
props |
props |
子傳父 |
$emit |
emits |
父傳子 |
$attrs |
attrs |
#子傳父 | ##$listeners | 無(合併到attrs方式) |
父傳子 | provide/inject | provide/inject |
#子元件存取父元件 | $parent | 無 |
#父元件存取子元件##$children |
無 |
|
父元件存取子元件
$ref |
expose&ref |
| ##兄弟元件傳值
EventBus | mitt |
| #它们的具体用法可以参考我的这篇文章盘点Vue2和Vue3的10种组件通信方式(值得收藏) - 掘金 (juejin.cn)
除了上面的传参方式你也可以回答Vuex和Pinia,前提你了解这两个状态管理器,因为你说了大概率下个问题就会问你Vuex和Pinia
vuex
面试问到这个问题的时候,不要上来就开始说什么state
,mutation
...。你要先阐述Vuex干嘛用的,什么时候需要用Vuex。
回答
Vuex是Vue中的全局状态管理框架,它可以管理应用的所有组件的状态。并不是每个项目都需要引入Vuex的,当我们的项目有很多个页面,并且这些页面共享着多个数据状态,此时我们可以引入Vuex。
Vuex有三个核心的概念,state
,mutations
,actions
,其中state
为存放数据的地方,mutations
中的函数作用则是用来修改state
,actions
中一般是用了处理一些异步操作的函数。
Vuex除了上面三个概念还有getters
,moudles
,getters
就像Vue中的计算属性computed
一样用来描述依赖响应式状态state中的复杂逻辑。moudles
则是可以将store分割成模块(module),每个模块都拥有自己的state
,mutations
,actions
等,在大型应用中经常用到
场景:当我们异步获取结果并赋值给state的时候,比如数据请求,我们可以在actions
中进行数据请求,拿到结果通过它的dispatch
方法调用mutations
中修改state
的函数,从而将结果赋值给了state
pinia
这个现在问的好像不多,从我最近面试来看只有我提到了才会问一下,但是以后问的肯定会越来越多。关于pinia问的一般是它和Vuex的区别,确切的说应该是它和Vuex4之间的区别
回答
pinia
其实就是Vuex5,它和Vuex的主要区别有以下几点
Pinia使用更简单,更符合开发者的开发习惯
pinia
中没有了mutations
,状态state
的修改可以直接进行修改,或者在actions
中修改,或者使用它的$patch
方法进行修改
pinia
中没有了modules
,如果想使用多个store,直接使用defineStore
定义多个store传入不同的id即可
更好的TS支持,不需要创建自定义的复杂包装器来支持TS
vue自定义指令
vue 官方提供了v-text、v-for、v-model、v-if 等常用的指令。除此之外vue 还允许开发者自定义指令。面试经常会问什么是自定义指令?你用自定义指令做过哪些功能?
回答1:什么是自定义指令?
自定义指令包含局部指令和全局指令,在模板中使用指令前必须先使用directives
选项注册。局部指令指在某个组件中注册,而全局则是将指令注册到全局,通常在main.js中注册。
自定义指令由一个包含类似组件生命周期钩子的对象来定义。它的生命周期钩子包含created
,beforeMount
,mounted
,beforeUpdate
,updated
,beforeUnmount
,unmounted
,
常用的钩子为mounted
和 updated
,它接受el
,binding
等参数.binding
参数的值一般包含绑定到这个元素上的信息,比如下面这个指令
<div v-example:foo.bar="baz">
登入後複製
它的binding会是这个对象
{
arg: 'foo',
modifiers: { bar: true },
value: /* `baz` 的值 */,
oldValue: /* 上一次更新时 `baz` 的值 */
}
登入後複製
回答2:你用自定义指令做过哪些功能?
数据埋点,通过绑定自定义事件传入点击当前元素需要埋点的事件名,在指令中监听当前元素的点击事件后调用后台接口将事件名传入
权限控制,通过绑定自定义事件传入控制当前元素的权限字段,在指令中获取到当前元素根据权限字段来控制该元素的状态(显示,隐藏等)
...
keep-alive
官网描述
<KeepAlive>
是一個內建元件,它的功能是在多個元件間動態切換時快取被移除的元件實例。
回答
通常我們切換元件的時候,上一個元件就會被銷毀,而當我們使用 <KeepAlive>
將其包裹的話這個組件就會被緩存,當這個組件再一次被顯示時就會保留之前的狀態。
keep-alive
接收兩個屬性include
和 exclude
,分別代表哪些元件要使用快取和哪些不需要緩存,它接收元件的名字數組,字串或正則,當我們使用動態元件component
或路由router-view
的時候可以使用
keep-alive
也接收max
屬性表示最大快取實例數,如果超出這個數字則最久沒有被存取的快取實例將會被銷毀。
keep-alive
有兩個生命週期,分別是activated
和deactivated
,activated
鉤子會在首次掛載或每次從快取重新插入的時候呼叫。 deactivated
鉤子則是在元件從DOM移除或元件卸載時呼叫
vue2混入-Mixin
# vue3中已經沒有Mixin
這個概念了,所以未來被問到的幾率會越來越小,但目前被問到的頻率還是很高的。一般會它的概念以及優缺點,有時還會問它與父組件的生命週期執行順序
vue官網描述:
混入(mixin) 提供了一種非常靈活的方式,來分發Vue 元件中的可重複使用功能。一個混入物件可以包含任意組件選項。當組件使用混入物件時,所有混入物件的選項將被「混合」進入該元件本身的選項。
回答1.Mixin
的作用將元件的公共邏輯提取出來,哪個元件需要用到時,直接將提取的這部分混入到組件內部即可
2. Mixin
的生命週期會在父元件生命週期之前執行,如果Mixin
中的屬性或方法與父元件衝突則會使用父元件中的
2. 優點:可以降低程式碼冗餘提高邏輯復用性。
3. 缺點:命名容易衝突,不好追溯源,後期排查不方便
vue響應式原理
這題問道的頻率極高,就我經歷的多場面試幾乎都會問到,而且是面試到vue方面的開門題。
下面是我自己的理解回答,以vue2為例,大家可以藉鏡參考
#vue的響應式原理是根據Object.defineProperty
#這個api來對資料進行劫持並結合發布者-訂閱者模式實現的
首先會利用Object.defineProperty
中的get
函數來對vue中的data的所有屬性進行存取劫持,中間會涉及到劫持data中更深層的屬性需要遞歸呼叫劫持方法。這裡是透過一個Observer
類別實現的
劫持到每一個屬性後會給這個屬性綁定多個訂閱者watcher
,因為一個屬性可能被用在很多地方;而這個watcher
中則包含更新視圖的函數update
。
watcher
和屬性的對應關係以及和視圖的聯繫則是透過編譯模板Compile
類別來實現的。 Compile
中會拿到整個dom物件,然後遍歷元素子節點取得到使用過vue中data屬性的則給該屬性直接添加一個watcher
並賦予一些更新當前視圖的方法.
每個屬性的多個訂閱者watcher
都會被加入到對應的陣列中,這裡則是透過Deps
類實現的,初始化watcher
的時候會呼叫Deps
中的addSub
方法將對應watcher
新增該類別的Subs
數組中
當data中的某個屬性改變時則會觸發Object.defineProperty
中的set
函數,這時便會呼叫該屬性的Deps
類別中的notify
函數遍歷Subs
陣列中的訂閱者watcher
並且呼叫其函數update
去觸發視圖的更新
Object.defineProperty和proxy區別
一般問完響應式原理可能會問這兩者的差異
回答
#Object.defineProperty
只能代理屬性,Proxy
代理的是對象。
物件上新增屬性,Proxy
可以監聽到,Object.defineProperty
不能。
Object.defineProperty
的代理行为是在破坏原对象的基础上实现的,Proxy 则不会破坏原对象,只是在原对象上覆盖了一层。
数组新增修改,Proxy
可以监听到,Object.defineProperty
不能。
Proxy
不兼容IE11
及以下
vue3内置组件
vue3新增了两个内置组件分别是Teleport
和Suspense
。
可以称之为传送门,作用将其插槽内容渲染到 DOM 中的另一个位置,接收两个参数to(要去的位置)和disabled(是否留在原位置)。接收比如下面代码
<teleport to="#popup">
<video src="./my-movie.mp4">
</teleport>
登入後複製
video将会被传送到id为popup的元素下。
<Suspense>
组件用于协调对组件树中嵌套的异步依赖的处理。
它一般用于包裹多个异步组件处理多个异步组件加载前与完成后的统一状态
<Suspense>
组件有两个插槽:#default
和 #fallback
,在初始渲染时,<Suspense>
将在内存中渲染其默认的插槽内容。如果在这个过程中遇到任何异步依赖,则会进入挂起状态等待异步组件加载完毕。在挂起状态期间,展示的是 #fallback
插槽内容
nextTick及原理
关于nextTick会问到它的用法,然后是它的原理,然后还可能问到JS的时间循环机制。
问题1:vue中的nextTick是干什么用的?
这个其实比较简单,用过都知道它是干嘛的,vue官方的解释是:
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
这是什么意思呢,其实vue中修改data不会立刻触发dom更新;而是把需要更新的Watcher加入到queueWatcher队列中,然后在合适的时机在nextTick中调用这些Watcher的更新函数进行dom更新,所以在data刚被修改的时候,我们是获取不到更新后的dom的,这时候便需要调用nextTick函数在它的回调函数中获取到变化后的dom
问题2:nextTick原理
nextTick原理是借助浏览器事件循环来完成的,因为每次事件循环之间都有一次视图渲染,nextTick尽量在视图渲染之前完成dom更新,所以nextTick优先使用的是promise(微任务)实现
每次执行nextTick时会将传入的回调函数放入一个队列中(callbacks数组),然后当在本次事件循环的同步代码执行完毕后开启一个微任务(promise或者MutationObserver)去依次执行这个callbacks中的回调函数。
但是当浏览器不支持promise的时候在vue2中会进行进行降级处理,依次使用setImmediate
、setTimeout
开启一个宏任务执行callbacks
当一个data数据更新时对应的watcher便会调用一次nextTick,将它对应的dom更新操作作为回调函数放入callbacks中,所以当我们想获取这个data更新后的dom需要在其值变化后也调用nextTick将回调函数传入排在上个更新dom的回调函数后面,所以我们可以在这个nextTick的回调函数中获取到更新后的data
问题3:js事件循环机制
不属于vue,后面文章再介绍
vue虚拟dom,diff算法
这题在工作中有用吗是???答案是没有用,但是在面试中有用啊,所以我们要会回答?
问题1:什么是虚拟dom?
简单来说就是一个描述dom结构的js对象
问题2:为什么要用虚拟dom?
问题3:说一下diff算法
diff演算法的本質是找出兩個物件之間的差異,目的是盡可能重複使用節點。在vue中是當狀態改變,用來計算改變後的虛擬DOM與改變前的虛擬DOM之間的差異的演算法。
具體實作不是幾句話就能說清楚的,推薦一篇文章聊聊Vue 的雙端diff 演算法- 掘金(juejin.cn)
結語
以上問題基本上都是查閱部分資料結合自己的理解總結而來,所以難免會有一些錯誤以及不妥之處,如果你發現了還希望不吝指出,萬分感謝。
(學習影片分享:web前端開發、程式設計基礎影片)
以上是【整理總結】20道高頻Vue面試題的詳細內容。更多資訊請關注PHP中文網其他相關文章!