Vue3常見API:
- reactive:
reactive返回proxy對象,如果發現被訪問的屬性是引用類型,還會用reactive遞歸處理,屬性是可以被修改的。- shallowReactive:
shallowReactive返回proxy對象,和reactive的區別是只建立第一層為響應式,如果發現被訪問的屬性是引用類型也不會進行遞歸代理,屬性是可以被修改的。- readonly:
readonly返回proxy對象,如果發現被訪問的屬性是引用類型會進行遞歸處理,但是屬性是只讀的,不能修改??梢宰鰌rops傳遞給子組件使用。- shallowReadonly:
shallowReadonly返回proxy對象,shallowReadonly 僅對對象的第一次層屬性進行只讀處理,對于嵌套的對象或數組,它不會進行只讀保護。
Vue3 新特性有哪些?
1、優化響應式系統:
- 使用ES6的 Proxy配合Reflect 代替vue2的 Object.defineProperty ,不僅可以更高效地追蹤依賴關系,還解決了 Vue 2 中響應式無法追蹤數組長度變化和對象屬性添加的問題。
2、更好的支持 typeScript:
Vue3在源碼使用了 TypeScript,可以為開發者 提供更好的類型檢查和類型推斷。3、新增 Composition API:
- Composition API 提供了一種新的代碼組織和復用方式,它允許開發者將邏輯拆分成獨立的函數(稱為“組合函數”),并在setup中按需使用。這使得代碼更加模塊化,易于測試和維護,特別是在大型項目中。
4、新增組件:
- Fragment: template 模板可以有多個根元素。
- Teleport 傳送門,允許Teleport中的內容傳送到任意的 DOM 中。
- Suspense 用于處理異步組件的加載狀態,提供了更好的用戶體驗。
5、支持多個根節點
6、Tree-shaking:支持搖樹優化:
Vue3.x中的核心API都支持tree-shaking,這些API都是通過包引入的方式而不是直接掛載在vue原型上,只會對使用到的功能或特性進行打包(按需打包),這意味著更多的功能和更小的體積。7、diff算法的優化:
- vue2中虛擬dom進行全量對比
- Vue 3 的虛擬 DOM Diff 算法通過引入靜態標記(PatchFlag)和靜態提升等優化手段,顯著提高了渲染性能。PatchFlag 允許 Vue 在比較新舊節點時跳過未發生變化的靜態部分,只關注動態變化的部分。靜態提升則通過復用未發生變化的節點,減少了不必要的節點創建和銷毀操作。
8、移除了一些功能:
- 移除v-on的鍵盤修飾符,鼓勵開發者使用鍵盤事件(如 keyCode 或 key)來替代;
- 移除過濾器filter。
Vue3性能提升主要是通過哪幾方面體現的?
1、編譯階段優化
- diff算法優化
vue3在diff算法中相比vue2增加了靜態標記Patchflag,在編譯template模板時,給vnode添加的一個PatchFlag標識信息,這個標識信息可以反映哪些vnode具有動態綁定的屬性或子節點,在與上次的節點進行對比時候,Vue會利用patchFlag來高效地識別出哪些是需要更新的節點,并僅對這些節點進行對比和更新操作。這樣可以避免不必要的DOM操作,從而提升渲染性能。- 靜態提升
Vue3會對不參與更新的元素做靜態提升,只會在初始化的時候創建一次,重新渲染時直接復用。在初始化的時候會對該靜態元素打上特殊靜態標記,值為-1,表示靜態提升,(特殊標志是負整數表示永遠不會用于 Diff),做了靜態提升后,未參與更新的元素,被放置在render 函數外,每次渲染的時候直接復用即可。
沒做靜態提升之前,未參與更新的元素也在render函數內部,會重復創建階段。詳細:
- Vue 2在對比較新舊節點時,會檢查每個節點是否是靜態的,如果是靜態的會跳過這個節點及其子節點的比較。雖然會跳過靜態節點的比較,但是Vue2并沒有做靜態提升,靜態根節點還是在render函數內,所以這些靜態根節點每次都會被重新創建渲染。
- vue3在diff算法中相比vue2增加了靜態標記Patchflag,在編譯template模板時,給vnode添加的一個PatchFlag標識信息,這個標識信息可以反映哪些vnode具有動態綁定的屬性或子節點,在與上次的節點進行對比時候,Vue會利用patchFlag來高效地識別出哪些是需要更新的節點,并僅對這些節點進行對比和更新操作。對不參與更新的元素做靜態提升,只會在初始化的時候創建一次,重新渲染時直接復用。在初始化的時候會對該靜態元素打上特殊靜態標記,值為-1,表示靜態提升,這樣可以避免不必要的DOM操作,從而提升渲染性能。
2、源碼體積優化
- 相比Vue2,Vue3整體體積變小了,除了移出一些不常用的API,最重要的是優化了對 Tree shanking的支持。Vue3中的核心API都支持tree-shaking,這些API都需通過包引入的方式才能使用,只會對使用到的功能或特性進行打包(按需打包),這意味著更多的功能和更小的體積。
3、響應式系統優化
- vue2主要采用 object.defineProperty來劫持對象屬性,通過進行深度遍歷所有屬性,給每個屬性添加getter和setter,實現響應式攔截。
- vue3采用proxy重寫了響應式系統,因為proxy是對整個對象進行監聽的,所以無需遞歸遍歷對象的屬性。
- 可以監聽到屬性的添加、刪除操作。
- 可以監聽到數組的索引和數組length屬性的操作。
4、事件優化
- 在Vue2中,每次渲染時都會重新創建事件處理函數,即使是相同的事件處理邏輯。
- 在Vue3中,引入了緩存事件處理函數的概念,它會將事件處理函數在編譯階段緩存起來,重新渲染的時候直接復用。
// 靜態提升例子:
// Vue2的靜態節點
render(){
return createVNode("div", null, "Hello World")
}
// Vue3的靜態節點
const staticLifting = createVNode("div", null, "Hello World")
function render(){
// 直接使用 staticLifting 即可
}
<button @click="i++">plus</button>
// vue2
render(ctx){
return createVNode("button", {
onClick: function($event){
ctx.i++;
}
})
}
// vue3
render(ctx, _cache){
return createVNode("button", {
onClick: cache[0] || (cache[0] = ($event) => (ctx.i++))
})
}
Vue3和Vue2的區別?
1. 響應式數據的監聽方式不同:
- Vue2 的響應式數據監聽是采用ES5的Object.definePropert() 對數據進行劫持的;
- Vue3 中使用ES6的Proxy()對數據進行代理劫持。
- 相比Object.definePropert(),ES6的Proxy()有以下優點:
- Proxy代理監聽的是整個對象,不需要通過遞歸+遍歷為對象屬性設置getter和setter進行劫持,提高了性能;
- Proxy可以監聽到對象屬性的動態添加和刪除;
- 可以監聽到數組的索引和數組length屬性的操作;
- 如果對象的屬性也是對象的話,只有在被訪問的時候才會進行遞歸響應觀測,提高了性能;
2. Vue3新增了組合式(Composition API):
- Vue2是使用options API的,options API內部的邏輯點是碎片化的,一個功能邏輯會出現在不同的位置,通過定義methods,computed,watch,data等options API,共同處理頁面邏輯。
- Vue3新增了Composition API(組合API),同時仍然兼容Vue2中的Options API。組合式API,組件根據邏輯功能來組織代碼的,一個功能所定義的所有 API 會放在一起,通過函數分割復用代碼,將其封裝成一個hooks,hooks需要在setup函數中使用。
3. 定義數據變量和方法不同:
- vue2是在對應的選項中定義變量、方法、計算屬性等。在data選項中定義響應式數據,在method中定義方法。
- Vue3需要從vue中導出reactive、ref、computed等API,這些API需要在一個setup鉤子函數中使用,通過使用reactive、ref來定義響應式數據。
4. template模板支持:
- Vue3 template 支持多個根節點;
- Vue2 template 只能支持單個根節點。
5. 生命周期函數:
- vue2的生命周期是選項式的,,主要生命周期函數是beforeCreate、created、beforeMount、Mounted、beforeUpdate、updated、beforeDestory、destroyed;
- vue3的生命周期是組合式的,需要通過包引入的方式才可以時使用;
- vue3用setup鉤子取代了beforeCreate和created鉤子函數,setup函數在beforeCreate之前調用;
- vue3將beforeDestory和destroyed這兩個鉤子函數的名稱改為onBeforeUnmount和onUnmounted。
6. Vue實例的創建方式不同:
- Vue2是通過new一個Vue實例來創建Vue實例對象的;
- Vue3需要引入createApp方法,然后調用該方法創建Vue對象。
// Vue2
import Vue from 'vue';
import App from './App.vue'
new Vue({
render: h => h(App)
}).$mount('#app');
// Vue3
import {createApp} from 'vue';
import App from './App.vue'
createApp(App).mount('#app')
Options Api與Composition Api的區別?
代碼組織:
1、Options Api:選項式API內部的邏輯點是碎片化的,一個功能邏輯會出現在不同的位置,通過定義methods,computed,watch,data等選項API,共同處理頁面邏輯。當組件變得復雜時,會降低代碼的可讀性和可維護性。
2、Composition API(組合式API),使用 ref、reactive、computed、watch 等函數來替代 Options API 中的對應選項。組件根據邏輯功能來組織代碼,通過函數分割復用代碼,一個功能所定義的所有 API 會放在一起,這使得代碼更加集中和模塊化,易于閱讀和維護。( 更加的 高內聚,低耦合 )。邏輯復用:
- 在vue2.0中,是通過mixin來實現邏輯復用的,當使用多個mixin會存在兩個非常明顯的問題:命名沖突、數據來源不清晰
- Composition Api是通過函數復用,通過將相同邏輯功能的代碼封裝成一個函數,這些代碼片段可以包含狀態、計算屬性、方法等,可以在多個組件中重復使用,而不會引起命名沖突或數據來源不明確的問題。
總結:
- 在代碼組織和邏輯復用方面,Composition API是優于Options API
- 因為Composition API幾乎是函數,會有更好的類型推斷;
- Composition API 對 tree-shaking 更友好,從而生成更小的包;
- Composition API中見不到this的使用,減少了this指向不明的情況;
- 如果是小型組件,可以繼續使用Options API,也是十分友好的。
- 更好的測試,由于 Composition API 中的邏輯更加模塊化,它們也更易于進行單元測試。
Vue3.0里為什么要用 Proxy API 替代 defineProperty API ?
1、vue2是采用 Object.defineProperty來監聽對象的操作的,通過遍歷+遞歸的方式給每個屬性添加getter和setter進行劫持。
存在以下的問題:
- 檢測不到對象屬性的添加和刪除,Vue提供了
delete刪除,但是這需要手動調用,有額外的負擔;
- 監聽不到通過數組索引添加新屬性和數組length屬性的操作;
- 在初始化階段,需要對對象屬性進行遍歷監聽,如果是嵌套對象,即使沒有被訪問到,也會被遞歸監聽,當對象過深的時候會造成性能問題。
2、Proxy監聽的是整個對象的操作:
- Proxy 創建了一個對象的代理,這個代理可以攔截針對對象的任何操作(如讀取、賦值、函數調用等),而不僅僅是屬性訪問。因此,它可以更全面地追蹤對象的變化。
- Proxy不需要預先知道要監聽哪些屬性,它可以動態地處理對象的屬性添加和刪除。
- 可以監聽到數組的索引和數組length屬性的操作;
- 對于嵌套對象,只有在被訪問到的時候才會對其進行遞歸響應觀測,這有助于提高性能;
- Proxy提供了多達13種攔截方法,這些方法可以覆蓋對象的各種操作,提供了更大的靈活性和控制力。
Proxy 只會代理對象的第一層,那么 Vue3 又是怎樣處理這個問題的呢?
當訪問到深層嵌套對象的話,會觸發getter,會判斷當前 Reflect.get 的返回值是否是已經代理過的對象,如果是一個普通對象則再使用 reactive 方法做代理, 這樣就實現了深度觀測。
說說Vue 3.0中Treeshaking特性?舉例說明一下?
1、什么是Tree shaking?
Tree shaking 是一種通過清除多余代碼方式來優化項目打包體積的技術,專業術語叫 (Dead code elimination)簡單來講,就是在保持代碼運行結果不變的前提下,去除無用的代碼
2、Tree shaking原理?
Tree shaking是基于ES6模塊化方案 (import與exports),模塊之間的依賴關系是高度確定和靜態的,與運行狀態無關,可以進行可靠的靜態分析。在打包過程中 靜態分析 模塊之間的導入導出,確定哪些導出的模塊沒有被引用并打上標記,最終將其刪除。
- 在Vue2中,很多API 都是掛載在Vue原型上的,程序無法檢測到該對象的哪些屬性在代碼中被使用到。
- 而Vue3優化了對tree shaking的支持,所有的核心API都支持Tree Shaking, 需要通過包引入的方式才可以使用。如果您不使用其某些功能,它們將不會包含在您的基礎包中。
Tree shaking無非就是做了兩件事:
- 編譯階段利用ES6 Module判斷哪些模塊已經加載
- 判斷哪些模塊和變量未被使用或者引用,進而刪除對應代碼
3、Tree shaking作用(好處)?- 減少程序體積(更小);
- 減少程序執行時間(更快);
- 便于將來對程序架構進行優化(更友好)。
watch 和 watchEffect 的區別?
watch 和 watchEffect 都是監聽器,watchEffect 是一個副作用函數。
它們之間的區別有:
- watch :既要指明監視的數據源,也要指明監視的回調。
- watchEffect 可以自動監聽數據源作為依賴。不用指明監視哪個數據,監視的回調中用到哪個數據,那就監視哪個數據。
- watch 可以訪問改變之前和之后的值,watchEffect 只能獲取改變后的值。
- watch 依賴的屬性改變后才會執行,初始化的時候不會立即執行,但是可以通過 watch 的配置項 immediate 來改變;而 watchEffect 初始化的時候會立即執行。
watchEffect有點像 computed :- computed 注重的計算出來的值(回調函數的返回值), 所以必須要寫返回值。
- watcheffect注重的是過程(回調函數的函數體),所以不用寫返回值。
ref與reactive的區別?
- ref與reactive 是 Vue3 新推出的主要 API 之一,主要用于創建響應式數據。
- ref 函數創建的響應式數據,在模板中可以直接被使用,在 JS 中需要通過 .value 的形式才能使用。
- ref 函數可以接收原始數據類型與引用數據類型。
- reactive 函數只能接收引用數據類型。
- ref 底層還是使用 reactive 來做,ref 是在 reactive 上在進行封裝的,增強了其能力,使它支持了對原始數據類型的處理。
- 在 Vue3 中 reactive 能做的,ref 也能做,reactive 不能做的,ref 也能做。
- ref還能獲取組件的實例。
setup() 函數特性:
- setup 函數有兩個參數:(props、context(包含attrs、slots、emit));
- setup函數在 生命周期 beforeCreate 和 created 兩個鉤子函數之前執行;
- setup函數中不能使用this,因為 setup 函數執行時,組件實例尚未被創建,Vue 為了避免我們錯誤的使用,直接將 setup函數中的this修改成了undefined;
- 與模板一起使用:需要返回一個對象,將需要在模板中使用的變量和方法return 出去,不然無法在模板中使用
- setup函數只能是同步的不能是異步的。
- setup函數默認暴露組件內部的屬性和方法。
- setup 函數中的 props 是響應式的,它是一個reactive。不能使用ES6的解構來結構props,因為它會消除 props 的響應性。如果需要解構 props,可以通過使用 setup 函數中的 toRefs 來完成解構操作:
import {toRefs} from 'vue'
setup(props) {
const { name } = toRefs(props);
console.log(name.value);
onMounted(() => {
console.log('name: ' + props.name);
})
}
script setup 是干啥的?
scrtpt setup 是 vue3.2 的語法糖,簡化了組合式 API 的寫法,并且運行性能更好。
script setup 語法糖的特點:
- 屬性和方法無需返回,可以直接在template模板中使用。
- 引入組件的時候,會自動注冊,無需通過 components 手動注冊。
- 使用 defineProps 接收父組件傳遞的值、defineEmits 定義自定義事件。
- 使用 useAttrs 獲取額外沒有在props配置的屬性,useSlots 獲取插槽。
- 默認不會對外暴露任何屬性方法,內部自動調用exporse方法,如果有需要可使用 defineExpose 指定暴露的屬性、方法。
- defineOptions可以關閉屬性透傳和定義組件name值。
export const enum PatchFlags {
// 表示vnode具有動態textContent的元素
TEXT = 1,
// 表示vnode具有動態的class
CLASS = 1 << 1,
// 表示具有動態的style
STYLE = 1 << 2,
// 表示具有動態的非class和style的props
PROPS = 1 << 3,
// 表示props具有動態的key,與CLASS、STYLE、PROPS沖突
FULL_PROPS = 1 << 4,
// 表示有監聽事件(在同構期間需要添加)
HYDRATE_EVENTS = 1 << 5,
// 表示vnode是個children順序不會改變的fragment
STABLE_FRAGMENT = 1 << 6,
// 表示children帶有key的fragment
KEYED_FRAGMENT = 1 << 7,
// 表示children沒有key的fragment
UNKEYED_FRAGMENT = 1 << 8,
// 表示vnode只需要非props的patch。例如只有標簽中只有ref或指令
NEED_PATCH = 1 << 9,
// 表示vnode存在動態的插槽。例如動態的插槽名
DYNAMIC_SLOTS = 1 << 10,
// 表示用戶在模板的根級別存在注釋而創建的片段,這是一個僅用于開發的標志,因為注釋在生產中被剝離
DEV_ROOT_FRAGMENT = 1 << 11,
// 以下都是一些特殊的flag,它們不能使用位運算進行匹配
// 表示vnode經過靜態提升
HOISTED = -1,
// diff算法應該退出優化模式
BAIL = -2
}
vue3 reactive響應式原理:
- vue3 響應式主要是采用發布者訂閱者模式 + es6的 Proxy 代理實現的。
- 首先判斷目標對象是否是一個對象,如果不是,直接警告提示。
- 判斷目標對象有沒有被 Proxy 代理過,如果已經被代理過了直接返回目標對象。
- 否則調用createReactiveObject方法對目標對象進行 Proxy 代理,設置getter和setter進行攔截。當 Proxy 對象中的屬性被訪問的時候,會觸發getter,此時會去獲取值,如果該值是對象且沒有被Proxy代理過的話,會遞歸調用reactive去實現對其進行 Proxy 代理。在getter中會調用track收集effect;當 對象的屬性被修改或者新增了屬性的是,會調用trigger去通過訂閱該屬性的effect做出相應的更新;
- track方法的作用就是收集effect,收集的effect會保存在全局WeakMap結構的targetMap對象中,其key值是需要代理的目標對象,value值是也是一個depsMap對象,它是一個 Map 結構的對象,是用來保存代理對象的key值所對應的所有effect,所有effect都是存儲在Set結構的集合中的。
- trigger方法主要是通知屬性對應的所有effect去更新,會根據代理對象target去targetMap中查找代理對象所對應的depsMap,然后根據訪問的代理對象的key值去depsMap查找該key的所對應的effect集合,然后遍歷去執行每個effect里的更新方法,進而做出相應的更新操作。
- effect是一個包裝函數,接收一個副作用函數,它通常代表了一個需要響應數據變化的操作。當屬性被訪問的時候,會觸發getter,此時會將當前激活的activeEffect收集到當前屬性的effect收集器中,待收到數據發生變化的消息時,會調用自身對應配置下的更新方法重新進行更新。
Vue3 ref原理:
ref利用了ES6的類的屬性訪問器原理,它有一個value屬性,用于保存ref的值,在構造函數被創建的時候會在constructor獲取初始值,如果值是對象的話,會使用reactive進行響應代理。value被設置了get和set進行監聽,當value被訪問的時候會觸發get,在get里調用track方法進行依賴的收集。當對value進行賦值的時候會觸發set,在set里會調用trigger方法通知所有的依賴該屬性的方法也就是所有的effect進行更新。如果修改的值是一個對象的話,會調用reactive來對其進行響應式處理。
為什么 ref 類型數據,必須要通過 .value 訪問值呢?
a. 因為 ref 既可以處理基本數據類型的數據,也可以處理對象類型的數據,但是基本數據類型的數據無法通過 proxy 進行代理。
b. 而 vue 結合ES6的類的屬性訪問器原理,通過 get value() 和 set value() 定義了兩個屬性函數,通過主動觸發這兩個函數的形式來進行依賴收集和依賴觸發。
c. 所以我們必須通過 .value 來保證數據響應式。
computed的原理:
- 計算屬性有函數式寫法和對象寫法,當用戶傳入的是一個函數的時候,默認使用的是getter只讀操作;當用戶傳入的是一個對象時,用戶可以設置getter,setter,此時計算屬性具備可讀可寫的能力。
- 在vue3中實現計算屬性,第一步就是處理、收集用戶設置的getter和setter;
- 然后實例化一個計算屬性的類,在類的構造器中,會實例化一個effect,這個effect接收用戶的設置的getter和一個調度函數,用于計算計算屬性的值和更新計算屬性的_dirty屬性。計算屬性的effect使用的是lazy配置,在初始化的時候是不會立即執行去獲取值的,因為要考慮到計算屬性是否有被使用。
- 計算屬性類中有一個_dirty屬性,默認為true,用于判斷計算屬性是否需要重新計算;還有一個_value屬性,利用ES6類的屬性訪問器原理,被設置了get和set進行監聽,當計算屬性被修改時,會觸發set,在set中只是簡單的執行用戶設置的setter;當計算屬性value被訪問的時候會觸發get,在get里會調用effect的run方法也就是用戶設置的getter去獲取值,此時會訪問依賴屬性的值,依賴的屬性會將當前活躍的計算屬性的effect收集起來。在get中還會將_dirty設置為false,如果依賴屬性沒有發生變化是不需要重新計算值的。
- 當依賴的屬性發生變化的時候,會去遍歷該屬性所有的effect,此時會執行計算屬性effect的調度函數,將_dirty設置為true,當計算屬性再次被訪問的時候,此時的_dirty為true,會重新計算值。