一键传输wifi互传
118.12M · 2026-02-04
今天咱们来聊聊 Vue 响应式系统——这个让你写代码时“数据自动变视图,视图自动变数据”的神奇魔法,vue3发布后,网上很多分析Vue3响应式源码,基于weakMap -> map -> set的这种数据节后实现响应式,,其实早就偷偷用双链表重构了响应式核心,玩出了新花样,今天就带大家扒一扒这份源码,新手也能看懂,老鸟也能补漏~~
先抛个灵魂拷问:为啥Vue3要放着好好的Set依赖收集方案不用,非要折腾双链表?答案很简单——Set方案太“笨”,性能拉胯还麻烦!Vue3早期的响应式依赖收集,用的是Set来存储副作用函数,看似简洁,但删除依赖时要遍历整个Set查找目标,更新的时候更是“大水漫灌”,明明只改了一个变量,却要触发一堆无关的更新,CPU都要哭了。
而双链表,就是Vue3给响应式“开的外挂”——轻量、高效、能精准定位,完美解决了Set收集的痛点。就像给依赖关系装了个“导航仪”,找依赖、更依赖、删依赖,一步到位,再也不用像Set那样瞎遍历浪费性能。话不多说,直接上源码,边看边吐槽~
如果还不知到源码阅读技巧,初始准备,可以看下博主上一篇文章# 90% 的人读不懂 Vue 源码,是因为没做这些准备
首先,我们创建一个demo文件index.html,通过断点方式来阅读源码,可以更清晰的看到代码执行过程中数据流的变化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue Effect Test</title>
<!-- 引入你本地 clone 的 vue.global.js -->
<script src="../../../../vue/dist/vue.global.js"></script>
</head>
<body>
<div id="text"></div>
<button id="btn">count++</button>
<script>
const { reactive, ref, effect } = Vue;
// 创建响应式数据
const state = reactive({
count1: 1,
count2: 2
});
// 绑定 effect:依赖收集 + 自动更新
effect(() => {
// 建立一个链表关系
document.getElementById('text').innerText =
`当前 count 值:${state.count1}`;
});
document.getElementById('btn').onclick = () => {
state.count1++;
};
</script>
</body>
</html>
阅读响应式代码,我们需要找到vue响应式模块reactivity模块下reactive.ts文件,然后找到reactive函数,可以看到响应式对象是通过 createReactiveObject函数创建出来的。
当我们深入到 createReactiveObject 函数内部,这个函数接收五个参数。通过逐行调试(debugger)执行其代码逻辑可以清晰看到,函数最终会返回一个 Proxy 对象 —— 这正是 Vue 响应式系统的核心实现基础。如此看来,大名鼎鼎的 Proxy 核心原理,拆解开来其实也并不复杂。
由上图可见,baseHandlers其实本质就是MutableReactiveHandler,作为createReactiveObject函数的第三个参数传递进去,可以看到 MutableReactiveHandler 中有 get, set, deleteProperty, has, ownKeys 等方法,get方法继承自 BaseReactiveHandler
查看get取值操作,可以看到get操作主要做的是track依赖收集,收集完毕后把count1初始值返回,供页面展示使用(忽略判断,我们只关心主线逻辑)
在 get 函数内部,有一个核心操作是 track(依赖追踪)。深入到 track 函数的实现逻辑中可以发现,Vue 是通过一套层级化的数据结构来组织响应式依赖收集的:最外层是 WeakMap,其下嵌套 Map,最终指向具体的 Dep 实例。我们进到track函数内部, 首先会查找当前targetMap中是否有target对应的map, 没有我们会创建一个map, 然后我们会查找当前count1是否有对应的Dep实例,如果不存在则创建一个Dep对象
我们看核心操作dep.track, 这是我们依赖收集的核心函数; 首先会通过Link类创建一个link节点, 其中 activeSub是当前激活的副作用函数,this是当前的dep对象, 这也是为啥说link是连接dep和sub的桥梁, 初始时候我们不做指针后移,但是访问count2是要进行链表新增操作的
接下来执行addSub, 我们看看addSub中到底做了什么? 其实就是更新sub对应的前后sub指针,由于我们当前只有一个副作用,所以prevSub是undefined,最后当前 link 挂到它所属的 dep 上,作为该 dep 订阅者链表的入口,这样我们就完成了count1的收集,count2类似的阅读思路
我们点击按钮,修改count1的值,此时我们想要做的是通知副作用函数的执行,从而让页面视图更新,那vue是怎么触发更新的呢?
其中run方法内部核心执行dep.trigger方法
dep.trigger方法内部,在finally尾部执行endBatch方法
trigger函数如下
判断是否肮,很显然我们更新了数据,需要执行副作用,所以执行this.runIfDirty
this.runIfDirty内部调用this.run方法
run方法中返回 this.fn()函数
其中fn就是我们的副作用函数,找到执行它就ok啦
这样我们就完成了副作用的执行
最后,双链表的结构图大致可以抽象为
好了,今天的源码拆解就到这里。
原创不易,如果对你有帮助,点个小赞、关注~