Skip to content

Vue3响应式原理

1. 回顾Vue2的响应式实现原理

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

2. Vue2存在的问题

  1. 新增属性、删除属性,界面不会更新
  2. 直接通过下标修改数组,界面不会自动更新。 当然Vue也提供额外的api手动调用:
vue
<template>
  <div>
    <ul>
      <li>名字: {{person.name}}</li>
      <li>年龄: {{person.age}}</li>
      <li>爱好: {{person.hobbyList}}</li>
      <li v-if="person.sex">性别: {{person.sex}}</li>
    </ul>
    <button @click="addSex">新增性别</button>
    <button @click="deleteSex">删除性别</button>
    <button @click="updateHobby">更改爱好看片为学习</button>
  </div>
</template>

<script>
import Vue from "vue";
export default {
  name: 'App',
  components: {},
  data(){
    return {
      person: {
        name:'张三',
        age: 18,
        hobbyList: ['吃饭', '看片']
      }
    }
  },
  methods: {
    addSex(){
      this.person.sex = '男'
      // 解决办法
      // this.$set(this.person, 'sex', '男')
      // Vue.set(this.person, 'sex', '男')
    },
    deleteSex(){
      delete this.person.sex
      // 解决办法
      // this.$delete(this.person, 'sex')
      // Vue.delete(this.person, 'sex')
    },
    updateHobby(){
      this.person.hobbyList[1] = '学习'
      // 解决办法
      // this.person.hobbyList.splice(1,1, '学习')
      // this.$set(this.hobbyList, 1, '学习')
      // Vue.set(this.hobbyList, 1, '学习')
    }
  }
}
</script>

Vue2数据响应式的局限性是因为Object.defineProperty()中只有get set方法,不能动态监测add是因为他的api里面传入现有监测属性的名字,不能监测delete是因为没有监测delete的方法,而且比较繁琐的是每个现有属性的监测都需要使用编写Object.defineProperty()

js
// 源数据
let person = {
    name: '张三',
    age:18
}
// 模拟Vue2中实现响应式
let p = {}
Object.defineProperty(p, 'name', {
    configurable:true, // 可以代理删除数据,但是没有delete()这种可以捕获的函数
    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
    }
})

3. Vue3的响应式实现原理

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

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

    MDN文档中描述的Proxy与Reflect:

    Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

    Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。Reflect 不是一个函数对象,因此它是不可构造的。

3.1 手动实现Vue3的proxy

js
// 模拟Vue3中的响应式
// Proxy在window上面,可以直接用, 使用Reflect获取操作对象属性,可以避免报错导致框架中try...catch泛滥影响代码质量
const p = new Proxy(person, {
    // 读取属性被调用, target指向person
    get(target, propName) {
        console.log(`有人读取了p身上的${propName}属性`)
        // return target[propName]
        return Reflect.get(target, propName)
    },
    // 修改新增属性被调用
    set(target, propName, newValue) {
        console.log(`有人修改了p身上的${propName}属性, 我要去更新页面了`)
        // target[propName] = newValue
        Reflect.set(target, propName)
    },
    // 删除属性被调用
    deleteProperty(target, propName) {
        console.log(`有人删除了p身上的${propName}属性, 我要去更新页面了`)
        // return delete target[propName]
        return Reflect.deleteProperty(propName)
    }
})