依赖收集与追踪的详细流程
依赖收集与追踪的流程概述
Vue3的响应式系统核心在于依赖收集与追踪。当数据变化时,能自动通知依赖它的副作用重新执行。这个过程通过Proxy拦截get/set操作,结合track
和trigger
函数实现。
响应式对象的创建
通过reactive()
函数创建响应式对象时,内部会调用createReactiveObject
:
function reactive(target) {
return createReactiveObject(
target,
mutableHandlers,
mutableCollectionHandlers
)
}
function createReactiveObject(target, baseHandlers) {
const proxy = new Proxy(target, baseHandlers)
return proxy
}
baseHandlers
包含Proxy的get/set陷阱:
const mutableHandlers = {
get: createGetter(),
set: createSetter()
}
getter中的依赖收集
当访问响应式属性时,触发get陷阱:
function createGetter() {
return function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
track(target, key) // 关键依赖收集
if (isObject(res)) {
return reactive(res) // 深层响应式
}
return res
}
}
track
函数建立属性与当前运行副作用的关系:
const targetMap = new WeakMap() // 全局依赖存储
function track(target, key) {
if (!activeEffect) return // 没有活跃副作用则不追踪
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
dep.add(activeEffect) // 将当前effect添加到依赖集合
activeEffect.deps.push(dep) // 反向记录
}
副作用的注册与执行
副作用通过effect
函数注册:
let activeEffect
function effect(fn) {
const effectFn = () => {
cleanup(effectFn) // 清除旧依赖
activeEffect = effectFn
fn()
}
effectFn.deps = []
effectFn()
}
清理旧依赖避免无效更新:
function cleanup(effectFn) {
for (const dep of effectFn.deps) {
dep.delete(effectFn)
}
effectFn.deps.length = 0
}
setter中的依赖触发
修改属性值时触发set陷阱:
function createSetter() {
return function set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (hasChanged(value, oldValue)) {
trigger(target, key) // 触发更新
}
return result
}
}
trigger
查找并执行相关副作用:
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const effects = depsMap.get(key)
const effectsToRun = new Set(effects)
effectsToRun.forEach(effect => effect())
}
嵌套effect的处理
组件渲染可能产生嵌套effect:
effect(() => {
console.log('外层effect')
effect(() => {
console.log('内层effect')
temp2 = obj.bar // 收集内层依赖
})
temp1 = obj.foo // 收集外层依赖
})
通过effectStack
维护执行上下文:
const effectStack = []
function effect(fn) {
const effectFn = () => {
cleanup(effectFn)
activeEffect = effectFn
effectStack.push(effectFn)
fn()
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
effectFn.deps = []
effectFn()
}
数组方法的特殊处理
数组的push/pop等方法需要额外处理:
const arrayInstrumentations = {
push() {
track(this, 'length') // 追踪length变化
return Array.prototype.push.apply(this, arguments)
}
}
function createGetter() {
return function get(target, key, receiver) {
if (isArray(target) && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
// ...原有逻辑
}
}
调度执行控制
通过scheduler控制触发时机:
function trigger(target, key) {
// ...
const run = (effect) => {
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
effectsToRun.forEach(run)
}
示例使用:
effect(() => {
console.log(obj.foo)
}, {
scheduler(effect) {
setTimeout(effect, 1000) // 延迟执行
}
})
计算属性的实现
计算属性基于effect的懒执行:
function computed(getter) {
let value
let dirty = true
const effectFn = effect(getter, {
lazy: true,
scheduler() {
dirty = true
trigger(obj, 'value') // 手动触发依赖
}
})
const obj = {
get value() {
if (dirty) {
value = effectFn()
dirty = false
}
track(obj, 'value') // 手动收集依赖
return value
}
}
return obj
}
watch的实现原理
watch基于effect和调度器:
function watch(source, cb) {
let getter
if (isFunction(source)) {
getter = source
} else {
getter = () => traverse(source)
}
let oldValue
const effectFn = effect(
() => getter(),
{
scheduler() {
const newValue = effectFn()
cb(newValue, oldValue)
oldValue = newValue
}
}
)
oldValue = effectFn()
}
递归读取属性确保全部追踪:
function traverse(value, seen = new Set()) {
if (!isObject(value) || seen.has(value)) return
seen.add(value)
for (const k in value) {
traverse(value[k], seen)
}
return value
}
响应式系统的性能优化
- 依赖收集层级优化:
function track(target, key) {
if (!shouldTrack) return // 全局开关
if (key === '__proto__' || key === 'constructor') return
// ...其余逻辑
}
- 避免重复trigger:
function set(target, key, value) {
if (Array.isArray(target) && key === 'length') {
if (value >= target.length) return // 忽略长度增大
}
// ...原有逻辑
}
- 批量更新机制:
let isFlushing = false
const queue = new Set()
function queueJob(job) {
queue.add(job)
if (!isFlushing) {
isFlushing = true
Promise.resolve().then(() => {
try {
queue.forEach(job => job())
} finally {
isFlushing = false
queue.clear()
}
})
}
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
下一篇:effect函数的内部工作原理