前端面試之vue2和vue3的區(qū)別有哪些
一、Vue3 與 Vue2 區(qū)別詳述
1. 生命周期
對于生命周期來說,整體上變化不大,只是大部分生命周期鉤子名稱上 + “on”,功能上是類似的。不過有一點(diǎn)需要注意,Vue3 在組合式API(Composition API,下面展開)中使用生命周期鉤子時需要先引入,而 Vue2 在選項(xiàng)API(Options API)中可以直接調(diào)用生命周期鉤子,如下所示。
// vue3 <script setup> import { onMounted } from 'vue'; // 使用前需引入生命周期鉤子 onMounted(() => { // ... }); // 可將不同的邏輯拆開成多個onMounted,依然按順序執(zhí)行,不會被覆蓋 onMounted(() => { // ... }); </script> // vue2 <script> export default { mounted() { // 直接調(diào)用生命周期鉤子 // ... }, } </script>
常用生命周期對比如下表所示。
vue2 | vue3 |
---|---|
beforeCreate | |
created | |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
Tips: setup 是圍繞 beforeCreate 和 created 生命周期鉤子運(yùn)行的,所以不需要顯式地去定義。
2. 多根節(jié)點(diǎn)
熟悉 Vue2 的朋友應(yīng)該清楚,在模板中如果使用多個根節(jié)點(diǎn)時會報錯,如下所示。
// vue2中在template里存在多個根節(jié)點(diǎn)會報錯 <template> <header></header> <main></main> <footer></footer> </template> // 只能存在一個根節(jié)點(diǎn),需要用一個<div>來包裹著 <template> <div> <header></header> <main></main> <footer></footer> </div> </template>
但是,Vue3 支持多個根節(jié)點(diǎn),也就是 fragment。即以下多根節(jié)點(diǎn)的寫法是被允許的。
<template> <header></header> <main></main> <footer></footer> </template>
3. Composition API
Vue2 是選項(xiàng)API(Options API),一個邏輯會散亂在文件不同位置(data、props、computed、watch、生命周期鉤子等),導(dǎo)致代碼的可讀性變差。當(dāng)需要修改某個邏輯時,需要上下來回跳轉(zhuǎn)文件位置。
Vue3 組合式API(Composition API)則很好地解決了這個問題,可將同一邏輯的內(nèi)容寫到一起,增強(qiáng)了代碼的可讀性、內(nèi)聚性,其還提供了較為完美的邏輯復(fù)用性方案。
4. 異步組件(Suspense)
Vue3 提供 Suspense 組件,允許程序在等待異步組件加載完成前渲染兜底的內(nèi)容,如 loading ,使用戶的體驗(yàn)更平滑。使用它,需在模板中聲明,并包括兩個命名插槽:default 和 fallback。Suspense 確保加載完異步內(nèi)容時顯示默認(rèn)插槽,并將 fallback 插槽用作加載狀態(tài)。
<tempalte> <suspense> <template #default> <List /> </template> <template #fallback> <div> Loading... </div> </template> </suspense> </template>
在 List 組件(有可能是異步組件,也有可能是組件內(nèi)部處理邏輯或查找操作過多導(dǎo)致加載過慢等)未加載完成前,顯示 Loading...(即 fallback 插槽內(nèi)容),加載完成時顯示自身(即 default 插槽內(nèi)容)。
5. Teleport
Vue3 提供 Teleport 組件可將部分 DOM 移動到 Vue app 之外的位置。比如項(xiàng)目中常見的 Dialog 彈窗。參考:前端vue面試題詳細(xì)解答
<button @click="dialogVisible = true">顯示彈窗</button> <teleport to="body"> <div class="dialog" v-if="dialogVisible"> 我是彈窗,我直接移動到了body標(biāo)簽下 </div> </teleport>
6. 響應(yīng)式原理
Vue2 響應(yīng)式原理基礎(chǔ)是 Object.defineProperty;Vue3 響應(yīng)式原理基礎(chǔ)是 Proxy。
- Object.defineProperty
基本用法:直接在一個對象上定義新的屬性或修改現(xiàn)有的屬性,并返回對象。
let obj = {}; let name = 'leo'; Object.defineProperty(obj, 'name', { enumerable: true, // 可枚舉(是否可通過 for...in 或 Object.keys() 進(jìn)行訪問) configurable: true, // 可配置(是否可使用 delete 刪除,是否可再次設(shè)置屬性) // value: '', // 任意類型的值,默認(rèn)undefined // writable: true, // 可重寫 get() { return name; }, set(value) { name = value; } });
Tips: writable
和 value
與 getter
和 setter
不共存。
搬運(yùn) Vue2 核心源碼,略刪減。
function defineReactive(obj, key, val) { // 一 key 一個 dep const dep = new Dep() // 獲取 key 的屬性描述符,發(fā)現(xiàn)它是不可配置對象的話直接 return const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // 獲取 getter 和 setter,并獲取 val 值 const getter = property && property.get const setter = property && property.set if((!getter || setter) && arguments.length === 2) { val = obj[key] } // 遞歸處理,保證對象中所有 key 被觀察 let childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, // get 劫持 obj[key] 的 進(jìn)行依賴收集 get: function reactiveGetter() { const value = getter ? getter.call(obj) : val if(Dep.target) { // 依賴收集 dep.depend() if(childOb) { // 針對嵌套對象,依賴收集 childOb.dep.depend() // 觸發(fā)數(shù)組響應(yīng)式 if(Array.isArray(value)) { dependArray(value) } } } } return value }) // set 派發(fā)更新 obj[key] set: function reactiveSetter(newVal) { ... if(setter) { setter.call(obj, newVal) } else { val = newVal } // 新值設(shè)置響應(yīng)式 childOb = observe(val) // 依賴通知更新 dep.notify() } }
那 Vue3 為何會拋棄它呢?那肯定是因?yàn)樗嬖谀承┚窒扌浴?/p>
主要原因:無法監(jiān)聽對象或數(shù)組新增、刪除的元素。
Vue2 相應(yīng)解決方案:針對常用數(shù)組原型方法push、pop、shift、unshift、splice、sort、reverse進(jìn)行了hack處理;提供Vue.set監(jiān)聽對象/數(shù)組新增屬性。對象的新增/刪除響應(yīng),還可以new個新對象,新增則合并新屬性和舊對象;刪除則將刪除屬性后的對象深拷貝給新對象。
- Proxy
Proxy 是 ES6 新特性,通過第2個參數(shù) handler 攔截目標(biāo)對象的行為。相較于 Object.defineProperty 提供語言全范圍的響應(yīng)能力,消除了局限性。
局限性:
(1)、對象/數(shù)組的新增、刪除
(2)、監(jiān)測 .length 修改
(3)、Map、Set、WeakMap、WeakSet 的支持
基本用法:創(chuàng)建對象的代理,從而實(shí)現(xiàn)基本操作的攔截和自定義操作。
let handler = { get(obj, prop) { return prop in obj ? obj[prop] : ''; }, set() { // ... }, ... };
搬運(yùn) vue3 的源碼 reactive.ts 文件。
function createReactiveObject(target, isReadOnly, baseHandlers, collectionHandlers, proxyMap) { ... // collectionHandlers: 處理Map、Set、WeakMap、WeakSet // baseHandlers: 處理數(shù)組、對象 const proxy = new Proxy( target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers ) proxyMap.set(target, proxy) return proxy }
7. 虛擬DOM
Vue3 相比于 Vue2,虛擬DOM上增加 patchFlag 字段。我們借助Vue3 Template Explorer來看。
<div id="app"> <h1>vue3虛擬DOM講解</h1> <p>今天天氣真不錯</p> <div>{{name}}</div> </div>
渲染函數(shù)如下所示。
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from vue const _withScopeId = n => (_pushScopeId(scope-id),n=n(),_popScopeId(),n) const _hoisted_1 = { id: app } const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(h1, null, vue3虛擬DOM講解, -1 /* HOISTED */)) const _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(p, null, 今天天氣真不錯, -1 /* HOISTED */)) export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock(div, _hoisted_1, [ _hoisted_2, _hoisted_3, _createElementVNode(div, null, _toDisplayString(_ctx.name), 1 /* TEXT */) ])) }
注意第3個_createElementVNode的第4個參數(shù)即 patchFlag 字段類型。
字段類型情況:1 代表節(jié)點(diǎn)為動態(tài)文本節(jié)點(diǎn),那在 diff 過程中,只需比對文本對容,無需關(guān)注 class、style等。除此之外,發(fā)現(xiàn)所有的靜態(tài)節(jié)點(diǎn)(HOISTED 為 -1),都保存為一個變量進(jìn)行靜態(tài)提升,可在重新渲染時直接引用,無需重新創(chuàng)建。
// patchFlags 字段類型列舉 export const enum PatchFlags { TEXT = 1, // 動態(tài)文本內(nèi)容 CLASS = 1 << 1, // 動態(tài)類名 STYLE = 1 << 2, // 動態(tài)樣式 PROPS = 1 << 3, // 動態(tài)屬性,不包含類名和樣式 FULL_PROPS = 1 << 4, // 具有動態(tài) key 屬性,當(dāng) key 改變,需要進(jìn)行完整的 diff 比較 HYDRATE_EVENTS = 1 << 5, // 帶有監(jiān)聽事件的節(jié)點(diǎn) STABLE_FRAGMENT = 1 << 6, // 不會改變子節(jié)點(diǎn)順序的 fragment KEYED_FRAGMENT = 1 << 7, // 帶有 key 屬性的 fragment 或部分子節(jié)點(diǎn) UNKEYED_FRAGMENT = 1 << 8, // 子節(jié)點(diǎn)沒有 key 的fragment NEED_PATCH = 1 << 9, // 只會進(jìn)行非 props 的比較 DYNAMIC_SLOTS = 1 << 10, // 動態(tài)的插槽 HOISTED = -1, // 靜態(tài)節(jié)點(diǎn),diff階段忽略其子節(jié)點(diǎn) BAIL = -2 // 代表 diff 應(yīng)該結(jié)束 }
8. 事件緩存
Vue3 的cacheHandler
可在第一次渲染后緩存我們的事件。相比于 Vue2 無需每次渲染都傳遞一個新函數(shù)。加一個 click 事件。
<div id="app"> <h1>vue3事件緩存講解</h1> <p>今天天氣真不錯</p> <div>{{name}}</div> <span onCLick=() => {}><span> </div>
渲染函數(shù)如下所示。
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from vue const _withScopeId = n => (_pushScopeId(scope-id),n=n(),_popScopeId(),n) const _hoisted_1 = { id: app } const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(h1, null, vue3事件緩存講解, -1 /* HOISTED */)) const _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(p, null, 今天天氣真不錯, -1 /* HOISTED */)) const _hoisted_4 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(span, { onCLick: () => {} }, [ /*#__PURE__*/_createElementVNode(span) ], -1 /* HOISTED */)) export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock(div, _hoisted_1, [ _hoisted_2, _hoisted_3, _createElementVNode(div, null, _toDisplayString(_ctx.name), 1 /* TEXT */), _hoisted_4 ])) }
觀察以上渲染函數(shù),你會發(fā)現(xiàn) click 事件節(jié)點(diǎn)為靜態(tài)節(jié)點(diǎn)(HOISTED 為 -1),即不需要每次重新渲染。
9. Diff算法優(yōu)化
搬運(yùn) Vue3 patchChildren 源碼。結(jié)合上文與源碼,patchFlag 幫助 diff 時區(qū)分靜態(tài)節(jié)點(diǎn),以及不同類型的動態(tài)節(jié)點(diǎn)。一定程度地減少節(jié)點(diǎn)本身及其屬性的比對。
function patchChildren(n1, n2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized) { // 獲取新老孩子節(jié)點(diǎn) const c1 = n1 && n1.children const c2 = n2.children const prevShapeFlag = n1 ? n1.shapeFlag : 0 const { patchFlag, shapeFlag } = n2 // 處理 patchFlag 大于 0 if(patchFlag > 0) { if(patchFlag && PatchFlags.KEYED_FRAGMENT) { // 存在 key patchKeyedChildren() return } els if(patchFlag && PatchFlags.UNKEYED_FRAGMENT) { // 不存在 key patchUnkeyedChildren() return } } // 匹配是文本節(jié)點(diǎn)(靜態(tài)):移除老節(jié)點(diǎn),設(shè)置文本節(jié)點(diǎn) if(shapeFlag && ShapeFlags.TEXT_CHILDREN) { if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { unmountChildren(c1 as VNode[], parentComponent, parentSuspense) } if (c2 !== c1) { hostSetElementText(container, c2 as string) } } else { // 匹配新老 Vnode 是數(shù)組,則全量比較;否則移除當(dāng)前所有的節(jié)點(diǎn) if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { patchKeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense,...) } else { unmountChildren(c1 as VNode[], parentComponent, parentSuspense, true) } } else { if(prevShapeFlag & ShapeFlags.TEXT_CHILDREN) { hostSetElementText(container, '') } if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { mountChildren(c2 as VNodeArrayChildren, container,anchor,parentComponent,...) } } } }
patchUnkeyedChildren 源碼如下所示。
function patchUnkeyedChildren(c1, c2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized) { c1 = c1 || EMPTY_ARR c2 = c2 || EMPTY_ARR const oldLength = c1.length const newLength = c2.length const commonLength = Math.min(oldLength, newLength) let i for(i = 0; i < commonLength; i++) { // 如果新 Vnode 已經(jīng)掛載,則直接 clone 一份,否則新建一個節(jié)點(diǎn) const nextChild = (c2[i] = optimized ? cloneIfMounted(c2[i] as Vnode)) : normalizeVnode(c2[i]) patch() } if(oldLength > newLength) { // 移除多余的節(jié)點(diǎn) unmountedChildren() } else { // 創(chuàng)建新的節(jié)點(diǎn) mountChildren() } }
10. 打包優(yōu)化
Tree-shaking:模塊打包 webpack、rollup 等中的概念。移除 JavaScript 上下文中未引用的代碼。主要依賴于 import 和 export 語句,用來檢測代碼模塊是否被導(dǎo)出、導(dǎo)入,且被 JavaScript 文件使用。
以 nextTick 為例子,在 Vue2 中,全局API暴露在Vue實(shí)例上,即使未使用,也無法通過 tree-shaking 進(jìn)行消除。
import Vue from 'vue'; Vue.nextTick(() => { // 一些和DOM有關(guān)的東西 });
Vue3 中針對全局和內(nèi)部的API進(jìn)行了重構(gòu),并考慮到 tree-shaking 的支持。因此,全局API現(xiàn)在只能作為ES模塊構(gòu)建的命名導(dǎo)出進(jìn)行訪問。
import { nextTick } from 'vue'; // 顯式導(dǎo)入 nextTick(() => { // 一些和DOM有關(guān)的東西 });
通過這一更改,只要模塊綁定器支持 tree-shaking,則Vue應(yīng)用程序中未使用的 api 將從最終的捆綁包中消除,獲得最佳文件大小。
受此更改影響的全局API如下所示。
- Vue.nextTick
- Vue.observable (用 Vue.reactive 替換)
- Vue.version
- Vue.compile (僅全構(gòu)建)
- Vue.set (僅兼容構(gòu)建)
- Vue.delete (僅兼容構(gòu)建)
內(nèi)部API也有諸如 transition、v-model 等標(biāo)簽或者指令被命名導(dǎo)出。只有在程序真正使用才會被捆綁打包。Vue3 將所有運(yùn)行功能打包也只有約22.5kb,比 Vue2 輕量很多。
11. TypeScript支持
Vue3 由 TypeScript 重寫,相對于 Vue2 有更好的 TypeScript 支持。
- Vue2 Options API 中 option 是個簡單對象,而 TypeScript 是一種類型系統(tǒng),面向?qū)ο蟮恼Z法,不是特別匹配。
- Vue2 需要vue-class-component強(qiáng)化vue原生組件,也需要vue-property-decorator增加更多結(jié)合Vue特性的裝飾器,寫法比較繁瑣。
二、Options API 與 Composition API
Vue 組件可以用兩種不同的 API 風(fēng)格編寫:Options API 和 Composition API。
1. Options API
使用 Options API,我們使用選項(xiàng)對象定義組件的邏輯,例如data、methods和mounted。由選項(xiàng)定義的屬性在 this 內(nèi)部函數(shù)中公開,指向組件實(shí)例,如下所示。
<template> <button @click="increment">count is: {{ count }}</button> </template> <script> export default { data() { return { count: 0 } }, methods: { increment() { this.count++; } }, mounted() { console.log(`The initial count is ${this.count}.`); }} </script>
2. Composition API
使用 Composition API,我們使用導(dǎo)入的 API 函數(shù)定義組件的邏輯。在 SFC 中,Composition API 通常使用
<template> <button @click="increment">Count is: {{ count }}</button> </template> <script setup> import { ref, onMounted } from 'vue'; const count = ref(0); function increment() { count.value++;} onMounted(() => { console.log(`The initial count is ${count.value}.`);}) </script>
以上就是前端面試之vue2和vue3的區(qū)別有哪些的詳細(xì)內(nèi)容,更多關(guān)于前端面試vue2 vue3區(qū)別的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue element插件this.$confirm用法及說明(取消也可以發(fā)請求)
這篇文章主要介紹了vue element插件this.$confirm用法及說明(取消也可以發(fā)請求),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10vue?如何刪除數(shù)組中的某一條數(shù)據(jù)
這篇文章主要介紹了vue?如何刪除數(shù)組中的某一條數(shù)據(jù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08vue-simple-uploader上傳成功之后的response獲取代碼
這篇文章主要介紹了vue-simple-uploader上傳成功之后的response獲取代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧~2020-09-09Vue實(shí)現(xiàn)自定義視頻和圖片上傳的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何通過Vue實(shí)現(xiàn)自定義視頻和圖片上傳的功能,文中的示例代碼簡潔易懂,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-04-04Vue Element前端應(yīng)用開發(fā)之用戶管理模塊的處理
本篇隨筆以權(quán)限管理模塊中的用戶管理為媒介,進(jìn)行相關(guān)功能的介紹和界面設(shè)計(jì)的處理。2021-05-05vue element-ui使用required進(jìn)行表單校驗(yàn)時自定義提示語問題
這篇文章主要介紹了vue element-ui使用required進(jìn)行表單校驗(yàn)時自定義提示語問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05vue項(xiàng)目實(shí)現(xiàn)搜索內(nèi)容變紅色顯示
這篇文章主要為大家介紹了vue項(xiàng)目實(shí)現(xiàn)搜索內(nèi)容變紅色顯示,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12