vue3.0为什么要用Proxy替代defineProperty

3,827 阅读4分钟

写在前面

如果你对Vue发布订阅的响应式实现有一定的了解,那么你一定知道defineProperty。

Object.defineProperty(obj, prop, descriptor)

Vue2.x中的响应式实现正是基于defineProperty中的descriptor,通过属性的getter、setter监听来实现的。

同时你应该知道的是,我们在Vue中使用下标的方式直接修改属性的值或者添加一个预先不存在的对象属性是无法做到setter监听的,这是defineProperty的局限性。

而我们在Vue中使用的push、pop、shift、unshift、splice等一系列修改原数组的方法,其实不是原生的数组方法,都是被Vue修改过的,其中加入了响应代码。

有消息称Vue3.0的响应式将会使用Proxy来实现,这一举动无疑优化了Vue的响应能力,但是也决定了Vue将会彻底抛弃IE阵营,我们是否应该默(qing)哀(zhu) 一分钟。

defineProperty中的getter、setter

defineProperty的局限性的最大原因是它只能针对单例属性做监听,Vue2.x中对data中的属性做了遍历 + 递归,为每个属性设置了getter、setter。

这也就是为什么Vue只能对data中预定义过的属性做出响应的原因。

defineProperty的用法

    var val = 1
    var obj = Object.defineProperty({}, 'sum', {
        enumerable: true,
        configurable: true,
        get() {
            return val
        },
        set(newValue) {
            val += newValue
        }
    })

测试代码:

测试代码

我们也可以这样写:

    var val = 1
    var obj = {
        get ['sum']() {
            return val
        },
        set ['sum'](newValue) {
            val += newValue
        }
    }

这段代码和上面代码结果是一样的。

这里的'sum'就是被监听的属性名,也就是我们需要监听的“一个”属性。

正是因为使用defineProperty每次只能绑定一个属性监听,所以Vue在遍历 + 递归时要有更大的性能消耗和更多的代码。

Proxy中的getter、setter

Vue3.0终于要使用Proxy,就像上文说的一样,首先应该对IE表示沉(xi)痛(wen)哀(le)悼(jian)。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

也就是说,Proxy的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作,这就完全可以代理所有属性了。

更多Proxy用法可以看 阮一峰大神的ES6入门-Proxy,本文只讲解getter、setter实现。

使用Proxy监听

    var data = {
        sum: 0
    }
    var proxy = new Proxy(data, {
        get(target, property) {
            return target[property]
        },
        set(target, property, value) {
            target[property] += value
        }
    })

Proxy测试

Proxy对象测试

当data为数组时:

Proxy数组测试

Proxy中getter、setter说明

get接收三个参数: get(target, propKey, receiver)

target是被监听对象,在示例代码中表示的是data propKey是属性名,即当前需要获取的属性名 receiver是监听对象,即Proxy实例,在示例代码中表示的是proxy

set接收四个参数: get(target, propKey, value, receiver)

targetpropKeyreceiver三个参数和get中一样 value表示被修改的值,即测试代码中的1

很明显的是,我们使用Proxy时并不需要关心某一个具体的属性,并且Proxy实例上的修改不会影响到被监听对象target中的值(前提是你不主动修改target)。

Proxy中getter、setter的局限性

让我们看看以下代码:

    var data = {
        a: { b: 0 }
    }
    var proxy = new Proxy(data, {
        get(target, property) {
            console.log(`path: ${property}`)
            return target[property]
        },
        set(target, property, value) {
            console.log(`path: ${property}`)
            target[property] += value
        }
    })

打印结果:

打印结果

很明显的是,proxy代理getter、setter只会关注浅层数据,而不会返回深层路径。

如果path在这里能够返回a.b那么将会带来一个很好的收益,不过既然Proxy规范没有实现那么我们也是无计可施。

而且从理解上来说修改proxy.a.b返回的修改对象名是a貌似也是正确的,现在我们需要做的是记住这一结论就好。

结语

Proxy和defineProperty中getter、setter的用法就说到这里。

可以预见的是,Vue3.0中使用Proxy代理将会带来很大的性能提升和更优的代码。

但是就像react中约定的一样,对于引用类型值的响应式的支持可能会变得比以前更不友好(需要开发者实现浅拷贝赋值)。

不过如果尤大依旧选择使用递归的方式来解决对象类型数据的响应式,我个人看来是有些得不偿失的。

从我自己的意愿来看,我更希望是借助于immutable,而不是走递归遍历的老路。

相信尤大一定会有自己的考量,最终的结果肯定是令大部分人满意的方案。

同时Vue3支持typescript的方向更是令我激动不已,作为尤大的粉丝,我们好好期待就是了。