vue 数据劫持原理
程序员文章站
2022-07-02 20:28:17
...
1,什么是 Object.defineProperty
- Object.defineProperty(obj,property,descriptor) 有三个参数 分别是
- obj 对象 property 属性值 属性值 当前属性所拥有的特性
descriptor 有如下属性
- get 获取值时触发
- set 修改值时触发
- enumerable 是否可枚举 如果值为false就不可以别for in Object.keys等循环
- value 当前值
- configurable 是否可修改
- writeable是否可重写
数据劫持是双向绑定中的数据改变时触发视图修改需要的,由上述可知,当数据变化可以用Object.defineProperty的set方法触发,修改视图。
let info = {
name: "sjf",
age: 18,
};
Object.keys(info).forEach((ele) => {
let value = info[ele];
Object.defineProperty(info, ele, {
enumerable: true,
configurable: true,
get() {
return value;
console.log("get", value);
},
set(newVal) {
console.log("数据发生了变化,监听到了");
if (newVal == value) {
return;
}
value = newVal;
},
});
});
setTimeout((ele) => {
info.age = 222;//数据发生了变化,监听到了
console.log(info.age);
}, 20);
2,对象的劫持
import { arrayMethods } from "./array"
class Observer{
constructor(value) {//需要对value属性重新定义
// value可能是对象 肯恩故
// value.__ob__ = this
//value.__ob__ 对象里还是对象,一直会导致死循环
// 有这个属性 表示被观测过
Object.defineProperty(value, "__ob__", {
value: this,
enumerable: false,// 不能被枚举,避免死循环
configurable:false//不能删除此属性
})
if (Array.isArray(value)) {
// 数组不用defineProperty来进行代理,性能不好
// push shift reverse sort 我要重写这些方法 增加更新逻辑
// value.__proto__ = arrayMethods//当是数组时改写方法为自己重写后的方法
Object.setPrototypeOf(value, arrayMethods)//循环将属性赋予上去
this.observeArray(value)//原有数组中的对象
} else {
this.walk(value)
}
}
observeArray(value) {
for (let i = 0; i < value.length; i++) {
observe(value[i])
}
}
walk(data) {
// 将对象中的所有key,重新用 defineProperty定义响应式的
Object.keys(data).forEach(key => {
defineReactive(data,key,data[key])
})
}
}
export function defineReactive(data, key, value) {
// vue2中数据嵌套不要过深,过深浪费性能
// value 可能也是一个对象
observe(value)//对结果递归拦截
Object.defineProperty(data, key, {
get() {
console.log("取值")
return value
},
set(newValue) {
console.log("设置值")
if (newValue === value) {
return
}
observe(value)
// 如果用户设置的是一个对象,就继续将用户设置的对象变成响应式的
value = newValue
}
})
}
export function observe(data) {
console.log("-----------", data)
// 需要对数据defineProperty 进行重新观测
// 只对对象类型进行观测,非对象类型无法观测
if (typeof data !== "object" || data == null) {
return
}
if (data.__ob__) {//数据被观测过
return
}
return new Observer(data)
}
代码解析
export function observe(data) {
console.log("-----------", data)
// 需要对数据defineProperty 进行重新观测
// 只对对象类型进行观测,非对象类型无法观测
if (typeof data !== "object" || data == null) {
return
}
if (data.__ob__) {//数据被观测过
return
}
return new Observer(data)
}
- observe 数据观测拦截
- 如果数据不是对象类型 或者 数据是 null类型 就终止数据观测
- 如果数据观测过 具有 __ob__属性就也终止数据观测
export function defineReactive(data, key, value) {
// vue2中数据嵌套不要过深,过深浪费性能
// value 可能也是一个对象
observe(value)//对结果递归拦截
Object.defineProperty(data, key, {
get() {
console.log("取值")
return value
},
set(newValue) {
console.log("设置值")
if (newValue === value) {
return
}
observe(value)
// 如果用户设置的是一个对象,就继续将用户设置的对象变成响应式的
value = newValue
}
})
}
- defineReactive定义响应式数据
- 数据传递过来的时候,value的值可能是对象所以用observe(value)对结果递归拦截
- 如果用户设置的是一个对象,就继续将用户设置的对象变成响应式的
class Observer{
constructor(value) {//需要对value属性重新定义
// value可能是对象 肯恩故
// value.__ob__ = this
//value.__ob__ 对象里还是对象,一直会导致死循环
// 有这个属性 表示被观测过
Object.defineProperty(value, "__ob__", {
value: this,
enumerable: false,// 不能被枚举,避免死循环
configurable:false//不能删除此属性
})
if (Array.isArray(value)) {
// 数组不用defineProperty来进行代理,性能不好
// push shift reverse sort 我要重写这些方法 增加更新逻辑
// value.__proto__ = arrayMethods//当是数组时改写方法为自己重写后的方法
debugger
Object.setPrototypeOf(value, arrayMethods)//循环将属性赋予上去
this.observeArray(value)//原有数组中的对象
} else {
this.walk(value)
}
}
observeArray(value) {
for (let i = 0; i < value.length; i++) {
observe(value[i])
}
}
walk(data) {
// 将对象中的所有key,重新用 defineProperty定义响应式的
Object.keys(data).forEach(key => {
defineReactive(data,key,data[key])
})
}
}
- 首先判断传入的数据是对象还是
- 数组类型,用Array.isArray 判断,
- 如果是数组类型
- 就将重写过后的数组方法arrayMethods设置到数据value的原型上
- 然后调用observeArray方法循环处理数据调用 observe 方法处理每一项数据
- 如果是对象类型
- 就使用object.keys 循环处理对象并调用defineReactive对对象的数据进行响应式处理
3,数据属性值为数组时,数据原型上的方法的重写,以监听数组的变化
let oldArrayProtoMethods = Array.prototype;
// 数组原来的方法
// oldArrayProtoMethods.push = function () {
// }
// 不能直接改写数组原有方法,不可靠,因为只有被vue控制的数组才需要改写
export let arrayMethods = Object.create(Array.prototype);
// Object.create() 继承 ?
// 改变原数组
// concat slice ... 都不能改变原数组
let methods = ["push", "pop", "shift", "unshift", "splice", "reverse", "sort"];
methods.forEach(method => {
// AOP切片编程
arrayMethods[method] = function (...args) {//重写数组方法
// todo ...
// 有可能用户新增的数据是对象格式,也需要进行拦截
let inserted
let ob = this.__ob__
switch (method) {
case 'push':
case "unshift":
inserted = args
break;
case "splice": //splice(0,1,2)
inserted = args.slice(2)
default:
break;
}
if (inserted) {
ob.observeArray(inserted)
}
console.log("数组变化")
let result = oldArrayProtoMethods[method].call(this, ...args)
return result
}
})
Object.create() 使用现有对象提供新对象的proto
只由unshift,splice会产生新增的值,这写新增的数据需要使用observeArray实现数据的观测