通过Proxy和Reflect实现vue的响应式原理
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通过Proxy和Reflect实现vue的响应式原理相关的知识,希望对你有一定的参考价值。
参考技术A Proxy是ES6中增加的类,表示代理。如果我们想要监听对象的操作过程,可以先创建一个代理对象,之后所有对于对象的操作,都由代理对象来完成,代理对象可以监听到我们对于原对象进行了哪些操作。
Proxy是一个类,通过new关键字创建对象,传入原对象和处理监听的捕获器
对代理所作的操作,同样会作用于原对象
捕获器就是用来监听对于对象操作的方法
以上get、set是捕获器中常用的两种,分别用于对象数据的“读取”操作和“设置”操作
Proxy里对应捕获器与普通对象的操作和定义是一一对应的
将这些捕获器以及对应的对象操作写在了以下示例中
通过捕获器去监听对象的修改、查询、删除等操作
以上捕获器中只有apply和constructor是属于函数对象的
apply方法执行apply捕获器,new操作执行constructor捕获器
Reflect也是ES6新增的一个API,表示反射。它是一个对象,提供了很多操作对象的方法,类似于Object中的方法,比如 Reflect.getPrototypeOf 和 Object.getPrototypeOf。
早期操作对象的方法都是定义在Object上,但Object作为构造函数,直接放在它身上并不合适,所以新增Reflect对象来统一操作,并且转换了对象中in、delete这样的操作符
Reflect中的方法与Proxy中是一一对应的
将之前通过Proxy设置代理的对象操作全都变为Reflect
实现的效果是完全一致的
Reflect中在进行get/set捕获器操作的时候,还有一个入参是receiver,指的是代理对象,用于改变this指向
(1) 如果没有receiver,那么当修改name属性时,objProxy先执行key为name时的get操作
(2) 然后代理到obj里的get方法,读取this的_name属性,此时的this是obj,会直接修改
obj._name,不会再经过objProxy
(3) 增加了receiver之后,执行obj的get方法,读取this的_name属性,此时this是proxy
对象,所以会再次到get的捕获器中
set操作同理
用于改变this的指向
此时创建的student对象虽然拥有Student的属性和方法,但是它的this指向Person
通过以下步骤一步步实现响应式
此时通过响应式函数 watchFn 将所有需要执行的函数收集进了数组中,然后当变量的值发生变化时,手动遍历执行所有的函数
以上只有一个数组来收集对象的执行函数,真实情况下,不止一个对象需要对操作状态进行监听,需要监听多个对象就可以使用类。
将收集操作和依次执行函数的方法都定义在类中
以上仍然是我们自己手动调用执行函数的方法,以下自动监听
此时的问题是,修改了对象的任一属性,所有的函数都会调用,没有按照一一对应的关系来保存对象的属性和对应的函数
此时proxy对应的depend是没有值的,所以此时没有任何打印的数据
此时已经能够根据属性值的变化而执行对应的函数了,但同一个函数会执行两次
此时已实现vue3的响应式~
vue2的实现就是将Proxy和Reflect替换成了Object.defineProperty和Object本身的一些方法
和上面实现的效果是一致的
以上就是通过Proxy和Reflect实现vue的响应式原理,关于js高级,还有很多需要开发者掌握的地方,可以看看我写的其他博文,持续更新中~
vue2的响应式原理学“废”了吗?继续观摩vue3响应式原理Proxy
一文了解Vue3的响应式原理
之前写过一篇文章谈论 vue2.x
的响应式原理,但因为 vue3
也来了,紧跟着 vue3
的步伐,周一开始学起了 vue3
的响应式原理。
大家应该都听过, vue3
用 proxy
来解决响应式原理,同时它解决了 vue2
中 Object.definePropery
存在的一些问题,但同时也带来了一些问题。
在下面的这篇文章中,将讲解关于 vue3
用 proxy
如何实现响应式,以及带来的一些问题。一起来学习吧💯
一、🟩回顾Object.defineProperty
这里需要大家对 ObjectProperty
的知识点有一个预先了解,如有需要了解可点击文章进行查看~
现在,我们来回顾下 Object.defineProperty
的缺点:
- 深度监听时需要一次性递归;
- 无法监听新增属性/删除属性(需要配合
Vue.set
或Vue.delete
使用); - 无法原生监听数组,需要特殊处理。
带着 Object.defineProperty
的这几个缺点,接下来我们开始进入 Proxy
的世界。
二、🟨Proxy基本使用
下面用一段代码来演示 Proxy
的基本使用。具体代码如下:
const data = {
name: 'monday',
age:18
}
//const data = ['a', 'b', 'c']
const proxyData = new Proxy(data, {
get(target, key, receiver){
//只处理本身(非原型的)属性
const ownKeys = Reflect.ownKeys(target)
if(oenKeys.includes(key)){
console.log('get', key) //监听
}
const result = Reflect.get(target, key, receiver)
return result // 返回结果
},
set(target, key, val, receiver){
//重复的数据,不处理
if(val === target[key]){
return true
}
const result = Reflect.set(target, key, val, receiver)
console.log('set', key, val)
//console.log('result', result) //true
return result // 是否设置成功
},
deleteProperty(target, key){
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key)
return result // 是否删除成功
}
})
通过以上代码可得,我们先定义一个对象字面量的 data
,之后在作为 Proxy
实例化的参数进行传递。且 proxyData
实现了 get
、 set
和 deleteProperty
的方法,可以对数据进行增删改操作。
三、🟦学习Proxy语法:Reflect
我们再来认识 Proxy
的一个好朋友,Reflect。
Reflect
对象有着和 Proxy
一一对应的能力,Reflect对象一共有 13 个静态方法,这也就是我们平常所听到的 proxy
有多达13种拦截行为,而 Reflect
的这13种静态方法匹配的就是 Proxy
的13种拦截行为 。
静态方法列表 |
---|
Reflect.get(target, name, receiver) |
Reflect.set(target, name, value, receiver) |
Reflect.has(obj, name) |
Reflect.deleteProperty(obj, name) |
Reflect.construct(target, args) |
Reflect.getPrototypeOf(obj) |
Reflect.setPrototypeOf(obj, newProto) |
Reflect.apply(func, thisArg, args) |
Reflect.defineProperty(target, propertyKey, attribute) |
Reflect.getOwnPropertyDescriptor(target, propertyKey) |
Reflect.isExtensible(target) |
Reflect.preventExtensions(target) |
Reflect.ownKeys(target) |
Reflect
的出现是为了替换掉 Object
上的工具函数,这里不做具体介绍,详情可查看文档 。
四、🟧Vue3如何用Proxy实现响应式
1、实现响应式
下面来实现Proxy的响应式。附上代码:
// 创建响应式
function reactive(target = {}) {
if (typeof target !== 'object' || target == null) {
// 不是对象或数组,则返回
return target
}
// 代理配置
const proxyConf = {
get(target, key, receiver) {
// 只处理本身(非原型的)属性
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(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.includes(key)) {
console.log('已有的 key', key)
} else {
console.log('新增的 key', key)
}
const result = Reflect.set(target, key, val, receiver)
console.log('set', key, val)
// console.log('result', result) // true
return result // 是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key)
// console.log('result', result) // true
return result // 是否删除成功
}
}
// 生成代理对象
const observed = new Proxy(target, proxyConf)
return observed
}
// 测试数据
const data = {
name: 'monday',
age: 18,
info: {
city: 'FuZhou',
a: {
b: {
c: {
d: {
e: 100
}
}
}
}
}
}
const proxyData = reactive(data)
我们在控制台来验证数据:
从上图中可以看到,用 proxy
来实现响应式,如果遇到需要深度递归的数组时,它不会像 defineProperty
那样深度递归,它会在什么时候 get
,什么时候再深度递归。本质上来讲就是,你获取到哪一层,那一层才会触发响应式。你获取不到的深层,它就不会触发响应式。且从代码中我们可以了解到, Proxy
在修改属性时,如果数据是重复的,则不进行处理。如果数据不重复,再进行处理。这样一来,就极大程度上提高了软件的性能。
2、Proxy总结
现在来对上述Proxy的内容做一个总结:
(1)深度监听,性能更好
defineProperty
是一次性递归完成;而 Proxy
是什么时候 get
,什么时候再深度递归。
(2)可监听 新增/删除 属性
在 vue2
中, defineProperty
是无法新增/删除属性的,需要配合 Vue.set
和 Vue.delete
来使用,而在 Vue3
中, Proxy
可以新增和删除属性,无需进行特殊处理。
(3)可监听数组变化
在 vue2
中,监听数组变化是需要进行特殊处理,且只能一次性深度递归完成。而在 vue3
中,可以监听数组变化,并且是什么时候get什么时候再递归,获取不到的深层,不会触发响应式。
3、两者对比
讲到这里,我们再把 vue2
中的 Object.defineProperty
和 vue3
中的 Proxy
做一个对比:
Proxy
能良好的规避Object.defineProperty
的问题;Proxy
无法兼容所有浏览器(如IE11
),且无法polyfill
。
五、🟪结束语
从某种程度上来说, vue3
的 Proxy
确实带来了一些好处,但同时也带来了一些问题。正因为如此, vue2
的 Object.defineProperty
还会存在很长一段时间。所以,新技术的使用总会经过一个从试用阶段到稳定阶段的过程。
关于vue3的响应式原理讲到这里就结束啦!如有疑问或文章有误欢迎评论区留言或私信交流~
- 关注公众号 星期一研究室 ,第一时间关注技术干货,更多有趣的专栏待你解锁~
- 如果这篇文章对你有用,记得 一键三连 再走哦!
- 我们下期见!🥂🥂🥂
以上是关于通过Proxy和Reflect实现vue的响应式原理的主要内容,如果未能解决你的问题,请参考以下文章
敲黑板,划重点!!!Vue3.0响应式实现原理 —— proxy()
敲黑板,划重点!!!Vue3.0响应式实现原理 —— proxy()
敲黑板,划重点!!!Vue3.0响应式实现原理 —— proxy()