JavaScript高级程序设计笔记 9

第9章 代理与反射 — 快速复习笔记

1. 代理(Proxy)

1.1 基本概念

  • 代理:拦截并自定义对象的基本操作(如属性查找、赋值、枚举、函数调用等)。
  • 创建let proxy = new Proxy(target, handler)
    • target:目标对象
    • handler:捕获器(trap)对象,定义拦截行为

1.2 捕获器(Handler Traps)

捕获器 对应操作
get(target, prop, receiver) 读取属性:proxy[prop]
set(target, prop, value, receiver) 赋值属性:proxy[prop] = value
has(target, prop) in 操作符:prop in proxy
deleteProperty(target, prop) delete proxy[prop]
ownKeys(target) Object.keys(proxy)
getOwnPropertyDescriptor(target, prop) Object.getOwnPropertyDescriptor(proxy, prop)
defineProperty(target, prop, descriptor) Object.defineProperty(proxy, prop, descriptor)
preventExtensions(target) Object.preventExtensions(proxy)
isExtensible(target) Object.isExtensible(proxy)
getPrototypeOf(target) Object.getPrototypeOf(proxy)
setPrototypeOf(target, proto) Object.setPrototypeOf(proxy, proto)
apply(target, thisArg, args) 函数调用:proxy(...args)
construct(target, args, newTarget) new 操作符:new proxy(...args)

1.3 简单示例

let target = { name: 'target' };
let handler = {
  get(target, prop, receiver) {
    console.log(`Getting ${prop}`);
    return prop === 'name' ? 'proxied' : Reflect.get(target, prop, receiver);
  },
  set(target, prop, value, receiver) {
    console.log(`Setting ${prop} = ${value}`);
    return Reflect.set(target, prop, value, receiver);
  }
};
let proxy = new Proxy(target, handler);
console.log(proxy.name);  // 输出"Getting name" → "proxied"
proxy.age = 20;           // 输出"Setting age = 20"

2. 反射(Reflect)

2.1 基本概念

  • Reflect 是一个内置对象,提供与捕获器一一对应的方法。
  • 所有 Reflect 方法都有返回值(如 set 返回布尔值,defineProperty 返回布尔值),便于捕获器中返回。
  • 通常用法:在捕获器中调用 Reflect 的对应方法,保持默认行为。

2.2 Reflect 常用方法

Reflect 方法 对应操作 返回值
Reflect.get(target, prop, receiver) 读取属性 属性值
Reflect.set(target, prop, value, receiver) 设置属性 布尔值(是否成功)
Reflect.has(target, prop) in 操作符 布尔值
Reflect.deleteProperty(target, prop) delete 布尔值
Reflect.ownKeys(target) 返回自身属性键数组 数组
Reflect.construct(target, args, newTarget) new 操作符 实例对象

2.3 使用 Reflect 的典型模式

let proxy = new Proxy(target, {
  get(target, prop, receiver) {
    // 自定义逻辑
    console.log('get', prop);
    // 调用默认行为
    return Reflect.get(target, prop, receiver);
  }
});

3. 代理的特性与限制

3.1 代理的局限性

  • 代理对象与目标对象不是同一个对象,严格相等 === 不成立。
  • 某些内部槽(如 DateMapPromise 等)无法通过代理完全模拟,因为原生对象依赖内部 [[Slot]]
  • 代理无法拦截某些不可配置或不可写的属性(会静默失败或抛出错误)。

3.2 可撤销代理

let { proxy, revoke } = Proxy.revocable(target, handler);
revoke(); // 撤销代理,之后任何操作都会抛出 TypeError

4. 代理的应用场景

4.1 数据验证/类型校验

let validator = {
  set(target, prop, value) {
    if (prop === 'age' && (typeof value !== 'number' || value < 0)) {
      throw new TypeError('age must be positive number');
    }
    return Reflect.set(target, prop, value);
  }
};
let person = new Proxy({}, validator);
person.age = 25; // OK
person.age = -1; // TypeError

4.2 日志记录

function logAccess(target) {
  return new Proxy(target, {
    get(target, prop) {
      console.log(`Property ${prop} accessed`);
      return Reflect.get(target, prop);
    }
  });
}

4.3 实现私有属性(命名约定但可拦截)

let privateData = new WeakMap();
class Person {
  constructor(name) {
    privateData.set(this, { name });
    return new Proxy(this, {
      get(target, prop) {
        if (prop === 'name') return privateData.get(target).name;
        return Reflect.get(target, prop);
      }
    });
  }
}

4.4 性能测试(函数调用拦截)

function measure(fn) {
  return new Proxy(fn, {
    apply(target, thisArg, args) {
      console.time('call');
      let result = Reflect.apply(target, thisArg, args);
      console.timeEnd('call');
      return result;
    }
  });
}

4.5 自动填充对象属性(默认值)

function defaultValue(target, defaultVal) {
  return new Proxy(target, {
    get(target, prop) {
      return prop in target ? Reflect.get(target, prop) : defaultVal;
    }
  });
}
let obj = defaultValue({}, 0);
console.log(obj.x); // 0

5. Reflect 的额外用途

  • 替代 Object 上某些命令式方法,并返回更有意义的布尔值(例如 Reflect.defineProperty 返回布尔值,而 Object.defineProperty 失败时抛出错误)。
  • 将函数调用优雅地转化为函数式风格:Reflect.apply(fn, thisArg, args) 等同于 fn.apply(thisArg, args)

6. 常见面试题速查

  1. Proxy 和 Object.defineProperty 有什么不同?
    → Proxy 可以拦截更多操作(如 indelete、函数调用等),并且可以代理整个对象(包括数组、动态属性),而 defineProperty 只能拦截属性读写。

  2. 为什么通常需要在捕获器中调用 Reflect 方法?
    → 保持默认行为;同时 Reflect 返回恰当的布尔值,让捕获器知道操作是否成功。

  3. Proxy 能代理嵌套对象吗?
    → 不能自动深层代理,需要递归包装(即给子对象也创建 Proxy)。

  4. 什么是可撤销代理?
    → 使用 Proxy.revocable() 创建,之后通过 revoke() 函数禁用代理,所有操作抛出错误。

  5. 代理的 this 指向问题?
    → 代理对象调用方法时,方法内部的 this 指向代理对象,而非目标对象。可以通过 Reflect.getreceiver 参数控制。

  6. 如何拦截函数调用?
    → 使用 apply 捕获器,目标必须是函数。

  7. Reflect 相比 Object 方法的优势?
    → 返回值更友好(布尔值而非抛出错误),可作为函数调用(如 Reflect.defineProperty),统一行为。

  8. 代理可以拦截 Symbol 属性吗?
    → 可以,Symbol 也是有效的属性键,捕获器同样适用。

  9. 什么情况下代理会失效或报错?
    → 对不可配置、不可写的属性进行不符合规范的拦截时会抛出 TypeError;撤销代理后所有操作抛出 TypeError

  10. Proxy 的性能如何?
    → 相比原生操作有一定开销,但合理使用对大多数应用影响不大。避免在热路径上大量使用。

posted @ 2024-04-15 09:20  Li_pk  阅读(8)  评论(0)    收藏  举报