一、Vue 面試真題
v-show
和v-if
的區別,答案如下所示:
v-show
通過CSS display
控制顯示與隱藏v-if
組件真正的渲染和銷燬,而不是顯示與隱藏- 頻繁切換顯示狀態用
v-show
,否則用v-if
- 爲何在
v-for
中用key
,答案如下所示:
- 必須用
key
,且不能是index
和random
diff
算法中通過tag
和key
來判斷,是否是sameNode
- 減少渲染次數,提升渲染性能
Vue
組件如何通訊,常見的方式,答案如下所示:
- 父子組件
props
和this.$emit
- 自定義事件
event.$on、event.$off 和 event.$emit
vuex
- 雙向數據綁定
v-model
的實現原理,答案如下所示:
input
元素的value = this.name
- 綁定
input
事件this.name = $event.target.value
data
更新觸發re-render
computed
有何特點,答案如如下所示:
- 緩存,
data
不變不會重新計算 - 提高性能
ajax
請求應該放在哪個生命週期,答案如下所示:
mounted
生命週期函數JS
是單線程的,ajax
是異步獲取數據- 放在
mounted
之前沒有用,只會讓邏輯更加混亂
- 如何將組件所有
props
傳遞給子組件,答案如下所示:
$props
<User v-bind = "$props" />
- 細節知識點,優先級不高
- 多個組件有相同的邏輯,如何抽離,答案如下所示:
- 使用
mixin
- 何時要使用異步組件,答案如下所示:
- 加載大組件
- 路由異步加載
- 何時需要使用
keep-alive
,答案如下所示:
- 緩存組件,不需要重複渲染
- 如多個靜態
tab
頁的切換 - 優化性能
- 何時需要使用
beforeDestory
,答案如下所示:
- 解綁自定義事件
event.$off
- 清除定時器
- 解綁自定義的
DOM
事件,如window scroll
等
Vuex
中action
和mutation
有何區別,答案如下所示:
action
中處理異步,mutation
不可以mutation
做原子操作action
可以整合多個mutation
Vue-router
常用的路由模式,答案如下所示:
hash
默認H5 history
,需要服務端支持- 兩者比較
- 監聽
data
變化的核心API
是什麼,答案如下所示:
Object.defineProperty
- 深度監聽、監聽數組
- 有何缺點
Vue
如何監聽數組變化,答案如下所示:
Object.defineProperty
不能監聽數組變化- 重新定義原型,重寫
push
和pop
等方法,實現監聽 Proxy
可以原生支持監聽數組變化
- 請描述響應式原理,答案如下所示:
- 監聽
data
變化 - 組件渲染和更新的流程
diff
算法的時間複雜度,答案如下所示:
O(n)
- 在
O(n^3)
基礎上做了一些調整
- 簡述
diff
算法過程,答案如下所示:
patch(elem, vnode)
和patch(vnode, newVnode)
patchVnode
、addVnodes
和removeVnodes
updateChildren
,key
很重要
Vue
爲何是異步渲染,$nextTick
何用,答案如下所示:
- 異步渲染以及合併
data
修改,以提高渲染性能 $nextTick
在DOM
更新完成之後,觸發回調
Vue
常見的性能優化方式,答案如下所示:
- 合理使用
v-show
和v-if
- 合理使用
computed
v-for
時加key
,以及避免和v-if
同時使用- 自定義事件、
DOM
事件以及銷燬 - 合理使用異步組件
- 合理使用
keep-alive
data
層級不要太深- 使用
vue-loader
在開發環境做模版編譯,預編譯 webpack
層面的優化- 前端通用的性能優化,如圖片懶加載
- 使用
SSR
二、Vue3 的相關考點
Vue3
雖然尚未發佈,還在開發中,但是還是很重要的,在面試中也還是會考察的。Vue3
中升級的內容,如下所示:
- 全部用
ts
重寫,響應式、vdom
、模版編譯等 - 性能提升,代碼量減少
- 會調整部分
API
Vue2.x
馬上就要過時了嗎,答案如下所示:
Vue3
從正式發佈到推廣開來,還需要一段時間Vue2.x
應用範圍非常廣,有大量項目需要維護、升級Proxy
存在瀏覽器兼容性問題,且不能polyfill
Vue3
比較重要的一點,就是重寫響應式
Object.defineProperty
的缺點,如下所示:
- 深度監聽需要一次性遞歸
- 無法監聽新增屬性和刪除屬性,
Vue.set
和Vue.delete
- 無法原生監聽數組,需要特殊處理
-
Proxy
實現響應式,基本使用、Reflect
和 實現響應式。對於Reflect
的作用,和Proxy
能力一一對應,規範化、標準化、函數式,替代掉Object
上的工具函數。 -
Proxy
實現響應式,深度監聽,性能更好,可監聽新增和刪除屬性,可監聽數組變化。Proxy
能夠規避Object.defineProperty
的問題,Proxy
無法兼容所有瀏覽器,無法polyfill
。 -
關於響應式的相關代碼,如下所示:
- observe.js
// 觸發更新視圖
function updateView() {
console.log('視圖更新')
}
// 重新定義數組原型
const oldArrayProperty = Array.prototype
// 創建新對象,原型指向 oldArrayProperty ,再擴展新的方法不會影響原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
arrProto[methodName] = function () {
updateView() // 觸發視圖更新
oldArrayProperty[methodName].call(this, ...arguments)
// Array.prototype.push.call(this, ...arguments)
}
})
// 重新定義屬性,監聽起來
function defineReactive(target, key, value) {
// 深度監聽
observer(value)
// 核心 API
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue !== value) {
// 深度監聽
observer(newValue)
// 設置新值
// 注意,value 一直在閉包中,此處設置完之後,再 get 時也是會獲取最新的值
value = newValue
// 觸發更新視圖
updateView()
}
}
})
}
// 監聽對象屬性
function observer(target) {
if (typeof target !== 'object' || target === null) {
// 不是對象或數組
return target
}
// 污染全局的 Array 原型
// Array.prototype.push = function () {
// updateView()
// ...
// }
if (Array.isArray(target)) {
target.__proto__ = arrProto
}
// 重新定義各個屬性(for in 也可以遍歷數組)
for (let key in target) {
defineReactive(target, key, target[key])
}
}
// 準備數據
const data = {
name: 'zhangsan',
age: 20,
info: {
address: '北京' // 需要深度監聽
},
nums: [10, 20, 30]
}
// 監聽數據
observer(data)
// 測試
// data.name = 'lisi'
// data.age = 21
// // console.log('age', data.age)
// data.x = '100' // 新增屬性,監聽不到 —— 所以有 Vue.set
// delete data.name // 刪除屬性,監聽不到 —— 所有已 Vue.delete
// data.info.address = '上海' // 深度監聽
data.nums.push(4) // 監聽數組
- proxy.js
// const data = {
// name: 'zhangsan',
// age: 20,
// }
const data = ['a', 'b', 'c']
const proxyData = new Proxy(data, {
get(target, key, receiver) {
// 只處理本身(非原型的)屬性
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('get', key) // 監聽
}
const result = Reflect.get(target, key, receiver)
return result // 返回結果
},
set(target, key, val, receiver) {
// 重複的數據,不處理
if (val === target[key]) {
return true
}
const result = Reflect.set(target, key, val, receiver)
console.log('set', key, val)
// console.log('result', result) // true
return result // 是否設置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key)
// console.log('result', result) // true
return result // 是否刪除成功
}
})
- proxy-observe.js
// 創建響應式
function reactive(target = {}) {
if (typeof target !== 'object' || target == null) {
// 不是對象或數組,則返回
return target
}
// 代理配置
const proxyConf = {
get(target, key, receiver) {
// 只處理本身(非原型的)屬性
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('get', key) // 監聽
}
const result = Reflect.get(target, key, receiver)
// 深度監聽
// 性能如何提升的?
return reactive(result)
},
set(target, key, val, receiver) {
// 重複的數據,不處理
if (val === target[key]) {
return true
}
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('已有的 key', key)
} else {
console.log('新增的 key', key)
}
const result = Reflect.set(target, key, val, receiver)
console.log('set', key, val)
// console.log('result', result) // true
return result // 是否設置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key)
// console.log('result', result) // true
return result // 是否刪除成功
}
}
// 生成代理對象
const observed = new Proxy(target, proxyConf)
return observed
}
// 測試數據
const data = {
name: 'zhangsan',
age: 20,
info: {
city: 'beijing',
a: {
b: {
c: {
d: {
e: 100
}
}
}
}
}
}
const proxyData = reactive(data)