该内容主要整理关于 Vue3.0 的相关面试题,关于 Vue 2 的相关面试题请移步至:Vue 全家桶篇,其他内容面试题请移步至 最新最全的前端面试题集锦 查看。
Proxy
API 替代 defineProperty
API?—— 响应式优化(高频,重点!!!)这是在面试中问的最多的一个问题,无论是大厂还是中小型公司,都喜欢问,也是Vue更新的重点。
defineProperty
API 的局限性最大原因是它只能针对单例属性做监听。
defineProperty
中的 descriptor
,对 data
中的属性做了遍历 + 递归,为每个属性设置了 getter
、setter
。data
中预定义过的属性做出响应的原因,在 Vue 中使用下标的方式直接修改属性的值或者添加一个预先不存在的对象属性是无法做到 setter
监听的,这是 defineProperty
的局限性。Proxy
API 的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作, 这就完全可以代理所有属性,将会带来很大的性能提升和更优的代码。
Proxy
可以理解成,在目标对象之前架设一层 “拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Object.defineProperty
把每一层对象数据都变成响应式的,这无疑会有很大的性能消耗。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
}
})
a. 生成 Block tree
VNode
树。Block tree
。Block tree
是一个将模版基于动态节点指令切割的嵌套区块,每个 区块内部的节点结构是固定的,每个区块只需要追踪自身包含的动态节点。b. slot
编译优化
slot
,那么每次父组件更新的时候,会强制使子组件 update
,造成性能的浪费。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
Composition
API,它能解决什么问题?watch
,computed
,methods
选项组织代码,而不是实际的业务逻辑。minxis
完成逻辑复用,但是当 mixin
变多的时候,会使得难以找到对应的 data
、computed
或者 method
来源于哪个 mixin
,使得类型推断难以进行。Composition
API的出现,主要是也是为了解决 Option
API带来的问题:
Compostion
API可以让开发者根据业务逻辑组织自己的代码,让代码具备更好的可读性和可扩展性,也就是说当下一个开发者接触这一段不是他自己写的代码时,他可以更好的利用代码的组织反推出实际的业务逻辑,或者根据业务逻辑更好的理解代码。mixin
也可以实现逻辑提取与复用,但是像前面所说的,多个 mixin
作用在同一个组件时,很难看出 property
是来源于哪个 mixin
,来源不清楚,另外,多个 mixin
的 property
存在变量命名冲突的风险。而 Composition
API刚好解决了这两个问题。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
是 React Hooks
中的一些例子,使得函数组件中也能增加状态和运行副作用。
我们也可以自定义一个 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. 原理分析
hook
底层是基于链表实现,调用的条件是每次组件被 render
的时候都会顺序执行所有的 hooks
。Composition
只会被注册调用一次,vue 能避开这些麻烦的问题,原因在于它对数据的响应是基于 Proxy
的,对数据直接代理观察。data
的地方,相关的 function
或者 template
都会被重新计算,因此避开了 React 可能遇到的性能上的问题)。render
,重新 render
又会重新把hooks
重新注册一次,所以 React 复杂程度会高一些。a. diff
算法优化
dom
是进行全量的对比。PatchFlag
):在与上次虚拟结点进行对比的时候,值对比带有 patch flag
的节点,并且可以通过 flag
的信息得知当前节点要对比的具体内容化。b. 静态提升(hoistStatic
)
c. 事件侦听器缓存(cacheHandlers
)
onClick
会被视为动态绑定,所以每次都会去追踪它的变化,但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可。a. 响应式系统提升
data
中的每个属性使用 definepropery
调用 getter
和 setter
使之变为响应式对象。如果属性值为对象,还会递归调用 defineproperty
使之变为响应式对象。proxy
对象重写响应式。proxy
的性能本来比 defineproperty
好,proxy
可以拦截属性的访问、赋值、删除等操作,不需要初始化的时候遍历所有属性,另外有多层属性嵌套的话,只有访问某个属性的时候,才会递归处理下一级的属性。length
属性;b. 编译优化
dom
,让首次渲染和更新dom
性能有更大的提升
diff
算法;diff
的时候只比较动态节点内容;Fragments
(升级 vetur
插件),template
里面不用创建唯一根节点,可以直接放同级标签和文本内容;hoistStatic
),当使用 hoistStatic
时,所有静态的节点都被提升到 render
方法之外,只会在应用启动的时候被创建一次,之后使用只需要应用提取的静态节点,随着每次的渲染被不停的复用;patch flag
,跳过静态节点,直接对比动态节点。在动态标签末尾加上相应的标记,只能带 patchFlag
的节点才被认为是动态的元素,会被追踪属性的修改,能快速的找到动态节点,而不用逐个逐层遍历,提高了虚拟 dom diff
的性能;cacheHandler
),避免每次触发都要重新生成全新的function
去更新之前的函数;c. 源码体积的优化
inline-template
、filter
等;tree-shaking
,通过树摇优化核心库代码体积,减少不必要的代码量;Composition
Api 与 Vue2.x 使用的 Options
Api 有什么区别?Options
Api
data
、methods
、props
等)的对象 options
;mixin
重用公用代码,也有问题:命名冲突,数据来源不清晰;Composition
Api
options
api在大型项目中,options
api不好拆分和重用的问题;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
感谢原作者辛苦付出