blog
blog copied to clipboard
深入响应式原理
// 维持一个执行副作用的栈
const runningEffects = [] // 当前正在运行的副作用
const createEffect = fn => {
// 将传来的 fn 包裹在一个副作用函数中
const effect = () => {
runningEffects.push(effect)
fn()
runningEffects.pop()
}
// 立即自动执行副作用
effect()
}
createEffect(() => {
sum = val1 + val2
})
Proxy 是一个对象,它包装了另一个对象,并允许你拦截对该对象的任何交互。
我们这样使用它:new Proxy(target, handler)
const dinner = {
meal: 'tacos'
}
const handler = {
get(target, property) {
console.log('intercepted!')
return target[property]
}
}
const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)
// intercepted!
// tacos
使用 Proxy 的一个难点是 this 绑定。我们希望任何方法都绑定到这个 Proxy,而不是目标对象,这样我们也可以拦截它们。值得庆幸的是,ES6 引入了另一个名为 Reflect 的新特性,它允许我们以最小的代价消除了这个问题:
const dinner = {
meal: 'tacos'
}
const handler = {
get(target, property, receiver) {
return Reflect.get(...arguments)
}
}
const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)
// tacos
使用 Proxy 实现响应性的第一步就是跟踪一个 property 何时被读取。我们在一个名为 track 的处理器函数中执行此操作,该函数可以传入 target 和 property 两个参数。
const dinner = {
meal: 'tacos'
}
const handler = {
get(target, property, receiver) {
track(target, property)
return Reflect.get(...arguments)
}
}
const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)
// tacos
这里没有展示 track 的实现。它将检查当前运行的是哪个副作用,并将其与 target 和 property 记录在一起。这就是 Vue 如何知道这个 property 是该副作用的依赖项。
最后,我们需要在 property 值更改时重新运行这个副作用。为此,我们需要在代理上使用一个 set 处理函数:
const dinner = {
meal: 'tacos'
}
const handler = {
get(target, property, receiver) {
track(target, property)
return Reflect.get(...arguments)
},
set(target, property, value, receiver) {
trigger(target, property)
return Reflect.set(...arguments)
}
}
const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)
// tacos
现在,我们对 Vue 如何实现这些关键步骤有了答案:
1、当一个值被读取时进行追踪:proxy 的 get 处理函数中 track 函数记录了该 property 和当前副作用。 2、当某个值改变时进行检测:在 proxy 上调用 set 处理函数。 3、重新运行代码来读取原始值:trigger 函数查找哪些副作用依赖于该 property 并执行它们。
该被代理的对象对于用户来说是不可见的,但是在内部,它们使 Vue 能够在 property 的值被访问或修改的情况下进行依赖跟踪和变更通知。