本文已参与「新人创作礼」活动,一起开启掘金创作之路。
vue2的响应式原理是通过object.defineProperty来劫持各个属性的setter和getter在数据发生变化的时候发布消息给订阅者,触发响应的监听回调。这里不再对vue2响应原理做过多解释,可以查看
在vue2中通过object.defineProperty实现响应式原理时新增属性、删除属性界面不会更新;直接通过下标修改数组,界面也不会自动更新(vue2也做了相对解决$set,$delete);下面来具体介绍$set、$delete和静默刷新
1、$set 、$delete
语法: - vm.$set("被设置的对象" , "属性/索引" , "值")- vm.$delete("被设置的对象" , "属性/索引") 下面看看直接添加、删除对象属性(代码仅供测试,可忽略)
<template>
<div>
<p>姓名:{{obj.name}}</p>
<p>年龄:{{obj.age}}</p>
<p>性别:{{obj.sex}}</p>
<button @click="addSex">添加性别sex</button>
<button @click="delAge">删除年龄age</button>
</div>
</template>
<script>
export default {
name: "",
data() {
return {
obj:{
name:'zs',
age:20
}
};
},
created() {},
computed: {},
methods: {
addSex(){
this.obj.sex = '男'
console.log("添加性别sex",this.obj);
},
delAge(){
delete this.obj.age
console.log("删除年龄age",this.obj);
}
},
};
</script>
<style lang="less" scoped></style>
2、静默刷新
addSex() {
this.flag = false;
this.obj.sex = "男"; //不是响应式
//this.$set(this.obj, "sex", "男"); //是响应式
console.log("添加性别sex", this.obj);
this.flag = true;
},
delAge() {
this.flag = false;
delete this.obj.age; //不是响应式
//this.$delete(this.obj, "age"); //是响应式
console.log("删除年龄age", this.obj);
this.flag = true;
},
可以看出静默刷新利用v-if重新加载节点实现响应刷新页面数据,这样面对大量添加的属性和未知属性也是比较方便的
3、proxy
语法:const p = new proxy("新对象" , "函数对象") 下面看看vue3的新增和删除属性会不会实现界面刷新
<template>
<div>
<p>姓名:{{ obj.name }}</p>
<p>年龄:{{ obj.age }}</p>
<p>性别:{{ obj.sex }}</p>
<button @click="addSex">添加性别sex</button>
<button @click="delAge">删除年龄age</button>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
name: "HelloWorld",
setup() {
let obj = reactive({
name: "zs",
age: 20,
});
function addSex() {
obj.sex = "男";
console.log("添加性别sex", this.obj);
}
function delAge() {
delete obj.age;
console.log("删除年龄age", this.obj);
}
return {
obj,
addSex,
delAge,
};
},
};
</script>
<style scoped></style>
可以看出vue3中对象属性的增加和删除,界面会随之更新,这是因为vue2的响应式为object.defineProperty而vue3是proxy搭配Reflect
//因为proxy就两个参数,所以怎么判断是哪个属性呢
const p = new Proxy(obj, {
get(target, prop) { //target:对象 prop:属性
console.log("get:", target, prop);
return prop;
},
set(target, prop, value) { //target:对象 prop:属性 value:新的值
console.log("set:", target, prop, value);
return true;
},
deleteProperty(target, prop) { //删除
console.log("delete:", target, prop);
return delete target[prop];
},
});
接下来将代码进行优化
<template>
<div>
<p>姓名:{{ obj.name }}</p>
<p>年龄:{{ obj.age }}</p>
<p>性别:{{ obj.sex }}</p>
<button @click="putAge">修改年龄</button>
<button @click="getName">读取姓名</button>
<button @click="delSex">删除性别</button>
<button @click="addClass">增加班级</button>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
name: "HelloWorld",
setup() {
let obj = reactive({
name: "zs",
age: 20,
sex: "男",
});
const p = new Proxy(obj, {
get(target, prop) {
console.log("get:", target, prop);
return prop;
},
set(target, prop, value) {
target[prop] = value;
console.log("set:", target, prop, value);
console.log("有人修改了", prop);
return true;
},
deleteProperty(target, prop) {
console.log("delete:", target, prop);
console.log("有人删除了", prop);
return delete target[prop];
},
});
function putAge() {
p.age = 18;
}
function getName() {
console.log("有人读取了", p.name);
}
function delSex() {
delete p.sex;
}
function addClass() {
p.class = "www"; //增加对象属性调用的是set
}
return {
obj,
putAge,
getName,
delSex,
addClass,
};
},
};
</script>
<style scoped></style>
下面做一下总结:
- 获取属性调用proxy中的get函数
- 修改和新增调用proxy中的set函数
- 删除调用的 proxy中的deleteProperty函数 而vue3中proxy是和Reflect搭配着使用的,下面来简单介绍下Reflect
4、Reflect
Reflect直译为映射,官方解释它是一个内置对象,提供拦截js操作的方法,这些方法与proxy handlers的方法相同,Reflect不是一个函数对象,因此它是不可构造的,它的所有属性和方法都是静态的(和Math一样),下面将代码进行改进一下
<template>
<div>
<p>姓名:{{ obj.name }}</p>
<p>年龄:{{ obj.age }}</p>
<p>性别:{{ obj.sex }}</p>
<p>班级:{{ obj.class }}</p>
<button @click="putAge">修改年龄</button>
<button @click="getName">读取姓名</button>
<button @click="delSex">删除性别</button>
<button @click="addClass">增加班级</button>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
name: "HelloWorld",
setup() {
let obj = reactive({
name: "zs",
age: 20,
sex: "男",
});
const p = new Proxy(obj, {
get(target, prop) {
console.log("get:", target, prop);
return Reflect.get(target, prop);
},
set(target, prop, value) {
target[prop] = value;
console.log("set:", target, prop, value);
console.log("有人修改了", prop);
return Reflect.set(target, prop, value);
},
deleteProperty(target, prop) {
console.log("delete:", target, prop);
console.log("有人删除了", prop);
return Reflect.deleteProperty(target, prop);
},
});
function putAge() {
p.age = 18;
}
function getName() {
console.log("有人读取了", p.name);
}
function delSex() {
delete p.sex;
}
function addClass() {
p.class = "www";
}
return {
obj,
putAge,
getName,
delSex,
addClass,
};
},
};
</script>
<style scoped></style>
那Reflect有什么特点和好处呢?
- 将object对象的一些明显属于语言内部的方法(比如object.defineProperty),放到Reflect对象上,现阶段,某些方法同时在object和Reflect对象上部署,未来的新方法只部署在Reflect对象上,也就是说Reflect可以拿到内部方法
- 修改object方法的返回结果,比如object.defineProperty在无法定义属性时会抛出错误,可用try{...}catch{...}来捕获错误消息,而Reflect直接会返回false
- 让object操作都变成函数行为,某些object操作是命令式的,Reflect会将其变成函数式- Reflect方法跟Proxy对象的方法是对应的,Proxy的方法在Reflect都能找到,尽管修改了Proxy默认行为,Reflect也是能够获取默认行为的 以上总结参考程序思维
总结:
- 通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等- 通过Reflect(反射):对被代理对象的属性进行操作