Proxy 与 DefineProperty 的区别是什么?

如何从语义层或者操作层里理解 Proxy 与 DefineProperty

Posted by My on May 10, 2022

一个是 vue3 的响应式原理,一个是 vue2 的,那两者的实质性区别到底是什么?我们分别来分析一下各自的特点。

Proxy

它在 MDN 上的解读是这样的:

The Proxy object enables you to create a proxy for another object,which can intercept and redefine fundamental operations for that object.

意思就是能为一个对象创建一个 「proxy」,并且这个 「proxy」 能拦截和重新定义原来对象的基本操作。

你可以这么分析,大雷音寺是一个对象,小雷音寺是代理,用来拦截或给唐僧制造 「陷阱」 的。

那什么又是对象的 「基本操作」 呢? 赋值 还是 读取属性 又或者说是 遍历 ?其实这些都是在「语法层面」上的操作。这其实是 js 这门语言为了让开发者使用起来更加方便或者是让程序的「可读性」更好,搞出来的一种独特的语法。那实际上,我们在使用语法的时候,会转换为对应的函数。

1
2
3
obj.a; // 【GET】
obj.b = 3; //【SET】
delete obj.a; //【DELETE】

例如在「读取对象属性」的时候,运行的函数是 GET 。这些内部运行的方法就是它的基本操作。

打开 ecma 262 文档看看对象的内部方法(6.1.7.2)。 不见了

而我们在执行 Object.defineProperty 这个方法的时候,内部执行的就是 DefineOwnProperty

来看 proxy 的方法介绍,它有一堆的内部方法,每一个方法都对应一个「捕获器」也可以叫做陷阱,用这个陷阱去拦截。所有的陷阱都是可选的,如果没有定义对应的陷阱,那就会保留源对象的默认行为。 不见了

1
2
3
4
5
6
7
8
9
10
11
const obj = {
  b: 40,
};
const handler = {
  get: function (target, prop) {
    console.log("prop", prop);
    return target[prop];
  },
};
const p = new Proxy(obj, handler);
console.log("p.b", p.b); // 40

这种情况,让 proxy 代理 obj ,那 proxy 就拦截了 obj 的内部方法 [[GET]] ,进而掉进了陷阱函数里了。

defineProperty

defineProperty 就只是在 「执行对象的一个基本操作 defineOwnProperty」。无法监听,无法拦截。

举一个例子

调用数组的 push 方法,或设置数组的 length属性。

Object.defineProperty 进行处理,直接就报错了(length 属性无法被重新定义)。所以说,直接用通过数组对象去调原型里的 push 是没办法监听到的,是 vue 在中间插入一个对象,这个对象重写了数组的方法。这样实际上我们的数组对象去调的时候,调的其实是 vue 的那个对象,里面重写原型了。

proxy 可以直接拦截到对象的基本操作,所以在陷阱函数里,就可以拦截到 arr.push(1) 的操作,包括是这个 push 属性、 length 属性、里面的变化的值等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const arr = [1, 2, 3];

const p = new Proxy(arr, {
  get(target, prop) {
    console.log("get", prop);
    return target[prop];
  },
  set(target, prop, value) {
    console.log("set", prop, value);
    target[prop] = value;
    return true;
  },
});

p.push(1);

// get push
// get length
// set 3 1
// set length 4