对Vue响应式变量的探究
前置
WeekMap
是一种特殊的map,他的键类型只能是对象。并且有以下特征
-
弱引用:对键是弱引用,不会阻止垃圾回收
-
不可枚举:无法获取所有键值对
-
长度不可知
set集合
具有元素唯一性
Effect副作用
是指除了返回函数值之外,还会与函数外部状态进行交互的操作
// 1. DOM 操作
document.getElementById('app').textContent = 'Hello'
// 2. 修改外部变量
let externalVar = 0
function increment() { externalVar++ }
// 3. 控制台输出
console.log('This is a side effect')
// 4. 网络请求
fetch('/api/data')
在Vue的响应式中特指修改target的目标变量的函数
响应式代码的基础结构
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
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
trigger(target, key)
return result
}
})
}
track函数
作用:在读取属性时,建立当前执行的 effect(副作用函数) 与 被访问函数 的依赖关系
// 存储依赖关系的全局数据结构
const targetMap = new WeakMap() // WeakMap<target, Map<key, Set<effect>>>
let activeEffect = null // 当前正在执行的effect
function track(target, key) {
if (!activeEffect) return // 没有活跃的effect,直接返回
// 1. 从targetMap中获取target对应的depsMap
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
// 2. 从depsMap中获取key对应的effect Set
let dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
// 3. 将当前activeEffect添加到dep中
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
// 同时effect也记录自己属于哪些dep(用于cleanup)
activeEffect.deps.push(dep)
}
}
简单总结以下这个函数的实现方法:在targetMap中查找到target,并且找到正确的key,将effect函数放入effect的集合中去。
trigger函数
作用:在修改target时,通知 依赖于该属性的effect 重新执行。
function trigger(target, key) {
// 1. 获取target对应的depsMap
const depsMap = targetMap.get(target)
if (!depsMap) return // 没有依赖,直接返回
// 2. 获取key对应的所有effect
const effects = depsMap.get(key)
// 3. 创建effectsToRun避免无限循环
const effectsToRun = new Set()
if (effects) {
effects.forEach(effect => {
// 避免当前正在执行的effect再次触发,导致无限循环
if (effect !== activeEffect) {
effectsToRun.add(effect)
}
})
}
// 4. 执行所有相关的effect
effectsToRun.forEach(effect => {
if (effect.options.scheduler) {
// 如果有调度器,通过调度器执行
effect.options.scheduler(effect)
} else {
// 否则直接执行
effect()
}
})
}
effect系统
function effect(fn, options = {}) {
const effectFn = () => {
cleanup(effectFn) // 先清除旧依赖
activeEffect = effectFn
const res = fn() // 在这里触发原始函数,并且更新响应式数据
activeEffect = undefined
return res
}
effectFn.options = options
effectFn.deps = [] // 存储所有包含此effect的dep集合
effectFn() // 立即执行一次
return effectFn
}
// 清除effect与所有dep的关联
function cleanup(effectFn) {
effectFn.deps.forEach(dep => {
dep.delete(effectFn)
})
effectFn.deps.length = 0
}