从vue2依赖收集的缺陷到vue3的proxy

    科技2025-08-09  3

    先简单解释下ES6中Proxy的语法, 然后从vue2的defineProperty的缺点去理解为什么使用Proxy 最后展示二者的区别

    Proxy

    const myProxy = new Proxy(target, handle)

    使用new Proxy的语法来创建一个拦截器,其中:

    target 是指要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理handler 是指一个通常以函数作为属性的对象,用来定制拦截行为后续触发拦截时都需要使用myProxy(如上例)实例才能触发拦截,而不是直接使用target

    Proxy一些常见方法

    方法描述handler.has()in 操作符的捕捉器handler.get(target, propKey, ?receiver(实例本身))属性(propKey)读取操作的捕捉器。handler.set(handler.set(target, propKey, value ?receiver(实例本身))属性设置操作的捕捉器。handler.deleteProperty()delete 操作符的捕捉器。handler.ownKeys()Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。handler.apply()函数调用操作的捕捉器。handler.construct()new 操作符的捕捉器

    举例:

    根据提示错误信息(get) // 状态码提示信息 const errorMessage = { 400: '错误请求', 401: '系统未授权,请重新登录', 403: '拒绝访问', 404: '请求失败,未找到该资源' } const proxy = new Proxy(errorMessage, { get(target,key) { const value = target[key] return value || '系统异常,请联系管理员' } }) // 输出 错误请求 console.log(proxy[400]) // 输出 系统异常,请联系管理员 console.log(proxy[500]) 校验类型(set): const target = { _id: 24, name: 'vuejs' } // 校验器 const validators = { name(val) { return typeof val === 'string'; }, _id(val) { return typeof val === 'number' && val > 1024; } } const createValidator = (target, validator) => { return new Proxy(target, { _validator: validator, set(target, propkey, value, proxy){ let validator = this._validator[propkey](value) // 根据校验器进行拦截 if(validator){ return Reflect.set(target, propkey, value, proxy) }else { throw Error(`Cannot set ${propkey} to ${value}. Invalid type.`) } } }) } const proxy = createValidator(target, validators) proxy.name = 'vue-js.com' // vue-js.com proxy.name = 10086 // Uncaught Error: Cannot set name to 10086. Invalid type. proxy._id = 1025 // 1025 proxy._id = 22 // Uncaught Error: Cannot set _id to 22. Invalid type

    defineProperty的缺陷

    对象属性新增删除无法检测,导致视图不更新数组的索引直接设置一个数组项无法检测,例:vm.items[indexOfItem] = newValue,新增索引也无法检测,导致视图不更新

    原因如下:

    Object.defineProperty 无法检测到对象属性的添加和删除 ,是因为这个方法只能监听已存在的属性。如果要解决可以使用Vue 提供的全局$set ,其本质也是给新增的属性手动 observer。数组的索引直接设置一个数组项无法检测,并不是defineProperty的锅,而是尤大在设计上对性能的权衡虽然说索引变更不是 defineProperty的锅,但新增索引的确是 defineProperty做不到的,所以就有了vue对数组的重写方法,还是跟$set一样,手动 observer。

    附:vue对数组的重写方法是指: 重新定义数组的push,pop,shift,unshift,splice,sort,reverse方法,调用以上方法时key的订阅者列表会通知订阅者们“值已改变”。如果调用的是push,unshift,splice方法,递归处理新增加的项。

    二者的区别

    使用Object.defineProperty需要遍历对象的每一个属性,对于性能会有一定的影响Proxy 作为新标准将受到浏览器厂商重点持续的性能优化Proxy 能观察的类型比 defineProperty 更丰富Proxy 不兼容IE,也没有 polyfill, defineProperty 能支持到IE9Object.definedProperty 是劫持对象的属性,新增元素需要再次 definedProperty。而 Proxy 劫持的是整个对象,不需要做特殊处理使用 defineProperty 时,我们修改原来的 obj 对象就可以触发拦截,而使用 proxy,就必须修改代理对象(new Proxy),即 Proxy 的实例才可以触发拦截
    Processed: 0.008, SQL: 8