VUE3响应式原理,及与VUE2响应式的区别
响应式原理和vue
响应原理的相关概念,在之前的博客中已经说明。vue3
的响应式实现是基于ES6
中的 proxy
和Reflect
实现的,与vue2
借助object.defineProperty
有所不同。要想理解vue3
的响应式实现,必须要先了解proxy
和 Reflect
的作用。
Proxy(代理模式)
Proxy 译为代理,用于修改某些操作的默认行为,就形同于在编程语言层面上做修改,属于元编程。可简单理解为在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,进而能实现对外界的访问进行过滤和改写。
语法
Proxy构造函数:var proxy = new Proxy(target, handler);
- target: 需要拦截的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理),但target 不能是原始值(如字符串、数字、布尔值、null 或 undefined)或不可遍历的数据结构(如普通变量)。
- handler: 用来定制拦截行为。
常用的拦截方法
方法 | 描述 |
---|---|
get(target, propKey, receiver) | 拦截对象属性的读取 |
set(target, propKey, value, receiver) | 拦截对象属性的设置 |
has(target, propKey) | 拦截propKey in proxy 的操作,返回一个布尔值。 |
deleteProperty(target, propKey) | 拦截对象属性删除操作 |
ownKeys(target) | 拦截Object.getOwnPropertyNames(proxy) 、Object.getOwnPropertySymbols(proxy) 、Object.keys(proxy) 、for...in 循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys() 的返回结果仅包括目标对象自身的可遍历属性。 |
apply(target, object, args) | 拦截 Proxy 实例作为函数调用的操作。比如proxy(...args) 、proxy.call(object, ...args) 、proxy.apply(...) 。 |
construct(target, args) | 拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args) 。 |
参数说明:
- target: 操作的目标对象
- propkey: 操作目标对象的关键字
- receiver: 当前的Proxy对象或者继承的Proxy对象
Proxy的this问题
Proxy可以代理针对目标对象的访问,但它不是目标对象的透明代理。简单理解就是在 Proxy 代理的情况下,目标对象内部的this
关键字会指向 Proxy 代理。,下面是个简单的例子:
const _name = new WeakMap();
class Person {
constructor(name) {
_name.set(this, name);
}
get name() {
return _name.get(this);
}
}
const jane = new Person('Jane');
jane.name // 'Jane'
const proxy = new Proxy(jane, {});
proxy.name // undefined 由于通过proxy.name访问时,this指向proxy,导致无法取到值
同理,有些原生对象的内部属性,Proxy 也无法代理这些原生对象的属性。
const target = new Date();
const proxy = new Proxy(target, {});
proxy.getDate();// TypeError: this is not a Date object.
Reflect
Reflect
对象与Proxy
对象一样,也是 ES6
为了操作对象而提供的新 API
。Reflect
对象的出现使得在 JavaScript 中进行元编程变得更加方便和一致。其设计的目的归结于一下几点:
- **为了更好地分离语言内部的方法和普通的对象操作方法:**将
Object
对象的一些明显属于语言内部的方法(比如Object.defineProperty
),放到Reflect
对象上,从而 - **提供了一组操作对象时的标准行为:**修改某些
Object
方法的返回结果,让其变得更合理。例如Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。 - 提供了一组与操作符相对应的方法: 让
Object
操作都变成函数行为。例如Reflect.get()
、Reflect.set()
、Reflect.has()
等,这些方法与对应的操作符(如.
、[]
、in
等)具有相似的功能。 - 让
Proxy
对象可以方便地调用对应的Reflect
方法:Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。总之,不管Proxy
怎么修改默认行为,你总可以在Reflect
上获取默认行为。
总的来说,Reflect 对象的设计目的是为了提供一组操作对象的方法,使得在 JavaScript 中进行元编程更加方便、一致和可靠,得开发者可以更加灵活地操作对象和修改程序的行为。
VUE3 数据劫持的简单复现
// 监听数据 reactive(引用数据类型) ref(基础数据类型)
function isObject(obj) {
return typeof obj === 'object' && obj !== null;
}
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
console.log("获取" + key + "值")
let res = Reflect.get(target, key, receiver)
// 递归调用
return isObject(res) ? reactive(res) : res
},
set(target, key, receiver) {
console.log("设置" + key + "值")
return Reflect.set(target, key, receiver)
},
deleteProperty(target, key) {
console.log("删除" + key + "值")
return Reflect.deleteProperty(target, key)
}
})
}
let test = reactive({name: '张三', request: { code: '200' }})
test.name = "李四"
console.log(test.request.code)
test.addKey = 'test'
delete test.addKey
console.log("数组对象操作")
let arr = reactive([1,2,3])
arr.push(4)
VUE2 与 VUE3 响应式的区别
VUE2 响应式(object.defineProperty)的缺陷
- 对对象的添加和删除操作,无法劫持 (解决方法:新增 set 和 delete)
- 对数组的
api
无法劫持到 (解决方法:重写数组的api
) - 存在深层嵌套关系,性能问题(无脑递归)
VUE3响应式
- 解决了
Vue2
中无法追踪数据新增或删除属性的问题 - 可以直接监听数组,无需想
Vue2
响应式那样需要重写数组拦截方式
vue2响应式的实现请看之前的博客
Proxy详解:https://www.bookstack.cn/read/es6-3rd/spilt.1.docs-proxy.md
Reflect详解:https://www.bookstack.cn/read/es6-3rd/docs-reflect.md