vue2响应式原理

通过Object.defineProperty()对属性的读取,修改进行数据劫持;

get捕获 获取的数据;set捕获 修改的数据;但是添加和删除属性是捕获不到的;

1
2
3
4
5
// 给哪个对象追加属性,追加什么属性,对象 get 和set方法
Object.defineProperties(p,"name",{
get(){},
set(){}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 源数据
let person={
name:"张三",
age:20
};
// 模拟vue2实现响应式
// 1.准备一个空对象
let p = {};
// 给哪个对象追加属性,追加什么属性,对象 get 和set方法
Object.defineProperty(p,"name",{
get(){//有人读取name时使用
return person.name; //将原数据返回
},
set(value){//有人修改name时使用
console.log("有人修了了name属性");
person.name=value;
}
});


Object.defineProperty(p,"age",{
get(){//有人读取name时使用
return person.age; //将原数据返回
},
set(value){//有人修改name时使用
console.log("有人修了了age属性");
person.age=value;
}
})

添加删除捕获不到;

vue2

vue2响应式出现的问题及解决方法

对象中,新增属性、删除属性、界面不会更新(数据会变);

问题复现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<template>
<div>
<h2>{{ person.name }}</h2>
<h2 v-show="person.age">{{ person.age }}</h2>
<h2 v-show="person.sex">{{ person.sex }}</h2>

<button @click="addSex">添加一个属性</button>
<button @click="delName">删除一个属性</button>
</div>
</template>

<script>
export default {
data() {
return {
person: {
name: "张三",
age: 18,
},
};
},
methods: {
// 添加属性
addSex() {
console.log(this.person.sex); //undefined
this.person.sex = "女";
console.log(this.person.sex); // 女
},
// 删除属性
delName() {
console.log(this.person.age); //18
delete this.person.age;
console.log(this.person.age); //undefined
},
},
};
</script>

<style>
</style>

点击添加属性的按钮;点击删除对象的按钮;

person对象中有值了,但是页面不渲染

obj

解决方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//使用 Vue.set的方式需要 从 vue中引入 Vue;
import Vue from "vue";
export default {
data() {
return {
person: {
name: "张三",
age: 18,
},
};
},
methods: {
// 添加属性
addSex() {
// 对象三个参数,给谁加,加的属性,加的值;
// this.$set(this.person,"sex","女"); //方法一,this代表 Vue的实例;
Vue.set(this.person,"sex","女"); //方法二,需要从 vue中引入 Vue
},
// 删除属性
delName() {
// 对象两个参数,删除谁,删除啥属性;
// this.$delete(this.person,"name"); //方法一,this代表 Vue的实例;
Vue.delete(this.person,"age"); //方法二,需要从 vue中引入 Vue
},
},
};

直接通过下标修改数组,界面不会自动更新

问题复现

点击之后数组变了,页面不渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>
<div>
<h2>{{ person.hobby }}</h2>
<button @click="changeLove">修改第一个爱好</button>
</div>
</template>

<script>
//使用 Vue.set 需要 从 vue中引入;
import Vue from "vue";
export default {
data() {
return {
person: {
name: "张三",
age: 18,
hobby:["学习","干饭"]
},
};
},
methods: {
// 通过下标修改数组
changeLove(){
console.log(this.person.hobby);
this.person.hobby[0]="study";
console.log(this.person.hobby);
}
},
};
</script>

<style>
</style>

arr

解决方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//使用 Vue.set 需要 从 vue中引入;
import Vue from "vue";
export default {
data() {
return {
person: {
name: "张三",
age: 18,
hobby:["学习","干饭"]
},
};
},
methods: {
// 通过下标修改数组
changeLove(){
// 数组三个参数, 修改谁,修改的索引,改成啥;
// this.$set(this.person.hobby,0,"study"); //方法一,this代表 Vue的实例;
// Vue.set(this.person.hobby,0,"studing"); //方法二,需要从 vue中引入 Vue
this.person.hobby.splice(0,1,"打游戏"); //方法三,开始的索引,删除几个,替换成谁;
}
},
};

vue3响应式原理

vue3响应式解决了vue2中响应式出现的问题;

通过Proxy(代理)对源对象变化的属性进行拦截,通过Reflect(反射)对源对象的属性进行操作;

知识点:

window.Proxy–>window内置的构造函数

1
2
3
4
5
6
7
8
// 源数据
let person = {
name: "张三",
age: 20
};
// 模拟vue3中的响应式
// Proxy的作用是 用p映射person的操作 参数:映射的对象、空对象
const p = new Proxy(person,{});

重写一下 Proxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 源数据
let person = {
name: "张三",
age: 20
};
// 模拟vue3中的响应式
// Proxy的作用是 用p映射person的操作 参数:映射的对象、对象get set
const p = new Proxy(person,{
get(target,propName){
// target为 源数据 , propName 为 获取的属性
console.log("某人读取了p上的属性",target,propName);

/**
* 这里有个小知识点:
* 变量1.变量2 == 变量1["变量2"] --> 即.变量2 中的 变量2 其实已经不是变量了
* 变量用[];字符串用点或者[""]
* */

// return target.propName; // undefined
return target[propName]; //张三
},
set(target,propName,value){
// target为 源数据 , propName 为 获取的属性, value是设置的数据
console.log(`修改了p身上的${propName},我要去更新界面了`);
target[propName]=value;
},
// 新增了一个删除方法
deleteProperty(target,propName){
console.log(`删除了p身上的${propName},我要去更新界面了`);
// delete target[propName]; //false ,因为需要返回值,没有返回值为 false;
return delete target[propName];
}
});

通过重写,发现 操作 都可以捕获到

vue3

但 vue3的底层 并不是通过 通过 获取到 target[propName] 这种来修改的;这种有缺陷,而是下面的Reflect;

知识点: 获取对象的某个属性,除了 obj.属性之外还有下面这个新增的方法

window.Reflect() ;反射对象

Reflect.get(反射的对象,key);Reflect.set(反射的对象,key,value);Reflect.deleteProperty(反射的对象,key)

传统的 出现相同属性会出错,封装时需要大量的try,,,catch;Reflect 不会出错;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 let obj = {a:1,b:2};
//Object.defineProperty会报错,封装的话 得需要大量的 try,catch
// Object.defineProperty(obj,'c',{
// get(){
// return 3;
// }
// })

// Object.defineProperty(obj,'c',{
// get(){
// return 4;
// }
// })
//会报错 代码不继续往下走了
// console.log(123) //不会输出的

// Reflect.defineProperty不会报错,且有返回值
const x1 = Reflect.defineProperty(obj,'c',{
get(){
return 3;
}
})

const x2= Reflect.defineProperty(obj,'c',{
get(){
return 4;
}
})
// console.log(x1); //true
// console.log(x2); //false
if(x2){
console.log("某某操作成了")
}else{
console.log("某某操作失败了");
}

将重写的代码稍加改动 (vue3响应式原理)

通过Proxy(代理)对源对象变化的属性进行拦截,通过Reflect(反射)对源对象的属性进行操作;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 源数据
let person = {
name: "张三",
age: 20
};
// 模拟vue3中的响应式
// Proxy的作用是 用p映射person的操作 参数:映射的对象、对象get set
const p = new Proxy(person,{
get(target,propName){
// target为 源数据 , propName 为 获取的属性
console.log("某人读取了p上的属性",target,propName);

/**
* 这里有个小知识点:
* 变量1.变量2 == 变量1["变量2"] --> 即.变量2 中的 变量2 其实已经不是变量了
* 变量用[];字符串用点或者[""]
* */

// return target.propName; // undefined
// return target[propName]; //张三
return Reflect.get(target,propName);
},
set(target,propName,value){
// target为 源数据 , propName 为 获取的属性, value是设置的数据
console.log(`修改了p身上的${propName},我要去更新界面了`);
// target[propName]=value;
Reflect.set(target,propName,value);
},
// 新增了一个删除方法
deleteProperty(target,propName){
console.log(`删除了p身上的${propName},我要去更新界面了`);
// delete target[propName]; //false ,因为需要返回值,没有返回值为 false;
// return delete target[propName];
return Reflect.deleteProperty(target,propName);
}
});