Skip to content

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

监听函数对象

Proxy

构造方法

new Proxy()

new Proxy()(target, handler)创建对象的代理(拦截器),用于拦截和自定义对象的底层操作。

  • targetobject,被代理的原始对象,可以是任何 JS 对象(包括数组、函数、类实例等)。

  • handlerobject,包含"捕获器"方法的对象,用于定义代理行为。

  • 返回:

  • proxyProxy,返回代理对象。特性

    • 包装了原始目标对象
    • 拦截所有对目标对象的操作
    • 如果未定义陷阱方法,操作会直接转发到目标对象
    • 代理对象本身没有存储实际数据(数据仍在目标对象中)
    • 使用 typeof 检测代理函数时返回 "function",其他代理对象返回 "object"

核心特性

  1. 性能考虑:代理操作比直接对象访问稍慢,避免在高性能关键路径中过度使用
  2. 透明性:代理对象与目标对象不完全相同(proxy !== target
  3. 原型链:代理对象会继承目标对象的原型链
  4. this 绑定:在陷阱方法中,this 指向 handler 对象,而不是代理或目标对象
  5. 不可代理:某些内置对象(如 DateMap)的内部插槽无法被代理完全捕获

示例

  1. 验证代理

    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 → 默认值

进阶扩展

  1. 可撤销代理

    Proxy 还提供了创建可撤销代理的方法:

    js
    const { 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

  • targetobject,被代理的目标对象。
  • propertystring|symbol,要设置的属性名称。
  • valueany,要设置的新值。
  • receiver?object,表示最初调用的对象,一般指代理对象本身或继承自代理的对象,关键用途
    • 处理 this 绑定问题。
    • 当访问发生在原型链上时,指向实际调用对象。
  • 返回:
  • isSuccessboolean,返回属性设置是否成功。

基本示例

  1. 基本赋值拦截

    js
    const 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

核心特性

  1. 必须遵守的不变式

    当使用 handler.set() 时,必须遵守以下 JS 不变式:

    1. 不可写属性
      • 如果目标对象属性是不可写的,则不能修改其值
      • 尝试修改必须返回 false(严格模式会抛出错误)
    2. 不可配置属性
      • 如果目标对象属性是不可配置的,则不能修改其值
      • 尝试修改必须返回 false
    3. Setter 缺失
      • 如果目标对象属性有 getter 但没有 setter,则不能设置值
      • 尝试设置必须返回 false
    js
    const target = {};
    Object.defineProperty(target, "readOnly", {
      value: "initial",
      writable: false
    });
    
    const proxy = new Proxy(target, {
      set() {
        // 必须遵守不变式
        return false; // 表示设置失败
      }
    });
    
    proxy.readOnly = "new value"; 
    // 非严格模式静默失败
    // 严格模式抛出 TypeError

进阶示例

  1. 使用 receiver 参数

    js
    const 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

  • targetobject,被代理的原始对象

  • propertystring|symbol,被访问的属性名

  • receiver?object,表示最初调用的对象,一般指代理对象本身或继承自代理的对象,关键用途

    • 处理 this 绑定问题

    • 当访问发生在原型链上时,指向实际调用对象

  • 返回:

  • valueany,返回获取到的任意类型的值。

基本示例

  1. 基本属性访问

    js
    const 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"

核心特性

  1. 拦截的操作
    • 属性访问:proxy.propertyproxy[property]
    • 原型链访问:Object.create(proxy).property
    • Reflect.get()
    • 大多数属性访问方法如 Object.getOwnPropertyDescriptor()

注意事项

  1. 递归陷阱:get 中使用 this 访问原始对象会导致无限递归,推荐使用 target 参数

    js
    // 错误示例 - 会导致无限递归
    const handler = {
      get(target, property) {
        return this[property]; // 错误!会再次触发 get
      }
    };
    
    // 正确做法
    const safeHandler = {
      get(target, property) {
        return target[property]; // 直接访问目标对象
      }
    };
  2. 特殊属性处理:

    • 避免拦截内置方法如 toJSON
    • 小心处理 __proto__ 属性

进阶扩展

  1. 默认值处理

    js
    const 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)"
  2. 正确处理 this 绑定

    js
    const 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

  • targetobject,被代理的原始对象。

  • propertystring | symbol,要检查的属性名称。

  • 返回:

  • hasboolena,返回属性是否存在于对象中。重要规则

    • 如果目标对象的属性是不可配置的,不能返回 false
    • 如果目标对象是不可扩展的,不能返回 true(除非属性确实存在)。

基本示例

  1. 基本属性检查

    js
    const 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

核心特性

  1. 拦截的操作

    handler.has() 会拦截以下操作:

    • property in proxy
    • property in Object.create(proxy)
    • with(proxy) { (property); }
    • Reflect.has(proxy, property)

defineProperty()

handler.defineProperty()(target,property,descriptor),用于拦截对象的属性定义操作:defineProperty()

  • targetobject,被代理的原始对象。

  • propertystring|symbol,要定义或修改的属性名称。

  • descriptorobject,要定义或修改的属性的描述符。

  • 返回:

  • isSuccessboolean,返回属性定义是否成功。重要规则

    • 如果目标对象不可扩展,不能添加新属性。
    • 不能添加或修改属性使其不可配置(如果目标属性不可配置)。
    • 如果目标属性不可写,不能修改属性值。

基本示例

  1. 基本属性定义

    js
    const 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"

核心特性

  1. 拦截的操作

    handler.defineProperty() 会拦截以下操作:

    • Object.defineProperty()
    • Object.defineProperties()
    • Reflect.defineProperty()

进阶示例

  1. 属性验证

    js
    const 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); // "无效的邮箱格式"
    }
  2. 防止冻结属性

    阻止属性被设置为不可配置

    js
    const 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

  • targetobject,被代理的原始对象。

  • propertystring|symbol,要删除的属性名称。

  • 返回:

  • isSuccessboolean,返回属性删除是否成功。重要规则

    • 如果目标对象的属性是不可配置的,不能返回 true
    • 在严格模式下,返回 false 会抛出 TypeError

