响应系统的核心设计原则

本文深入探讨了JavaScript中实现响应式数据的不同方式,包括使用Object.defineProperty和Proxy技术的区别及应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

JS的程序性

初步具备响应性

 

 v2的响应性Object.defineProperty

 v2的响应性Object.defineProperty缺陷

v3通过使用proxy来取代Object.defineProperty

proxy和Object.defineProperty对比

proxy的好搭档Reflect

总结 


JS的程序性

<script>
    // 给定一个对象product
    let product = {
        price: 10,
        quantity: 2
    }
    // 设置变量total
    let total = product.price * product.quantity
    // 打印总价格
    console.log(`总价格:${total}`);
    // 修改产品的数量
    product.quantity = 5
    console.log(`总价格:${total}`);
</script>

控制台打印结果

 在上面的代码中明明更改了产品的数量,但是产品的总价格依然是20。


初步具备响应性

<script>
    // 给定一个对象product
    let product = {
        price: 10,
        quantity: 2
    }
    // 定义变量total用来存储产品总价
    let total = 0
    // 定义一个effect方法用来计算产品总价
    let effect = () => {
       total = product.price * product.quantity
    }
    // 调用effect方法
    effect()
    console.log(`总价格为:${total}`); //  20
    // 修改产品数量
    product.quantity = 5
    effect()
    console.log(`总价格为:${total}`); // 50
</script>

控制台打印结果

 所以,当我们需要对产品的属性进行修改的时候,我们可以通过调用effect的方法来进行更新。每次手动调用effect方法比较麻烦。


 v2的响应性Object.defineProperty

Object.defineProperty链接

 代码实现流程

 将产品数量变为10的时候,控制台打印结果为

 代码片段

<script>
    // 定义这个变量quantity的意义在于避免setter和getter死循环
    let quantity = 2
    // 给定一个对象product
    let product = {
        price: 10,
        quantity: quantity
    }
    // 定义变量total作为产品总价
    let total = 0
    // 定义一个effect方法用来计算产品总价
    let effect = () => {
        total = product.price * product.quantity
    }
    // 调用effect方法
    effect()
    console.log(`总价格${total}`); //20

    // Object.defineProperty,第一个参数为指定对象,第二个参数为指定对象的属性
    Object.defineProperty(product, 'quantity', {
        // 当对quantity属性赋值的时候会触发setter行为
        set(newVal) {
            console.log('setter');
            // 修改的是变量quantity
            quantity = newVal
            // 调用effect方法的时候,会触发getter行为
            effect()
        },
        // 当对quantity属性读取值的时候会触发getter行为
        get() {
            console.log('getter');
            // 返回修改完的最新变量quantity
            return quantity
        }
    })
</script>


 v2的响应性Object.defineProperty缺陷

 案例通过vue2构建

<template>
  <div id="app">
    <ul>
      <li v-for="(value, key, index) in obj" :key="index">
        {{ key }} --- {{ value }}
      </li>
    </ul>
    <button @click="addObjProp">添加对象属性</button>
    <ul>
      <li v-for="(item, index) in arr" :key="index">
        {{ item }}
      </li>
    </ul>
    <button @click="addArrIndex">添加数组元素</button>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      obj: {
        name: "张三",
        age: 16,
      },
      arr: [1, 2],
    };
  },
  methods: {
    addObjProp() {
      this.obj.sex = "男";
      console.log(this.obj);
    },
    addArrIndex() {
      this.arr[2] = 3;
      console.log(this.arr);
    },
  },
};
</script>

<style>
</style>

 控制台打印结果

 综上所述,给对象添加一个没有在data中声明的属性时,新增属性不是响应式的,数组也是如此。

其原因就是v2是通过Object.defineProperty来实现的响应性,对于Object.defineProperty而言它只能监听指定对象指定属性的getter和setter,属性具备响应性的就是因为其被监听了getter和setter行为,这就意味着想要属性具备响应性就必须知道对象中存在该属性,但是因为js的限制,无法检测到对象新增的属性,所以新增的属性就没办法通过Object.defineProperty来监听getter和setter行为,所以其就不具备响应性。 

将上面的话翻译过来就是,因为JavaScript不能监听对象中新增的属性,所以新增的属性没办法通过Object.defineProperty来监听其本身的getter和setter行为,所以当数组和对象发生变化的时候,vue检测不到,也就不具备响应性了~!


v3通过使用proxy来取代Object.defineProperty

