[toc]
## Vue 面試專題
### 簡述MVVM
**什么是MVVM?**
`視圖模型雙向綁定`,是`Model-View-ViewModel`的縮寫,也就是把`MVC`中的`Controller`演變成`ViewModel。Model`層代表數據模型,`View`代表UI組件,`ViewModel`是`View`和`Model`層的橋梁,數據會綁定到`viewModel`層并自動將數據渲染到頁面中,視圖變化的時候會通知`viewModel`層更新數據。以前是操作DOM結構更新視圖,現在是`數據驅動視圖`。
**MVVM的優點:**
1.`低耦合`。視圖(View)可以獨立于Model變化和修改,一個Model可以綁定到不同的View上,當View變化的時候Model可以不變化,當Model變化的時候View也可以不變;\
2.`可重用性`。你可以把一些視圖邏輯放在一個Model里面,讓很多View重用這段視圖邏輯。\
3.`獨立開發`。開發人員可以專注于業務邏輯和數據的開發(ViewModel),設計人員可以專注于頁面設計。\
4.`可測試`。
### Vue底層實現原理
vue.js是采用數據劫持結合發布者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter和getter,在數據變動時發布消息給訂閱者,觸發相應的監聽回調\
Vue是一個典型的MVVM框架,模型(Model)只是普通的javascript對象,修改它則試圖(View)會自動更新。這種設計讓狀態管理變得非常簡單而直觀
**Observer(數據監聽器)** : Observer的核心是通過Object.defineProprtty()來監聽數據的變動,這個函數內部可以定義setter和getter,每當數據發生變化,就會觸發setter。這時候Observer就要通知訂閱者,訂閱者就是Watcher
**Watcher(訂閱者)** : Watcher訂閱者作為Observer和Compile之間通信的橋梁,主要做的事情是:
1. 在自身實例化時往屬性訂閱器(dep)里面添加自己
1. 自身必須有一個update()方法
1. 待屬性變動dep.notice()通知時,能調用自身的update()方法,并觸發Compile中綁定的回調
**Compile(指令解析器)** : Compile主要做的事情是解析模板指令,將模板中變量替換成數據,然后初始化渲染頁面視圖,并將每個指令對應的節點綁定更新函數,添加鑒定數據的訂閱者,一旦數據有變動,收到通知,更新試圖
傳送門:? [20分鐘吃透Diff算法核心原理](https://juejin.cn/post/6994959998283907102#heading-2)
### 談談對vue生命周期的理解?
每個`Vue`實例在創建時都會經過一系列的初始化過程,`vue`的生命周期鉤子,就是說在達到某一階段或條件時去觸發的函數,目的就是為了完成一些動作或者事件
- `create階段`:vue實例被創建\
`beforeCreate`: 最初調用觸發,創建前,此時data和methods中的數據都還沒有初始化,data和events都不能用\
`created`: 創建完畢,data中有值,未掛載,data和events已經初始化好,data已經具有響應式;在這里可以發送請求
- `mount階段`: vue實例被掛載到真實DOM節點\
`beforeMount`:在模版編譯之后,渲染之前觸發,可以發起服務端請求,去數據,ssr中不可用,基本用不上這個hook\
`mounted`: 在渲染之后觸發,此時可以操作DOM,并能訪問組件中的DOM以及$ref,SSR中不可用
- `update階段`:當vue實例里面的data數據變化時,觸發組件的重新渲染\
`beforeUpdate` :更新前,在數據變化后,模版改變前觸發,切勿使用它監聽數據變化\
`updated`:更新后,在數據改變后,模版改變后觸發,常用于重渲染案后的打點,性能檢測或觸發vue組件中非vue組件的更新
- `destroy階段`:vue實例被銷毀\
`beforeDestroy`:實例被銷毀前,組件卸載前觸發,此時可以手動銷毀一些方法,可以在此時清理事件、計時器或者取消訂閱操作\
`destroyed`:卸載完畢后觸發,銷毀后,可以做最后的打點或事件觸發操作
#### 組件生命周期
生命周期(父子組件) 父組件beforeCreate --> 父組件created --> 父組件beforeMount --> 子組件beforeCreate --> 子組件created --> 子組件beforeMount --> 子組件 mounted --> 父組件mounted -->父組件beforeUpdate -->子組件beforeDestroy--> 子組件destroyed --> 父組件updated
**加載渲染過程**?父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
**掛載階段**?父created->子created->子mounted->父mounted
**父組件更新階段**?父beforeUpdate->父updated
**子組件更新階段**?父beforeUpdate->子beforeUpdate->子updated->父updated
**銷毀階段**?父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
### `computed與watch`
通俗來講,既能用 computed 實現又可以用 watch 監聽來實現的功能,推薦用 computed, 重點在于 computed 的緩存功能 computed 計算屬性是用來聲明式的描述一個值依賴了其它的值,當所依賴的值或者變量 改變時,計算屬性也會跟著改變; watch 監聽的是已經在 data 中定義的變量,當該變量變化時,會觸發 watch 中的方法。
**watch 屬性監聽** 是一個對象,鍵是需要觀察的屬性,值是對應回調函數,主要用來監聽某些特定數據的變化,從而進行某些具體的業務邏輯操作,監聽屬性的變化,需要在數據變化時執行異步或開銷較大的操作時使用
**computed 計算屬性** 屬性的結果會被`緩存`,當`computed`中的函數所依賴的屬性沒有發生改變的時候,那么調用當前函數的時候結果會從緩存中讀取。除非依賴的響應式屬性變化時才會重新計算,主要當做屬性來使用 `computed`中的函數必須用`return`返回最終的結果 `computed`更高效,優先使用。`data 不改變,computed 不更新。`
**使用場景** `computed`:當一個屬性受多個屬性影響的時候使用,例:購物車商品結算功能 `watch`:當一條數據影響多條數據的時候使用,例:搜索數據
#### 組件中的data為什么是一個函數?
1.一個組件被復用多次的話,也就會創建多個實例。本質上,這些實例用的都是同一個構造函數。 2.如果data是對象的話,對象屬于引用類型,會影響到所有的實例。所以為了保證組件不同的實例之間data不沖突,data必須是一個函數。
#### 為什么v-for和v-if不建議用在一起
1.當 v-for 和 v-if 處于同一個節點時,v-for 的優先級比 v-if 更高,這意味著 v-if 將分別重復運行于每個 v-for 循環中。如果要遍歷的數組很大,而真正要展示的數據很少時,這將造成很大的性能浪費
2.這種場景建議使用 computed,先對數據進行過濾
注意:3.x 版本中?`v-if`?總是優先于?`v-for`?生效。由于語法上存在歧義,建議避免在同一元素上同時使用兩者。比起在模板層面管理相關邏輯,更好的辦法是通過創建計算屬性篩選出列表,并以此創建可見元素。
解惑傳送門 ? [# v-if 與 v-for 的優先級對比非兼容](https://v3.cn.vuejs.org/guide/migration/v-if-v-for.html#%E6%A6%82%E8%A7%88)
### React/Vue 項目中 key 的作用
- key的作用是為了在diff算法執行時更快的找到對應的節點,`提高diff速度,更高效的更新虛擬DOM`;
vue和react都是采用diff算法來對比新舊虛擬節點,從而更新節點。在vue的diff函數中,會根據新節點的key去對比舊節點數組中的key,從而找到相應舊節點。如果沒找到就認為是一個新增節點。而如果沒有key,那么就會采用遍歷查找的方式去找到對應的舊節點。一種一個map映射,另一種是遍歷查找。相比而言。map映射的速度更快。
- 為了在數據變化時強制更新組件,以避免`“就地復用”`帶來的副作用。
當 Vue.js 用?`v-for`?更新已渲染過的元素列表時,它默認用“就地復用”策略。如果數據項的順序被改變,Vue 將不會移動 DOM 元素來匹配數據項的順序,而是簡單復用此處每個元素,并且確保它在特定索引下顯示已被渲染過的每個元素。重復的key會造成渲染錯誤。
### 數組扁平化轉換
在說到模版編譯的時候,有可能會提到數組的轉換,一般就用遞歸處理
將 [1,2,3,[4,5]] 轉換成
```
{
children:[
{
value:1
},
{
value:2
},
{
value:3
},
{
children:[
{
value:4
},
{
value:5
}
]
},
]
}
```
```js
// 測試數組
var arr =[1,2,3,[4,5]];
// 轉換函數
function convert(arr){
//準備一個接收結果數組
var result = [];
// 遍歷傳入的 arr 的每一項
for(let i=0;i<arr.length;i++){
//如果遍歷到的數字是number,直接放進入
if(typeof arr[i] == 'number'){
result.push({
value:arr[i]
});
} else if(Array.isArray(arr[i])){
//如果遍歷到這個項目是數組,那么就遞歸
result.push({
children: convert(arr[i])
});
}
}
return result;
}
var o = convert(arr);
console.log(o);
```
### vue組件的通信方式
- `props`/`$emit` 父子組件通信
父->子`props`,子->父 `$on、$emit` 獲取父子組件實例 `parent、children` ` Ref `獲取實例的方式調用組件的屬性或者方法 父->子孫 `Provide、inject` 官方不推薦使用,但是寫組件庫時很常用
- `$emit`/`$on` 自定義事件 兄弟組件通信
`Event Bus` 實現跨組件通信 `Vue.prototype.$bus = new Vue()` 自定義事件
- vuex 跨級組件通信
Vuex、`$attrs、$listeners` `Provide、inject`
### $emit 后面的兩個參數是什么
1、父組件可以使用 props 把數據傳給子組件。
2、子組件可以使用 $emit,讓父組件監聽到自定義事件 。
`vm.$emit( event, arg );`//觸發當前實例上的事件,要傳遞的參數
`vm.$on( event, fn );`//監聽event事件后運行 fn;
子組件
```vue
<template>
<div class="train-city">
<h3>父組件傳給子組件的toCity:{{sendData}}</h3>
<br/><button @click='select(`大連`)'>點擊此處將‘大連’發射給父組件</button>
</div>
</template>
<script>
export default {
name:'trainCity',
props:['sendData'], // 用來接收父組件傳給子組件的數據
methods:{
select(val) {
let data = {
cityName: val
};
this.$emit('showCityName',data);//select事件觸發后,自動觸發showCityName事件
}
}
}
</script>
```
父組件
```vue
<template>
<div>
<div>父組件的toCity{{toCity}}</div>
<train-city @showCityName="updateCity" :sendData="toCity"></train-city>
</div>
<template>
<script>
export default {
name:'index',
components: {},
data () {
return {
toCity:"北京"
}
},
methods:{
updateCity(data){//觸發子組件城市選擇-選擇城市的事件
this.toCity = data.cityName;//改變了父組件的值
console.log('toCity:'+this.toCity)
}
}
}
</script>
```
### nextTick的實現
1. `nextTick`是`Vue`提供的一個全局`API`,是在下次`DOM`更新循環結束之后執行延遲回調,在修改數據之后使用`$nextTick`,則可以在回調中獲取更新后的`DOM`;
1. Vue在更新DOM時是異步執行的。只要偵聽到數據變化,`Vue`將開啟1個隊列,并緩沖在同一事件循環中發生的所有數據變更。如果同一個`watcher`被多次觸發,只會被推入到隊列中-次。這種在緩沖時去除重復數據對于避免不必要的計算和`DOM`操作是非常重要的。`nextTick`方法會在隊列中加入一個回調函數,確保該函數在前面的dom操作完成后才調用;
1. 比如,我在干什么的時候就會使用nextTick,傳一個回調函數進去,在里面執行dom操作即可;
1. 我也有簡單了解`nextTick`實現,它會在`callbacks`里面加入我們傳入的函數,然后用`timerFunc`異步方式調用它們,首選的異步方式會是`Promise`。這讓我明白了為什么可以在`nextTick`中看到`dom`操作結果。
### nextTick的實現原理是什么?
在下次 DOM 更新循環結束之后執行延遲回調,在修改數據之后立即使用 nextTick 來獲取更新后的 DOM。 nextTick主要使用了宏任務和微任務。 根據執行環境分別嘗試采用Promise、MutationObserver、setImmediate,如果以上都不行則采用setTimeout定義了一個異步方法,多次調用nextTick會將方法存入隊列中,通過這個異步方法清空當前隊列。
### 使用過插槽么?用的是具名插槽還是匿名插槽或作用域插槽
vue中的插槽是一個非常好用的東西slot說白了就是一個占位的 在vue當中插槽包含三種一種是默認插槽(匿名)一種是具名插槽還有一種就是作用域插槽 匿名插槽就是沒有名字的只要默認的都填到這里具名插槽指的是具有名字的
### keep-alive的實現
keep-alive是Vue.js的一個內置組件。它能夠不活動的組件實例保存在內存中,而不是直接將其銷毀,它是一個抽象組件,不會被渲染到真實DOM中,也不會出現在父組件鏈中。
作用:實現組件緩存,保持這些組件的狀態,以避免反復渲染導致的性能問題。 需要緩存組件 頻繁切換,不需要重復渲染
場景:tabs標簽頁 后臺導航,vue性能優化
原理:`Vue.js`內部將`DOM`節點抽象成了一個個的`VNode`節點,`keep-alive`組件的緩存也是基于`VNode`節點的而不是直接存儲`DOM`結構。它將滿足條件`(pruneCache與pruneCache)`的組件在`cache`對象中緩存起來,在需要重新渲染的時候再將`vnode`節點從`cache`對象中取出并渲染。
### keep-alive 的屬性
它提供了include與exclude兩個屬性,允許組件有條件地進行緩存。
include定義緩存白名單,keep-alive會緩存命中的組件;exclude定義緩存黑名單,被命中的組件將不會被緩存;max定義緩存組件上限,超出上限使用LRU的策略置換緩存數據。
在動態組件中的應用
```js
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
<component :is="currentComponent"></component>
</keep-alive>
```
在vue-router中的應用
```js
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
<router-view></router-view>
</keep-alive>
```
vue 中完整示例
```
<keep-alive>
<coma v-if="test"></coma>
<comb v-else="test"></comb>
</keep-alive>
<button @click="test=handleClick">請點擊</button>
export default {
data () {
return {
test: true
}
},
methods: {
handleClick () {
this.test = !this.test;
}
}
}
```
參考:
[keep-alive 官網](https://cn.vuejs.org/v2/api/#keep-alive)
[keep-alive實現原理](https://www.jianshu.com/p/9523bb439950)
[Vue keep-alive的實現原理](https://blog.csdn.net/weixin_38189842/article/details/103999989)
### mixin
mixin 項目變得復雜的時候,多個組件間有重復的邏輯就會用到mixin
多個組件有相同的邏輯,抽離出來
mixin并不是完美的解決方案,會有一些問題
vue3提出的Composition API旨在解決這些問題【追求完美是要消耗一定的成本的,如開發成本】
場景:PC端新聞列表和詳情頁一樣的右側欄目,可以使用mixin進行混合
劣勢:1.變量來源不明確,不利于閱讀 2.多mixin可能會造成命名沖突 3.mixin和組件可能出現多對多的關系,使得項目復雜度變高
### vue 如何實現模擬 v-model 指令
可以使用 vue 自定義指令 Vue.directive() 模擬
具體參考:[vue自定義指令模擬v-model指令](https://blog.csdn.net/qq_39157944/article/details/106262546)
### 如何實現 v-model,說下思路
### Vue Router 相關
### Vuex的理解及使用場景
Vuex 是一個專為 Vue 應用程序開發的狀態管理模式。每一個 Vuex 應用的核心就是 store(倉庫)。
1. Vuex 的狀態存儲是響應式的;當 Vue 組件從 store 中讀取狀態的時候,
若 store 中的狀態發生變化,那么相應的組件也會相應地得到高效更新 2. 改變 store 中的狀態的唯一途徑就是顯式地提交 (commit) mutation, 這樣使得我們可以方便地跟蹤每一個狀態的變化 Vuex主要包括以下幾個核心模塊:
1. State:定義了應用的狀態數據
1. Getter:在 store 中定義“getter”(可以認為是 store 的計算屬性),
就像計算屬性一樣,getter 的返回值會根據它的依賴被緩存起來, 且只有當它的依賴值發生了改變才會被重新計算 3. Mutation:是唯一更改 store 中狀態的方法,且必須是同步函數 4. Action:用于提交 mutation,而不是直接變更狀態,可以包含任意異步操作 5. Module:允許將單一的 Store 拆分為多個 store 且同時保存在單一的狀態樹中

- JavaScript
- 1. DOM事件流
- 2. 模擬 new, Object create(), bind
- 5. 封裝函數進行字符串駝峰命名的轉換
- 6. 什么是promise
- 7. 判斷一個數是否為數組
- 10. __proto__和prototype以及原型,原型鏈,構造函數
- 11. 繼承
- 12. 閉包
- 13. 回調函數
- 14. var 和 let 區別
- 15. this、bind、call、apply
- 16.undefined和null的區別
- 17.內存泄漏
- 18.垃圾回收機制
- html css
- 1. 元素垂直水平居中
- 2. 清除浮動
- 3. bootstrap柵格系統
- 4. px rpx em rem vw 的區別
- 5. 兩種盒子模型
- 6. 合集
- web類
- 1. html5的新特性以及理解(web標簽語義化)
- 2. 什么是路由,關于前端路由和后端路由
- 3. 對優質代碼的理解
- 4. cookie 和 sessionStorage和localStorage
- 5. 瀏覽器內核
- 6. http 狀態碼
- 7. href 和 src 的區別
- 8. link 和 @import 的區別
- 9. http 狀態碼
- 10. websocket
- 11. 瀏覽器解析url
- 12.http緩存
- vue
- 1.vue2和vue3有哪些區別
- 1. 對 mvvvm 的理解
- 2. mvvm的優缺點
- 3. 數據雙向綁定的原理
- 4. 生命周期
- 5. 組件如何通信
- 6. computed和watch的區別
- 7. proxy 和 Object.defineProperty
- 8. 虛擬dom和 diff算法
- 9. 路由的嵌套與傳參
- 10. 路由導航鉤子
- 11. axios 的理解
- 12. vue自定義指令 diretive
- 13. diff 的實現
- 14. 實現一個簡單的雙向綁定
- 15. 為什么 data 是一個函數
- 題譜
- js
- 手寫篇
- css
- vue
- react
- 算法
- 自我介紹
- 八股文
- 源項目地址
- 1.計算機網絡
- 2.瀏覽器
- 3.html和css
- 4.javascript
- 6.typescript
- 7.vue
- 8.react
- 大廠面試
- 面試題大全
- 常見性能優化
- 面試實戰
- 面試分析
- 押題
- 1.微前端在項目中的實際應用
- 2.性能優化
- vue相關
- 1.說一說HashRouter和HistoryRouter的區別和原理
- 無敵之路,牛客網面試題自測記錄
- 前端基礎
- 1.html
- 2.js基礎
- 珠峰性能優化
- WebWorker
- url到渲染
- 瀏覽器加載機制
- 自我介紹1
- 手寫題
- 1.compose
- 2.setTimeout模擬setInterval
- 3.手寫數組拍平
- 4.手寫promise.all
- 5.手寫深拷貝
- webpack
- 實戰