VUE2与VUE3响应式的原理及区别

318 阅读2分钟

VUE2与VUE3响应式的原理及区别

一、vue2响应式缺陷

1、对接添加的新属性或者删除已有属性界面不会更新(不是响应式)

2、当响应式数据为数组时直接通过下表(arr[x] = xxx)或者更新数据的长度(arr=[0] arr[1] = 1)界面不会更新(不是响应式)

<template>
  <div class="test">
    <div v-for="(item, key) in person" :key="key">
      {{ key }}-{{ item }}
    </div>
    <button @click="addObj">添加对象属性</button>
    <div v-for="item in arr" :key="item">
    {{item}}
    </div>
    <button @click="changeArr">添加</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      person: {
        name: 'ls',
        age: 29
      },
      arr: [1, 2, 3]
    }
  },
  mounted () {
    ;
  },
  methods: {
    addObj() {
      this.person.sex = '男' // 点击后不是响应式
    },
    changeArr() {
      arr[2] = 4 // 点击后不是响应式
      arr[8] = 8 // 点击后不是响应式
      
    }
  },
}
</script>
<style lang="scss" scoped>
</style>

Vue3中不存在以上两个问题请往下看

二、Vue2的响应式原理

  • 核心

对象:通过defineProperty对对象的已有属性值的读取和修改进行劫持(监听/拦截)

/**
 * const vm = new Vue({
 *  el: '#app',
 *  data: {
 *    name: 'ls' ,
 *    age: 29
 *  }
 * })
 */

// vm充当vue实例

const vm = {}

// data中的响应式数据

const data = {
  name: 'ls',
  age: 29
}

// 通过遍历data将其数值绑定到实例vm上,对属性进行修改和读取进行拦截

Object.entries(data).forEach(([key, value]) => {
  let inval = value
  /*
  defineProperty Object.defineProperty(obj, prop, descriptor)
    obj
    要定义属性的对象。
    prop
    要定义或修改的属性的名称或 Symbol 。
    descriptor
    要定义或修改的属性描述符。
    @return 被传递给函数的对象
   */
  Object.defineProperty(vm, key, {
    set (newVal) {
      console.log('执行set')
      inval = newVal
    },
    get () {
      console.log('执行get')
      return inval
    }
  })
})

// 读取值
console.log(vm.name) // '执行get' ls
// 修改值
vm.name = 'xm' // '执行set'

console.log(vm.name) // '执行get' xm

vm.sex = '男' // 不会执行set方法

console.log(vm.name) // 只能打印出'男' 不会执行get方法
  • 疑问?

按照以上逻辑vue2中数组中的push、splice、pop等改变原有数组的方法也是不能响应式。但事实上Vue的这些数组方法能做到响应式。为什么?

事实是push、splice、pop等方法已经被vue重写。请看以下代码

// 把数组方法放到一个对象中
const obj = {
  push () {},
  pop () {},
  shift () {},
  unshift () {},
  splice () {},
  sosrt () {},
  reverse () {}
}
// 遍历对象,并且监听
Object.keys().forEach(key => {
  Object.defineProperty(obj, key, {
    value: function (...args) {
        console.log('我执行了')
      return Array.prototype[key].call(this, ...args)
    }
  })
})
const arr = []
// arr.__proto__其实就是Array.prototype,将原型设置为 obj相当于 new Array(实例) 继承了obj
arr.__proto__ = obj // arr继承了obj中方法
arr.push(1) // 会打印 '我执行了'

三、Vue3的响应式原理

const person = {
  name: 'ls',
  age: 29
}

// 代理对象

const proxyPerson = new Proxy(person, {
  set (target, key, val) {
    console.log('劫持set', key)
    return Reflect.set(target, key, val)
  },
  get (target, key) {
    console.log('劫持get', key)
    return Reflect.get(target, key)
  },
  deleteProperty(target, key) {
    console.log('劫持delete', key)
    return Reflect.deleteProperty(target, key)
  }
})

// 读取值
console.log(proxyPerson === person) // false
console.log(proxyPerson.name) // '劫持get' name ls
// 设置值

proxyPerson.name = 'xm' // '劫持set' name xm
proxyPerson.age = 18 // '劫持set' age 18

console.log(person) 
/*
{
  name: 'ls',
  age: 29
}
*/


  • 总结

    Vue3通过使用Proxy代理的方式拦截对象本身,所以Vue3中添加/删除属性都是响应式的,包括通过数组下标修改数组值也是响应式的。

  • end