-
Notifications
You must be signed in to change notification settings - Fork 52
实现一个数据劫持器
LYF edited this page Sep 27, 2020
·
5 revisions
这是我以前做的本地的笔记,现在移到这儿
class Observer {
constructor(data) {
this.observe(data)
}
observe(data) {
// 只有是对象,才能劫持
if (!data || typeof data !== 'object') return
Object.keys(data).forEach(key => {
const val = data[key]
this.defineReactive(data, key, val)
// 递归劫持
this.observe(val)
})
}
defineReactive(data, key, val) {
const self = this
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
return val
},
set(newVal) {
if (newVal !== val) {
console.log(`set ${newVal} .....`)
// 新设置的值也可能是对象,所以也要用observe方法搞一遍
self.observe(newVal)
val = newVal
}
}
})
}
}
实现数据劫持,核心是三个点:
- Object.defineProperty
- 闭包
- 递归劫持
Object.defineProperty
结合闭包,把val
封闭到闭包里,每次set
数据的时候,用newVal
更新val
的值即可,每个val
或者newVal
可能是一个对象,那么我们要递归地进行劫持
测试
new Observer(data)
data.name = '张无忌' // set 张无忌 .....
console.log(data.name) // 张无忌
data.name = 'pod4g' // set pod4g .....
console.log(data.name) // pod4g
data.props.skills.push('typescript') // 不会触发set勾子,因为`Object.defineProperty`无法劫持数组,这也是为什么vue额外提供了操作数据的方法,在这些方法里才能做劫持
data.props.skills = [...data.props.skills, 'typescript'] // 只有改变skills的引用才能触发set: set java,javascript,node.js,mysql,html,css,typescript .....
console.log(data)
data.props.data.addr = '首都' // set 首都 .....
console.log(data)
vue不能检测:
- 对象属性的添加或者移除
- 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:vm.items.length = newLength
其中第1、3是能力问题,Object.defineProperty
确实无法检测对象属性的增添,length
属性由于configurable: false
,故也无法对其descriptor进行重写
2是设计问题,Object.defineProperty
其实是可以检测数组的索引变化的,但是vue没有这么做,原因是为了性能考虑,尤大的原话是「性能代价和获得的用户体验收益不成正比」,ROI太低了。。
关于vue不能检测数组的文章: