Vue3.x为什么要用Proxy来代替Object.defineProperty?

什么是Proxy

proxy:代理,JavaScript中用来表示由它来’代理’某些操作。通常,当谈到JavaScript语言时,我们讨论的是ES6标准提供的新特性,本文也不例外。 我们将讨论JavaScript代理以及它们的作用,但在我们深入研究之前,我们先来看一下Proxy的定义是什么。

MDN上的定义是:代理对象是用于定义基本操作的自定义行为(例如,属性查找,赋值,枚举,函数调用等)。

换句话说,我们可以说代理对象是我们的目标对象的包装器,我们可以在其中操纵其属性并阻止对它的直接访问。 你可能会发现将它们应用到实际代码中很困难,我鼓励你仔细阅读这个概念,它可能会改变你的观点。

proxy会在目标对象之前架设一层’拦截’,外界对该对象的访问,都必须先通过这层拦截,因此可以对外界的访问进行过滤和改写

Object.defineProperty

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

为什么能实现响应式

通过defineProperty 两个属性,get及set

  • get

属性的 getter 函数,当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值

  • set

属性的 setter 函数,当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。默认为 undefined

vue3为什么要用proxy?

vue2中,通过递归遍历data中的所有的property,并使用Object.definePropertyproperty全部转为getter/setter,在getter中做数据依赖收集处理,在setter中监听数据的变化,并通知订阅当前数据的地方,但由于JavaScript的限制,这种实现方式有几个问题:

  • 无法检测对象属性的添加和移除,需要通过Vue.set/Vue.delete来保证响应系统的运行符合预期
  • 无法监控到数组下标及数组长度的变化,当直接通过数组的下标给数组设置值或者改变数组长度时,不能实时响应
  • 性能问题,当data中数据比较多且层级很深的时候,因为要遍历data中所有数据并给其设置响应式,会导致性能下降

proxy恰恰解决了上述问题。

vue3中,使用proxy替换了原来遍历对象使用Object.defineProperty方法给属性添加set/get。
vue的核心能力之一是监听用户定义的状态变化并响应式刷新DOM。
vue2是通过替换状态对象属性的getter和setter来实现的,vue3则通过proxy进行。
改为proxy后,可以突破vue当前的限制,解决之前vue2的无法监听新增属性,还能提供更好的性能表现。

vue的新版本(和重写版本)出现有两个关键的因素:

  • 主流浏览器对新的JavaScript语言特性的普遍可用性
  • 随着时间的推移,当前代码库中的设计和架构问题逐渐暴露

proxy对比Object.defineProperty

在这里插入图片描述
Object.defineProperty只能遍历对象属性进行劫持

function observe(obj) {
    
    
    if (typeof obj !== 'object' || obj == null) {
    
    
        return
    }
    Object.keys(obj).forEach(key => {
    
    
        defineReactive(obj, key, obj[key])
    })
}

Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的

function reactive(obj) {
    
    
    if (typeof obj !== 'object' && obj != null) {
    
    
        return obj
    }
    // Proxy相当于在对象外层加拦截
    const observed = new Proxy(obj, {
    
    
        get(target, key, receiver) {
    
    
            const res = Reflect.get(target, key, receiver)
            console.log(`获取${
      
      key}:${
      
      res}`)
            return res
        },
        set(target, key, value, receiver) {
    
    
            const res = Reflect.set(target, key, value, receiver)
            console.log(`设置${
      
      key}:${
      
      value}`)
            return res
        },
        deleteProperty(target, key) {
    
    
            const res = Reflect.deleteProperty(target, key)
            console.log(`删除${
      
      key}:${
      
      res}`)
            return res
        }
    })
    return observed
}

Proxy可以直接监听数组的变化(push、shift、splice)

const obj = [1,2,3]
const proxtObj = reactive(obj)
obj.psuh(4) // ok

Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等,这是Object.defineProperty不具备的

正因为defineProperty自身的缺陷,导致Vue2在实现响应式过程需要实现其他的方法辅助(如重写数组方法、增加额外set、delete方法)

// 数组重写
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto)
['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(method => {
    
    
  arrayProto[method] = function () {
    
    
    originalProto[method].apply(this.arguments)
    dep.notice()
  }
});
 
// set、delete
Vue.set(obj,'bar','newbar')
Vue.delete(obj),'bar')

Proxy 不兼容IE,也没有 polyfill, defineProperty 能支持到IE9

总结

proxy是用来操作对象并且扩展对象能力的,而Object.defineProperty只是单纯地操作对象的属性

vue2是用Object.defineProperty实现数据响应的,但是受限于Object.defineProperty的实现,必须递归遍历至对象的最底层

vue3用proxy来拦截对象,不管是对对象执行任何操作,都会先通过proxy的处理逻辑

Guess you like

Origin blog.csdn.net/qq_39765048/article/details/121117167