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

相关推荐
王解41 分钟前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
老码沉思录41 分钟前
写给初学者的React Native 全栈开发实战班
javascript·react native·react.js
我不当帕鲁谁当帕鲁1 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂1 小时前
工程化实战内功修炼测试题
前端·javascript
放逐者-保持本心,方可放逐2 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
毋若成4 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css
红中马喽4 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
Black蜡笔小新5 小时前
网页直播/点播播放器EasyPlayer.js播放器OffscreenCanvas这个特性是否需要特殊的环境和硬件支持
前端·javascript·html
秦jh_6 小时前
【Linux】多线程(概念,控制)
linux·运维·前端
蜗牛快跑2136 小时前
面向对象编程 vs 函数式编程
前端·函数式编程·面向对象编程