Vue3源码梳理:响应式系统的前世今生
2023-12-16 22:28:49
    		响应性数据的前世
- js的程序性: 一套固定的,不会发生变化的执行流程
1 )没有响应的数据
// 定义商品对象
const product = {
  price: 10,
  quantity: 2
}
// 总价格
let total = product.price * product.quantity
console.log(`总价格:${total}`) // 20
// 修改商品的数量
product.quantity = 5
console.log(`总价格:${total}`) // 20
- 这是一段非常普通的js程序,当最后 product.quantity发生改变的时候,最终结果并没有发生变化
- 这里,当商品数量发生变化,总价格也会发生变化是我们的期望
- 由于js程序性的约束,我们只能得到20,我们想让程序变得更智能
2 )进一步改造
// 定义商品对象
const product = {
  price: 10,
  quantity: 2
}
// 总价格
let total = 0
// 定义一个 effect 函数
const effect = () => {
  total = product.price * product.quantity // 访问属性,这里是 getter行为
}
effect()
console.log(`总价格:${total}`) // 20
// 修改商品的数量
product.quantity = 5 // 修改属性,这里是 setter 行为
effect() // 注意这里
console.log(`总价格:${total}`) // 50
- 这里,封装了一个effect方法,这个方法是重新计算 total 的方法
- 当 product.quantity数据发生改变的时候,手动调用了一次 effect 方法
- 以上的方式是每次手动触发 effect 方法进行一次 类似 getter 操作
- 这样手动操作,是比较麻烦的
- 为此,js中的API可以有效解决这个问题
响应式数据的今生
1 )关于响应性数据
- 响应数据:是指影响视图变化的数据
2 ) vue2核心响应式API Object.defineProperty() 方法
let quantity = 2
const product = {
  price: 10,
  quantity
}
// 总价格
let total = 0
// 计算总价格函数
const effect = () => {
  total = product.price * product.quantity
}
effect()
console.log(`总价格:${total}`) // 20
// 响应式变化
Object.defineProperty(product, 'quantity', {
  set(newVal) {
    console.log('setter')
    quantity = newVal
    effect()
  },
  get() {
    console.log('getter')
    return quantity // 这里的变量是暴露在最外面的,不是很好
  }
})
- 这样可以在指定对象上,指定属性上的 getter 和 setter 行为,以此来触发effect(更新程序)
- 这样来说,相对更智能了
3 ) Obeject.defineProperty() 在设计上的缺陷
- 存在一个致命缺陷:vue官网/深入响应式原理/检测变化的注意事项 
  - 由于js的限制,vue不能检测数组和对象变化
 
代码示例,如下
<template>
  <div>
    <ul>
      <li v-for="(val, key, index) in obj" :key="index">
        {{ key }} --- {{ val }}
      </li>
    </ul>
    <button @click="addObjKey">为对象增加属性</button>
    <div> ---------------- </div>
    <ul>
      <li v-for="(item, index) in arr" :key="index">
        {{ item }} --- {{ index }}
      </li>
    </ul>
    <button @click="addArrItem">为数组增加元素</button>
  </div>
</template>
<script>
  export default {
    name: 'App',
    data() {
      return {
        obj: {
          name: '张三',
          age: 30
        },
        arr: [
          '张三', '李四'
        ]
      }
    },
    methods: {
      addObjKey() {
        this.obj.gender = '男'
        console.log(this.obj)
      },
      addArrItem() {
        this.arr[2] = '王五'
        console.log(this.arr)
      }
    }
  }
</script>
- 上面两个按钮点击后,数据会更新,但是页面视图不会更新
- 当对象新增一个没有在data中声明的属性时,新增的属性不是响应式的
- 当为数组通过下标形式新增一个元素时,新增的元素不是响应式的
- why? 
  - Object.defineProperty 只能监听指定对象,指定属性的 getter 和 setter
- js限制是指:没有办法知道为某一个对象新增了某一个属性这类行为,新增属性会失去响应性
 
4) Vue3中的 Proxy
-  文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy 
-  Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。 
-  语法 const p = new Proxy(target, handler)- target 表示proxy包装的目标对象,可以是任何对象: 原生数组,函数,甚至另一个对象
- p 是 proxy的实例,是代理对象
- handler 是一个对象,可以在这个对象上指定getter和setter
 
