面试题:Proxy 相较于 Object.defineProperty 有什么优势?
Object.defineProperty 详解
-
语法:
Object.defineProperty(obj, prop, descriptor) -
功能:在一个对象上定义一个新属性或修改其现有属性,并返回此对象。
-
参数:
-
obj要定义或修改属性的对象 -
prop字符串或 Symbol,指定要定义或修改的属性键 -
descriptor对象,指定要定义或修改的属性的描述符通过
descriptor,可以设置prop指定的属性值,getter、setter等等特性。默认情况下,Object.defineProperty添加的属性是不可写、不可枚举和不可配置的。
-
-
属性描述符(
descriptor)的语法:descriptor有两种类型,数据描述符和访问器描述符,其只能是这两种类型之一。根据传入的属性描述符类型不同,给对象中定义或修改的属性的特性也将有所不同。因此,属性也可以分为数据属性和访问器属性。因为
descriptor是一个对象,因此有一系列可选的配置项。有些配置项是数据描述符特有的,有些配置项是访问器描述符特有的,有些配置项则是二者共享使用的。根据是否存在特有配置项,从而可以判断当前属性描述符的类型。注意,一个属性描述符中不能同时拥有两种类型的特有配置项。适用情况 配置 取值 描述 默认值 数据/访问器描述符 configurabletrue/false该配置取值为 false时,
①属性类型不可更改(数据属性、访问器属性)
②属性不可被删除
③ 属性描述符的配置项不可更改(特例,对于可写的数据属性,可以修改其数据描述符的value和writable)
否则:TypeErrorfalse数据/访问器描述符 enumerabletrue/false该配置取值为 true时,属性可以在对象的属性枚举中出现。(如for...in,Object.keys()等)false数据描述符 value任何有效的 JavaScript 值 数据属性值 undefined数据描述符 writabletrue/false该配置取值为 true时,属性可写(即可以使用赋值运算符=更改属性值)false访问器描述符 get属性 getter函数当访问属性时,会不带参调用该函数,返回值被作为该属性的值。 undefined访问器描述符 set属性 setter函数当给属性赋值时,会调用该函数,并携带一个参数(表示要赋给属性的值)。 undefined注意事项:
-
get、set配置项对应的函数中的this不一定是属性所在对象,而是通过该属性访问的对象(由于继承关系的存在,当子对象访问父对象的getter、setter函数时,其中this指向子对象)。let parent = Object.defineProperty({}, "self", {get() {return this;} });let child = Object.create(parent);console.log(child.self === child); // true console.log(child.self === parent); // false console.log(parent.self === child); // false console.log(parent.self === parent); // true -
如果属性描述符没有包含任何特有配置项(
value、writable、get、set),此时该属性描述符被视作数据描述符。 -
如果属性描述符同时包含两种类型的特有配置项,则会抛出异常。
-
因为判断属性描述符是数据描述符还是访问器描述符时,常需要参考其中的属性,但是属性描述符其原型上的属性也会被考虑在内,这有可能会带来潜在问题,有以下两种解决方式。
-
方式一:创建一个原型为
null的属性描述符const descriptor = Object.create(null); -
方式二:冻结 Object 原型对象
(Object.freeze || Object)(Object.prototype);Object.freeze方法用于冻结一个对象,从而 ①不能向对象中添加新属性 ②不能删除对象中已有属性 ③不能修改对象中已有属性 ④ 不可有改对象的原型
-
-
Proxy 详解
-
语法:
const p = new Proxy(target, handler) -
功能:用于创建一个对象的代理,从而实现拦截或自定义对象的基本操作(属性查找、赋值、枚举、函数调用等等)。
-
参数
target:被代理的对象,可以是任何类型的对象(包含原生数组、函数、甚至代理)。handler:对象,其中的属性通常是函数,用于自定义代理p的各种操作所对应的具体的执行逻辑。
-
处理器对象(
handler)的语法:handler中定义了一批特定属性,取值通常为函数,称之为捕获器(trap)。配置项 描述 触发条件 getPrototypeOf()Object.getPrototypeOf()方法的捕捉器。当读取代理对象的原型时,该方法调用。如果该方法返回的不是对象也不是null,则会报错TypeError。handler.getPrototypeOf()setPrototypeOf()Object.setPrototypeOf()方法的捕捉器。成功修改原型则返回true,否则false。handler.setPrototypeOf()isExtensible()Object.isExtensible()方法的捕捉器。handler.isExtensible()preventExtensions()Object.preventExtensions()方法的捕捉器。handler.preventExtensions()getOwnPropertyDescriptor()Object.getOwnPropertyDescriptor()方法的捕捉器。handler.getOwnPropertyDescriptor()defineProperty()Object.defineProperty()方法的捕捉器。handler.defineProperty()has()in操作符的捕捉器。handler.has()get()属性读取操作的捕捉器。 handler.get()set()属性设置操作的捕捉器。 handler.set()deleteProperty()delete 操作符的捕捉器。 handler.deleteProperty()ownKeys()Object.getOwnPropertyNames()方法和Object.getOwnPropertySymbols()方法的捕捉器。handler.ownKeys()apply()函数调用操作的捕捉器。 handler.apply()construct()new 操作符的捕捉器。 handler.construct()
Proxy 相较于 Object.defineProperty 的优势
通过上边的介绍,可以看出 Proxy 有以下几点优势
- 拦截范围更广:
Proxy支持属性访问、修改、删除、枚举、原型操作、对象构造、方法调用、属性存在性检测、获取属性描述符、定义属性、in操作符等的拦截。而Object.defineProperty只能拦截属性访问、修改。 - 拦截实现更简单:
Proxy可以直接拦截整个对象。而Object.defineProperty只能对单个属性进行拦截,如果要拦截整个对象,则需要在对象上逐个进行属性拦截。 - 可撤销的拦截:
Proxy.revocable()方法可以创建一个可撤销的代理对象。而Object.defineProperty一旦对属性进行了定义,就无法撤销。 - 支持对数组的全方位拦截:
Proxy的set捕获器可以捕捉数组的任何操作,包含通过索引修改数组元素,通过数组方法修改数组元素,以及数组长度的变化。但是Object.defineProperty则无法实现。
REFERENCES
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
https://juejin.cn/post/7275551128854560779