从Object.defineProperty到Proxy

众所周知,在vue2开发面试中总会问到响应式的实现方式

我们都知道是 Object.defineProperty,使用 getter/setter 对对象的进行 拦截后进行重写对象;而在Vue3.0中则重构为 Proxy

那两者区别在哪?为啥要重构?(还得学😏)

首先我们再来回顾一下 defineProperty 的定义

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

上面给两个词划了重点,对象上,属性,我们可以理解为是针对对象上的某一个属性做处理的

其实这句话已经告诉我们 Object.defineProperty 的局限性了,它只能对对象的某一个属性进行拦截,而不能对整个对象进行拦截


js 复制代码
data  () {
  return  {
    obj: {
      a: 1
    }
  }
}

methods: {
  update () {
    this.obj.b = 2
  }
}

用过vue2的同学都遇到过,当我们给对象新增一个属性的时候,是无法监听到的,这就是它的局限性😒😒

这个其实很好理解,我们先要明白 vue 中 data init 的时机,data init 是在生命周期 created 之前的操作,会对 data 绑定一个观察者 Observer,之后 data 中的字段更新都会通知依赖收集器Dep触发视图更新

总结: defineProperty 本身,是对对象上的属性做操作,而非对象本身!

顺便复习一下vue2数组是如何实现响应式的呢?

数组变异

由于 JavaScript 的限制,Vue 不能检测以下数组的变动: 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue

虽然说索引变更不是 defineProperty 的锅,但新增索引的确是 defineProperty 做不到的,所以就有了数组的变异方法,vue2中对数组的变异方法进行了重写,重写后的方法会通知依赖收集器Dep触发视图更新

js 复制代码
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);

// 重写数组的方法,以触发响应式更新
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function (method) {
  // 原始数组方法
  const original = arrayProto[method];

  // 重写的方法
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args);

    // 获取数组对象的 __ob__(Observer 实例)
    const ob = this.__ob__;

    // 对于某些方法,可能需要额外的操作,例如 splice
    let inserted;
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args;
        break;
      case 'splice':
        inserted = args.slice(2);
        break;
    }
    
    // 如果有新的元素插入,需要将其变成响应式
    if (inserted) ob.observeArray(inserted);

    // 通知依赖更新
    ob.dep.notify();
    return result;
  });
});

大白话总结一下,就是对数组的变异方法进行了重写,还是跟$set一样,手动 observer,重写后的方法会通知依赖收集器Dep触发视图更新

Proxy 的出现,解决了 defineProperty 的局限性

proxy 译为代理,可以理解为在操作目标对象前架设一层代理,将所有本该我们手动编写的程序交由代理来处理,生活中也有许许多多的"proxy", 如代购,中介,因为他们所有的行为都不会直接触达到目标对象

proxy的参数

  • target 目标对象
  • handler 代理对象,可以拦截目标对象的各种操作,比如读取属性值、设置属性值、获取对象的长度、判断一个对象是否存在某个属性等等

怎么说呢?好像和 defineProperty 有点像,但是又不一样🤦‍♀️,我们来看看 proxy 的用法

js 复制代码
const proxy = new Proxy(target, handler)
js 复制代码
const target = {
  a: 1,
  b: 2
}

const handler = {
  get (target, key) {
    console.log('get', key)
    return target[key]
  },
  set (target, key, value) {
    console.log('set', key, value)
    target[key] = value
    // 严格模式下,set代理如果没有返回true,就会报错
    return true
  }
}

const proxy = new Proxy(target, handler)

proxy.a // get a
proxy.a = 2 // set a 2

// 直接新增属性试试
proxy.c = 3 // set c 3
// 牛逼吧,直接就可以监听到新增属性了,打印target也是有c属性的这里就不贴图了

// 再来个数组试试
const arr = [1, 2, 3]

const proxyArr = new Proxy(arr, handler)

proxyArr.push(4) // set 3 4

// 打印arr也是有4的,知道为什么用proxy了吧🤡

proxy的优势(GPT总结的,我懒得写(copy)文档了)🤺🤺

  • 更全面的拦截操作 :Proxy 允许你拦截和自定义对象上的各种操作,包括属性的读取、设置、删除、枚举、函数调用等。这使得你可以更精确地控制对象的行为,而不仅仅是属性的 getter 和 setter。

  • 更灵活的属性访问:你可以使用 Proxy 定义属性的 getter 和 setter,从而实现更灵活的属性访问和修改,包括计算属性、惰性加载等。

  • 更易于创建动态属性:Proxy 允许你动态地创建和管理属性,而不需要提前定义它们。这对于处理动态数据结构非常有用。

  • 更易于处理数组和集合:在处理数组和集合时,Proxy 更直观且强大。你可以拦截数组的 push、pop、shift、unshift、splice 等操作,而无需手动设置 setter。

  • 更好的错误处理:Proxy 提供了严格的错误处理机制,你可以在陷阱函数中处理错误或阻止不符合条件的操作。

需要注意的是,尽管 Proxy 具有许多优势,但它也具有一些性能开销,因此在性能敏感的应用程序中可能不适用。另外,Proxy 在某些环境下可能不被支持,特别是在旧的浏览器版本中(你懂的🤐)。

相关推荐
M_emory_10 分钟前
解决 git clone 出现:Failed to connect to 127.0.0.1 port 1080: Connection refused 错误
前端·vue.js·git
Ciito13 分钟前
vue项目使用eslint+prettier管理项目格式化
前端·javascript·vue.js
成都被卷死的程序员1 小时前
响应式网页设计--html
前端·html
mon_star°1 小时前
将答题成绩排行榜数据通过前端生成excel的方式实现导出下载功能
前端·excel
Zrf21913184551 小时前
前端笔试中oj算法题的解法模版
前端·readline·oj算法
文军的烹饪实验室2 小时前
ValueError: Circular reference detected
开发语言·前端·javascript
Martin -Tang3 小时前
vite和webpack的区别
前端·webpack·node.js·vite
迷途小码农零零发3 小时前
解锁微前端的优秀库
前端
王解4 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
我不当帕鲁谁当帕鲁4 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis