• 初学Vue(全家桶)-第19天(vue3):vue3响应式原理


    初学vue

    vue3响应式原理

    1、回顾vue2中的响应式原理

    1.1 原理

    1、对于对象类型:
    通过Object.defineProperty()对属性的读取,修改进行拦截(数据劫持)
    2、对于数组类型:通过重写更新数组的一系列方法来实现拦截(对数组的变更方法进行了包裹)

    Object.defineProperty(data,'xxx',{
    	get(){},
    	set(){}
    })
    
    • 1
    • 2
    • 3
    • 4

    模拟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.2 存在的问题

    1、新增属性、删除属性,界面不会自动更新(vue2捕获不到)
    2、直接通过下标修改数组,界面不会自动更新(vue2捕获不到)

    1.3 解决存在的问题

    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>
    
    • 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
    • 45
    • 46
    • 47
    • 48

    2、vue3中的响应式原理

    实现原理

    • 通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写,属性的添加,属性的删除等
    • 通过Reflect(反射):对被代理对象(源对象)的属性进行操作
    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)
    	}
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    模拟分析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

    在这里插入图片描述

  • 相关阅读:
    神经网络训练过程可视化,可视化神经网络工具
    idea安装汉化插件
    计算机物联网控制|5.5常规控制方案 5.6先进控制方案
    WebWall-03.XSS(跨站脚本漏洞)
    盘点支付账户体系(分账接口)的9大价值
    Linux下redis单机安装、主从搭建及哨兵模式搭建及springboot整合测试
    Spring之CGLIB和JDK动态代理底层实现
    springboot(ssm 精准扶贫管理系统 乡村扶贫平台Java(code&LW)
    电压放大器在电子实验中有哪些作用
    力扣(LeetCode)247. 中心对称数 II(2022.09.04)
  • 原文地址:https://blog.csdn.net/lalala_dxf/article/details/125395604