vue3.0 特性你有什么了解的嗎?
Vue 3.0 正走在發布的路上,Vue 3.0 的目標是讓 Vue 核心變得更小、更快、更強大,因此 Vue 3.0 增加以下這些新特性:
(1)監測機制的改變
3.0 將帶來基于代理 Proxy 的 observer 實現,提供全語言覆蓋的反應性跟蹤。這消除了 Vue 2 當中基于 Object.defineProperty 的實現所存在的很多限制:
- 只能監測屬性,不能監測對象
- 檢測屬性的添加和刪除;
- 檢測數組索引和長度的變更;
- 支持 Map、Set、WeakMap 和 WeakSet。
新的 observer 還提供了以下特性:
- 用于創建 observable 的公開 API。這為中小規模場景提供了簡單輕量級的跨組件狀態管理解決方案。
- 默認采用惰性觀察。在 2.x 中,不管反應式數據有多大,都會在啟動時被觀察到。如果你的數據集很大,這可能會在應用啟動時帶來明顯的開銷。在 3.x 中,只觀察用于渲染應用程序最初可見部分的數據。
- 更精確的變更通知。在 2.x 中,通過 Vue.set 強制添加新屬性將導致依賴于該對象的 watcher 收到變更通知。在 3.x 中,只有依賴于特定屬性的 watcher 才會收到通知。
- 不可變的 observable:我們可以創建值的“不可變”版本(即使是嵌套屬性),除非系統在內部暫時將其“解禁”。這個機制可用于凍結 prop 傳遞或 Vuex 狀態樹以外的變化。
- 更好的調試功能:我們可以使用新的 renderTracked 和 renderTriggered 鉤子精確地跟蹤組件在什么時候以及為什么重新渲染。
(2)模板
模板方面沒有大的變更,只改了作用域插槽,2.x 的機制導致作用域插槽變了,父組件會重新渲染,而 3.0 把作用域插槽改成了函數的方式,這樣只會影響子組件的重新渲染,提升了渲染的性能。
同時,對于 render 函數的方面,vue3.0 也會進行一系列更改來方便習慣直接使用 api 來生成 vdom 。
(3)對象式的組件聲明方式
vue2.x 中的組件是通過聲明的方式傳入一系列 option,和 TypeScript 的結合需要通過一些裝飾器的方式來做,雖然能實現功能,但是比較麻煩。3.0 修改了組件的聲明方式,改成了類式的寫法,這樣使得和 TypeScript 的結合變得很容易。
此外,vue 的源碼也改用了 TypeScript 來寫。其實當代碼的功能復雜之后,必須有一個靜態類型系統來做一些輔助管理。現在 vue3.0 也全面改用 TypeScript 來重寫了,更是使得對外暴露的 api 更容易結合 TypeScript。靜態類型系統對于復雜代碼的維護確實很有必要。
(4)其它方面的更改
vue3.0 的改變是全面的,上面只涉及到主要的 3 個方面,還有一些其他的更改:
- 支持自定義渲染器,從而使得 weex 可以通過自定義渲染器的方式來擴展,而不是直接 fork 源碼來改的方式。
- 支持 Fragment(多個根節點)和 Protal(在 dom 其他部分渲染組建內容)組件,針對一些特殊的場景做了處理。
- 基于 treeshaking 優化,提供了更多的內置功能。
v-model 可以被用在自定義組件上嗎?如果可以,如何使用?
可以。v-model 實際上是一個語法糖,如:
<input v-model="searchText">
復制代碼
實際上相當于:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
復制代碼
用在自定義組件上也是同理:
<custom-input v-model="searchText">
復制代碼
相當于:
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
復制代碼
顯然,custom-input 與父組件的交互如下:
- 父組件將
searchText
變量傳入custom-input 組件,使用的 prop 名為value
; - custom-input 組件向父組件傳出名為
input
的事件,父組件將接收到的值賦值給searchText
;
所以,custom-input 組件的實現應該類似于這樣:
Vue.component('custom-input', {
props: ['value'],
template: ` <input v-bind:value="value" v-on:input="$emit('input', $event.target.value)" > `
})
復制代碼
Vue 中 computed 和 watch 有什么區別?
計算屬性 computed:
(1)支持緩存,只有依賴數據發生變化時,才會重新進行計算函數;
(2)計算屬性內不支持異步操作;
(3)計算屬性的函數中都有一個 get(默認具有,獲取計算屬性)和 set(手動添加,設置計算屬性)方法;
(4)計算屬性是自動監聽依賴值的變化,從而動態返回內容。
偵聽屬性 watch:
(1)不支持緩存,只要數據發生變化,就會執行偵聽函數;
(2)偵聽屬性內支持異步操作;
(3)偵聽屬性的值可以是一個對象,接收 handler 回調,deep,immediate 三個屬性;
(3)監聽是一個過程,在監聽的值變化時,可以觸發一個回調,并做一些其他事情。
Vue模版編譯原理知道嗎,能簡單說一下嗎?
簡單說,Vue的編譯過程就是將template
轉化為render
函數的過程。會經歷以下階段:
- 生成AST樹
- 優化
- codegen
首先解析模版,生成AST語法樹
(一種用JavaScript對象的形式來描述整個模板)。 使用大量的正則表達式對模板進行解析,遇到標簽、文本的時候都會執行對應的鉤子進行相關處理。
Vue的數據是響應式的,但其實模板中并不是所有的數據都是響應式的。有一些數據首次渲染后就不會再變化,對應的DOM也不會變化。那么優化過程就是深度遍歷AST樹,按照相關條件對樹節點進行標記。這些被標記的節點(靜態節點)我們就可以跳過對它們的比對
,對運行時的模板起到很大的優化作用。
編譯的最后一步是將優化后的AST樹轉換為可執行的代碼
。
keep-alive 使用場景和原理
keep-alive 是 Vue 內置的一個組件,可以實現組件緩存,當組件切換時不會對當前組件進行卸載。
- 常用的兩個屬性 include/exclude,允許組件有條件的進行緩存。
- 兩個生命周期 activated/deactivated,用來得知當前組件是否處于活躍狀態。
- keep-alive 的中還運用了 LRU(最近最少使用) 算法,選擇最近最久未使用的組件予以淘汰。
vue 中使用了哪些設計模式
1.工廠模式 - 傳入參數即可創建實例
虛擬 DOM 根據參數的不同返回基礎標簽的 Vnode 和組件 Vnode
2.單例模式 - 整個程序有且僅有一個實例
vuex 和 vue-router 的插件注冊方法 install 判斷如果系統存在實例就直接返回掉
3.發布-訂閱模式 (vue 事件機制)
4.觀察者模式 (響應式數據原理)
5.裝飾模式: (@裝飾器的用法)
6.策略模式 策略模式指對象有某個行為,但是在不同的場景中,該行為有不同的實現方案-比如選項的合并策略
v-show 與 v-if 有什么區別?
v-if 是真正的條件渲染,因為它會確保在切換過程中條件塊內的事件監聽器和子組件適當地被銷毀和重建;也是惰性的:如果在初始渲染時條件為假,則什么也不做——直到條件第一次變為真時,才會開始渲染條件塊。
v-show 就簡單得多——不管初始條件是什么,元素總是會被渲染,并且只是簡單地基于 CSS 的 “display” 屬性進行切換。
所以,v-if 適用于在運行時很少改變條件,不需要頻繁切換條件的場景;v-show 則適用于需要非常頻繁切換條件的場景。
Vue-router 路由有哪些模式?
一般有兩種模式:
(1)hash 模式:后面的 hash 值的變化,瀏覽器既不會向服務器發出請求,瀏覽器也不會刷新,每次 hash 值的變化會觸發 hashchange 事件。
(2)history 模式:利用了 HTML5 中新增的 pushState() 和 replaceState() 方法。這兩個方法應用于瀏覽器的歷史記錄棧,在當前已有的 back、forward、go 的基礎之上,它們提供了對歷史記錄進行修改的功能。只是當它們執行修改時,雖然改變了當前的 URL,但瀏覽器不會立即向后端發送請求。
Vue 模板編譯原理
Vue 的編譯過程就是將 template 轉化為 render 函數的過程 分為以下三步
第一步是將 模板字符串 轉換成 element ASTs(解析器)
第二步是對 AST 進行靜態節點標記,主要用來做虛擬DOM的渲染優化(優化器)
第三步是 使用 element ASTs 生成 render 函數代碼字符串(代碼生成器)
Vue 初始化頁面閃動問題如何解決?
出現該問題是因為在 Vue 代碼尚未被解析之前,尚無法控制頁面中 DOM 的顯示,所以會看見模板字符串等代碼。
解決方案是,在 css 代碼中添加 v-cloak 規則,同時在待編譯的標簽上添加 v-cloak 屬性:
[v-cloak] { display: none; }
<div v-cloak>
{{ message }}
</div>
談談對keep-alive的了解
keep-alive可以實現組件的緩存,當組件切換時不會對當前組件進行卸載。常用的2個屬性
include/exclude,2個生命周期
activated,
deactivated
nextTick 使用場景和原理
nextTick 中的回調是在下次 DOM 更新循環結束之后執行的延遲回調。在修改數據之后立即使用這個方法,獲取更新后的 DOM。主要思路就是采用微任務優先的方式調用異步方法去執行 nextTick 包裝的方法
相關代碼如下
let callbacks = [];
let pending = false;
function flushCallbacks() {
pending = false; //把標志還原為false
// 依次執行回調
for (let i = 0; i < callbacks.length; i++) {
callbacks[i]();
}
}
let timerFunc; //定義異步方法 采用優雅降級
if (typeof Promise !== "undefined") {
// 如果支持promise
const p = Promise.resolve();
timerFunc = () => {
p.then(flushCallbacks);
};
} else if (typeof MutationObserver !== "undefined") {
// MutationObserver 主要是監聽dom變化 也是一個異步方法
let counter = 1;
const observer = new MutationObserver(flushCallbacks);
const textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true,
});
timerFunc = () => {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
} else if (typeof setImmediate !== "undefined") {
// 如果前面都不支持 判斷setImmediate
timerFunc = () => {
setImmediate(flushCallbacks);
};
} else {
// 最后降級采用setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0);
};
}
export function nextTick(cb) {
// 除了渲染watcher 還有用戶自己手動調用的nextTick 一起被收集到數組
callbacks.push(cb);
if (!pending) {
// 如果多次調用nextTick 只會執行一次異步 等異步隊列清空之后再把標志變為false
pending = true;
timerFunc();
}
}
那vue中是如何檢測數組變化的呢?
數組就是使用object.defineProperty
重新定義數組的每一項,那能引起數組變化的方法我們都是知道的,pop
、push
、shift
、unshift
、splice
、sort
、reverse
這七種,只要這些方法執行改了數組內容,我就更新內容就好了,是不是很好理解。
- 是用來函數劫持的方式,重寫了數組方法,具體呢就是更改了數組的原型,更改成自己的,用戶調數組的一些方法的時候,走的就是自己的方法,然后通知視圖去更新。
- 數組里每一項可能是對象,那么我就是會對數組的每一項進行觀測,(且只有數組里的對象才能進行觀測,觀測過的也不會進行觀測)
vue3:改用proxy
,可直接監聽對象數組的變化。
虛擬 DOM 的優缺點?
優點:
- 保證性能下限: 框架的虛擬 DOM 需要適配任何上層 API 可能產生的操作,它的一些 DOM 操作的實現必須是普適的,所以它的性能并不是最優的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虛擬 DOM 至少可以保證在你不需要手動優化的情況下,依然可以提供還不錯的性能,即保證性能的下限;
- 無需手動操作 DOM: 我們不再需要手動去操作 DOM,只需要寫好 View-Model 的代碼邏輯,框架會根據虛擬 DOM 和 數據雙向綁定,幫我們以可預期的方式更新視圖,極大提高我們的開發效率;
- 跨平臺: 虛擬 DOM 本質上是 JavaScript 對象,而 DOM 與平臺強相關,相比之下虛擬 DOM 可以進行更方便地跨平臺操作,例如服務器渲染、weex 開發等等。
缺點:
- 無法進行極致優化: 雖然虛擬 DOM + 合理的優化,足以應對絕大部分應用的性能需求,但在一些性能要求極高的應用中虛擬 DOM 無法進行針對性的極致優化。
Vue 中的 key 到底有什么用?
key 是給每一個 vnode 的唯一 id,依靠 key,我們的 diff 操作可以更準確、更快速 (對于簡單列表頁渲染來說 diff 節點也更快,但會產生一些隱藏的副作用,比如可能不會產生過渡效果,或者在某些節點有綁定數據(表單)狀態,會出現狀態錯位。)
diff 算法的過程中,先會進行新舊節點的首尾交叉對比,當無法匹配的時候會用新節點的 key 與舊節點進行比對,從而找到相應舊節點.
更準確 : 因為帶 key 就不是就地復用了,在 sameNode 函數 a.key === b.key 對比中可以避免就地復用的情況。所以會更加準確,如果不加 key,會導致之前節點的狀態被保留下來,會產生一系列的 bug。
更快速 : key 的唯一性可以被 Map 數據結構充分利用,相比于遍歷查找的時間復雜度 O(n),Map 的時間復雜度僅僅為 O(1)
Vue中的key到底有什么用?
key
是為Vue中的vnode標記的唯一id,通過這個key,我們的diff操作可以更準確、更快速
diff算法的過程中,先會進行新舊節點的首尾交叉對比,當無法匹配的時候會用新節點的key
與舊節點進行比對,然后超出差異.
diff程可以概括為:oldCh和newCh各有兩個頭尾的變量StartIdx和EndIdx,它們的2個變量相互比較,一共有4種比較方式。如果4種比較都沒匹配,如果設置了key,就會用key進行比較,在比較的過程中,變量會往中間靠,一旦StartIdx>EndIdx表明oldCh和newCh至少有一個已經遍歷完了,就會結束比較,這四種比較方式就是首、尾、舊尾新頭、舊頭新尾.
- 準確: 如果不加
key
,那么vue會選擇復用節點(Vue的就地更新策略),導致之前節點的狀態被保留下來,會產生一系列的bug. - 快速: key的唯一性可以被Map數據結構充分利用,相比于遍歷查找的時間復雜度O(n),Map的時間復雜度僅僅為O(1).
寫過自定義指令嗎 原理是什么
指令本質上是裝飾器,是 vue 對 HTML 元素的擴展,給 HTML 元素增加自定義功能。vue 編譯 DOM 時,會找到指令對象,執行指令的相關方法。
自定義指令有五個生命周期(也叫鉤子函數),分別是 bind、inserted、update、componentUpdated、unbind
1. bind:只調用一次,指令第一次綁定到元素時調用。在這里可以進行一次性的初始化設置。
2. inserted:被綁定元素插入父節點時調用 (僅保證父節點存在,但不一定已被插入文檔中)。
3. update:被綁定于元素所在的模板更新時調用,而無論綁定值是否變化。通過比較更新前后的綁定值,可以忽略不必要的模板更新。
4. componentUpdated:被綁定元素所在模板完成一次更新周期時調用。
5. unbind:只調用一次,指令與元素解綁時調用。
原理
1.在生成 ast 語法樹時,遇到指令會給當前元素添加 directives 屬性
2.通過 genDirectives 生成指令代碼
3.在 patch 前將指令的鉤子提取到 cbs 中,在 patch 過程中調用對應的鉤子
4.當執行指令對應鉤子函數時,調用對應指令定義的方法
Vue為什么沒有類似于React中shouldComponentUpdate的生命周期?
考點: Vue的變化偵測原理
前置知識: 依賴收集、虛擬DOM、響應式系統
根本原因是Vue與React的變化偵測方式有所不同
React是pull的方式偵測變化,當React知道發生變化后,會使用Virtual Dom Diff進行差異檢測,但是很多組件實際上是肯定不會發生變化的,這個時候需要用shouldComponentUpdate進行手動操作來減少diff,從而提高程序整體的性能.
Vue是pull+push的方式偵測變化的,在一開始就知道那個組件發生了變化,因此在push的階段并不需要手動控制diff,而組件內部采用的diff方式實際上是可以引入類似于shouldComponentUpdate相關生命周期的,但是通常合理大小的組件不會有過量的diff,手動優化的價值有限,因此目前Vue并沒有考慮引入shouldComponentUpdate這種手動優化的生命周期.
computed 的實現原理
computed 本質是一個惰性求值的觀察者。
computed 內部實現了一個惰性的 watcher,也就是 computed watcher,computed watcher 不會立刻求值,同時持有一個 dep 實例。
其內部通過 this.dirty 屬性標記計算屬性是否需要重新求值。
當 computed 的依賴狀態發生改變時,就會通知這個惰性的 watcher,
computed watcher 通過 this.dep.subs.length 判斷有沒有訂閱者,
有的話,會重新計算,然后對比新舊值,如果變化了,會重新渲染。 (Vue 想確保不僅僅是計算屬性依賴的值發生變化,而是當計算屬性最終計算的值發生變化時才會觸發渲染 watcher 重新渲染,本質上是一種優化。)
沒有的話,僅僅把 this.dirty = true。 (當計算屬性依賴于其他數據時,屬性并不會立即重新計算,只有之后其他地方需要讀取屬性的時候,它才會真正計算,即具備 lazy(懶計算)特性。)