Vue3笔记 - Vue3的响应式原理

vue2.x的响应式

  • 实现原理:
    • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。

    • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。

      Object.defineProperty(data, 'count', {
          get () {}, 
          set () {}
      })
      
      • 存在问题:
        • 新增属性、删除属性, 界面不会更新。
        • 直接通过下标修改数组, 界面不会自动更新。

Vue3.0的响应式

  • 实现原理:
    • 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。

    • 通过Reflect(反射): 对源对象的属性进行操作。

    • MDN文档中描述的Proxy与Reflect:

      • Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
      • Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/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, prop)
          }
      })
      
      

5.reactive对比ref

  • 从定义数据角度对比:
    • ref用来定义:基本类型数据
    • reactive用来定义:对象(或数组)类型数据
    • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象
  • 从原理角度对比:
    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。
    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
  • 从使用角度对比:
    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
    • reactive定义的数据:操作数据与读取数据:均不需要.value
<!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>
    <!-- 


        
        5、reactive对比ref
            从定义数据角度对比:
                - ref用来定义:基本类型数据
                - reactive用来定义:对象(或数组)类型数据。
                - 备注:ref也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive转为代理对象。
            动员力角度对比:
                - ref通过Object.defineProperty()getset来实现响应式(数据劫持)
                - reactive通过使用Proxy来实现响应(数据劫持),并通过Reflect操作源对象内部的数据
            从使用角度对比:
                - ref定义的数据:操作数据需要.value,读取数据是模板中直接读取不需要.value
                - reactive定义的数据:操作与读取数据,均不需要.value
     -->
    <script>
        let person = {
            name: '张三',
            age: 18
        }
        //模拟vue2中的响应式
        //#region
        // let p = {};
        // Object.defineProperty(p, 'name', {
        //     configurable: true,
        //     get() { //有人读取name时调用
        //         return person.name
        //     },
        //     set(value) {//有人修改name时调用
        //         console.log('有人修改了name属性 ,我发现了,我要去更新界面了');
        //         person.name = value
        //     }
        // })
        // Object.defineProperty(p, 'age', {
        //     get() {//有人读取age时调用
        //         return person.age
        //     },
        //     set(value) {//有人修改时age时调用
        //         console.log('有人修改了age属性 ,我发现了,我要去更新界面了');
        //         person.age = value
        //     }
        // })
        //#endregion

        //模拟vue3中的响应式
        // console.log(window.Proxy);
        //#region
        const p = new Proxy(person, {
            // 有人读取p的某个属性调用
            get(target, proName) {
                console.log(`有人读取了p身上的${proName}属性`);
                // return target[proName];//真正的修改源数据
                return Reflect.get(target, proName)  // 真正的实现响应式
            },
            // 有人修改p的某个属性时或给p追加某个属性时调用
            set(target, proName, value) {
                console.log(`有人修改了p身上的${target}属性,我要去更新界面了!`);
                // target[proName] = value//真正的修改源数据
                return Reflect.set(target, proName, value)  // 真正的实现响应式

            },
            // 有人删除p的某个属性时调用
            deleteProperty(target, proName) {
                console.log(`有人删除了p身上的${target}属性,我要去更新界面了!`);
                // return delete target[proName]//真正的修改源数据
                return Reflect.set(target, proName)  // 真正的实现响应式
            }
        });

        //#endregion

        let obj = { a: 1, b: 2 };
        // 通过Object.defineProperty去操作
        //#region
        // 报错不能重复定义
        // try {
        //     //    使用Object.defineProperty,不健壮,一旦抛出错误,整个程序都跑不起来了
        //     // 所以需要配合try catch使用捕获异常
        //     Object.defineProperty(obj, 'c', {
        //         get() {
        //             return 3;
        //         }
        //     })

        //     Object.defineProperty(obj, 'c', {
        //         get() {
        //             return 4;
        //         }
        //     })
        // } catch (error) {
        //     console.log(error);
        // }
        //#endregion

        // 通过Reflect.defineProperty去操作    Reflect -> 反射对象,就不用写try catch
        //#region
        // const x1 = Reflect.defineProperty(obj, 'c', {
        //     get() {
        //         return 3
        //     }
        // })
        // console.log(x1);
        // const x2 = Reflect.defineProperty(obj, 'c', {
        //     get() {
        //         return 4
        //     }
        // })

        // if (x2) {
        //     console.log('某某某某修改成功!');
        // } else {
        //     console.log('某某某某修改失败!');
        // }
        // console.log(x2);
        //#endregion
        // console.log('@@@');
    </script>
</body>

</html>

总结一下:

  1. 通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等等。
  2. 通过Reflect(反射):对源对象的属性进行操作。
使用Reflect的原因:
  1. 使用Object.defineProperty,不健壮,一旦抛出错误,整个程序都跑不起来了, 所以需要配合try catch使用捕获异常
  2. 通过Reflect.defineProperty去操作 Reflect -> 反射对象,就不用写try catch
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值