前端开发攻略---从源码角度分析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 的方式更加现代化和优雅,提升了整体的性能和可维护性。

相关推荐
该用户已不存在16 小时前
这6个网站一旦知道就离不开了
前端·后端·github
Ai行者心易16 小时前
10天!前端用coze,后端用Trae IDE+Claude Code从0开始构建到平台上线
前端·后端
东东23316 小时前
前端开发中如何取消Promise操作
前端·javascript·promise
掘金安东尼16 小时前
官方:什么是 Vite+?
前端·javascript·vue.js
柒崽16 小时前
ios移动端浏览器,vh高度和页面实际高度不匹配的解决方案
前端
渣哥16 小时前
你以为 Bean 只是 new 出来?Spring BeanFactory 背后的秘密让人惊讶
javascript·后端·面试
烛阴16 小时前
为什么游戏开发者都爱 Lua?零基础快速上手指南
前端·lua
大猫会长17 小时前
tailwindcss出现could not determine executable to run
前端·tailwindcss
Moonbit17 小时前
MoonBit Pearls Vol.10:prettyprinter:使用函数组合解决结构化数据打印问题
前端·后端·程序员
533_17 小时前
[css] border 渐变
前端·css