代码改造,示例如下
// 定义商品对象
const product = {
  price: 10,
  quantity: 2
}
// 生成代理对象, 注意事项:使用时不能使用被代理对象(原对象),而应该使用代理对象
// proxy 代理的是整个对象,而非某个对象的某个属性
const proxyProduct = new Proxy(product, {
  set(target, key, newVal, receiver) {
    // console.log('setter')
    target[key] = newVal
    // 这里触发 effect 重新计算
    effect()
    return true
  },
  get(target, key, receiver) {
    // console.log('getter')
    return target[key]
  }
})
// 总价格
let total = 0
// 定义一个 effect 函数
const effect = () => {
  total = proxyProduct.price * proxyProduct.quantity // 访问属性,这里是 getter行为
}
effect()
console.log(`总价格:${total}`) // 20
// 修改商品的数量, 注意这里是修改的代理对象的值,而非被代理对象的值
proxyProduct.quantity = 5 // 修改属性,这里是 setter 行为
effect()
console.log(`总价格:${total}`) // 50
- 通过修改代理对象的值,来让被代理对象同步发生变化
- 这里使用 proxy 完成了 和 Object.defineProperty一样的效果
- 总结: 
  - proxy: 
    - Proxy 将一个对象 (被代理对象), 得到一个新的对象 (代理对象), 同时拥有被代理对象中所有的属性
- 当想要修改对象的指定属性时,我们使用 代理对象 进行修改
- 代理对象的任何一个属性都可以触发 handler 的getter和setter
 
- Object.defineProperty 
    - 该api为指定对象的指定属性 设置 属性描述符
- 当想要修改对象的指定属性时,可以使用原对象进行修改
- 通过属性描述符,只有 被监听 的指定属性,才可以触发 getter 和 setter
 
- 所以,当 vue3 通过 Proxy 实现响应性核心 api 之后, vue 将不会再存在新增属性时失去响应性的问题
 
- proxy: 
    
5 ) proxy的最佳合伙API: Reflect, 拦截js对象操作
-  https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect 
-  Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handler (en-US) 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。 const obj = { name: '张三' } Reflect.get(obj, 'name') // '张三'
-  Reflect.get(target, propertyKey[, receiver]) - 可以看到,这个api 有三个参数
- target 需要取值的目标对象
- propertyKey 需要获取的值的键值
- receiver 如果target对象中指定了getter,receiver则为getter调用时的this值
 
测试代码如下:
// p1 对象
const p1 = {
  lastName: '张',
  firstName: '三',
  get fullName() {
     return this.lastName + this.firstName
  }
}
// p2 对象
const p2 = {
  lastName: '李',
  firstName: '四',
  get fullName() {
     return this.lastName + this.firstName
  }
}
// 测试
console.log(p1.fullName) // 张三
console.log(Reflect.get(p1, 'fullName')) // 张三
console.log(Reflect.get(p1, 'fullName', p2)) // 李四 这里,改变了getter的this指向,this指向了 p2, 所以 getter中获取的是 p2的fullName属性
console.log(p2.fullName) // 李四
使用 proxy 和 Reflect 一起使用
// p1 对象
const p1 = {
  lastName: '张',
  firstName: '三',
  get fullName() {
     return this.lastName + this.firstName
  }
}
const proxy = new Proxy(p1, {
  get(target, key, receiver) {
    console.log('getter')
    return target[key]
  }
})
console.log(proxy.fullName) // 这里进行一次getter操作,会执行一次
- 上述代码只会触发一次getter, 因为其中的this指向是target,也就是原对象p1
- 但是,我们的理解,如果做任意的取值都会触发一次getter, 也就是 访问fullName的时候会触发一次getter, 但是fullName里面也有两次getter
- 这时候,我们想要触发三次getter 如何修改呢
// p1 对象
const p1 = {
  lastName: '张',
  firstName: '三',
  get fullName() {
     return this.lastName + this.firstName
  }
}
const proxy = new Proxy(p1, {
  get(target, key, receiver) {
    console.log('getter', key)
    // return target[key]
    return Reflect.get(target, key, receiver) // 注意,修改这里
  }
})
console.log(proxy.fullName) // 这里进行一次getter操作,会执行一次
- 这时候 proxy.fullName 会触发 三次 getter的行为 
  - 先输出:fullName 的getter
- 再输出:lastName 的getter
- 最后输出:firstName 的getter
 
- 某些场景下,使用 return target[key]会存在bug,
- 请使用 return Reflect.get(target, key, receiver)代替
    			文章来源:https://blog.csdn.net/Tyro_java/article/details/135038191
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
    	本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!