Vue3的Proxy和DefineProperty的区别,你真的了解吗?

324 阅读3分钟

这个问题绝大多数人都被问起过,可是怎么答感觉都不太满意,但其实你只要理解过后,一句话就可以回答出来,而且是极度的精准。

1.Proxy

MDN上的解释:用于创建一个对象的代理,可以拦截和重定义一个对象的基本方法,那么什么叫做对象的基本方法呢?先看下面对象的基本操作。

let obj = {};

// 添加一个属性
obj.a = 3; // 相当于调用了[[Set]]

// 读取一个属性
obj.a; // 相当于调用了[[Get]]

// 设置对象的原型
Object.setPrototypeOf(obj, {a:3}); // 相当于调用了 [[SetPrototypeOf]]

// 读取对象的原型
Object.getPrototypeOf(obj); // 相当于调用了 [[GetPrototypeOf]]

// 遍历对象的属性,相当于调用了对象的 [[getOwnPropertyKeys]]
for(const key in obj){
    
}

上面的基本操作其实本质上会转换成调用对象的内部方法,比如obj.a=3 会调用对象的内部方法 [[Set]],这方法在外面是访问不到的,只有在内部才能访问到。也就是说,平时我们js语法层面,或者是API层面,去对对象进行基本操作,最终在底层都是在调用对象的内部方法。换句话说,你对对象的基本操作,是逃不过这个基本方法的。在ES262文档里面可以看到所有的内部方法

image.png

这里的内部方法,就是Proxy解释中说的基本方法。数组本质上也是一个对象,区别于常规对象,普遍把数组叫做异质对象,常规对象和异质对象的不同体现在[[DefineOwnProperty]]这个内部方法上。Proxy重定义对象基本方法的同时也可以对这些基本方法进行拦截,换句话说,Proxy可以拦截对象的一些操作。你可能会问,函数也能拦截吗?答案是可以的函数在内部也是一个对象,只不过多了两个内部方法[[Call]][[Construct]],你通过new调用而已。

2.DefineProperty

MDN解释:会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。

const arr = [1, 2, 3];

// 往数组里面加一项,会导致数组的长度也变化
arr.push(1); 

// 尝试使用DefineProperty拦截一个数组的length变化
Object.defineProperty(arr, 'length', {
    get(){
        console.log('get', 'length');
        return 2;
    },
    set(value){
        console.log('set', 'length', value);
    }
})

这个时候你会发现,vue2没办法直接拦截数组原型,只能在数组本身查找数组原型的过程中重写push、shift、unshift这些方法。更改数组的原型链,你在vue2中调用的push方法,其实调用的是重写过后的push方法,这样就可以进行拦截。

image.png

总结: Proxy拦截的是针对对象的所有基本方法,他的拦截是全面的,没有任何遗漏;而defineProperty只是众多基本方法其中之一,作用是对现有对象的拦截,拦截面是不全面的。