基本示例

  1. 基本属性删除

    js
    const 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

核心特性

  1. 拦截的操作

    handler.deleteProperty() 会拦截以下操作:

    • delete proxy.property
    • delete proxy[property]
    • Reflect.deleteProperty(proxy, property)

注意事项

  1. 删除不可配置属性会报错

    严格模式下: 无法删除不可配置的属性

    js
    const 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); // 严格模式下: "无法删除不可配置的属性"
    }

进阶示例

  1. 防止删除关键属性

    js
    const 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()

  • targetfunction,被代理的目标函数对象

  • thisArgany,函数调用时的 this 上下文

  • argsarray,函数调用时传递的参数列表

  • 返回:

  • resultany,可以返回任何类型的值

基本示例

  1. 基本函数调用

    js
    function 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!

核心特性

  1. 拦截的操作

    handler.apply() 会拦截以下操作:

    • proxy(...args)
    • Function.prototype.apply()
    • Function.prototype.call()
    • Reflect.apply()

进阶示例

  1. 参数验证

    js
    function 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)); // 抛出错误
  2. this 上下文处理

    js
    const 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()

  • targetfunction,被代理的目标构造函数

  • argsarray,构造函数调用时传递的参数列表

  • newTargetfunction,用作新创建对象原型的构造函数,默认是代理对象本身。

  • 返回:

  • instanceobject,返回一个实例对象。

基本示例

  1. 基本构造函数拦截

    js
    class 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"

核心特性

  1. 拦截的操作

    handler.construct() 会拦截以下操作:

    • new proxy(...args)
    • Reflect.construct()

进阶示例

  1. 单例模式

    js
    class 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()

  • targetobject,被代理的原始对象

  • 返回:

  • protoobject|null必须返回一个对象或 null

基本示例

  1. 基本原型访问

    js
    const target = {};
    const handler = {
      getPrototypeOf(target) {
        console.log('访问原型');
        return { custom: true };
      }
    };
    
    const proxy = new Proxy(target, handler);
    console.log(Object.getPrototypeOf(proxy)); // { custom: true }

核心特性

  1. 拦截的操作

    handler.getPrototypeOf() 会拦截以下操作:

    • Object.getPrototypeOf(proxy)
    • Reflect.getPrototypeOf(proxy)
    • proxy.__proto__
    • Object.prototype.isPrototypeOf()
    • instanceof 操作符

进阶示例

  1. 返回假原型

    js
    class 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()

  • targetobject,被代理的原始对象。

  • prototypeobject|null,要设置为对象新原型的对象。

  • 返回:

  • isSuccessboolean,返回原型设置是否成功。

基本示例

  1. 基本原型设置

    js
    const 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"

核心特性

  1. 拦截的操作

    handler.setPrototypeOf() 会拦截以下操作:

    • Object.setPrototypeOf(proxy, prototype)
    • Reflect.setPrototypeOf(proxy, prototype)
    • proxy.__proto__ = prototype(非标准,但广泛支持)

isExtensible()

handler.isExtensible()(target),用于拦截对象可扩展性检查的操作:isExtensible()

  • targetobject,被代理的原始对象。

  • 返回:

  • isExtensibleboolean,返回对象是否可扩展(必须与 Object.isExtensible(target) 的结果一致)。

