Vue3 proxy实现双向数据绑定的原理解析
1.什么是Proxy?它的作用是?Proxy可以理解成,在目标对象之前架设一层 "拦截",当外界对该对象访问的时候,都必须经过这层拦截,而Proxy就充当了这种机制,类似于代理的含义,它可以对外界访问对象之前进行过滤和改写该对象。2.Object.defineProperty的缺点深度监听需要一次性递归 (遍历每个对象的每个属性,如果对象嵌套很深的话,需要使用递归调用。)无法监听新增属性/删除属性
1.什么是Proxy?它的作用是?
Proxy
可以理解成,在目标对象之前架设一层 "拦截
",当外界对该对象访问的时候,都必须经过这层拦截,而Proxy就充当了这种机制,类似于代理的含义,它可以对外界访问对象之前进行过滤和改写该对象
。
2.Object.defineProperty的缺点
- 深度监听需要一次性递归 (遍历每个对象的每个属性,如果对象嵌套很深的话,需要使用递归调用。)
- 无法监听新增属性/删除属性(Vue.set Vue.delete,未在 data 中定义的属性会报 undefined)
- 无法原生监听数组,需要特殊处理
因此vue3中之后就改用Proxy来更好的解决如上面的问题,为 data 对象代理get set deleteProperty 三个方法。
Proxy基本语法
const obj = new Proxy(target, handler)
被代理之后返回的对象 = new Proxy(被代理对象,要代理对象的操作)
handler中常用的对象方法如下:
- get(target, propKey, receiver)
- set(target, propKey, value, receiver)
- has(target, propKey)
- construct(target, args):
- apply(target, object, args)
如上是Proxy中handler 对象的方法,其实它和Reflect里面的方法类似的
下面我们来了解一下 reflect 对象
reflect 对象(详细描述可见es6中的 reflect)
reflect 对象原型就是 Object
1)将 object对象的一些明显属性语言内部的方法(如 Object.defineProperty)放到reflect 对象上,就能从 reflect 对象上拿到语言内部的方法
2)在使用对象的 Object.defineProperty(obj, name, {})时,如果出现异常的话,会抛出一个错误,需要使用try catch去捕获,但是使用 Reflect.defineProperty(obj, name, desc) 则会返回false
Reflect 有13个属性,这里我们只用到前4个:
-
Reflect.get(目标对象,属性名,上下文对象) -- 读取对象属性
-
Reflect.set(目标属性,属性名,属性值,上下文对象)-- 设置对象属性
-
Reflect.deleteProperty(目标对象,属性名)-- 删除对象属性
-
Reflect.ownKeys(目标对象)-- 返回由目标对象自身的属性(只处理本身-非原型的属性)组成的数组
-
Reflect.apply(目标函数,调用目标函数时绑定的 this 对象,参数列表) – 通过指定的参数列表调用目标函数
-
Reflect.has(目标对象,属性名) – 检测对象上是否有此属性
-
Reflect.constructor(被运行的目标函数,参数数组,生成的实列对象是谁的实列)(如果没有最后一个参数,默认生成的实列对象就和target构造函数是一样的)
-
Reflect.definedPrototype(目标对象,属性名,描述符) – 定义对象属性,返回一个逻辑值
-
Reflect.isExtensible(目标对象) – 用于检查一个对象是否可扩展(添加新属性或方法)
-
Reflect.preventExtensions(目标对象) – 阻止新属性添加到目标对象中
-
Reflect.getOwnPropertyDescriptor(目标对象,属性)-- 返回对象中的属性描述符
-
Reflect.getPrototypeOf(目标对象) – 返回一个对象原型
proxy 实现数据监听
// 创建响应式
function reactive(target {}) {
if (typeof target !== 'object' || typeof target == null) {
return target // 不是对象或数组直接返回
}
// 代理配置
const proxyConf = {
get(targe, key, receiver) {
// 只处理本身(非原型)的属性
const ownKeys = Reflect.ownKeys(target)
if(ownKeys.include(key)) {
console.log('get', key) // 监听
}
const result = Reflect.get(target, key, receiver) // 返回不做处理
return reactive(result) // 递归调用,这里所做的优化是只在调用到对象深层次的属性时才会触发递归
}
set(target, key, val, receiver) {
// 重复的数组,不处理
if(val === target[key]) {
return true;
}
const ownKeys = Reflect.ownKeys(target)
if(ownKeys.include(key)) {
console.log('set 已有的属性', key) // 监听
} else {
console.log('新增的属性', key)
}
const result = Reflect.set(target, key, val, receiver)
console.log('set', key, val)
return result // 是否设置成功
}
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('deleteProperty', key)
return result
}
})
// 生成代理 对象
const observed = new Proxy(target, proxyConf)
return observed;
}
// 测试数据
const data = {
name: 'zhangsan',
age: 20,
info: {
city: 'beijing',
a: {
b: {
c: {
d: e
}
}
}
}
}
在 get 方法中直接返回 result 时, 我们访问 data.name,data.age ,打印的对象都是被 proxy 包裹的对象,但是我们访问 info.city 时,get 到的对象是info并不是 city,city 还是普通对象,所以我们也需要把它变成 proxy 对象,所以用reactive(result)进行递归
但是只有我们读取到对象深层次的属性时,才会触发递归,即我们访问到 info.a.b 时,info, info.a, info.a.b 等深层时才会触发响应式,info.a.b.c,info.a.b.c.d等依然是普通对象
object.definePropety 的深度监听是一次性就全部监听的,而 proxy 的深度监听是在 get 的时候才去递归的,是一个惰性的,很慢的过程,这就是 proxy 性能的优化
proxy 优缺点总结
规避了 Object.definedProperty的问题
proxy 无法兼容所有浏览器,无法进行polyfill
更多推荐
所有评论(0)