最优学习方法:快速掌握代码设计的步骤
-
抓住核心目标(Why):
- 先问:这段代码要解决什么问题?(答案:实现数据响应式,支持数据变化时自动更新依赖。)
- 明确目标后,理解代码的每一部分如何服务于这个目标。
-
分层拆解(What):
- 把代码分成小块,逐层理解功能(从全局变量到具体函数)。
- 关注输入输出和数据流,而不是一开始钻研细节。
-
动手调试(How):
- 在代码中加 console.log,运行并观察数据流和执行顺序。
- 修改参数或逻辑,验证自己的理解。
-
可视化梳理(Connect):
- 用图表或伪代码把逻辑串起来,快速建立心理模型。
-
总结复述(Master):
- 用自己的话解释代码,假装教别人,找出理解盲点。
快速理解代码:结构化拆解与分析
1. 抓住核心目标
这段代码的核心是实现 Vue 3 的响应式系统:
- 数据变化 → 自动触发副作用(如渲染)。
- 用 Proxy 监听数据,用 bucket 收集和触发依赖。
2. 分层拆解代码
我将代码分成 5 个关键部分,逐一解释:
(1)全局变量:依赖存储和状态管理
const bucket = new WeakMap(); // 全局容器,存对象的依赖关系
let activeEffect = null; // 当前正在执行的副作用函数
- bucket: 像一个“依赖数据库”,用 WeakMap 存每个对象及其属性的依赖。
- 为什么用 WeakMap?对象不用时自动清理,避免内存泄漏。
- activeEffect: 标记当前运行的副作用(如渲染函数),用于收集依赖。
快速理解:bucket 是“仓库”,activeEffect 是“工人”,工人干活时把依赖存进仓库。
(2)track 函数:收集依赖
function track(target, key) {
if (!activeEffect) return; // 没工人,不干活
let depsMap = bucket.get(target); // 获取对象依赖图
if (!depsMap) bucket.set(target, (depsMap = new Map())); // 没有就新建
let deps = depsMap.get(key); // 获取属性依赖集合
if (!deps) depsMap.set(key, (deps = new Set())); // 没有就新建
deps.add(activeEffect); // 把工人加进依赖集合
}
- 作用:当读取数据(如 data.count)时,把当前副作用存进 bucket。
- 数据结构:
- WeakMap: target → Map
- Map: key → Set
- Set: 存 activeEffect(避免重复)。
- 流程:检查 → 初始化 → 添加。
(3)trigger 函数:触发更新
function trigger(target, key) {
const depsMap = bucket.get(target); // 找到对象依赖图
if (!depsMap) return;
const deps = depsMap.get(key); // 找到属性依赖集合
if (deps) deps.forEach(effect => effect()); // 执行所有依赖
}
- 作用:数据变化时,找到相关依赖并执行。
- 流程:查找 → 执行。
(4)reactive 函数:数据监听
[[Reflect 的使用]]
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key); // 读取时收集依赖
return Reflect.get(target, key, receiver); // 返回值
},
set(target, key, value, receiver) {
Reflect.set(target, key, value, receiver); // 设置新值
trigger(target, key); // 触发更新
return true;
}
});
}
- 作用:用 Proxy 包裹对象,拦截 get(读取)和 set(修改)。
- 核心:
- get: 收集依赖。
- set: 触发更新。
(5)effect 函数:副作用管理
function effect(fn) {
activeEffect = fn; // 设置工人
fn(); // 立即执行,触发 get,收集依赖
activeEffect = null; // 清空
}
- 作用:运行副作用(如打印或渲染),并在执行时收集依赖。
- 流程:标记 → 执行 → 清理。
3. 数据流与执行顺序
用测试代码理解:
const data = reactive({ count: 0 });
effect(() => console.log("count is:", data.count));
data.count = 1;
- 初始化:
- reactive 创建 Proxy 对象。
- effect 执行:
- activeEffect = fn。
- fn() 触发 get → track 把 fn 存进 bucket。
- 修改数据:
- data.count = 1 触发 set → trigger 执行 fn。
测试代码
// 测试代码
const data = reactive({ count: 0 });
effect(() => {
console.log("count is:", data.count);
});
data.count = 1; // 输出: "count is: 1"
data.count = 2; // 输出: "count is: 2"
使用断点进行流程分析, reactive 定义之后使用 Proxy 进行代理, get 就 track, set 就trigger。 接下来遇到 effect effect 会知己先执行一遍并且进行 track 操作, 所以 count 的依赖就有了 effect 中的 fn, 到修改 count 的时候就会触发 trigger 重新执行这个 function。
项目代码: github.com/Arabeseque/…