Vue3 题解QA
# Vue3 题解 QA
# Vue3
为何弃用defineProperty
Object.defineProperty
无法低耗费的监听到数组下标的变化,导致通过数组下标添加元素,不能实时响应;举例来说:因为数组的位置不固定,数量多变,正常对象 key 对应 value 一般不会变,但是如果数组删除了某个元素,比如第一个元素被删除或者头部增加一个元素,那么将导致后面所有的 key 对应 value 错位,如果 6 个元素,也就会触发 5 次 set。Vue 将数组的 7 个变异方法进行了重写,也就是更改了 Array 原型上的方法达到劫持变化。Object.defineProperty
只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历。如果属性值是对象,还需要深度遍历。Proxy
可以劫持整个对象, 并返回一个新的对象。Proxy
不仅可以代理对象,还可以代理数组。- 可以代理动态增加的属性。
- 可以监听删除的属性。
- 监听数组的索引和 length 属性
Tips: Proxy 虽然是劫持的整个对象,但也是浅层劫持,属性值是对象时同样也需要深度遍历,不然该属性对象的变化也无法监测到。
# 除了使用Proxy
之外,Vue3
的快还体现在什么地方?
# 源码体积的优化
- 重写了虚拟
dom
# 不主动数据劫持
Vue2
里面的数据被劫持,均是defineProperty
主动为其添加getter\setter
方法的。Vue3
里面的数据劫持均为被动,对数据有操作,其才会被更改为响应式数据。- 体现在整个系统上,则起码在初始阶段,系统不会那么繁忙。
# 代码编译优化
- 使用了 组合
API
来代替vue2
中的Options API
- 组件内不需要根节点了,使用
fragment
(代码片段)代替了,fragment
(代码片段)不会在页面显示 vue3
中标记和提升所有的静态根节点,diff
的时候只需要对比动态节点内容
# Composition Api
和Options Api
- 代码更利于维护和封装;
Vue2
中,我们会在一个vue
文件的data
,methods
,computed
,watch
中定义属性和方法,共同处理页面逻辑 ,一个功能的实现,代码过于分散;vue3
中,代码是根据逻辑功能来组织的,一个功能的所有api
会放在一起(高内聚,低耦合),提高可读性和可维护性,基于函数组合的API
更好的重用逻辑代码;Vue3
中用setup
函数代替了Vue2
中的befareCreate
和created
;Vue3
中用onUnmounted
代替了Vue2
中的beforeDestory
;Vue3
中用unmounted
代替了Vue2
中的destroyed
;
# Proxy
相对于Object.defineProperty
的优点
- 代码的执行效果更快;
Proxy
可以直接监听对象而非属性;Proxy
可以直接监听数组的变化;Proxy
有多达13
种拦截方法,不限于apply
、ownKeys
、deleteProperty
、has
等等是Object.defineProperty
不具备的;Proxy
返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty
只能遍历对象属性直接修改;Proxy
不需要初始化的时候遍历所有属性,另外有多层属性嵌套的话,只有访问某个属性的时候,才会递归处理下一级的属性;
# Vue 3.0
在编译方面有哪些优化?
vue3.x
中标记和提升所有的静态节点,diff
的时候只需要对比动态节点内容Fragments
(升级vetur
插件):template
中不需要唯一根节点,可以直接放文本或者同级标签- 静态提升(
hoistStatic
),当使用hoistStatic
时,所有静态的节点都被提升到render
方法之外.只会在应用启动的时候被创建一次,之后使用只需要应用提取的静态节点,随着每次的渲染被不停的复用 patch flag
, 在动态标签末尾加上相应的标记,只有带patchFlag
的节点才被认为是动态的元素,会被追踪属性的修改,能快速的找到动态节点,而不用逐个逐层遍历,提高了虚拟dom diff
的性能- 缓存事件处理函数
cacheHandler
,避免每次触发都要重新生成全新的function
去更新之前的函数 tree shaking
通过摇树优化核心库体积,减少不必要的代码量
# vue3 中如何进行像 2.X 那样的函数式调用组件
这里依旧用 vue2.x 那里的 message 组件类似的写法,重点在于 2 与 3 之间使用的渲染方法的区别。而 vue3 将其核心拆离出来,使用者可以自定义 render 函数生成在不同环境下的代码,如:androd、ios 等。
Vue3.0 的渲染分为
- Render Phase 渲染阶段 在渲染阶段,将会调用 render 函数,它返回一个虚拟节点
- Mount Phase 挂载阶段 在挂载阶段,会使用虚拟节点,并调用 Dom Api 挂载到 web 上
- patch Phase 补丁阶段 在补丁阶段,渲染器会将旧的虚拟节点和新的虚拟节点(当 reactivity 监听到变化时,会再次调用 render 函数生成新的虚拟节点)进行比较并只更新网页变化的部分
import { createVNode, render, isVNode, nextTick } from "vue";
import ImageConstructor from "./image.vue";
let vm;
let container;
/**
* 传参:
* @param urlList {若为dom元素,则查找dom元素内所有的image图片,若为数组,则展示数组内的所有图片}
* @param zIndex {层级}
* @param emptyError {未找到img控制台提醒}
* @param activeIndex {当前需要展示的图片路径,没有则默认展示数组第一张, number|string}
*/
const Image = function (
opts: any = {
urlList: [],
emptyError: false,
activeIndex: 0,
}
) {
if (opts.urlList instanceof HTMLDocument) {
opts.urlList = Array.from(opts.urlList.getElementsByTagName("img")).map(
(img) => img.src || ""
);
}
if (!(opts.urlList instanceof Array) || !opts.urlList.length) {
if (opts.emptyError) {
console.error("所选模块|传入图片数组为空!");
}
return;
}
if (typeof opts.activeIndex === "string") {
opts.activeIndex = opts.urlList.findIndex(opts.activeIndex) || 0;
}
container = document.createElement("div");
opts.close = close;
vm = createVNode(ImageConstructor, opts);
console.log(vm);
render(vm, container);
document.body.appendChild(container);
};
function close() {
render(null, vm);
nextTick(() => {
document.body.removeChild(container);
});
}
export default Image;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
在 2x 中其调用方法是挂载在其原型对象上,3x 则给我们提供了指定的挂载路径
// 以下app为Vue为实例
//2x
app.prototype.$message = xxx;
// 3x
app.config.globalProperties.$message = xxx;
2
3
4
5
6
# render 函数 2 与 3 的不同
//2 api
render(h) {
return h('div', {
attrs: {
id: 'foo'
},
on: {
click: this.xxx
}
}, 'hello')
}
// 3 api
import {h} from 'vue';
render() {
return h('div', {
id: 'foo',
onClick:this.xxx,
}, 'hello')
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
籍此,我们可以看到两者的不同,一是传入的 options 参数完全扁平化开,若组件内有需要的参数将会作为 props 传入,否则将传入 attrs 中。特别的一提:$listener 也在 3.x 版本并入了 attrs 中。第二点是 createVNode 方法不再针对某个组件渲染,而是在 vue 中全局引入{h}。在 2 中,倘若想将一个大型的组件分割成很多个小的组件的时候,你可能必须要将这个 h 函数一路传递给这些分割函数,从而造成了一些麻烦,而全局的引入降低了心智负担。
# Vue3 与 Vue2 的不同
vue3.0 版本将所有的功能以包的形式分割开来,其中有一个@vue/reactivity 包,里面有 reactive、effect、computed、ref,通过 reactive({haha : 'asdasas'}) 进行了 proxy 代理,通过 effect 为状态,像 react。
Vue3.0 源码有哪几个核心模块组成? reactivity 文件中,分别有 effect、reactive、computed、ref、公共常量及依赖收集的模块
Vue3.0 中响应式原理的作用是什么? 为什么比 Vue2.0 性能高?
- 3。0 中改用了 proxy 进行依赖收集,对对象进行递归劫持,对处理值的变化进行判断。
- 2。0 的时候是通过 Dep 和 Watcher 进行依赖的收集和分发,3.0 使用了 effect 将视图渲染和数据响应式分离开来。且采用模块化的引入的形式,在不使用某一些功能的时候即不需要引入功能模块,可以减少打包编译的大小和时间。vue3。0 是在使用到当前对象时才初始化代理,而 vue2.0 的响应式是在一开始便用递归重写被代理对象的 Object.defineProperty。
proxy 和 defineProperty 有什么区别嘛? 一个是劫持操作,一个是劫持属性。 proxy 只需要代理了一个对象的 set、get 操作,那么相当于对这个对象的所有子属性做了劫持。 而 defineProperty 则需要对每一个属性进行遍历然后劫持。因此在 2.x 里,vue 需要对 data 数据进行递归遍历,对 data 数据做深度的 Observer 处理,因此如果传入的 data 是比较深的层级,或者有很多没有用到的数据,都会影响到 vue 的初始化的速度。而 3.x 只需要在对象上做一层代理,而这个对象上就算有很多无需做响应式处理的数据也没关西,因为子对象是否需要被递归代理是在用到的时候才决定的。可以说相比 2.x 基本上没有了 initData 这部分的时间。也无须要通过$set 来添加新属性的监听了。
Vue3 中计算属性为什么要优先于 effect 执行 因为如果通过 effect 去获取计算属性中的值的时候,计算属性通过 dirty 来进行缓存避免多次计算值的调用。如果计算属性内所需要被监听的值发生了改变,但是 computed.value 并没有被及时的修改,就会导致 effect 出来的值不是最新的值,所以需要优先执行并且将 dirty 设为 true。
Vue3 中计算属性中 dirty 的含义 dirty 是为了对计算属性的值是否发生变更所做的一个锁,函数内通过识别 dirty 的值来判断是否需要重新运行计算属性中传入的函数/对象 setter。已避免计算属性在 getter 的过程中多次触发执行 effect。