前言
罪恶的一年过去了,发现我自己面试老是被各种问题难倒! 思来想去发现!主要还是基础不牢!地动山摇!所以打算好好去学习一下vue3的源码,做一些整理笔记,如果有哪里不对还请各位大佬批评指正
思路:
- 通过es6新增proxy创建代理对象;
- 在effect副作用函数中访问时 进行依赖收集
WeakMap<target, Map<key, Set<effect>>>
- 在修改对应属性的时候 判断属性的值是否发生了变化
- 变化则执行对应的effect
创建proxy对象
- 通过 new Proxy(target, handler) 方法创建 Proxy 对象。其中,target 是要拦截的对象,handler 是一个对象,它定义了拦截 target 的操作的钩子函数;
- target是传入的需要进行响应式的对象;
- handler要做的事情是在访问属性(get)的时候进行依赖收集,设置属性(set)的时候进行触发更新
- 在创建时, 如果是已经代理过的代理对象 或 以及存在的代理对象直接返回,否则才会创建
javascript
// proxy 需要搭配 reflect 使用
export const mutableHandlers: ProxyHandler<object> = {
get(target, key, recevier) {
if (key === ReactiveFlags.IS_REACTIVE) {
return true
}
// 当取值的时候 应该让响应式属性 和 effect 映射起来
// 依赖收集
track(target, key)
let res = Reflect.get(target, key, recevier)
if (isObject(res)) {
// 当取值也是对象的时候 进行递归代理
return reactive(res)
}
return res
},
set(target, key, value, recevier) {
// 找到属性 让对应的effect 重新执行
let oldValue = target[key]
let result = Reflect.set(target, key, value, recevier)
if (oldValue !== value) {
// 需要触发页面更新
trigger(target, key, value, oldValue)
}
// 触发更新
return result
}
}
Effect 副作用函数
- 只要依赖属性变化了 就要执行回调
- effect的作用是
- 创建ReactiveEffect实例
- 并且执行回调函数
- 返回ReactiveEffect实例
- ReactiveEffect的作用
- 通过 activeEffect 记录当前正在执行的 effect
- 提供run方法执行回调函数和stop方法停止响应式追踪
javascript
class ReactiveEffect {
_trackId = 0 // 用于记录当前effect执行了几次
_depsLengths = 0 // 记录上一次的依赖长度
_running = 0 // 是否正在执行
deps = [] // 记录当前effect 依赖了哪些属性
active = true // 创建的effect 是响应式的
// fn 用户编写的函数
// 如果fn中依赖的数据发生变化后,需要重新调用 -> run()
constructor(
public fn,
public scheduler
) {
}
run() {
// 让fn执行
if (!this.active) {
// 不是激活的 执行后 什么都不做 不用额外处理
return this.fn()
}
let lastEffect = activeEffect
try {
activeEffect = this
// effect 重新执行前 需要将上一次的依赖清空 effect.deps
preCleanEffect(this)
this._running++
// 执行fn -> 会触发get -> 收集依赖
return this.fn() //依赖收集 -> state.name state.age
} catch (err) {
console.log(err)
} finally {
this._running--
// 执行后 将上一次的依赖清空
postCleanEffect(this)
activeEffect = lastEffect
}
}
stop() {
this.active = false
}
}
javascript
export function effect(fn, options?) {
// 创建一个响应式effect 数据变化后可以重新执行
// 创建一个effect 只要依赖属性变化了 就要执行回调
const _effect = new ReactiveEffect(fn, () => {
_effect.run()
})
_effect.run()
if (options) {
Object.assign(_effect, options) // 合并配置项到_effect对象
}
const runner = _effect.run.bind(_effect) // 绑定this指向
runner.effect = _effect // 在runner上保存effect实例
return runner // 返回runner函数供外部调用
}
依赖收集
- 通过WeakMap和Map建立这样的对应关系
WeakMap<target, Map<key, Set<effect>>>
target是目标对象,key是对象中的键,effect是传入的副作用函数;
plain
{
target:target, //目标对象
Map:[{
key:key,
dep:Map[{track_Id,effect}]
}]
}
- 首先需要判断是否在effect副作用函数中访问的属性,只有在effect访问的属性才需要进行依赖收集
- 创建weakMap的实例targetMap存储目标对象和依赖收集器
- 依赖收集器中存储键值key与dep的对应
- dep中存储effect和id的对应关系,将当前的effect 放入到dep(映射表)中; 并且在当前的effect中deps存储实现双向记忆
- 后续可以根据值的变化触发此dep中存放的effect
修改属性执行effect
- 从targetMap找到对应的目标对象的依赖收集器
- 从该依赖收集器中找到对应键值的dep集合
- 便利dep集合中的所有effect,检查是否有scheduler,如果有scheduler并且目前没有effect在运行中就执行scheduler (相当于执行了effect的回调)
补充
- 在 get 拦截器中,如果属性值是对象,会递归调用 reactive 将其转换为响应式对象。这种惰性代理的方式可以优化性能
- activeEffect 是一个全局变量,用于记录当前正在执行的 effect,以便在依赖收集时将其与属性关联
- 在每次 effect 重新执行前,会清理上一次的依赖,避免无效的依赖关系
- WeakMap 的键是对象,且是弱引用,不会阻止垃圾回收。这可以避免内存泄漏
- 双向记忆:effect.deps 存储了所有依赖的属性集合,同时每个属性的 dep 也存储了依赖它的 effect。这种双向记忆的设计方便在 effect 停止时清理依赖
- scheduler 是一个调度器,用于控制 effect 的执行时机。例如,Vue 3 的异步更新队列就是通过 scheduler 实现的
flowchart TB
subgraph 初始化阶段
A[创建响应式对象] --> B[使用 Proxy 包装原始对象]
B --> C[定义 get/set 拦截器]
end
subgraph 依赖收集阶段
D[访问属性] --> E{是否在 effect 中?}
E -->|是| F[执行 track 逻辑]
F --> G[建立 WeakMap 依赖关系]
G --> H[存储 effect 到 target/key 的 Set 中]
E -->|否| I[直接返回属性值]
end
subgraph 触发更新阶段
J[修改属性值] --> K{值是否变化?}
K -->|是| L[执行 trigger 逻辑]
L --> M[从 WeakMap 找到依赖集合]
M --> N{是否存在 scheduler?}
N -->|是| O[调度执行 effect]
N -->|否| P[直接执行 effect.run]
end
subgraph Effect 管理
Q[调用 effect fn] --> R[创建 ReactiveEffect 实例]
R --> S[首次执行 effect.run]
S --> T[触发 getter 依赖收集]
T --> U[绑定依赖双向关系]
V[停止 effect] --> W[清理所有依赖]
end
A --> D
H --> T
L --> P
P --> S