defineProperty 数据劫持
实现目的: 对data对象进行劫持
实现思路:
- data可能是一个对象或者是一个函数,都变成对象进行处理
- 利用defineProperty进行劫持
- 对劫持过得对象进行代理,例如劫持过得对象获取是Vue._data_.XXX 变成 Vue.XXX
难点: defineProperty进行劫持的时候使用了闭包,这样传递的value不会被销毁掉
function initData(vm) { // 初始化数据
let data = vm.$options.data; // data可能是函数或者是对象
data = typeof data === 'function' ? data.call(vm) : data
vm._data = data;
// 对数据进行劫持 vue2 里采用了 defineProperty
observe(data);
// 对数据进行代理,用户不用vue._data.xxx,直接vue.xxx
for (let key in data) {
proxy(vm, "_data", key)
}
}
// 进行代理
function proxy(vm, target, key) {
Object.defineProperty(vm, key, {
get() {
return vm[target][key];
},
set(newValue) {
return vm[target][key] = newValue;
}
})
}
// 对数据进行劫持
class Observe {
constructor(data) {
// Object.defineProperty 只能劫持已经存在的属性,后增的删除的是不知道的
this.walk(data)
}
walk(data) { // 循环对象,依次劫持
// 重新定义属性 性能瓶颈在这 换了proxy性能明显提高
Object.keys(data).forEach(key => defineReactive(data, key, data[key]))
}
}
export function defineReactive(data, key, val) { // 闭包,get,set引用了val不销毁
observe(val) // 递归 对所有属性进行劫持
Object.defineProperty(data, key, {
get() {
return val
},
set(newVal) {
if (val === newVal) return
val = newVal
},
});
}
export default function observe(data) {
// 对对象进行劫持
if (typeof data !== 'object' || data == null) {
return // 只对对象进行劫持
}
// 如果一个对象被劫持了,那就不需要在被劫持了(要判读一个对象是否被劫持过,可以增添一个实例,用实例来判断一下)
return new Observe(data)
}