Vue3的响应式原理的核心是: - 通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等等。 - 通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作。
相关的参考文档:
1.什么是Proxy MDN 上的描述是:Proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。 其实就是在对目标对象的操作之前提供了拦截,可以对外界的操作进行过滤和改写,修改某些操作的默认行为,这样我们可以不直接操作对象本身,而是通过操作对象的代理对象来间接来操作对象,达到预期的目的。
下面我们看个例子,就一目了然了。
let obj = {
a : 1
}
let proxyObj = new Proxy(obj,{
get : function (target,prop) {
return prop in target ? target[prop] : undefined
},
set : function (target,prop,value) {
target[prop] = 888;
}
})
console.log(proxyObj.a); // 1
console.log(proxyObj.b); // undefined
proxyObj.a = 100;
console.log(proxyObj.a) // 888
上述例子中,我们事先定义了一个对象 obj , 通过 Proxy 构造器生成了一个 proxyObj 对象,并对其的 set(写入) 和 get (读取) 行为重新做了修改。
当我们访问对象内原本存在的属性时,会返回原有属性内对应的值,如果试图访问一个不存在的属性时,会返回0 ,即我们访问 proxyObj.a 时,原本对象中有 a 属性,因此会返回 1 ,当我们试图访问对象中不存在的 b 属性时,就返回 undefined,当我们试图去设置新的属性值的时候,总是会返回 888 ,因此,即便我们对 proxyObj.a 赋值为 100 ,但是并不会生效,依旧会返回 888!
2.Vue3的响应式原理分析
Vue3的响应式通过Proxy+Reflect实现。 实例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Proxy 与 Reflect</title>
</head>
<body>
<script>
const user = {
name: "John",
age: 12
};
/*
proxyUser是代理对象, user是被代理对象
后面所有的操作都是通过代理对象来操作被代理对象内部属性
*/
const proxyUser = new Proxy(user, {
get(target, prop) {
console.log('劫持get()', prop)
return Reflect.get(target, prop)
},
set(target, prop, val) {
console.log('劫持set()', prop, val)
return Reflect.set(target, prop, val); // (2)
},
deleteProperty(target, prop) {
console.log('劫持delete属性', prop)
return Reflect.deleteProperty(target, prop)
}
});
// 读取属性值
console.log(proxyUser === user)
console.log(proxyUser.name, proxyUser.age)
// 设置属性值
proxyUser.name = 'bob'
proxyUser.age = 13
console.log(user)
// 添加属性
proxyUser.sex = '男'
console.log(user)
// 删除属性
delete proxyUser.age
console.log(user)
</script>
</body>
</html>
运行结果:
false
劫持get() name
劫持get() age
John 12
劫持set() name bob
劫持set() age 13
{name: "bob", age: 13}
劫持set() sex 男
{name: "bob", age: 13, sex: "男"}
劫持delete属性 age
{name: "bob", sex: "男"}
把上小节案例改写如下:
通过点击按钮实现更新学生的课程列表。
<template>
<!--与vue2模板的区别:不需要定义一个根标签-->
<h1>第一个Vue3案例</h1>
<hr>
<table class="mytab">
<tr>
<th>学号</th>
<th>姓名</th>
<th>年龄</th>
<th>课程</th>
</tr>
<tr>
<td>{{proxyStu.sid}}</td>
<td>{{proxyStu.sname}}</td>
<td>{{proxyStu.age}}</td>
<td>
<li v-for="(c,index) in proxyStu.courses" :key="index">{{c}}</li>
</td>
</tr>
<tr>
<td colspan="4">
<button @click="changeStudentInfo">更改学生属性</button>
</td>
</tr>
</table>
</template>
<script lang="ts">
import {defineComponent, ref, reactive} from 'vue';
export default defineComponent({
name: 'App',
setup() {
let stu: any = {
sid: 'S001',
sname: 'zhangsan',
age: 20,
courses:['英语', '物理', '化学']
}
//返回代理对象
const proxyStu = reactive(stu);
function changeStudentInfo() {
proxyStu.courses[1] = '语文'
proxyStu.courses.push('历史');
console.log(stu);
console.log(proxyStu);
}
return {
stu,
proxyStu,
changeStudentInfo
}
}
});
</script>
运行效果:
