数据响应式是什么?
所谓 数据响应式 就是建立 响应式数据 与 依赖(调用了响应式数据的操作)之间的关系,当响应式数据发生变化时,可以通知那些使用了这些响应式数据的依赖操作进行相关更新操作,可以是DOM更新,也可以是执行一些回调函数。
Vue2 和 Vue3 响应式的区别
从Vue2 到 Vue3 都使用了响应式,那么它们之间有什么区别?
- Vue2响应式:基于
Object.defineProperty()
实现的。 - Vue3响应式:基于
Proxy()
实现的。
为什么Vue3会选择Proxy()替代defineProperty()?看下面两个例子:
Vue2响应式
// Vue2响应式声明
defineReactive(data,key,val){
Object.defineProperty(data,key,{
enumerable:true,
configurable:true,
get:function(){
console.log(`对象属性:${key}访问defineReactive的get!`)
return val;
},
set:function(newVal){
if(val===newVal){
return;
}
val = newVal;
console.log(`对象属性:${key}访问defineReactive的set!`)
}
})
}
Vue2定义对象:
let obj = {};
this.defineReactive(obj,'name','sapper');
// 修改obj的name属性
obj.name = '小明';
console.log('obj',obj.name);
// 为obj添加age属性
obj.age = 18;
console.log('obj',obj);
console.log('obj.age',obj.age);
// 为obj添加数组属性
obj.hobby = ['游戏', '原神'];
obj.hobby[0] = '王者';
console.log('obj.hobby',obj.hobby);
// 为obj添加对象属性
obj.student = {school:'大学'};
obj.student.school = '学院';
console.log('obj.student.school',obj.student.school);
从上图可以看出,使用defineProperty()定义了包含name属性的对象obj,然后添加age属性、添加hobby属性(数组)、添加student属性(对象),并分别访问,都没有触发obj对象中的get、set方法。也就是说defineProperty()定义对象不能监听添加额外属性或修改额外添加的属性的变化,再看这样一个例子:
let obj = {};
// 初始化就添加hobby
this.defineReactive(obj,'hobby',['游戏', '原神']);
// 改变数组下标0的值
obj.hobby[0] = '王者';
console.log('obj.hobby',obj.hobby);
假设一开始就为obj添加hobby属性,发现修改数组下标0的值,并没有触发obj里的set方法,也就是说defineProperty()定义对象不能监听根据自身数组下标修改数组元素的变化,注意,如果是直接用defineProperty()定义数组元素是可以监听的,但是对于数组比较大的时候就很牺牲性能,尤其考虑到性能就没有使用这种方法。
Vue3响应式
看一下Proxy代理的对象例子:
// proxy实现
let targetProxy = {name:'sapper'};
let objProxy = new Proxy(targetProxy,{
get(target,key){
console.log(`对象属性:${key}访问Proxy的get!`)
return target[key];
},
set(target,key,newVal){
if(target[key]===newVal){
return;
}
console.log(`对象属性:${key}访问Proxy的set!`)
target[key]=newVal;
return target[key];
}
})
对象应用:
// 修改objProxy的name属性
objProxy.name = '工兵';
console.log('objProxy.name',objProxy.name);
// 为objProxy添加age属性
objProxy.age = 12;
console.log('objProxy.age',objProxy.age);
// 为objProxy添加hobby属性
objProxy.hobby = ['游戏', '原神'];
objProxy.hobby[0] = '王者';
console.log('objProxy.hobby',objProxy.hobby);
// 为objProxy添加对象属性
objProxy.student = {school:'大学'};
objProxy.student.school = '学院';
console.log('objProxy.student.school',objProxy.student.school);
从上图可以发现Proxy与defineProperty的明显区别之处,Proxy能支持对象添加或修改触发get、set方法,不管对象内部有什么属性。
defineProperty
Object.defineProperty():
- defineProperty() 定义对象不能监听添加额外属性或修改额外添加的属性的变化;
- defineProperty() 定义对象不能监听根据自身数组下标修改数组元素的变化。
Vue里的用法例子:
data() {
return {
name: 'sapper',
student: {
name: 'sapper',
hobby: ['原神', '天涯明月刀'],
},
};
},
methods: {
deleteName() {
delete this.student.name;
console.log('删除了name', this.student);
},
updateArr() {
this.student.hobby[0] = '王者';
console.log('更新了this.student的hobby', this.student);
},
addItem() {
this.student.age = 21;
console.log('添加了this.student的属性', this.student);
}
}
上图中确实可以修改data里的属性,但是页面不能及时渲染,所以Vue2提供了两个属性方法解决了这个问题:Vue.$set
和Vue.$delete
。
注意不能直接this._ data.age这样去添加age属性,也是不支持的。
this.$delete(this.student, 'name'); // 删除student对象属性name
this.$set(this.student.hobby, 0, '王者'); // 更新student对象属性hobby数组
this.$set(this.student, 'age', '21'); // 添加student对象属性age
上图中可以修改data里的属性,并且页面能够及时渲染。
proxy
Proxy:解决了上面两个弊端,proxy可以实现:
- 可以直接监听对象而非对象属性,可以监听对象添加额外属性的变化;
const user = {name:'张三'}
const obj = new Proxy(user,{
get:function (target,key){
console.log("get run");
return target[key];
},
set:function (target,key,val){
console.log("set run");
target[key]=val;
return true;
}
})
obj.age = 22;
console.log(obj); // 监听对象添加额外属性打印 set run!
- 可以直接监听数组的变化。
const obj = new Proxy([2,1],{
get:function (target,key){
console.log("get run");
return target[key];
},
set:function (target,key,val){
console.log("set run");
target[key]=val;
return true;
}
})
obj[0] = 3;
console.log(obj); // 监听到了数组元素的变化打印 set run!
-
Proxy 返回的是一个新对象,而 Object.defineProperty() 只能遍历对象属性直接修改。
-
支持多达13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等,是Object.defineProperty() 不具备的。
总的来说,Vue3响应式使用Proxy解决了Vue2的响应式的诟病,从原理上说,它们所做的事情都是一样的,依赖收集和依赖更新。