响应系统的设计与实现
// 存储副作用函数的桶
const bucket = new WeakMap()
// 原始数据
export const listenInit = (data) => {
return new Proxy(data, {
// 拦截读取操作
get(target, key) {
track(target, key)
// 返回属性值
return target[key]
},
// 拦截设置操作
set(target, key, newVal) {
// 设置属性值
target[key] = newVal
trigger(target, key)
return Reflect.set(...arguments)
},
})
}
// 在 get 拦截函数内调用 track 函数追踪变化
function track(target, key) {
// 没有 activeEffect,直接 return
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)
activeEffect.deps.push(deps)
}
// 在 set 拦截函数内调用 trigger 函数触发变化
function trigger(target, key) {
const depsMap = bucket.get(target)
if (!depsMap) return
const effects = depsMap.get(key)
const effectsToRun = new Set()
effects &&
effects.forEach((effectFn) => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
effectsToRun.forEach((effectFn) => {
// 如果一个副作用函数存在调度器,调用调度器,然后将副作用函数作为参数传递
if (effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn)
} else {
//否则直接执行副作用函数
effectFn()
}
})
}
// 用一个全局变量存储被注册的副作用函数
let activeEffect
let effectStack = []
// effect 函数用于注册副作用函数
export const jobEffect = (fn, options = {}) => {
const effectFn = () => {
cleanup(effectFn)
// 当 effectFn 执行时,将其设置为当前激活的副作用函数
activeEffect = effectFn
effectStack.push(effectFn)
const res = fn()
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
return res
}
// 将options挂载到effectFn上
effectFn.options = options
// activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
effectFn.deps = []
// 执行副作用函数
if (!options.lazy) {
effectFn()
}
return effectFn
}
function cleanup(effectFn) {
// 遍历 effectFn.deps 数组
for (let i = 0; i < effectFn.deps.length; i++) {
// deps 是依赖集合
const deps = effectFn.deps[i]
// 将 effectFn 从依赖集合中移除
deps.delete(effectFn)
}
// 最后需要重置 effectFn.deps 数组
effectFn.deps.length = 0
}
// 定义一个任务队列
const jobQueue = new Set()
// 使用 Promise.resolve() 创建一个 promise 实例,我们用它将一个任务添加到微任务队列
const p = Promise.resolve()
// 一个标志代表是否正在刷新队列
let isFlushing = false
function flushJob() {
console.log('flushing!!')
// 如果队列正在刷新,则什么都不做
if (isFlushing) return
console.log('flusingDone!!!')
// 设置为 true,代表正在刷新
isFlushing = true
// 在微任务队列中刷新 jobQueue 队列
p.then(() => {
jobQueue.forEach((job) => job())
}).finally(() => {
// 结束后重置 isFlushing
isFlushing = false
})
}
export const scheduler = (fn) => {
// 每次调度时,将副作用函数添加到 jobQueue 队列中
jobQueue.add(fn)
// 调用 flushJob 刷新队列
flushJob()
}
export const jobWatch = (source, cb, options = {}) => {
let getter
if (typeof source === 'function') {
getter = source
} else {
getter = () => traverse(source)
}
// 定义旧值与新值
let oldValue, newValue
// 提取 scheduler 调度函数为一个独立的 job 函数
const job = () => {
newValue = effectFn()
cb(newValue, oldValue)
oldValue = newValue
}
// 使用 effect 注册副作用函数时,开启 lazy 选项,并把返回值存储到 effectFn 中以便后续手动调用
const effectFn = jobEffect(() => getter(), {
lazy: true,
scheduler: job,
})
if (options.immediate) {
// 当 immediate 为 true 时立即执行 job,从而触发回调执行
job()
} else {
oldValue = effectFn()
}
}
function traverse(value, seen = new Set()) {
// 如果要读取的数据是原始值,或者已经被读取过了,那么什么都不做
if (typeof value !== 'object' || value === null || seen.has(value)) return
// 将数据添加到 seen 中,代表遍历地读取过了,避免循环引用引起的死循环
seen.add(value)
// 暂时不考虑数组等其他结构
// 假设 value 就是一个对象,使用 for...in 读取对象的每一个值,并递归地调用 traverse 进行处理
for (const k in value) {
traverse(value[k], seen)
}
return value
}
// 调用方法
import { listenInit, jobWatch } from './jobQueue'
let errorMessage = listenInit({ msg: '' })
jobWatch(
() => errorMessage.msg,
(newVal, oldVal) => {
if (newVal != oldVal) {
message.error(newVal)
}
}
)
《Vue.js设计与实现》,书写的真好,学习到很多,vue3实现数据响应的方式了解了。
“他的眼睛很小,跟猪的差不多,却闪动着十足的热情。不知是怎的,人们见到这样的人总是心生厌恶” ——《巴黎伦敦落魄记》
这么回事嘛,要内敛一些呀。

浙公网安备 33010602011771号