前端开发攻略---从源码角度分析Vue3的Propy比Vue2的defineproperty到底好在哪里。一篇文章让你彻底弄懂响应式原理。

1、思考

Vue的响应式到底要干什么?

  • 无非就是要知道当你读取对象的时候,要知道它读了。要做一些别的事情

  • 无非就是要知道当你修改对象的时候,要知道它改了。要做一些别的事情

  • 所以要想一个办法,把读取和修改的动作变成一个函数,读取和修改的时候分别调用对应的函数

  • 在ES6之,只能通过Object.defineproperty 给它变成一个get和set函数。当读取这个属性的时候运行get,修改这个属性的时候运行set

  • 在ES6之**后,**就能通过Porxy去代理整个对象

2、Vue2的做法

针对某个对象中某个属性的做法

通过Object.defineProperty去针对某个对象的属性去进行监听

javascript 复制代码
const obj = {
  a: 1,
  b: 2,
  c: {
    d: 3,
    e: 4,
  },
}

// 保存初始值
let v = obj.a

Object.defineProperty(obj, 'a', {
  get() {
    console.log('a', '读取')
    return v
  },
  set(val) {
    // 当原来的值与重新赋值的值不一样的时候才进行修改
    if (val !== v) {
      console.log('a', '更改了')
      v = val
    }
  },
})

当我们去读取 obj.a 这个属性的时候 get 函数就会调用。

当我们去修改 obj.a 这个属性的时候 set 函数就会调用。

针对某个对象中多个属性的做法

Vue2的源码 中,有一个函数叫做 observe(观察器)。 在这个函数中,去深度遍历对象中的每一个属性,给每一个属性添加 Object.defineProperty, 这样就能对对象中的每一个属性叫做监听。这个过程就叫做 观察

javascript 复制代码
const obj = {
  a: 1,
  b: 2,
  c: {
    d: 3,
    e: 4,
  },
}
// 辅助函数 判断这个值是不是一个对象
function _isObject(v) {
  return typeof v === 'object' && v !== null
}
function observe(obj) {
  for (const k in obj) {
    let v = obj[k]
    // 如果这一个属性仍然是一个对象的话,就需要深度遍历
    if (_isObject(v)) {
      observe(v)
    }
    Object.defineProperty(obj, k, {
      get() {
        console.log(k, '读取了')
        return v
      },
      set(val) {
        // 当原来的值与重新赋值的值不一样的时候才进行修改
        if (val !== v) {
          console.log(k, '更改了')
          v = val
        }
      },
    })
  }
}
observe(obj)

当我们去读取对象中的某个 属性的时候 get 函数就会调用。

当我们去修改对象中的某个 属性的时候 set 函数就会调用。

打印内容解释:

  • obj.a = 3 更改
  • obj.c.d = 4 先读取 obj.c 的值,再更改 obj.c.d的值
  • obj.c.e 先读取 obj.c 的值,再读取 obj.c.e的值

总结

  1. 在Vue2里面观察的方式就是 深度遍历每一个属性 把每一个属性的读取和赋值变成函数get和set。

  2. 在这种做法下有一个天生的缺陷,由于它是针对每个属性的监听,所以就必须要进行深度的遍历,这样会有效率损失。

  3. 由于在 **observe(obj)**观察这个步骤里边完成了深度遍历,也就是说在这个时间点里边,这些属性被我们监听到了都被改成get和set了

  4. 但是这一步一旦做完了之后。再去新增的话它就不知道了。比如 obj.qwertr = 3 对于这个属性而言,它就是没有监听的,因为监听的步骤已经结束了

  5. 这就是为什么Vue2它无法监听属性的新增 ,当然也包括属性的删除 。它也收不到通知。因为在 Object.defineProperty 既不会运行get也不会运行set

3、Vue3的做法

其实核心道理都是一样的。无论是Vue2还是Vue3,都必须要把读取和赋值 变成函数。只不过Vue3变成函数的方式不一样。在Vue3里面不会 去对对象的每一个属性进行监听了,而是直接监听整个对象。将来不管是在这个对象中添加还是删除属性都不怕了。因为监听的是整个对象,这要动了这个对象就能收到通知。

做法

Vue3使用的是Proxy

javascript 复制代码
const obj = {
  a: 1,
  b: 2,
  c: {
    d: 3,
    e: 4,
  },
}

const proxy = new Proxy(obj, {
  // 读这个对象的属性的时候收到通知
  get(target, k) {
    // target就是obj,k是属性名
    let v = target[k]
    console.log(k, '读取')
    return v
  },
  // 修改这个对象的属性的时候收到通知
  set(target, k, val) {
    // target就是obj,k是属性名,val是新值
    if (target[k] !== val) {
      target[k] = val
      console.log(k, '更改')
      return target[k]
    }
  },
  // 删除对象的属性的时候收到通知
  deleteProperty(target, k, val) {
    console.log(k, '删除')
    return target[k]
  },
})

会产生一个代理对象propx,将来去读属性 也好,重新给属性赋值也好都是通过这个代理对象去做的。

总结

  1. Proxy 对象可以直接代理整个对象,而不需要遍历对象属性进行劫持,这样可以减少运行时的性能开销。在 Vue 2 中,由于每个属性都需要单独设置 get 和 set,对于大量的属性或嵌套属性,这种劫持可能会导致性能下降。
  2. 另外,使用 Proxy 的方式更符合现代 JavaScript 的发展趋势,更好地利用了 JavaScript 引擎的优化。

4、总结

综上所述,Vue 3 中使用 Proxy 对象代替了 Vue 2 中的 Object.defineProperty,带来了更强大、更灵活和更高效的属性拦截和代理功能,同时也提升了开发体验和调试效率。这些改进使得 Vue 3 在处理 Props 的方式更加现代化和优雅,提升了整体的性能和可维护性。

相关推荐
天宇&嘘月2 小时前
web第三次作业
前端·javascript·css
小王不会写code2 小时前
axios
前端·javascript·axios
发呆的薇薇°3 小时前
vue3 配置@根路径
前端·vue.js
luoluoal3 小时前
基于Spring Boot+Vue的宠物服务管理系统(源码+文档)
vue.js·spring boot·宠物
luckyext3 小时前
HBuilderX中,VUE生成随机数字,vue调用随机数函数
前端·javascript·vue.js·微信小程序·小程序
小小码农(找工作版)3 小时前
JavaScript 前端面试 4(作用域链、this)
前端·javascript·面试
前端没钱4 小时前
前端需要学习 Docker 吗?
前端·学习·docker
前端郭德纲4 小时前
前端自动化部署的极简方案
运维·前端·自动化
海绵宝宝_4 小时前
【HarmonyOS NEXT】获取正式应用签名证书的签名信息
android·前端·华为·harmonyos·鸿蒙·鸿蒙应用开发
码农土豆5 小时前
chrome V3插件开发,调用 chrome.action.setIcon,提示路径找不到
前端·chrome