proxy链接

 通过proxy代替Object.definfProperty

代码实现

<script>
    // 给定一个对象product
    let product = {
        price: 10,
        quantity: 2
    }
    // product被代理对象
    // proxtProduct代理对象
    // 只能使用代理对象触发setter和getter
    const proxyProduct = new Proxy(product, {
        set(target, key, value, receiver) {
            console.log(target, key, value, receiver);
            target[key] = value
            return true
            console.log('setter');
        },
        get(target, key, receiver) {
            console.log(target, key, receiver);
            console.log('getter');
        }
    })
    // 定义变量total作为产品总价
    let total = 0
    // 定义一个effect方法用来计算产品总价
    let effect = () => {
        // 因为代理对象和被代理对象拥有相同的属性,所以使用proxyProduct
        total = proxyProduct.price * proxyProduct.quantity
    }
    // 调用effect方法
    effect()
    console.log(`总价格${total}`); //20
</script>
  1. 通过new Proxy生成一个proxy的实例,使用proxyProduct来接受
  2. proxy的第一个参数就是被代理对象,第二个参数handler是一个object用来写setter和getter
  3. set拥有四个参数,target(被代理对象),key(需要修改的属性),value(需要修改的属性值),recevier(proxy实例),
  4. get拥有三个参数,target(被代理对象),key(需要修改的属性),recevier(proxy实例)
  5. set中return true是proxy的固定格式。
  6. 代理对象和被代理对象拥有相同的属性,所以在effect方法中使用proxyProduct

proxy和Object.defineProperty对比

  1. Object.defineProperty通过指定对象和指定属性的形式,添加setter和getter方法,只有被监听的属性才能触发setter和getter。
  2. proxy通过代理对象的方式,可以对被代理对象的任意一个属性触发handler中的setter和getter
  3. 所以当v3将Object.defineProperty替换为proxy后,就不会出现在data外添加对象或数组新元素时,新增元素不具备响应性的问题了。

proxy的好搭档Reflect拦截js操作

reflect链接

Reflect.get方法,第一个参数就是目标对象,第二个参数就是目标对象的键名,第三个参数就是this指向值。

<script>
    const p1 = {
        lastName: '张',
        firstName: '三',
        // 添加get标识符的意义在于可以通过p1.fullname触发方法
        get fullName() {
            return this.lastName + this.firstName
        }
    }
    const p2 = {
        lastName: '李',
        firstName: '四',
        // 添加get标识符的意义在于可以通过p1.fullname触发方法
        get fullName() {
            return this.lastName + this.firstName
        }
    }
    console.log(p1.fullName);//张三
    console.log(Reflect.get(p1, 'fullName'));//张三
    console.log(Reflect.get(p1, 'fullName', p2));//李四
</script>

 proxy+reflect

 因为代理对象拥有和被代理对象一样的属性,所以通过代理对象proxy.fullName拿到了张三,getter只触发了一次,但是由代码可得,getter应该被触发了三次,第一次读取fullname,第二次读取lastname,第三次读取firstName,所以应该打印三次getter触发。这是因为只有proxy代理对象触发getter行为才会打印,proxy只读取了一个fullName,因此只打印一次,另外的两次是在p1中触发的读取,不算代理对象proxy的,它的那个this是指向p1的,因此想要正确的触发三次,就需要把this指向代理对象proxy,所以reflect.get的第三个参数放上代理对象就可以将this指向代理对象了,所以只需要将原来的return target[key]改为return Reflect.get(targer,key,receiver)就可以了。

 receiver其实就是代理对象proxy。

当我们需要去监听代理对象的getter和setter的时候,就需要使用reflect.get方法, 使receiver(proxy)作为this,而不是target[key]以达到期望值。就像上面的getter行为本该触发三次结果只触发了一次的情况。

总结 

当需要实现响应式数据的时候,就需要多做一些事情,例如v2通过Object.defineProperty来完成响应性数据,但是其缺陷在于当你想要在data外添加新元素的时候,新元素不具备响应性,因为Object.defineProperty需要通过指定对象指定需要添加getter和setter方法,所以在某些情况下v2中的对象和数组会失去响应性,v3通过proxy代替了Object.defineProperty来完成响应性,虽然说代理对象拥有了被代理对象一模一样的属性,但是一旦在被代理对象中通过this去触发setter和getter的时候,无法被监听到。所以需要配合Reflect.get方法来完善其本身,保证每一次的getter和setter都能被监听到。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值