Skip to content

Vue2/3 中响应式的区别 ​

2024-01-28 19:46:39

众所周知,Vue2 中的响应式是通过 Object.defineProperty 来实现的,而 Vue3 中是通过 ES6 的 Proxy 来实现的

Vue2 ​

我们先来看下 Vue2 是如何做的

ts
const obj = {
  a: 1,
  b: 2,
  c: {
    d: 3,
    e: 4,
  },
}

function observe(obj) {
  for (let key in obj) {
    let v = obj[key]
    if (typeof v === 'object' && v !== null) {
      observe(v)
    }
    Object.defineProperty(obj, key, {
      get() {
        console.log('get', key)
        return v
      },
      set(val) {
        if (val !== v) {
          console.log('set', key)
          v = val
        }
      },
    })
  }
}

observe(obj)

obj.c = 1
delete obj.b

本质上就是封装了一个 observe 函数,将对象的每个 key 进行监听 getter 和 setter,这样就可以获取到对象的访问和赋值,在里面插入想要进行的操作,并且判断属性值是否是对象,如果还是对象的话就进行递归

那么这样的做法就有一个天生的缺陷,由于要对每个属性进行监听,所以进行了深度遍历,造成了性能问题,并且在执行 observe 函数时对传入的对象进行深度遍历完成了属性监听,也就是说当前对象有的属性都被监听到了,但是在这之后对这个对象添加的属性就监听不到了,因为此时监听的步骤已经结束了,在 vue 的生命周期 created 之前就完成了

TIP

在使用 delete 关键字删除对象的属性时也触发不到监听

Vue3 ​

那么我们看在 Vue3 里是如何做的

ts
const obj = {
  a: 1,
  b: 2,
  c: {
    d: 3,
    e: 4,
  },
}

function observe(obj) {
  const proxy = new Proxy(obj, {
    get(target, key) {
      let v = target[key]
      if (typeof v === 'object' && v !== null) {
        v = observe(v)
      }
      console.log('get', key)
      return v
    },
    set(target, key, val) {
      if (target[key] !== val) {
        console.log('set', key)
        target[key] = val
      }
    },
    deleteProperty(target, key) {
      console.log('delete', key)
      delete target[key]
    },
  })
  return proxy
}

const proxy = observe(obj)

proxy.c = 1
delete proxy.b

使用 Proxy 就不会再有前面的问题了,不用再管对象有多少个属性,因为 Proxy 监听的是整个对象。但是我们在看代码的时候第 15 行不也是进行了递归调用吗?为什么就不会有这个问题呢?和前面 Vue2 对比你会发现,使用 Proxy 的递归,是只有在访问到这个属性的时候才会去进行深度遍历,而 Vue2 是在初始的时候就会对整个对象的所有属性进行监听,这两者的区别是很大的,而对象添加新属性时对象是能够收到通知的

并且通过 Proxy 的 deleteProperty我们是可以监听的对象的删除操作的

Released under the MIT License.