该文章内容为以下视频的学习笔记:
前言_哔哩哔哩_bilibili前言是秋招解决方案:深入 Vue3 源码,带你彻底打通 Vue3 源码面试的第1集视频,该合集共计13集,视频收藏或关注UP主,及时了解更多相关视频内容。https://www.bilibili.com/video/BV1D8411B7Tx?p=1&vd_source=4298755c4e2edc1fe805c41cc2d7379a
1.Vue2 和 Vue3 的区别
- 先明确改变的地方很多,并进行列举
- 然后从两个重点问题中进行回答
- 最后进行一个总结收尾
vue3 使用 TS 进行了完全的重构,改变的地方还是挺多的,比如:
-
新增的 Composition API(注意:vue3 也支持 Options API)
-
模块化的 API 调用(可以有效的进行 TreeShaking)
-
基于 Fragment 的多个根标签
-
响应式的实现原理
-
diff 算法优化
-
生命周期的变化
-
新增的一些组件,比如:teleport、suspense 这些
-
.....
主要两个比较核心的变化:
-
响应式实现原理的改变
-
diff 算法优化的变化
(1)响应式原理:
在 vue2 中响应式核心还是通过 Object.defineProperty 进行实现的。通过 data 方法返回的对象作为 target。这样无论是 简单数据类型 还是 复杂数据类型 ,都可以直接通过 Object.defineProperty 监听 getter 和 setter 行为。
但是,由于 Object.defineProperty 只能监听指定对象、指定属性的响应性,所以 vue 需要对 data 中返回的复杂数据类型进行循环监听。
那么这样,当我们为响应式数据 动态新增属性(为对象新增一个之前不存在的属性,文档)时,会出现失去响应性的问题。
那么为了解决这个问题,vue2 增加了 Vue.set 的 API ,相当于主动触发了一次 Object.defineProperty。但是,这种方式其实并不方便,需要用户主动触发。
所以,vue3 中改用了 Proxy(也是因为浏览器逐渐升级,不再需要过分兼容旧的浏览器)。利用 Proxy 代理的特性解决了这个问题。
(2)diff 算法的优化:
Vue2 中的 diff 大家都喜欢把它叫做 双端 Diff 对比 。大致的思路是通过:新旧两组节点的四个端点(新节点组的开头、新节点组的结尾、旧节点组的开头、旧节点组的结尾) 进行对比,并试图找到可以复用的节点。
而 Vue3 中的 diff 大家都喜欢叫它做 快速 Diff
(注意:快速 diff 并不是官网声明的名字,只是国内都这么叫)。里面涉及到了 最长递增子序列 的概念,整体还是有点复杂的。
总体来说,Vue3 带来的变化很大。通过 Composition API,特别是 3.2 之后新增了 <script setup>
语法糖,让 vue 的使用更加接近了原生 js 实现。
2.Vue3 中的响应式实现原理
- 先明确响应式的实现方式
- 针对两种方案进行解析
- 最后总结核心点
Vue3 的响应式实现主要有两个部分:reactive、ref。
reactive 主要是通过 proxy 进行的响应式实现,核心是监听复杂数据类型的 getter 和 setter 行为。
当监听到 getter 行为的时候那么就收集当前的依赖行为,也就是 effect 。当触发 setter 行为的时候,那么就触发刚才收集的依赖。那么此时,所有获取到当前数据的地方都会更新执行,也就是完成了响应性。
但是 proxy 只能监听复杂数据类型,没有办法监听简单数据类型。所以 vue 专门提供了 ref 方法。ref 方法既可以处理简单数据类型、也可以处理复杂数据类型。它的实现在 3.2 之前和 3.2 之后是不同的。3,2 之前主要通过 Object.defineProperty 进行实现,在 3.2 版本的时候,根据社区贡献改为了 get value 和 set value 标记的方式进行实现。这也是为什么 ref 类型的数据必须要通过 .value 的方式使用的原因(本质上是触发 value 方法)。
当 ref 接收复杂数据类型的时候,会直接通过 toReactive 方法,把复杂数据类型交给 reactive 进行处理。
📜总结:整个的 vue3 响应性,主要就是由这两大块来进行实现的。proxy 处理复杂数据类型,get value 和 set value 处理简单数据类型。核心都是监听 setter 和 getter ,然后触发 effect 的方式。
3.computed 实现原理
- 把 computed 与 ref 进行类比
- 着重说调度器和脏处理
- 总结核心内容
computed 和 ref 的实现是有一些类似的,比如:
-
它们本质上都是一个类(ComputedRefImpl)
-
都是通过 get value 和 set value 监听 getter 和 setter 行为的
但是因为 computed 的计算属性特性(依赖的响应式数据发生变化时,才会重新计算),所以在源码的实现上有一些区别,这个区别主要体现在两个地方:
-
调度器:scheduler
-
执行检查(脏状态):_dirty
1️⃣调度器 scheduler
它是作为 ReactiveEffect 的第二个参数存在的回调函数。当触发依赖的时候,会直接执行这个回调函数。
在这个回调函数中,会根据当前的脏值状态来决定是否需要触发依赖。
2️⃣ _dirty
它其实就是一个 boolean 的变量。
-
true:表示需要获取 计算之后 的最新数据
-
false:表示当前数据就是最新的,不需要重新计算
在每次去触发 get value (computed.value)的时候,都会根据这个 _dirty 的值来判断计算的触发。
📜总结:总的来说,计算属性的核心还是体现在 是否需要重新计算 这里。判断的方式就是通过 _dirty 进行的。而 scheduler 主要提供了函数的作用,在函数内部还是需要依赖 _dirty 来决定触发依赖的时机。
4.watch 实现原理
- 说明 watch的处理逻辑
- 着重说明依赖收集和触发、懒执行
- 总结核心内容
watch 是一个典型的懒执行 API,它的逻辑更加纯粹:在监听的响应式数据变化时,重新执行回调函数就可以了。核心的点有两个:
-
如何监听依赖 || 触发依赖的
-
如何进行懒执行的
1️⃣ watch 监听依赖 || 触发依赖的机制
watch 的监听和触发也是依赖的 setter 和 getter 行为。
这里的 setter 行为触发是比较明确的,本质上就是监听的响应式数据触发 setter 行为。
而 getter 行为的触发是依赖于内部的 traverse 方法进行的。traverse 方法就是 依次遍历数据,分别触发 getter 行为。
2️⃣懒执行本质上就是通过 options 中的 immediate 参数,逻辑比较简单。
因为 watch 内部通过 job 的方法来触发 callback(回调函数),如果 immediate 为 true 那么就主动触发一次 job 就可以了。
📜总结:
watch 的实现会更加纯粹一些。只需要关注好 setter 和 getter 以及 懒执行的特性即可。