该内容主要整理关于 Vue3.0 的相关面试题,关于 Vue 2 的相关面试题请移步至:Vue 全家桶篇,其他内容面试题请移步至 最新最全的前端面试题集锦 查看。
目录
- 1. Vue3.0 里为什么要用 `Proxy` API 替代 `defineProperty` API?—— 响应式优化(高频,重点!!!)
- 2. Vue3.0 编译做了哪些优化?(底层,源码)
- 3. Vue3.0 新特性 —— 为什么要新增 `Composition` API,它能解决什么问题?
- 4. Vue3.0 新特性 —— `Composition` API 与 React.js 中 `Hooks` 的异同点?(难点问题)
- 5. Vue3.0 是如何变得更快的?(底层,源码)
- 6. Vue3.0 性能提升主要是通过哪几方面体现的?
- 7. Vue3.0 所采用的 `Composition` Api 与 Vue2.x 使用的 `Options` Api 有什么区别?
- 8. Vue3.0 响应式系统的实现原理?
1. Vue3.0 里为什么要用 Proxy
API 替代 defineProperty
API?—— 响应式优化(高频,重点!!!)
这是在面试中问的最多的一个问题,无论是大厂还是中小型公司,都喜欢问,也是Vue更新的重点。
defineProperty
API 的局限性最大原因是它只能针对单例属性做监听。- Vue2 中的响应式实现正是基于
defineProperty
中的descriptor
,对data
中的属性做了遍历 + 递归,为每个属性设置了getter
、setter
。 - 这也就是为什么 Vue 只能对
data
中预定义过的属性做出响应的原因,在 Vue 中使用下标的方式直接修改属性的值或者添加一个预先不存在的对象属性是无法做到setter
监听的,这是defineProperty
的局限性。
- Vue2 中的响应式实现正是基于
Proxy
API 的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作, 这就完全可以代理所有属性,将会带来很大的性能提升和更优的代码。Proxy
可以理解成,在目标对象之前架设一层 “拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
- 响应式是惰性的
- 在 Vue2 中,对于一个深层属性嵌套的对象,要劫持它内部深层次的变化,就需要递归遍历这个对象,执行
Object.defineProperty
把每一层对象数据都变成响应式的,这无疑会有很大的性能消耗。 - 在 Vue3 中,使用
Proxy
API 并不能监听到对象内部深层次的属性变化,因此它的处理方式是在getter
中去递归响应式,这样的好处是真正访问到的内部属性才会变成响应式,简单的可以说是按需实现响应式,减少性能消耗。 - 基础用法:
let datas = { num: 0 } let proxy = new Proxy(datas, { get(target, property) { return target[property] }, set(target, property, value) { target[property] += value } })
- 在 Vue2 中,对于一个深层属性嵌套的对象,要劫持它内部深层次的变化,就需要递归遍历这个对象,执行
2. Vue3.0 编译做了哪些优化?(底层,源码)
a. 生成 Block tree
- Vue2 的数据更新并触发重新渲染的粒度是组件级的,单个组件内部 需要遍历该组件的整个
VNode
树。
**在 2.0 里,渲染效率的快慢与组件大小成正相关:组件越大,渲染效率越慢。**并且,对于一些静态节点,又无数据更新,这些遍历都是性能浪费。 - Vue3 做到了通过编译阶段对静态模板的分析,编译生成了
Block tree
。
Block tree
是一个将模版基于动态节点指令切割的嵌套区块,每个 区块内部的节点结构是固定的,每个区块只需要追踪自身包含的动态节点。
所以,在3.0里,渲染效率不再与模板大小成正相关,而是与模板中动态节点的数量成正相关。
b. slot
编译优化
- Vue2 中,如果有一个组件传入了
slot
,那么每次父组件更新的时候,会强制使子组件update
,造成性能的浪费。 - Vue3 优化了
slot
的生成,使得非动态slot
中属性的更新只会触发子组件的更新。
动态slot
指的是在slot
上面使用v-if
,v-for
,动态slot
名字等会导致slot
产生运行时动态变化但是又无法被子组件track
(跟踪) 的操作。
c. diff
算法优化
-
Vue2 的
diff
算法叫做全量比较,顾名思义,就是当数据改变的时候,会从头到尾的进行vDom
对比,即使有些内容是永恒固定不变的。
-
Vue3 的
diff
算法有个叫静态标记(PatchFlag
)的小玩意,
啥是静态标记呢?
简单点说,就是如果你的内容会变,我会给你一个flag
,下次数据更新的时候我直接来对比你,我就不对比那些没有标记的了。
看例子:export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("p", null, "'HelloWorld'"), _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */) // 上面这个1就是静态标记 ])) }
那么肯定有人又会问了,为啥是个1呢?
TEXT = 1 // 动态文本节点 CLASS=1<<1,1 // 2//动态class STYLE=1<<2,// 4 //动态style PROPS=1<<3,// 8 //动态属性,但不包含类名和样式 FULLPR0PS=1<<4,// 16 //具有动态key属性,当key改变时,需要进行完整的diff比较。 HYDRATE_ EVENTS = 1 << 5,// 32 //带有监听事件的节点 STABLE FRAGMENT = 1 << 6, // 64 //一个不会改变子节点顺序的fragment KEYED_ FRAGMENT = 1 << 7, // 128 //带有key属性的fragment 或部分子字节有key UNKEYED FRAGMENT = 1<< 8, // 256 //子节点没有key 的fragment NEED PATCH = 1 << 9, // 512 //一个节点只会进行非props比较 DYNAMIC_SLOTS = 1 << 10 // 1024 // 动态slot HOISTED = -1 // 静态节点 // 指示在diff算法中退出优化模式 BALL = -2
3. Vue3.0 新特性 —— 为什么要新增 Composition
API,它能解决什么问题?
- Vue2.0 中,随着功能的增加,组件变得越来越复杂,越来越难维护,而难以维护的根本原因是 Vue 的 API 设计迫使开发者使用
watch
,computed
,methods
选项组织代码,而不是实际的业务逻辑。 - 另外 Vue2.0 缺少一种较为简洁的低成本的机制来完成逻辑复用,虽然可以
minxis
完成逻辑复用,但是当mixin
变多的时候,会使得难以找到对应的data
、computed
或者method
来源于哪个mixin
,使得类型推断难以进行。 - 所以
Composition
API的出现,主要是也是为了解决Option
API带来的问题:- 第一个是代码组织问题,
Compostion
API可以让开发者根据业务逻辑组织自己的代码,让代码具备更好的可读性和可扩展性,也就是说当下一个开发者接触这一段不是他自己写的代码时,他可以更好的利用代码的组织反推出实际的业务逻辑,或者根据业务逻辑更好的理解代码。 - 第二个是实现代码的逻辑提取与复用,当然
mixin
也可以实现逻辑提取与复用,但是像前面所说的,多个mixin
作用在同一个组件时,很难看出property
是来源于哪个mixin
,来源不清楚,另外,多个mixin
的property
存在变量命名冲突的风险。而Composition
API刚好解决了这两个问题。
- 第一个是代码组织问题,
4. Vue3.0 新特性 —— Composition
API 与 React.js 中 Hooks
的异同点?(难点问题)
a. React.js 中的 Hooks
基本使用
-
React
Hooks
允许你 “勾入” 诸如组件状态和副作用处理等 React 功能中。Hooks
只能用在函数组件中,并允许我们在不需要创建类的情况下将状态、副作用处理和更多东西带入组件中。 -
React 核心团队奉上的采纳策略是不反对类组件,所以你可以升级 React 版本、在新组件中开始尝试
Hooks
,并保持既有组件不做任何更改。 -
案例:
import React, { useState, useEffect } from "react"; const NoteForm = ({ onNoteSent }) => { const [currentNote, setCurrentNote] = useState(""); useEffect(() => { console.log(`Current note: ${currentNote}`); }); return ( <form onSubmit={e => { onNoteSent(currentNote); setCurrentNote(""); e.preventDefault(); }} > <label> <span>Note: </span> <input value={currentNote} onChange={e => { const val = e.target.value && e.target.value.toUpperCase()[0]; const validNotes = ["A", "B", "C", "D", "E", "F", "G"]; setCurrentNote(validNotes.includes(val) ? val : ""); }} /> </label> <button type="submit">Send</button> </form> ); };
-
useState
和useEffect
是 ReactHooks
中的一些例子,使得函数组件中也能增加状态和运行副作用。 -
我们也可以自定义一个
Hooks
,它打开了代码复用性和扩展性的新大门。
b. Vue Composition
API 基本使用
-
Vue
Composition
API 围绕一个新的组件选项setup
而创建。setup()
为 Vue 组件提供了状态、计算值、watcher
和生命周期钩子。 -
并没有让原来的 API(
Options-based
API)消失。允许开发者 结合使用新旧两种 API(向下兼容)。 -
案例:
<template> <form @submit="handleSubmit"> <label> <span>Note:</span> <input v-model="currentNote" @input="handleNoteInput"> </label> <button type="submit">Send</button> </form> </template> <script> import { ref, watch } from "vue"; export default { props: ["divRef"], setup(props, context) { const currentNote = ref(""); const handleNoteInput = e => { const val = e.target.value && e.target.value.toUpperCase()[0]; const validNotes = ["A", "B", "C", "D", "E", "F", "G"]; currentNote.value = validNotes.includes(val) ? val : ""; }; const handleSubmit = e => { context.emit("note-sent", currentNote.value); currentNote.value = ""; e.preventDefault(); }; return { currentNote, handleNoteInput, handleSubmit, }; } }; </script>
c. 原理分析
- React
hook
底层是基于链表实现,调用的条件是每次组件被render
的时候都会顺序执行所有的hooks
。 - Vue
Composition
只会被注册调用一次,vue 能避开这些麻烦的问题,原因在于它对数据的响应是基于Proxy
的,对数据直接代理观察。
(这种场景下,只要任何一个更改data
的地方,相关的function
或者template
都会被重新计算,因此避开了 React 可能遇到的性能上的问题)。 - React 中,数据更改的时候,会导致重新
render
,重新render
又会重新把hooks
重新注册一次,所以 React 复杂程度会高一些。
5. Vue3.0 是如何变得更快的?(底层,源码)
a. diff
算法优化
- Vue2 中的 虚拟
dom
是进行全量的对比。 - Vue3 中新增了静态标记(
PatchFlag
):在与上次虚拟结点进行对比的时候,值对比带有patch flag
的节点,并且可以通过flag
的信息得知当前节点要对比的具体内容化。
b. 静态提升(hoistStatic
)
- Vue2:无论元素是否参与更新,每次都会重新创建。
- Vue3:对不参与更新的元素,只会被创建一次,之后会在每次渲染时候被不停的复用。
c. 事件侦听器缓存(cacheHandlers
)
- 默认情况下
onClick
会被视为动态绑定,所以每次都会去追踪它的变化,但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可。
6. Vue3.0 性能提升主要是通过哪几方面体现的?
a. 响应式系统提升
- vue2 在初始化的时候,对
data
中的每个属性使用definepropery
调用getter
和setter
使之变为响应式对象。如果属性值为对象,还会递归调用defineproperty
使之变为响应式对象。 - vue3 使用
proxy
对象重写响应式。proxy
的性能本来比defineproperty
好,proxy
可以拦截属性的访问、赋值、删除等操作,不需要初始化的时候遍历所有属性,另外有多层属性嵌套的话,只有访问某个属性的时候,才会递归处理下一级的属性。 - 优势:
- 可以监听动态新增的属性;
- 可以监听删除的属性 ;
- 可以监听数组的索引和
length
属性;
b. 编译优化
- 优化编译和重写虚拟
dom
,让首次渲染和更新dom
性能有更大的提升- vue2 通过标记静态根节点,优化
diff
算法; - vue3 标记和提升所有静态根节点,
diff
的时候只比较动态节点内容;
- vue2 通过标记静态根节点,优化
Fragments
(升级vetur
插件),template
里面不用创建唯一根节点,可以直接放同级标签和文本内容;- 静态提升(
hoistStatic
),当使用hoistStatic
时,所有静态的节点都被提升到render
方法之外,只会在应用启动的时候被创建一次,之后使用只需要应用提取的静态节点,随着每次的渲染被不停的复用; patch flag
,跳过静态节点,直接对比动态节点。在动态标签末尾加上相应的标记,只能带patchFlag
的节点才被认为是动态的元素,会被追踪属性的修改,能快速的找到动态节点,而不用逐个逐层遍历,提高了虚拟dom diff
的性能;- 缓存事件处理函数(
cacheHandler
),避免每次触发都要重新生成全新的function
去更新之前的函数;
c. 源码体积的优化
- vue3 移除了一些不常用的api,例如:
inline-template
、filter
等; - 使用
tree-shaking
,通过树摇优化核心库代码体积,减少不必要的代码量;
7. Vue3.0 所采用的 Composition
Api 与 Vue2.x 使用的 Options
Api 有什么区别?
-
Options
Api- 包含一个描述组件选项(
data
、methods
、props
等)的对象options
; - API开发复杂组件,同一个功能逻辑的代码被拆分到不同选项 ;
- 使用
mixin
重用公用代码,也有问题:命名冲突,数据来源不清晰;
- 包含一个描述组件选项(
-
Composition
Api- vue3 新增的一组 api,它是基于函数的 api,可以更灵活的组织组件的逻辑;
- 解决
options
api在大型项目中,options
api不好拆分和重用的问题;
8. Vue3.0 响应式系统的实现原理?
a. 新api reactive
设置对象为响应式对象。接收一个参数,判断这参数是否是对象。不是对象则直接返回这个参数,不做响应式处理。
创建拦截器 handerler
,设置 get/set/deleteproperty
。
get
- 收集依赖(
track
); - 如果当前
key
的值是对象,则为当前key
的对象创建拦截器handler
,设置get/set/deleteProperty
; - 如果当前的
key
的值不是对象,则返回当前key
的值。
- 收集依赖(
set
- 设置的新值和老值不相等时,更新为新值,并触发更新(
trigger
)。
- 设置的新值和老值不相等时,更新为新值,并触发更新(
deleteProperty
- 当前对象有这个
key
的时候,删除这个key
并触发更新(trigger
)。
- 当前对象有这个
b. 副作用处理 effect
- 接收一个函数作为参数。作用是:访问响应式对象属性时去收集依赖
c. 收集依赖 track
- 接收两个参数:
target
和key
- 如果没有
activeEffect
,则说明没有创建effect
依赖 - 如果有
activeEffect
,则去判断WeakMap
集合中是否有target
属性 WeakMap
集合中没有target
属性,则set(target, (depsMap = new Map()))
WeakMap
集合中有target
属性,则判断target
属性的map
值的depsMap
中是否有key
属性depsMap
中没有key
属性,则set(key, (dep = new Set()))
depsMap
中有key
属性,则添加这个activeEffect
- 如果没有
d. 触发依赖 trigger
- 判断
WeakMap
中是否有target
属性,WeakMap
中有target
属性,则判断target
属性的map
值中是否有key
属性,有的话循环触发收集的effect()
。
持续更新中。。。
参考文章:
1、https://blog.csdn.net/qq_35942348/article/details/110677399
2、https://blog.csdn.net/weixin_40599109/article/details/110938941
感谢原作者辛苦付出