提问
- Object.defineProperty()和proxy的区别?
- 为什么vue3要选用proxy,好处是什么?
proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
Proxy的用法,这个大家都知道
代码语言:javascript复制const p = new Proxy(target, handler)
剖析一下内部实现 ECMAScript 2017 (ECMA-262)
可以看到接收两个参数(target,handler)
- 如果target是undefined,报错
- 运行ProxyCreate(
target
,handler
)
下面是ProxyCreate的实现
排除一下错误处理,核心代码从5开始
先创建一个新的空对象p,
设置p对象的内部方法(除了call]和[Construct])设置为[9.5指定的定义,
然后设置p的call和Construct方法,
再设置内部属性[ProxyTarget]和[ProxyHandler]
返回对象p
我们可以用它们拦截什么?
对于对象的大多数操作,JavaScript 规范中有一个所谓的“内部方法”,它描述了最底层的工作方式。例如 [[Get]]
,用于读取属性的内部方法,[[Set]]
,用于写入属性的内部方法,等等。这些方法仅在规范中使用,我们不能直接通过方法名调用它们。
Proxy 捕捉器会拦截这些方法的调用。它们在 proxy 规范 和下表中被列出。
对于每个内部方法,此表中都有一个捕捉器:可用于添加到 new Proxy
的 handler
参数中以拦截操作的方法名称:
对于对象的大多数操作,JavaScript 规范中有一个所谓的“内部方法”,它描述了最底层的工作方式。例如 [[Get]]
,用于读取属性的内部方法,[[Set]]
,用于写入属性的内部方法,等等。这些方法仅在规范中使用,我们不能直接通过方法名调用它们。
Proxy 捕捉器会拦截这些方法的调用。它们在 proxy 规范 和下表中被列出。
对于每个内部方法,此表中都有一个捕捉器:可用于添加到 new Proxy
的 handler
参数中以拦截操作的方法名称:
内部方法 | Handler 方法 | 何时触发 |
---|---|---|
|
| 读取属性 |
|
| 写入属性 |
|
|
|
|
|
|
|
| 函数调用 |
|
|
|
|
| [Object.getPrototypeOf |
|
| [Object.setPrototypeOf |
|
| [Object.isExtensible |
|
| [Object.preventExtensions |
|
| [Object.defineProperty, Object.defineProperties |
|
| [Object.getOwnPropertyDescriptor, |
|
| [Object.getOwnPropertyNames, Object.getOwnPropertySymbols, |
Reflect
Reflect
是一个内建对象,可简化 Proxy
的创建。
前面所讲过的内部方法,例如 [[Get]]
和 [[Set]]
等,都只是规范性的,不能直接调用。
Reflect
对象使调用这些内部方法成为了可能。它的方法是内部方法的最小包装。
尤其是,Reflect
允许我们将操作符(new
,delete
,……)作为函数(Reflect.construct
,Reflect.deleteProperty
,……)执行调用。这是一个有趣的功能,但是这里还有一点很重要。
对于每个可被 Proxy
捕获的内部方法,在 Reflect
中都有一个对应的方法,其名称和参数与 Proxy
捕捉器相同。
所以,我们可以使用 Reflect
来将操作转发给原始对象。
我们可以把捕捉器重写得更短:
代码语言:javascript复制get(target, prop, receiver) {
return Reflect.get( ... arguments);
}
Reflect
调用的命名与捕捉器的命名完全相同,并且接受相同的参数。它们是以这种方式专门设计的。
因此,return Reflect...
提供了一个安全的方式,可以轻松地转发操作,并确保我们不会忘记与此相关的任何内容。
proxy 的局限性
1. 无法代理内部插槽
许多内建对象,例如 Map
,Set
,Date
,Promise
等,都使用了所谓的“内部插槽”。
例如:
代码语言:javascript复制let map = new Map();
let proxy = new Proxy(map, {});
proxy.set('test', 1); // Error
解决方法 在get的时候将get要返回的值先绑定目标对象后返回
代码语言:javascript复制let map = new Map();
let proxy = new Proxy(map, {
get(target, prop, receiver) {
let value = Reflect.get(...arguments);
return typeof value == 'function' ? value.bind(target) : value;
}
});
proxy.set('test', 1);
alert(proxy.get('test')); // 1(工作了!)
2. 私有字段也和上面一样
3. peoxy != target
这个很好理解 ,代理对象和目标对象是不=== 的
总结
Proxy
是对象的包装器,将代理上的操作转发到对象,并可以选择捕获其中一些操作。
它可以包装任何类型的对象,包括类和函数。
语法为:
代码语言:javascript复制let proxy = new Proxy(target, {
/* trap */
});
……然后,我们应该在所有地方使用 proxy
而不是 target
。代理没有自己的属性或方法。如果提供了捕捉器(trap),它将捕获操作,否则会将其转发给 target
对象。
我们可以捕获
- get,set,deleteProperty 等操作
- 函数调用(apply捕捉器)
- new操作(construct 捕捉器)
Reflect 旨在补充 Proxy。对于任意 Proxy
捕捉器,都有一个带有相同参数的 Reflect
调用。我们应该使用它们将调用转发给目标对象。
Proxy的局限
- 无法代理内部对象的内部插槽
- 无法代理私有字段
- 代理对象和目标对象不相等
Object.defineProperty
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
语法
代码语言:css复制Object.defineProperties(obj, props)
描述
对象里目前存在的属性描述符有两种主要形式:数据(属性)描述符和存取描述符(访问器属性)。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由 getter 函数和 setter 函数所描述的属性。一个描述符只能是这两者其中之一;不能同时是两者。参考视频讲解:进入学习
属性描述符
value
— 值writable
— 如果为true
,则值可以被修改,否则它是只可读的。enumerable
— 如果为true
,则会被在循环中列出,否则不会被列出。configurable
— 如果为true
,则此属性可以被删除,这些特性也可以被修改,否则不可以。
访问器属性
get
—— 一个没有参数的函数,在读取属性时工作,set
—— 带有一个参数的函数,当属性被设置时调用,enumerable
—— 与数据属性的相同,configurable
—— 与数据属性的相同。
回答第一个问题
Object.defineProperty()和proxy的区别?
Object.defineProperty | Proxy |
---|---|
新增/修改一个对象的属性,定义其描述,返回该对象 | 代理目标对象,对其操作拦截,返回代理对象 |
有数据描述符和访问器描述符两种 | 对其13种操作进行拦截 |
只能代理常规对象 | 可以代理任何对象(函数,数组,类) |
不能代理内部对象的内部插槽 |
回答第二个问题
为什么vue3要选用proxy,好处是什么?
- 能够代理任何对象包括数组和函数,对象
- 比Object.defineProperty()更多的基本语义得操作(get,set,delete...)
- 不用循环遍历对象然后再使用Object.defineProperty(),Proxy可以代理对象内所有的属性。
- Object.defineProperty()只能劫持对象的属性(给对象添加属性vue无法检测到)