keep-alive 組件有什么作用?
keep-alive 是 vue 的內置組件,一般情況下,組件進行切換的時候,默認是會進行銷毀的,如果我們想在某個組件切換后不進行銷毀,而是保存之前的狀態,那么就可以利用 keep-alive 來實現。
keep-alive是一個抽象組件:它自身不會渲染一個 DOM 元素,也不會出現在父組件鏈中;使用keep-alive包裹動態組件時,會緩存不活動的組件實例,而不是銷毀它們。
場景:
用戶在某個列表頁面選擇篩選條件過濾出一份數據列表,由列表頁面進入數據詳情頁面,再返回該列表頁面,
我們希望:列表頁面可以保留用戶的篩選(或選中)狀態。
keep-alive就是用來解決這種場景。當然keep-alive不僅僅是能夠保存頁面/組件的狀態這么簡單,它還可以避免組件反復創建和渲染,有效提升系統性能。
總的來說,keep-alive用于保存組件的渲染狀態。
include 和exclude 值為字符串或正則表達式匹配的組件 name 會不會被緩存。
//router.js
{
path: '/',
name: 'xxx',
component: ()=>import('../src/views/xxx.vue'),
meta:{
keepAlive: true // 需要被緩存
}
},
(exclude優先)
有兩個獨立的生命周期actived 和 deactived,
使用 keep-alive 包裹的組件在切換時不會被銷毀,而是緩存到內存中并執行 deactived 鉤子函數,命中緩存渲染后會執行 actived 鉤子函數。
vue生命周期
生命周期函數中的this指向是vm 或 組件實例對象。
關于VueComponent
vue 組件中 data 必須是一個函數?
如果 data 是一個對象,當復用組件時,因為 data 都會指向同一個引用類型地址,其中一個組件的 data 一旦發生修改,其他組件中的 data 也會被修改。
如果 data 是一個返回對象的函數,因為每次重用組件時返回的都是一個新對象,引用地址不同,不會出現如上問題。
Vue 中 v-if 和 v-show 有什么區別?
v-if 在進行切換時,會直接對標簽進行創建或銷毀,不顯示的標簽不會加載在 DOM 樹中。 v-show 在進行切換時,會對標簽的 display 屬性進行切換,通過 display 不顯示來隱藏元素。
一般來說,v-if 的性能開銷會比 v-show 大,切換頻繁的標簽更適合使用 v-show。
Vue 中 computed 和 watch 有什么區別?
computed:
支持緩存,只有依賴數據發生變化時,才會重新進行計算函數;
計算屬性內不支持異步操作;
計算屬性的函數中都有一個 get和set
計算屬性是自動監聽依賴值的變化,從而動態返回內容。
get什么時候調用?1.初次讀取fullName時。2.所依賴的數據發生變化時。
watch:
不支持緩存,只要數據發生變化,就會執行偵聽函數;
偵聽屬性內支持異步操作,
偵聽屬性的值可以是一個對象,接收 handler 回調,deep,immediate
監聽是一個過程,在監聽的值變化時,可以在里面操作一些方法,compute不可以,例如加一個定時器。
Vue-router 路由有哪些模式?
hash 模式
后面的 hash 值的變化,瀏覽器既不會向服務器發出請求,瀏覽器也不會刷新,每次 hash 值的變化會觸發 hashchange 事件。
history 模式
利用了 HTML5 中新增的 pushState() 和 replaceState() 方法。
這兩個方法應用于瀏覽器的歷史記錄棧,在當前已有的 back、forward、go 的基礎之上,它們 提供了對歷史記錄進行修改的功能。
vue的 $forceUpdate 和$set
$forceUpdate(用來全局強制刷新,性能消耗高)
迫使VUE實例重新渲染。
注意:它僅僅影響實例本身和插入插槽內容的子組件,而不是所有的子組件。
使用場景:
1.路由切換時,頁面數據比較復雜
2.改變多維數組里面的數據
3.data中的變量為數組或對象,我們直接去給某個對象或數組添加屬性,頁面是識別不到的
原理
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
實例需要重新渲染,是在依賴發生變化的時候會通知watcher,然后通知watcher來調用update方法.
this.set(object, index, new) ,this.set()方法是vue自帶的,可對數組和對象進行賦值,并觸發監聽的方法。(用來指向性強制刷新,性能消耗低)
注意項: 注意對象不能是 Vue 實例,或者 Vue 實例的根數據對象。
this.$set(this.$data, 'age', 24) //不能給vue實例的根數據對象設置,會報錯
向響應式的對象中添加一個屬性,并確保這個新的屬性同樣也是響應式的,同時觸發相應視圖的更新。$set必須用于向響應式的對象中添加新的屬性,因為Vue無法探測普通的新增屬性。
diff算法
什么是虛擬DOM
一個用來表示真實DOM的對象
虛擬DOM比真實DOM快,這句話不對。
虛擬DOM算法操作真實DOM,性能高于直接操作真實DOM。
虛擬DOM和虛擬DOM算法是兩種概念。
虛擬DOM算法 = 虛擬DOM + Diff算法
什么是Diff算法
總結:Diff算法是一種對比算法。對比舊虛擬DOM和新虛擬DOM,找出更改了的虛擬節點,并只更新這個虛擬節點所對應的真實節點,不更新其他數據沒發生改變的節點,實現精準地更新真實DOM,進而提高效率。
使用虛擬DOM算法的損耗計算:
總損耗 = 虛擬DOM增刪改+(與Diff算法效率有關)真實DOM差異增刪改+(較少的節點)排版與重繪
直接操作真實DOM的損耗計算:
總損耗 = 真實DOM完全增刪改+(可能較多的節點)排版與重繪
Diff算法的原理
新舊虛擬DOM對比的時候,Diff算法比較只會在同層級進行, 不會跨層級比較。
Diff對比流程
當數據改變時,觸發setter,并通過Dep.notify去通知所有訂閱者Watcher,訂閱者們就會調用patch方法,給真實DOM打補丁,更新相應的視圖。
patch方法
作用:對比當前同層的虛擬節點是否為同一種類型的標簽
是:繼續執行patchVnode方法進行深層比對
否:沒必要對比了,直接整個節點替換成新虛擬節點
function patch(oldVnode, newVnode) {
// 比較是否為一個類型的節點
if (sameVnode(oldVnode, newVnode)) {
// 是:繼續進行深層比較
patchVnode(oldVnode, newVnode)
} else {
// 否
const oldEl = oldVnode.el // 舊虛擬節點的真實DOM節點
const parentEle = api.parentNode(oldEl) // 獲取父節點
createEle(newVnode) // 創建新虛擬節點對應的真實DOM節點
if (parentEle !== null) {
api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)) // 將新元素添加進父元素
api.removeChild(parentEle, oldVnode.el) // 移除以前的舊元素節點
// 設置null,釋放內存
oldVnode = null
}
}
return newVnode
}
sameVnode方法
patch關鍵的一步就是sameVnode方法判斷是否為同一類型節點
比較key,標簽名,是否都為注釋節點,是否定義了data,標簽為input時,type必須相同
function sameVnode(oldVnode, newVnode) {
return (
oldVnode.key === newVnode.key && // key值是否一樣
oldVnode.tagName === newVnode.tagName && // 標簽名是否一樣
oldVnode.isComment === newVnode.isComment && // 是否都為注釋節點
isDef(oldVnode.data) === isDef(newVnode.data) && // 是否都定義了data
sameInputType(oldVnode, newVnode) // 當標簽為input時,type必須是否相同
)
}
patchVnode方法
找到對應的真實DOM,稱為el
判斷newVnode和oldVnode是否指向同一個對象,如果是,那么直接return
如果他們都有文本節點并且不相等,那么將el的文本節點設置為newVnode的文本節點。
如果oldVnode有子節點而newVnode沒有,則刪除el的子節點
如果oldVnode沒有子節點而newVnode有,則將newVnode的子節點真實化之后添加到el
如果兩者都有子節點,則執行updateChildren函數比較子節點,這一步很重要
function patchVnode(oldVnode, newVnode) {
const el = newVnode.el = oldVnode.el // 獲取真實DOM對象
// 獲取新舊虛擬節點的子節點數組
const oldCh = oldVnode.children, newCh = newVnode.children
// 如果新舊虛擬節點是同一個對象,則終止
if (oldVnode === newVnode) return
// 如果新舊虛擬節點是文本節點,且文本不一樣
if (oldVnode.text !== null && newVnode.text !== null && oldVnode.text !== newVnode.text) {
// 則直接將真實DOM中文本更新為新虛擬節點的文本
api.setTextContent(el, newVnode.text)
} else {
// 否則
if (oldCh && newCh && oldCh !== newCh) {
// 新舊虛擬節點都有子節點,且子節點不一樣
// 對比子節點,并更新
updateChildren(el, oldCh, newCh)
} else if (newCh) {
// 新虛擬節點有子節點,舊虛擬節點沒有
// 創建新虛擬節點的子節點,并更新到真實DOM上去
createEle(newVnode)
} else if (oldCh) {
// 舊虛擬節點有子節點,新虛擬節點沒有
//直接刪除真實DOM里對應的子節點
api.removeChild(el)
}
}
}
updateChildren方法(重要)
新舊虛擬節點的子節點對比,就是發生在updateChildren方法中
image.png
首尾指針法,新的子節點集合和舊的子節點集合,各有首尾兩個指針,兩兩組合,進行比較,有四種情況。
總共有五種比較情況:
1、oldS 和 newS 使用sameVnode方法進行比較,sameVnode(oldS, newS)
2、oldS 和 newE 使用sameVnode方法進行比較,sameVnode(oldS, newE)
3、oldE 和 newS 使用sameVnode方法進行比較,sameVnode(oldE, newS)
4、oldE 和 newE 使用sameVnode方法進行比較,sameVnode(oldE, newE)
5、如果以上邏輯都匹配不到,再把所有舊子節點的 key 做一個映射到舊節點下標的 key -> index 表,然后用新 vnode 的 key 去找出在舊節點中可以復用的位置。
最終的渲染結果都要以newVDOM為準
**用index做key為什么不好? **
在進行子節點的diff算法過程中,會進行舊首節點和新首節點的sameNode對比。
如果在一個列表前面加一個標簽,導致相同key的節點會去進行patchVnode更新文本,而原本就有的最后一個節點,被當做了新節點。
前面的都進行patchVnode更新文本,最后一個進行了新增。這樣效率降低。
vue核心(MVVM)
只關注試圖層
數據驅動,組件化
數據雙向綁定核心:MVVM
『View』:視圖層(UI 用戶界面)
『ViewModel』:業務邏輯層(一切 js 可視為業務邏輯)
『Model』:數據層(存儲數據及對數據的處理如增刪改查)
Model 和 ViewModel 之間的交互是雙向的,因此 View 的變化會自動同步到 Model,而 Model 的變化也會立即反映到 View 上顯示。
當用戶操作 View,ViewModel 感知到變化,然后通知 Model 發生相應改變;
反之當 Model 發生改變,ViewModel 也能感知到變化,使 View 作出相應更新。
簡述 MVVM
MVVM 是 Model-View-ViewModel 的縮寫。
MVVM 是一種設計思想。
Model 層代表數據模型,也可以在 Model 中定義數據修改和操作的業務邏輯
View 代表 UI 組件,它負責將數據模型轉化成 UI 展現出來,
ViewModel 是一個同步 View 和 Model 的對象。
在 MVVM 架構下,View 和 Model 之間并沒有直接的聯系,而是通過 ViewModel 進行交互,Model 和 ViewModel 之間的交互是雙向的, 因此 View 數據的變化會同步到 Model 中,而 Model 數據的變化也會立即反應到 View 上。
ViewModel 通過雙向數據綁定把 View 層和 Model 層連接了起來。
View 和 Model 之間的同步工作完全是自動的,無需人為干涉。
因此開發者只需關注業務邏輯,不需要手動操作DOM, 不需要關注數據狀態的同步問題,復雜的數據狀態維護完全由 MVVM 來統一管理。
簡述 Vue.js 的優點
1低耦合。
視圖(View)可以獨立于 Model 變化和修改,一個 ViewModel 可以綁定到不同的 "View" 上,當 View 變化的時候 Model 可以不變,當 Model 變化的時候 View 也可以不變。
2可重用性。
你可以把一些視圖邏輯放在一個 ViewModel 里面,讓很多 View 重用這段視圖邏輯。
3獨立開發。
開發人員可以專注于業務邏輯和數據的開發(ViewModel,設計人員可以專注于頁面設計。
4方便測試。
界面素來是比較難于測試的,開發中大部分 Bug 來至于邏輯處理,由于 ViewModel 分離了許多邏輯,可以對 ViewModel 構造單元測試。
易用 靈活 高效。
Vue 并沒有完全遵循 MVVM 的思想
嚴格的 MVVM 要求 View 不能和 Model 直接通信,而 Vue 提供了$refs 這個屬性,讓 Model 可以直接操作 View,違反了這一規定,所以說 Vue 沒有完全遵循 MVVM
vue配置反向代理解決跨域
配置:config/index.js中的proxyTable
dev{
proxyTable: {
'/api': {
target: 'http://192.168.0.1:200', // 要代理的域名
changeOrigin: true,//允許跨域
pathRewrite: {
'^/api': '' // 這個是定義要訪問的路徑,名字隨便寫
}
}
}
// /api/getMenu相當于*http://192.168.0.1:200/getMenu
// /api相當于http://192.168.0.1:200
this.$http.get("/api/getMenu", {
}
.then(res => {
})
.catch(function(error) {
});
注意: 以上面代碼設置的為例,會把請求中所有帶有/api字段的都替換掉,例如api/getMenu/api,前后兩個都會被替換,導致404等錯誤,在代理數量比較多的時候容易出現這個問題。
以上配置只是在開發環境(dev)中解決跨域。
要解決生產環境的跨域問題,則在config/dev.env.js和config/prod.env.js里也就是開發/生產環境下分別配置一下請求的地址API_HOST。
開發環境中我們用上面配置的代理地址api,生產環境下用正常的接口地址。配置代碼如下:
module.exports = merge(prodEnv, {
NODE_ENV: '"development"', //開發環境
API_HOST:"/api/"
})
module.exports = {
NODE_ENV: '"production"', //生產環境
API_HOST:'"http://40.00.100.100:3002/"'
}
原理
瀏覽器是禁止跨域的,但是服務端不禁止,在本地運行npm run dev等命令時實際上是用node運行了一個服務器,IP與后端不一致,所以會產生跨域問題,需要使用如JSONP、跨域代理等手段進行跨域請求。
而vue已經幫我們配置好了,只需要設置一下proxyTable就行。
因此proxyTable實際上是將請求發給自己的服務器,再由服務器轉發給后臺服務器,做了一層代理,so出現跨域問題。
底層
vue-cli采用http-proxy-middleware插件來進行代理服務器等各項配置。
Vue 組件通訊有哪幾種方式
props 和 $emit父組件向子組件傳遞數據是通過 prop 傳遞的,子組件傳遞數據給父組件是通過$emit 觸發事件來做到的**
$parent,$children 獲取當前組件的父組件和當前組件的子組件
$attrs 和$listeners A->B->C。Vue 2.4 開始提供了$attrs 和$listeners 來解決這個問題
先來看$attrs和$listeners的定義在Vue文檔中:
vm.$attrs包含了父作用域中不作為 prop 被識別 (且獲取) 的 attribute 綁定 (class 和 style 除外)。當一個組件沒有聲明任何 prop 時,這里會包含所有父作用域的綁定 (class 和 style 除外),并且可以通過 v-bind="$attrs" 傳入內部組件——在創建高級別的組件時非常有用。
$listeners包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它可以通過 v-on="$listeners" 傳入內部組件——在創建更高層次的組件時非常有用。
parent.png
child.png
child2.png
效果
可以看到父組件App.vue中通過v-bind給Child1傳遞了三個值,在子組件中未被Props接收的值都在
listeners將父作用域中的v-on事件監聽器,傳入child2
inheritAttrs 屬性以及作用
當一個組件設置了inheritAttrs: false后(默認為true),那么該組件的非props屬性(即未被props接收的屬性)將不會在組件根節點上生成html屬性,以為為對比圖
父組件中通過 provide 來提供變量,然后在子組件中通過 inject 來注入變量。(官方不推薦在實際業務中使用,但是寫組件庫時很常用)
$refs 獲取組件實例
envetBus 兄弟組件數據傳遞 這種情況下可以使用事件總線的方式
vuex 狀態管理
異步請求在哪一步發起?
可以在鉤子函數 created、beforeMount、mounted 中進行異步請求,因為在這三個鉤子函數中,data 已經創建,可以將服務端端返回的數據進行賦值。
如果異步請求不需要依賴 Dom 推薦在 created 鉤子函數中調用異步請求,因為在 created 鉤子函數中調用異步請求有以下優點:
能更快獲取到服務端數據,減少頁面 loading 時間;
ssr 不支持 beforeMount 、mounted 鉤子函數,所以放在 created 中有助于一致性;
vue 內置指令
v-cloak可以解決插值閃爍問題(防止代碼被人看見),在元素里加入 v-cloak即可
<p v-cloak>{{msg}}</p>
[v-cloak]{
display: none;
}
怎樣理解 Vue 的單向數據流
數據總是從父組件傳到子組件,子組件沒有權利修改父組件傳過來的數據,只能請求父組件對原始數據進行修改。
這樣會防止從子組件意外改變父級組件的狀態,從而導致你的應用的數據流向難以理解。
如果實在要改變父組件的 prop 值 可以再 data 里面定義一個變量,并用 prop 的值初始化它 之后用$emit 通知父組件去修改
Vue 如何檢測數組變化
push,shift,pop,unshift,splice,sort,reverse方法進行重寫
所以在 Vue 中修改數組的索引和長度是無法監控到的。需要通過以上 7 種變異方法修改數組才會觸發數組對應的 watcher 進行更新
重寫了數組中的那些原生方法,首先獲取到這個數組的Observer對象,如果有新的值,就調用observeArray繼續對新的值觀察變化(也就是通過target__proto__ == arrayMethods來改變了數組實例的型),然后手動調用notify,通知渲染watcher,執行update。
vue3
1支持多個根結點,改成用fragment,
2 Composition API
setup在組件內作為 Composition API的入口,
執行時機是在 beforeCreate 之前執行
使用setup時,它接受兩個參數:
props: 組件傳入的屬性
context:提供了this中最常用的三個屬性:attrs、slot 和emit
setup 中接受的props是響應式的, 當傳入新的 props 時,會及時被更新。由于是響應式的, 所以不可以使用 ES6 解構,解構會消除它的響應式。用toRefs()解決
ref就能處理 js 基本類型, 比如ref也是可以定義對象的雙向綁定.取值得寫.value
但是reactive函數確實可以代理一個對象, 但是不能代理基本類型,例如字符串、數字、boolean 等。
toRefs 用于將一個 reactive 對象轉化為屬性全部為 ref 對象的普通對象。
修改:setup在beforeCreate之前,所有生命周期名字都加上on,beforeDestory
改成onBeforeUnmount,destory改成onUnmounted
watch 與 watchEffect 的用法
watch(source, callback, [options])
參數說明:
source: 可以支持 string,Object,Function,Array; 用于指定要偵聽的響應式變量
callback: 執行的回調函數
options:支持 deep、immediate 和 flush 選項。
需要偵聽多個數據源時, 可以進行合并, 同時偵聽多個數據
watch([() => state.age, year], ([curAge, newVal], [preAge, oldVal]) => {
console.log("新值:", curAge, "老值:", preAge); console.log("新值:", newVal,
"老值:", oldVal); });
偵聽復雜的嵌套對象
const state = reactive({
room: {
id: 100,
attrs: {
size: "140平方米",
type: "三室兩廳",
},
},
});
watch(
() => state.room,
(newType, oldType) => {
console.log("新值:", newType, "老值:", oldType);
},
{ deep: true }
);
默認情況下,watch 是惰性的, 那什么情況下不是惰性的, 可以立即執行回調函數呢?
給第三個參數中設置immediate: true即可。
stop 停止監聽
const stopWatchRoom = watch(() => state.room, (newType, oldType) => {
console.log("新值:", newType, "老值:", oldType);
}, {deep:true});
setTimeout(()=>{
// 停止監聽
stopWatchRoom()
}, 3000)
Object.defineProperty 與 Proxy
Object.defineProperty只能劫持對象的屬性, 而 Proxy 是直接代理對象
由于Object.defineProperty只能劫持對象屬性,需要遍歷對象的每一個屬性,如果屬性值也是對象,就需要遞歸進行深度遍歷。但是 Proxy 直接代理對象, 不需要遍歷操作
Object.defineProperty對新增屬性需要手動進行Observe
因為Object.defineProperty劫持的是對象的屬性,所以新增屬性時,需要重新遍歷對象, 對其新增屬性再次使用Object.defineProperty進行劫持。也就是 Vue2.x 中給數組和對象新增屬性時,需要使用$set才能保證新增的屬性也是響應式的, $set內部也是通過調用Object.defineProperty去處理的。
Teleport 是什么呢?樣式在外層,可以受內部控制
即希望繼續在組件內部使用Dialog, 又希望渲染的 DOM 結構不嵌套在組件的 DOM
<body>
<div id="app"></div>
<div id="dialog"></div>
</body>
<template>
<teleport to="#dialog">
<div class="dialog">
122
</div>
</teleport>
</template>
<div class="header">
<navbar />
<Dialog v-if="dialogVisible"></Dialog>
</div>
Suspense 顯示有數據沒數據時樣式
Suspense 只是一個帶插槽的組件,只是它的插槽指定了default 和 fallback 兩種狀態。
<Suspense>
<template #default>
<async-component></async-component>
</template>
<template #fallback>
<div>
Loading...
</div>
</template>
</Suspense>
slot
數據代理
1.Vue中的數據代理:
通過vm對象來代理data對象中屬性的操作(讀/寫)
2.Vue中數據代理的好處:
更加方便的操作data中的數據
3.基本原理:
通過Object.defineProperty()把data對象中所有屬性添加到vm上。
為每一個添加到vm上的屬性,都指定一個getter/setter。
在getter/setter內部去操作(讀/寫)data中對應的屬性。
v-html,v-text
v-html,v-text會替換掉節點中的值,{{XX}}不會
自定義指令
v-modal 可以在自定義組件上使用嗎?
可以
vue模版編譯原理
1模板編譯,將模板代碼轉化為 AST;
2優化 AST,方便后續虛擬 DOM 更新;
3生成代碼,將 AST 轉化為可執行的代碼;
const baseCompile = (template, options) => {
// 解析 html,轉化為 ast
const ast = parse(template.trim(), options)
// 優化 ast,標記靜態節點
optimize(ast, options)
// 將 ast 轉化為可執行代碼
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
}