Proxy
[TOC]
索引
Proxy
构造方法:
- new Proxy():
(target, handler)
,创建对象的代理(拦截器),用于拦截和自定义对象的底层操作。
handler
方法:
监听普通对象:
- handler.set():
(target, property, value, receiver)
,用于拦截对象属性赋值操作:objProxy.xxx=value
。 - handler.get():
(target, property, receiver?)
,用于拦截属性读取操作:objProxy.xxx
。 - handler.has():
(target, property)
,用于拦截属性存在性检查in
操作:in
。 - handler.defineProperty():
(target,property,descriptor)
,用于拦截对象的属性定义操作:defineProperty()
。 - handler.deleteProperty():
(target, property)
,用于拦截对象的属性删除delete
操作:delete
。
监听函数对象:
- handler.apply():
(target, thisArg, args)
,用于拦截函数调用的操作:fnProxy.apply()
。 - handler.construct():
(target, args, newTarget)
,用于拦截构造函数的new
操作:new FnProxy()
。 - handler.getPrototypeOf():
(target)
,用于拦截对象原型[[Prototype]]
访问的操作:getPrototypeOf()
。 - handler.setPrototypeOf():
(target, prototype)
,用于拦截对象原型[[Prototype]]
设置操作:setPrototype()
。 - handler.isExtensible():
(target)
,用于拦截对象可扩展性检查的操作:isExtensible()
。 - handler.preventExtensions():
(target)
,用于拦截对象的不可扩展性设置操作:preventExtensions()
。 - handler.ownKeys():
(target)
,用于拦截对象自身属性键的枚举操作:ownKeys()等
。 - handler.getOwnPropertyDescriptor():
(target, prop)
,用于拦截对象属性描述符的获取操作:getOwnPropertyDescriptor()
。
Proxy
构造方法
new Proxy()
new Proxy():(target, handler)
,创建对象的代理(拦截器),用于拦截和自定义对象的底层操作。
target:
object
,被代理的原始对象,可以是任何 JS 对象(包括数组、函数、类实例等)。handler:
object
,包含"捕获器"方法的对象,用于定义代理行为。返回:
proxy:
Proxy
,返回代理对象。特性:- 包装了原始目标对象
- 拦截所有对目标对象的操作
- 如果未定义陷阱方法,操作会直接转发到目标对象
- 代理对象本身没有存储实际数据(数据仍在目标对象中)
- 使用
typeof
检测代理函数时返回"function"
,其他代理对象返回"object"
核心特性:
- 性能考虑:代理操作比直接对象访问稍慢,避免在高性能关键路径中过度使用
- 透明性:代理对象与目标对象不完全相同(
proxy !== target
) - 原型链:代理对象会继承目标对象的原型链
this
绑定:在陷阱方法中,this
指向handler
对象,而不是代理或目标对象- 不可代理:某些内置对象(如
Date
、Map
)的内部插槽无法被代理完全捕获
示例:
验证代理:
js// 创建一个简单的验证代理 const validator = { set(target, property, value) { if (property === 'age') { if (typeof value !== 'number' || value < 0) { throw new TypeError('年龄必须是正数'); } } target[property] = value; return true; // 表示设置成功 }, get(target, property) { console.log(`访问属性: ${property}`); return property in target ? target[property] : '默认值'; } }; const person = {}; const personProxy = new Proxy(person, validator); // 测试代理 personProxy.name = '张三'; // 正常设置 console.log(personProxy.name); // 输出: 访问属性: name → 张三 try { personProxy.age = -5; // 抛出错误 } catch (e) { console.error(e.message); // 输出: 年龄必须是正数 } console.log(personProxy.nonExisting); // 输出: 访问属性: nonExisting → 默认值
进阶扩展:
可撤销代理:
Proxy
还提供了创建可撤销代理的方法:jsconst { proxy, revoke } = Proxy.revocable(target, handler); // 正常使用代理 proxy.name = '可撤销代理'; // 撤销代理 revoke(); try { console.log(proxy.name); // 抛出 TypeError } catch (e) { console.error('代理已撤销:', e.message); }
handler
监听普通对象
set()@
handler.set():(target, property, value, receiver)
,用于拦截对象属性赋值操作:objProxy.xxx=value
。
- target:
object
,被代理的目标对象。 - property:
string|symbol
,要设置的属性名称。 - value:
any
,要设置的新值。 - receiver?:
object
,表示最初调用的对象,一般指代理对象本身或继承自代理的对象,关键用途:- 处理
this
绑定问题。 - 当访问发生在原型链上时,指向实际调用对象。
- 处理
- 返回:
- isSuccess:
boolean
,返回属性设置是否成功。
基本示例:
基本赋值拦截:
jsconst validator = { set(target, prop, value) { if (prop === "age") { if (typeof value !== "number" || value < 0) { throw new TypeError("Age must be a positive number"); } } target[prop] = value; return true; } }; const person = new Proxy({}, validator); person.age = 30; // 成功 person.age = -5; // 失败,抛出 TypeError
核心特性:
必须遵守的不变式:
当使用
handler.set()
时,必须遵守以下 JS 不变式:- 不可写属性:
- 如果目标对象属性是不可写的,则不能修改其值
- 尝试修改必须返回
false
(严格模式会抛出错误)
- 不可配置属性:
- 如果目标对象属性是不可配置的,则不能修改其值
- 尝试修改必须返回
false
- Setter 缺失:
- 如果目标对象属性有 getter 但没有 setter,则不能设置值
- 尝试设置必须返回
false
jsconst target = {}; Object.defineProperty(target, "readOnly", { value: "initial", writable: false }); const proxy = new Proxy(target, { set() { // 必须遵守不变式 return false; // 表示设置失败 } }); proxy.readOnly = "new value"; // 非严格模式静默失败 // 严格模式抛出 TypeError
- 不可写属性:
进阶示例:
使用 receiver 参数:
jsconst proto = new Proxy({}, { set(target, prop, value, receiver) { console.log(`Setting ${prop} on`, receiver === proxy ? "proxy" : "child"); Reflect.set(target, prop, value, receiver); return true; } }); const proxy = Object.create(proto); // 直接设置代理 proxy.name = "Proxy"; // "Setting name on proxy" // 创建继承对象 const child = Object.create(proxy); child.id = 123; // "Setting id on child"
get()@
handler.get():(target, property, receiver?)
,用于拦截属性读取操作:objProxy.xxx
。
target:
object
,被代理的原始对象property:
string|symbol
,被访问的属性名receiver?:
object
,表示最初调用的对象,一般指代理对象本身或继承自代理的对象,关键用途:处理
this
绑定问题当访问发生在原型链上时,指向实际调用对象
返回:
value:
any
,返回获取到的任意类型的值。
基本示例:
基本属性访问:
jsconst target = { name: 'Alice', age: 30 }; const handler = { get(target, property) { console.log(`读取属性: ${property}`); return target[property]; } }; const proxy = new Proxy(target, handler); console.log(proxy.name); // "读取属性: name" → 输出: "Alice"
核心特性:
- 拦截的操作:
- 属性访问:
proxy.property
和proxy[property]
- 原型链访问:
Object.create(proxy).property
Reflect.get()
- 大多数属性访问方法如
Object.getOwnPropertyDescriptor()
- 属性访问:
注意事项:
递归陷阱:get 中使用 this 访问原始对象会导致无限递归,推荐使用 target 参数
js// 错误示例 - 会导致无限递归 const handler = { get(target, property) { return this[property]; // 错误!会再次触发 get } }; // 正确做法 const safeHandler = { get(target, property) { return target[property]; // 直接访问目标对象 } };
特殊属性处理::
- 避免拦截内置方法如
toJSON
- 小心处理
__proto__
属性
- 避免拦截内置方法如
进阶扩展:
默认值处理:
jsconst handler = { get(target, property) { return property in target ? target[property] : `默认值 (${property})`; } }; const proxy = new Proxy({ name: 'Bob' }, handler); console.log(proxy.name); // "Bob" console.log(proxy.age); // "默认值 (age)"
正确处理 this 绑定:
jsconst target = { _name: 'John', get name() { return this._name; } }; const handler = { get(target, property, receiver) { // 使用 Reflect.get 确保正确的 this 绑定 return Reflect.get(target, property, receiver); } }; const proxy = new Proxy(target, handler); proxy._name = 'Jane'; console.log(proxy.name); // "Jane" (正确绑定)
has()@
handler.has():(target, property)
,用于拦截属性存在性检查 in
操作:in
。
target:
object
,被代理的原始对象。property:
string | symbol
,要检查的属性名称。返回:
has:
boolena
,返回属性是否存在于对象中。重要规则:- 如果目标对象的属性是不可配置的,不能返回
false
。 - 如果目标对象是不可扩展的,不能返回
true
(除非属性确实存在)。
- 如果目标对象的属性是不可配置的,不能返回
基本示例:
基本属性检查:
jsconst target = { name: 'Alice', age: 30 }; const handler = { has(target, property) { console.log(`检查属性存在性: ${property}`); return property in target; } }; const proxy = new Proxy(target, handler); console.log('name' in proxy); // 输出: 检查属性存在性: name → true console.log('email' in proxy); // 输出: 检查属性存在性: email → false
核心特性:
拦截的操作:
handler.has()
会拦截以下操作:property in proxy
property in Object.create(proxy)
with(proxy) { (property); }
Reflect.has(proxy, property)
defineProperty()
handler.defineProperty():(target,property,descriptor)
,用于拦截对象的属性定义操作:defineProperty()
。
target:
object
,被代理的原始对象。property:
string|symbol
,要定义或修改的属性名称。descriptor:
object
,要定义或修改的属性的描述符。返回:
isSuccess:
boolean
,返回属性定义是否成功。重要规则:- 如果目标对象不可扩展,不能添加新属性。
- 不能添加或修改属性使其不可配置(如果目标属性不可配置)。
- 如果目标属性不可写,不能修改属性值。
基本示例:
基本属性定义:
jsconst target = {}; const handler = { defineProperty(target, property, descriptor) { console.log(`定义属性: ${String(property)}`); return Reflect.defineProperty(target, property, descriptor); } }; const proxy = new Proxy(target, handler); Object.defineProperty(proxy, 'name', { value: 'Alice', writable: true, enumerable: true, configurable: true }); // 输出: "定义属性: name" console.log(proxy.name); // "Alice"
核心特性:
拦截的操作:
handler.defineProperty()
会拦截以下操作:Object.defineProperty()
Object.defineProperties()
Reflect.defineProperty()
进阶示例:
属性验证:
jsconst user = {}; const handler = { defineProperty(target, property, descriptor) { // 验证 email 格式 if (property === 'email' && !descriptor.value.includes('@')) { throw new TypeError('无效的邮箱格式'); } return Reflect.defineProperty(target, property, descriptor); } }; const proxy = new Proxy(user, handler); // 有效邮箱 Object.defineProperty(proxy, 'email', { value: 'alice@example.com', writable: true }); // 无效邮箱 - 抛出错误 try { Object.defineProperty(proxy, 'email', { value: 'invalid-email' }); } catch (e) { console.error(e.message); // "无效的邮箱格式" }
防止冻结属性:
阻止属性被设置为不可配置
jsconst config = {}; const handler = { defineProperty(target, property, descriptor) { // 防止属性被冻结 if (descriptor.configurable === false) { console.warn(`属性 ${property} 不能被设为不可配置`); return false; // 阻止操作 } return Reflect.defineProperty(target, property, descriptor); } }; const proxy = new Proxy(config, handler); // 尝试定义不可配置属性 - 失败 const result = Object.defineProperty(proxy, 'apiKey', { value: 'secret123', configurable: false }); console.log(result); // false
deleteProperty()@
handler.deleteProperty():(target, property)
,用于拦截对象的属性删除delete
操作:delete
。
target:
object
,被代理的原始对象。property:
string|symbol
,要删除的属性名称。返回:
isSuccess:
boolean
,返回属性删除是否成功。重要规则:- 如果目标对象的属性是不可配置的,不能返回
true
。 - 在严格模式下,返回
false
会抛出TypeError
。
- 如果目标对象的属性是不可配置的,不能返回
基本示例:
基本属性删除
jsconst target = { name: 'Alice', age: 30 }; const handler = { deleteProperty(target, property) { console.log(`删除属性: ${String(property)}`); return Reflect.deleteProperty(target, property); } }; const proxy = new Proxy(target, handler); delete proxy.age; // 输出: "删除属性: age" console.log('age' in proxy); // false
核心特性:
拦截的操作:
handler.deleteProperty()
会拦截以下操作:delete proxy.property
delete proxy[property]
Reflect.deleteProperty(proxy, property)
注意事项:
删除不可配置属性会报错:
严格模式下: 无法删除不可配置的属性
jsconst obj = {}; Object.defineProperty(obj, 'fixed', { value: 42, configurable: false // 不可配置 }); const handler = { deleteProperty(target, property) { // 尝试删除不可配置属性 return Reflect.deleteProperty(target, property); } }; const proxy = new Proxy(obj, handler); // 尝试删除不可配置属性 - 抛出错误 try { delete proxy.fixed; } catch (e) { console.error(e.message); // 严格模式下: "无法删除不可配置的属性" }
进阶示例:
防止删除关键属性:
jsconst config = { apiKey: 'secret123' }; const handler = { deleteProperty(target, property) { if (property === 'apiKey') { console.warn('不能删除API密钥'); return false; // 阻止删除 } return Reflect.deleteProperty(target, property); } }; const proxy = new Proxy(config, handler); delete proxy.apiKey; // 输出警告,属性未被删除 console.log(proxy.apiKey); // "secret123"
监听函数对象
apply()
handler.apply():(target, thisArg, args)
,用于拦截函数调用的操作:fnProxy.apply()
。
target:
function
,被代理的目标函数对象thisArg:
any
,函数调用时的 this 上下文args:
array
,函数调用时传递的参数列表返回:
result:
any
,可以返回任何类型的值
基本示例:
基本函数调用:
jsfunction greet(name) { return `Hello, ${name}!`; } const handler = { apply(target, thisArg, args) { console.log(`调用函数: ${target.name}`); console.log(`参数: ${args.join(', ')}`); return target.apply(thisArg, args); } }; const proxy = new Proxy(greet, handler); console.log(proxy('Alice')); // 输出: // 调用函数: greet // 参数: Alice // Hello, Alice!
核心特性:
拦截的操作:
handler.apply()
会拦截以下操作:proxy(...args)
Function.prototype.apply()
Function.prototype.call()
Reflect.apply()
进阶示例:
参数验证:
jsfunction divide(a, b) { return a / b; } const safeDivide = new Proxy(divide, { apply(target, thisArg, args) { const [a, b] = args; if (b === 0) throw new Error('除数不能为零'); return target(a, b); } }); console.log(safeDivide(10, 2)); // 5 console.log(safeDivide(10, 0)); // 抛出错误
this 上下文处理:
jsconst obj = { value: 10, double() { return this.value * 2; } }; const proxy = new Proxy(obj.double, { apply(target, thisArg, args) { // 正确传递 this 上下文 return target.apply(thisArg, args); } }); console.log(proxy.call(obj)); // 20(正确),this 指向 obj console.log(proxy()); // NaN(this.value 为 undefined),this 指向 window
construct()
handler.construct():(target, args, newTarget)
,用于拦截构造函数的 new
操作:new FnProxy()
。
target:
function
,被代理的目标构造函数args:
array
,构造函数调用时传递的参数列表newTarget:
function
,用作新创建对象原型的构造函数,默认是代理对象本身。返回:
instance:
object
,返回一个实例对象。
基本示例:
基本构造函数拦截:
jsclass Person { constructor(name) { this.name = name; } } const handler = { construct(target, args, newTarget) { console.log(`创建 ${target.name} 实例`); return new target(...args); } }; const PersonProxy = new Proxy(Person, handler); const person = new PersonProxy('Alice'); // 输出: "创建 Person 实例" console.log(person.name); // "Alice"
核心特性:
拦截的操作:
handler.construct()
会拦截以下操作:new proxy(...args)
Reflect.construct()
进阶示例:
单例模式:
jsclass Database { constructor(config) { this.config = config; } } const singletonHandler = { instance: null, construct(target, args, newTarget) { if (!this.instance) { this.instance = new target(...args); } return this.instance; } }; const SingletonDatabase = new Proxy(Database, singletonHandler); const db1 = new SingletonDatabase({ url: 'localhost' }); const db2 = new SingletonDatabase({ url: 'example.com' }); console.log(db1 === db2); // true console.log(db1.config); // { url: 'localhost' }
getPrototypeOf()
handler.getPrototypeOf():(target)
,用于拦截对象原型[[Prototype]]
访问的操作:getPrototypeOf()
。
target:
object
,被代理的原始对象返回:
proto:
object|null
,必须返回一个对象或null
基本示例:
基本原型访问:
jsconst target = {}; const handler = { getPrototypeOf(target) { console.log('访问原型'); return { custom: true }; } }; const proxy = new Proxy(target, handler); console.log(Object.getPrototypeOf(proxy)); // { custom: true }
核心特性:
拦截的操作:
handler.getPrototypeOf()
会拦截以下操作:Object.getPrototypeOf(proxy)
Reflect.getPrototypeOf(proxy)
proxy.__proto__
Object.prototype.isPrototypeOf()
instanceof
操作符
进阶示例:
返回假原型:
jsclass SecretClass {} const secretProto = SecretClass.prototype; const handler = { getPrototypeOf(target) { return Object.prototype; // 返回假原型 } }; const proxy = new Proxy(new SecretClass(), handler); console.log(proxy instanceof SecretClass); // false console.log(Object.getPrototypeOf(proxy) === Object.prototype); // true
setPrototypeOf()
handler.setPrototypeOf():(target, prototype)
,用于拦截对象原型[[Prototype]]
设置操作:setPrototype()
。
target:
object
,被代理的原始对象。prototype:
object|null
,要设置为对象新原型的对象。返回:
isSuccess:
boolean
,返回原型设置是否成功。
基本示例:
基本原型设置:
jsconst target = {}; const handler = { setPrototypeOf(target, prototype) { console.log(`设置新原型: ${prototype?.constructor.name || 'null'}`); return Reflect.setPrototypeOf(target, prototype); } }; const proxy = new Proxy(target, handler); Object.setPrototypeOf(proxy, { custom: true }); // 输出: "设置新原型: Object"
核心特性:
拦截的操作:
handler.setPrototypeOf()
会拦截以下操作:Object.setPrototypeOf(proxy, prototype)
Reflect.setPrototypeOf(proxy, prototype)
proxy.__proto__ = prototype
(非标准,但广泛支持)
isExtensible()
handler.isExtensible():(target)
,用于拦截对象可扩展性检查的操作:isExtensible()
。
target:
object
,被代理的原始对象。返回:
isExtensible:
boolean
,返回对象是否可扩展(必须与Object.isExtensible(target)
的结果一致)。
基本示例:
基本可扩展性检查:
jsconst target = {}; const handler = { isExtensible(target) { console.log('检查可扩展性'); return Reflect.isExtensible(target); } }; const proxy = new Proxy(target, handler); console.log(Object.isExtensible(proxy)); // 输出: "检查可扩展性" → true
核心特性:
拦截的操作:
handler.isExtensible()
会拦截以下操作:Object.isExtensible(proxy)
Reflect.isExtensible(proxy)
返回值一致性规则:
返回值必须与
Object.isExtensible(target)
的结果一致。jsconst target = {}; Object.preventExtensions(target); // 不可扩展 const invalidHandler = { isExtensible(target) { return true; // 错误!必须返回 false } }; // 尝试使用会抛出 TypeError // const proxy = new Proxy(target, invalidHandler);
进阶示例:
强制不可扩展:
jsconst handler = { isExtensible(target) { return false; // 始终报告不可扩展 } }; const proxy = new Proxy({}, handler); console.log(Object.isExtensible(proxy)); // false
结合
preventExtensions
使用:jsconst handler = { isExtensible(target) { return true; // 始终报告可扩展 }, preventExtensions(target) { return false; // 阻止设为不可扩展 } }; const proxy = new Proxy({}, handler); Object.preventExtensions(proxy); // 失败 console.log(Object.isExtensible(proxy)); // true
代理链:
jsconst target = {}; const handler1 = { isExtensible(target) { console.log('Handler 1'); return Reflect.isExtensible(target); } }; const handler2 = { isExtensible(target) { console.log('Handler 2'); return Reflect.isExtensible(target); } }; const proxy1 = new Proxy(target, handler1); const proxy2 = new Proxy(proxy1, handler2); Object.isExtensible(proxy2); // 输出: // Handler 2 // Handler 1
preventExtensions()
handler.preventExtensions():(target)
,用于拦截对象的不可扩展性设置操作:preventExtensions()
。
target:
object
,被代理的目标对象。返回:
isSuccess:
boolean
,返回是否成功将目标对象设置为不可扩展。
基本示例:
基础用法:
jsconst target = {}; const handler = { preventExtensions(target) { console.log("拦截 preventExtensions"); Object.preventExtensions(target); // 关键:实际设置目标不可扩展 return true; } }; const proxy = new Proxy(target, handler); Object.preventExtensions(proxy); // 输出 "拦截 preventExtensions" console.log(Object.isExtensible(proxy)); // false
核心特性:
拦截的操作:
Object.preventExtensions(proxy)
Reflect.preventExtensions(proxy)
返回值需和对象实际isExtensible保持一致:
若不一致可以通过
Object.preventExtensions(target)
显示修改jsconst handler = { preventExtensions(target) { // 1. 确保目标不可扩展 Object.preventExtensions(target); // 2. 返回 true 表示成功 return true; } };
ownKeys()
handler.ownKeys():(target)
,用于拦截对象自身属性键的枚举操作:ownKeys()等
。
target:
object
,被代理的目标对象。返回:
ownKeys:
array
,返回目标对象所有不可配置自身属性的键。返回值规则:- 不可配置属性要求:
- 必须包含目标对象所有不可配置的自身属性键
- 如果遗漏不可配置属性,会抛出
TypeError
- 类型约束:
- 返回值必须是字符串或 Symbol 的可迭代对象(通常为数组)
- 不能包含非字符串/Symbol 值(如数字、对象等)
- 不可重复性:
- 返回数组中不能有重复键名
- 完整性要求:
- 必须包含目标对象所有不可配置自身属性键
- 可以额外添加可配置属性或新属
- 不可配置属性要求:
基本示例:
基础用法:
jsconst target = { name: 'John', age: 30, [Symbol('id')]: 123 }; const handler = { ownKeys(target) { // 返回自定义属性键列表 return ['name', 'age', 'email', Symbol('newSymbol')]; } }; const proxy = new Proxy(target, handler); console.log(Reflect.ownKeys(proxy)); // 输出: ['name', 'age', 'email', Symbol(newSymbol)]
必须包含不可配置属性(关键!)
jsconst target = {}; Object.defineProperty(target, 'secret', { value: 42, configurable: false, // 不可配置属性 enumerable: true }); const handler = { ownKeys(target) { // 必须包含不可配置属性 'secret' return ['public', 'secret']; // 正确 // return ['public']; // 错误:会抛出 TypeError } }; const proxy = new Proxy(target, handler); console.log(Reflect.ownKeys(proxy)); // ['public', 'secret']
核心特性:
- 拦截的操作:
Object.keys(proxy)
Object.getOwnPropertyNames(proxy)
Object.getOwnPropertySymbols(proxy)
Reflect.ownKeys(proxy)
for...in
循环(部分实现)
getOwnPropertyDescriptor()
handler.getOwnPropertyDescriptor():(target, prop)
,用于拦截对象属性描述符的获取操作:getOwnPropertyDescriptor()
。
target:
object
, 被代理的目标对象prop:
string|symbol
,要获取描述符的属性返回:
descriptor:
object|undefined
,存在返回属性描述符对象;不存在返回 undefined。
基本示例:
基础用法:
jsconst target = { age: 30 }; const handler = { getOwnPropertyDescriptor(target, prop) { if (prop === 'age') { return { value: 42, // 修改值 writable: false, // 改为不可写 enumerable: true, configurable: true }; } return Reflect.getOwnPropertyDescriptor(target, prop); } }; const proxy = new Proxy(target, handler); const descriptor = Object.getOwnPropertyDescriptor(proxy, 'age'); console.log(descriptor.value); // 42 console.log(descriptor.writable); // false
核心特性:
拦截的操作:
Object.getOwnPropertyDescriptor(proxy, prop)
Reflect.getOwnPropertyDescriptor(proxy, prop)
Object.keys()
(间接调用)for...in
循环(间接调用)JSON.stringify()
(间接调用)
返回值需和对象实际属性描述符保持一致:
返回的描述符会被验证,如果无效会抛出 TypeError 错误。
与
ownKeys()
的关联:Object.keys()
等操作依赖此方法获取属性描述符
常见错误:
目标不可扩展时返回不存在的属性:
js// 错误示例:目标不可扩展时返回不存在的属性 const target = Object.preventExtensions({}); const proxy = new Proxy(target, { getOwnPropertyDescriptor() { return { value: 'fake' }; // TypeError } });