1、对于对象类型:
通过Object.defineProperty()对属性的读取,修改进行拦截(数据劫持)
2、对于数组类型:通过重写更新数组的一系列方法来实现拦截(对数组的变更方法进行了包裹)
Object.defineProperty(data,'xxx',{
get(){},
set(){}
})
模拟vue2响应式原理
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script type="text/javascript"> // 源数据 let person = { name:'张三', age:18 } // 模拟Vue2中实现响应式 let p = {} Object.defineProperty(p,'name',{ configurable:true; // 有人读取name时调用 get(){ return person.name }, // 有人修改name时调用 set(value){ person.name = value console.log("有人修改了name属性,现在去更新界面!") } }) Object.defineProperty(p,'age',{ configurable:true; // 有人读取age时调用 get(){ return person.age }, // 有人修改age时调用 set(value){ console.log("有人修改了age属性,现在去更新界面!") person.age = value } }) </script> </body> </html>
- 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
- 41
- 42
- 43
- 44
结果:
可以看到,vue2并不能响应式的新增属性,删除属性时还有进行configurable:true;的配置,此外对于数组的操作也是不方便。
1、新增属性、删除属性,界面不会自动更新(vue2捕获不到)
2、直接通过下标修改数组,界面不会自动更新(vue2捕获不到)
1、修改更新添加操作
(1)通过this.$set(''对象","属性'',"新定义的属性值")新增或者修改属性和属性值(也可以是Vue.set("对象","属性'',"新定义的属性值"))
(2)或者通过this.$set("数组名",索引,"修改后的值")修改数组中指定索引出的数据(也可以是Vue.set("数组名",索引,"修改后的值")
2、删除操作
(1)通过this.$delete(''对象","属性'')删除对象中指定属性(也可以是Vue.delete(''对象","属性''))
(2)通过Vue.delete("对象","属性")删除对象中指定属性
(也可以是Vue.delete(数组名,索引)删除数组指定索引处的值)

<template>
<div>
<h2>我是Vue2写的效果</h2>
<h4>姓名:{{ person.name }}</h4>
<h4 v-show="person.age">年龄:{{ person.age }}</h4>
<h4 v-show="person.gender">性别:{{ person.gender }}</h4>
<h4 v-show="person.hobbies">爱好:{{person.hobbies}}</h4>
<button @click="addGender">添加一个gender属性</button>
<button @click="deleteAge">删除age属性</button>
<button @click="changeArr">更改数组第一个索引的值</button>
</div>
</template>
<script>
export default {
name: "Test01",
data() {
return {
person: {
name: "张三",
age: 21,
hobbies:['唱歌',"跳舞","睡觉"]
},
};
},
methods: {
addGender() {
// 错误示范
// this.person.gender="男"
// 正确做法:
this.$set(this.person, "gender", "男");
//第二种方法
// Vue.set(this.person,"gender","男")
},
deleteAge() {
this.$delete(this.person,'age')
},
changeArr(){
this.$set(this.person.hobbies,0,"摄影")
}
},
};
</script>
<style>
</style>
new Proxy(data,{
// 拦截读取属性值
get(target, prop){
return Reflect.get(target,prop)
},
set(target,prop,value){
return Reflect.set(target,prop,value)
},
deleteProperty(target,prop){
return Reflect.deleteProperty(target,porp)
}
})
模拟分析vue3中的响应式原理
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script type="text/javascript"> let person = { name:"张三", age:18 } // 模拟Vue3中实现响应式 // 实现Proxy代理,p是代理对象 // new Proxy()中传两个参数,一个是源对象,第二个参数必须传,没有数据就传一个空对象 const p = new Proxy(person,{ /*读取属性:有人读取p的某个属性时调用*/ // get中有两个参数,第一个参数是源对象,第二个参数是读取的属性 get(target,propName){ console.log(`有人读取了p身上的${propName}属性,我要去更新界面了`); // target[propName]是中括号访问属性的写法,因为propName接收的是属性,不能使用点访问 return target[propName] }, /*修改\添加属性:有人读写p的某个属性时调用*/ // set中有三个参数,第一个参数是源对象,第二个参数是读取的属性,第三个参数是要添加/修改的值 set(target,propName,value){ console.log(`有人修改了p身上的${propName}属性,我要去更新界面了`); target[propName] = value }, /*删除属性:有人删除p的某个属性时调用*/ deleteProperty(target,propName){ console.log(`有人删除了p身上的${propName}属性,我要去更新界面了`) return delete target[propName] } }) </script> </body> </html>
- 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
结果:
但需要注意的是,set(),get(),deleteProperty()方法中都是通过return target[propName]直接修改源数据,vue3底层并不是这么做的,而是通过reflect(反射)实现对属性的操作。如下:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script type="text/javascript"> let person = { name:"张三", age:18 } // 模拟Vue3中实现响应式 // 实现Proxy代理,p是代理对象 // new Proxy()中传两个参数,一个是源对象,第二个参数必须传,没有数据就传一个空对象 const p = new Proxy(person,{ /*读取属性*/ // get中有两个参数,第一个参数是源对象,第二个参数是读取的属性 get(target,prop){ console.log(`有人读取了p身上的${prop}属性,我要去更新界面了`); // 通过Reflect映射对象对属性进行操作 return Reflect.get(target,prop) }, /*修改\添加属性*/ // set中有三个参数,第一个参数是源对象,第二个参数是读取的属性,第三个参数是要添加/修改的值 set(target,prop,value){ console.log(`有人修改了p身上的${prop}属性,我要去更新界面了`); // 通过Reflect映射对象对属性进行操作 return Reflect.set(target,prop,value) }, /*删除属性*/ // deleteProperty中有两个参数,第一个参数是源对象,第二个参数是要删除的属性 deleteProperty(target,prop){ console.log(`有人删除了p身上的${prop}属性,我要去更新界面了`) // 通过Reflect映射对象对属性进行操作 return Reflect.deleteProperty(target,prop) } }) </script> </body> </html>
- 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
- 41
- 问题:通过Object.defineProperty也可以进行添加属性,那为什么不直接用这个方法呢?
答:
例如,当使用Object.defineProperty去重复添加一个属性时,,会报出错误,并且整段代码全部停止运行,此时需要try…catch进行捕获,后面的代码才会继续执行完<script type="text/javascript"> let obj = {a:1,b:2} Object.defineProperty(obj,'c',{ get(){ return 3 } }) Object.defineProperty(obj,'c',{ get(){ return 4 } }) // 下面这句代码因为上面报错所以不会执行 console.log("@@@") </script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
使用try…catch捕获异常后才可以让代码继续往下运行<script type="text/javascript"> let obj = {a:1,b:2} try{ Object.defineProperty(obj,'c',{ get(){ return 3 } }) Object.defineProperty(obj,'c',{ get(){ return 4 } }) }catch(error){ console.log(error) } // 下面这句代码因为上面捕获了错误,所以会执行 console.log("@@@") </script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
而Reflect对象身上同样有defineProperty方法,但和Object身上的defineProperty方法不同的是,在相同情况下会运行完整段代码,无需捕获异常,Reflect.defineProperty方法的返回值为布尔类型,如果重复定义了相同的属性,那么第一次定义的属性的返回值才会为true,其他的为false,这样做对框架的封装更加友好。// 源对象 let obj = {a:1,b:2} const x1 = Reflect.defineProperty(obj,'c',{ get(){ return 3 } }) console.log(x1) // true const x2 = Reflect.defineProperty(obj,'c',{ get(){ return 4 } }) console.log(x2); // false if(x1){ // 满足条件则执行下面的代码 console.log("某某某操作成功了") }else{ // 不满足条件则执行下面的代码 console.log("某某某操作失败了") } console.log("@@@")
- 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