基本示例

  1. 基本可扩展性检查

    js
    const target = {};
    const handler = {
      isExtensible(target) {
        console.log('检查可扩展性');
        return Reflect.isExtensible(target);
      }
    };
    
    const proxy = new Proxy(target, handler);
    console.log(Object.isExtensible(proxy)); // 输出: "检查可扩展性" → true

核心特性

  1. 拦截的操作

    handler.isExtensible() 会拦截以下操作:

    • Object.isExtensible(proxy)
    • Reflect.isExtensible(proxy)
  2. 返回值一致性规则

    返回值必须与 Object.isExtensible(target) 的结果一致。

    js
    const target = {};
    Object.preventExtensions(target); // 不可扩展
    
    const invalidHandler = {
      isExtensible(target) {
        return true; // 错误!必须返回 false
      }
    };
    
    // 尝试使用会抛出 TypeError
    // const proxy = new Proxy(target, invalidHandler);

进阶示例

  1. 强制不可扩展

    js
    const handler = {
      isExtensible(target) {
        return false; // 始终报告不可扩展
      }
    };
    
    const proxy = new Proxy({}, handler);
    console.log(Object.isExtensible(proxy)); // false
  2. 结合 preventExtensions 使用

    js
    const handler = {
      isExtensible(target) {
        return true; // 始终报告可扩展
      },
      preventExtensions(target) {
        return false; // 阻止设为不可扩展
      }
    };
    
    const proxy = new Proxy({}, handler);
    Object.preventExtensions(proxy); // 失败
    console.log(Object.isExtensible(proxy)); // true
  3. 代理链

    js
    const 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()

  • targetobject,被代理的目标对象。

  • 返回:

  • isSuccessboolean,返回是否成功将目标对象设置为不可扩展。

基本示例

  1. 基础用法

    js
    const 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

核心特性

  1. 拦截的操作

    • Object.preventExtensions(proxy)
    • Reflect.preventExtensions(proxy)
  2. 返回值需和对象实际isExtensible保持一致

    若不一致可以通过 Object.preventExtensions(target) 显示修改

    js
    const handler = {
      preventExtensions(target) {
        // 1. 确保目标不可扩展
        Object.preventExtensions(target); 
        // 2. 返回 true 表示成功
        return true;
      }
    };

ownKeys()

handler.ownKeys()(target),用于拦截对象自身属性键的枚举操作:ownKeys()等

  • targetobject,被代理的目标对象。

  • 返回:

  • ownKeysarray,返回目标对象所有不可配置自身属性的键。返回值规则

    • 不可配置属性要求
      • 必须包含目标对象所有不可配置的自身属性键
      • 如果遗漏不可配置属性,会抛出 TypeError
    • 类型约束
      • 返回值必须是字符串或 Symbol 的可迭代对象(通常为数组)
      • 不能包含非字符串/Symbol 值(如数字、对象等)
    • 不可重复性
      • 返回数组中不能有重复键名
    • 完整性要求
      • 必须包含目标对象所有不可配置自身属性键
      • 可以额外添加可配置属性或新属

基本示例

  1. 基础用法

    js
    const 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)]
  2. 必须包含不可配置属性(关键!)

    js
    const 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']

核心特性

  1. 拦截的操作
    • Object.keys(proxy)
    • Object.getOwnPropertyNames(proxy)
    • Object.getOwnPropertySymbols(proxy)
    • Reflect.ownKeys(proxy)
    • for...in 循环(部分实现)

getOwnPropertyDescriptor()

handler.getOwnPropertyDescriptor()(target, prop),用于拦截对象属性描述符的获取操作:getOwnPropertyDescriptor()

  • targetobject, 被代理的目标对象

  • propstring|symbol,要获取描述符的属性

  • 返回:

  • descriptorobject|undefined,存在返回属性描述符对象;不存在返回 undefined。

基本示例

  1. 基础用法

    js
    const 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

核心特性

  1. 拦截的操作

    • Object.getOwnPropertyDescriptor(proxy, prop)
    • Reflect.getOwnPropertyDescriptor(proxy, prop)
    • Object.keys()(间接调用)
    • for...in 循环(间接调用)
    • JSON.stringify()(间接调用)
  2. 返回值需和对象实际属性描述符保持一致

    返回的描述符会被验证,如果无效会抛出 TypeError 错误。

  3. ownKeys() 的关联

    Object.keys() 等操作依赖此方法获取属性描述符

常见错误

  1. 目标不可扩展时返回不存在的属性

    js
    // 错误示例:目标不可扩展时返回不存在的属性
    const target = Object.preventExtensions({});
    const proxy = new Proxy(target, {
      getOwnPropertyDescriptor() {
        return { value: 'fake' }; // TypeError
      }
    });