一、认识 Proxy
new Proxy(target, handler)
target
Proxy 会对 target 对象进行包装。它可以是任何类型的对象,包括内置的数组,函数甚至是另一个代理对象。
handler
它是一个对象,它的属性提供了某些操作发生时所对应的处理函数。
tips: 完整使用教程可移步: 阮一峰ES6入门教程(Proxy 和 Reflect)
二、尝鲜
- Demo:
function reactive(obj) { return new Proxy(obj, { get(target, key, receiver) { const res = Reflect.get(target, key, receiver) console.log('获取', key, res) return res }, set(target, key, value, receiver) { const res = Reflect.set(target, key, value, receiver) console.log('设置', key, res) return res }, deleteProperty(target, key) { const res = Reflect.deleteProperty(target, key) console.log('删除', key, res) return res } }) } const obj = { foo: 'foo', bar: { a: 1 }, arr: [1, 2, 3] } const state = reactive(obj) // 获取 state.foo // 设置 state.foo = 'foooooooo' // 删除 delete state.foo // 添加属性 state.dong = 'dong' state.dong // 嵌套对象 state.bar.a = 10 // 数组 state.arr state.arr.push(5)
- 结论:除了嵌套对象无法捕获,其他的操作都能拦截
- 递归实现深度响应:
const isObject = val => val !== null && typeof val === 'object' function reactive(obj) { if (!isObject(obj)) return obj return new Proxy(obj, { get(target, key, receiver) { const res = Reflect.get(target, key, receiver) console.log('获取', key, res) return isObject(res) ? reactive(res) : res }, ... }) } ...
三、缓存代理对象,避免重复代理
-
存在重复代理的两种情况:
const state = reactive(obj)
const state2 = reactive(obj)const state = reactive(reactive(obj))
-
淦就完事了
// 避免重复代理 const toProxy = new WeakMap() // 形如{ obj: observed } const toRaw = new WeakMap() // 形如{ observed: obj } const observed = new Proxy(obj, {...}) // 缓存 toProxy.set(obj, observed) toRaw.set(observed, obj) return observed
四、响应式探究
- 这里我们实现 Vue
watch
的机制,computed
类似。 - Vue3 使用方式:
effect(() => state.xxx, (val, oldVal) => {})
,第一个回调函数指明需要watch
的字段,第二个为需要处理的回调函数。 - 思考:①要想监听某个对象的变化,是不是只需要在
get()
函数里面建立字段的映射关系就行了呢?②要想回调函数调用什么时机会触发呢?是不是在set()
函数里面做就行了?有思路了就直接开干! - 还得考虑一个问题就是我们用什么样的数据结构存储
代理对象 - 属性 - 回调函数
的关系呢?考虑到这几项都可能多组,这里采用new WeakMap()、new Map()、new Set()
的结构。 - 从调用出发实现
effect(getField, callback)
,上代码:function effect(getField, callback) { try { effectStack.push(callback) getField() // 此处会触发 get 操作 } catch (error) { console.error(error) } finally { effectStack.pop() } }
observed
函数改造const observed = new Proxy(obj, { get(target, key, receiver) { const res = Reflect.get(target, key, receiver) // console.log('获取', key, res) track(target, key) // 此处建立数据的绑定关系 return isObject(res) ? reactive(res) : res }, set(target, key, value, receiver) { const oldVal = Reflect.get(target, key, receiver) const res = Reflect.set(target, key, value, receiver) // console.log('设置', oldVal, res) trigger(target, key, value, oldVal) // 此处触发对应的回调函数 return res }, deleteProperty(target, key) { const oldVal = Reflect.get(target, key) const res = Reflect.deleteProperty(target, key) // console.log('删除', key, res) trigger(target, key, undefined, oldVal) // 此处触发对应的回调函数 return res } })
- 接下来实现
track(target, key)
、trigger(target, key, value, oldVal)
两个函数,上代码:function track(target, key) { const effect = effectStack[effectStack.length - 1] if (effect) { let depsMap = targetMap.get(target) if (!depsMap) { depsMap = new Map() targetMap.set(target, depsMap) } let deps = depsMap.get(key) if (!deps) { deps = new Set() depsMap.set(key, deps) } deps.add(effect) } } function trigger(target, key, val, oldVal) { const depsMap = targetMap.get(target) if (depsMap) { const deps = depsMap.get(key) if (deps) { deps.forEach(effect => { effect(val, oldVal) }) } } }
- 加上测试代码:
effect(() => state.foo, (val, oldVal) => { console.log(val, oldVal) }) effect(() => state.bar.a, (val, oldVal) => { console.log(val, oldVal) })
- 恭喜你,测试通过了!( ̄▽ ̄)~*
五、完整代码
const targetMap = new WeakMap() // {target, {key: []}}
const effectStack = []
const isObject = val => val !== null && typeof val === 'object'
// 避免重复代理
const toProxy = new WeakMap() // 形如{ obj: observed }
const toRaw = new WeakMap() // 形如{ observed: obj }
function reactive(obj) {
if (!isObject(obj)) return obj
// 避免重复执行代理
if (toProxy.has(obj)) {
return toProxy.get(obj)
}
// 避免代理代理过的对象
if (toRaw.has(obj)) {
return obj
}
const observed = new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
// console.log('获取', key, res)
track(target, key)
return isObject(res) ? reactive(res) : res
},
set(target, key, value, receiver) {
const oldVal = Reflect.get(target, key, receiver)
const res = Reflect.set(target, key, value, receiver)
// console.log('设置', oldVal, res)
trigger(target, key, value, oldVal)
return res
},
deleteProperty(target, key) {
const oldVal = Reflect.get(target, key)
const res = Reflect.deleteProperty(target, key)
// console.log('删除', key, res)
trigger(target, key, undefined, oldVal)
return res
}
})
// 缓存
toProxy.set(obj, observed)
toRaw.set(observed, obj)
return observed
}
function effect(getField, callback) {
try {
effectStack.push(callback)
getField()
} catch (error) {
console.error(error)
} finally {
effectStack.pop()
}
}
// 建立映射关系
function track(target, key) {
const effect = effectStack[effectStack.length - 1]
if (effect) {
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
deps.add(effect)
}
}
// 触发
function trigger(target, key, val, oldVal) {
const depsMap = targetMap.get(target)
if (depsMap) {
const deps = depsMap.get(key)
if (deps) {
deps.forEach(effect => {
effect(val, oldVal)
})
}
}
}
const obj = { foo: 'foo', bar: { a: 1 }, arr: [1, 2, 3] }
const state = reactive(obj)
effect(() => state.foo, (val, oldVal) => {
console.log(val, oldVal)
})
effect(() => state.bar.a, (val, oldVal) => {
console.log(val, oldVal)
})
// 获取
state.foo
// 设置
state.foo = 'foooooooo'
// 删除
delete state.foo
// 添加属性
state.dong = 'dong'
state.dong
// 嵌套对象
state.bar.a = 10
// 数组
state.arr
state.arr.push(5)
欢迎提出意见与建议,关注,收藏来一波! skr~