- 感知数据变化的方法很直接,就是进行数据劫持或数据代理。往往通过 Object.defineProperty 实现。这个方法可以定义数据的 getter 和 setter
let data = {
stage: '状态',
course: {
title: '标题',
author: '作者',
publishTime: '出版时间'
}
}
Object.keys(data).forEach(key => {
let currentValue = data[key]
// Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
Object.defineProperty(data, key, {
// 当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false
enumerable: true,
// 当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
configurable: false,
get() {
console.log(`getting ${key} value now, getting value is:`, currentValue)
return currentValue
},
set(newValue) {
currentValue = newValue
console.log(`setting ${key} value now, setting value is`, currentValue)
}
})
})
这段代码对 data 数据的 getter 和 setter 进行定义拦截,当读取或者改变 data 的值时:
data.course
data.course = 'Test'!
但是这种实现会有问题,例如:
data.course.title = 'newTitle'
出现这个问题的原因是因为实现代码只进行了一层 Object.defineProperty,或者说只对 data 的第一层属性进行了 Object.defineProperty,对于嵌套的引用类型数据结构:data.course,同样应该进行拦截。
为了达到深层拦截的目的,将 Object.defineProperty 的逻辑抽象为 observe 函数,并改用递归实现:
let data = {
stage: '状态',
course: {
title: '标题',
author: '作者',
publishTime: '出版时间'
}
}
const observe = data => {
if (!data || typeof data !== 'object') {
return
}
Object.keys(data).forEach(key => {
let currentValue = data[key]
observe(currentValue)
Object.defineProperty(data, key, {
enumerable: true,
configurable: false,
get() {
console.log(`getting ${key} value now, getting value is: `, currentValue)
return currentValue
},
set(newValue) {
currentValue = newValue;
console.log(`setting ${key} value now, setting value is: `, currentValue)
}
})
})
}
observe(data)
这样就实现了深层数据拦截:
data.course.title = 'newTitle'
在 set 代理中,并没有对 newValue 再次递归进行 observe(newValue)。也就是说,如果赋值是一个引用类型:
data.course.title = {
title: 'newTitle2'
}
无法实现对 data.course.title 数据的观察。
在尝试对 data.course.title 赋值时,首先会读取 data.course,因此输出:getting course value now, getting value is: {// ...},赋值后,触发 data.course.title 的 setter,输出:setting title value now, setting value is newTitle。
网友评论