《Vue进阶教程》第二十七课:实现侦听对象
往期内容:
1) 基本实现
如果第一个参数不是副作用函数, 可以将其包装成一个副作用函数
function watch(source, cb) {
let getter
if (typeof source === 'function') {
getter = source
} else {
getter = () => source
}
effect(getter, {
scheduler() {
cb()
},
})
}
由于并没有触发代理对象的取值操作, 因此不会收集依赖
考虑实现一个函数, 遍历访问source
中的每一个属性
function traverse(value) {
for (const k in value) {
// 将代理对象的每个属性访问一次
value[k]
}
}
function watch(source, cb) {
let getter
if (typeof source == 'function') {
getter = source
} else {
getter = () => traverse(source)
}
effect(getter, {
scheduler() {
cb()
},
})
}
2) 支持嵌套
如果代理对象中存在嵌套情况
- 在reactive中要递归为嵌套的对象创建代理
- 在traverse中要递归访问嵌套的属性
示例
get(target, key) {
if (key == '__v_isReactive') return true // 新增
// console.log(`自定义访问${key}`)
// 收集依赖
track(target, key)
if (typeof target[key] == 'object') {
// 递归处理对象类型
return reactive(target[key])
}
return target[key]
},
function traverse(value) {
for (const k in value) {
if (typeof value[k] == 'object') {
traverse(value[k])
} else {
// 将代理对象的每个属性访问一次
value[k]
}
}
}
3) 解决循环引用问题
如果按上述写法, 会出现递归循环引用
, 陷入死循环
什么是循环引用
如果一个对象的某个属性引用自身, 在递归时会死循环
问题示例
const state = reactive({ name: 'xiaoming', address: { city: '武汉' } })
state.test = state
// watch接收响应式对象的情况
watch(state, () => {
console.log('该函数默认不执行, 只有状态更新时执行...')
})
setTimeout(() => {
// 侦听对象时, 是深度侦听
state.address.city = '北京'
}, 1000)
以上问题可以简化为
const obj = {
foo: 'foo',
}
// obj的一个属性引用obj本身, 出现循环引用问题
obj.bar = obj
function traverse(value) {
for (const k in value) {
if (typeof value[k] == 'object') {
traverse(value[k])
} else {
// 将代理对象的每个属性访问一次
value[k]
}
}
}
traverse(obj)
为了避免递归循环引用
陷入死循环, 改造traverse
方法
function traverse(value, seen = new Set()) {
// 如果这个对象已经被遍历过了, 直接返回
if (typeof value !== 'object' || seen.has(value)) return
// 将遍历的对象记录下来, 再递归时判断
seen.add(value)
for (let key in value) {
// 这里取值, 会触发proxy对象的getter操作
traverse(value[key], seen)
}
}
原文地址:https://blog.csdn.net/2401_84715637/article/details/144749771
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!