JavaScript Proxy 对象详解与应用

420 阅读3分钟

Proxy 是 ES6 引入的一个元编程(Metaprogramming)特性,可以对对象的基本操作进行拦截和自定义。它的出现大大增强了 JavaScript 的语言能力,让我们能够像“劫持”对象一样,对其行为进行精细控制。

Vue3 的响应式系统就是基于 Proxy 实现的,因此理解 Proxy 对于深入学习现代前端框架有重要意义。

一、Proxy 的基本语法

const proxy = new Proxy(target, handler);
  • target
    被代理的对象(可以是对象、数组、函数甚至另一个 Proxy)。
  • handler
    包含捕捉器(trap)的对象,每个 trap 是一个函数,用于拦截目标对象的某种操作。

二、常见的捕捉器(Traps)

捕捉器 (Trap)说明示例操作
get拦截属性读取obj.propobj["prop"]
set拦截属性赋值obj.prop = 123
has拦截属性存在性"prop" in obj
deleteProperty拦截属性删除delete obj.prop
ownKeys拦截对象键枚举Object.keys(obj)for...in
apply拦截函数调用fn(...args)
construct拦截构造函数调用new Fn(...args)

三、Reflect 与 Proxy 的关系

const proxy = new Proxy(obj, {
  get(target, prop, receiver) {
    console.log("读取属性", prop);
    return Reflect.get(target, prop, receiver);
  },
  set(target, prop, value, receiver) {
    console.log("设置属性", prop, value);
    return Reflect.set(target, prop, value, receiver);
  }
});

四、Proxy 工作原理图示

flowchart LR
    A[外部代码访问 obj.prop] --> B[Proxy 代理层]
    B -->|触发 handler.get| C[自定义逻辑]
    C -->|可修改/校验/记录日志| D[Reflect.get 调用目标对象]
    D --> E[返回最终值给外部代码]

五、基础示例

1. 属性读取与设置拦截

const user = { name: "Alice", age: 20 };

const proxyUser = new Proxy(user, {
  get(target, prop) {
    console.log(`读取属性:${prop}`);
    return Reflect.get(target, prop);
  },
  set(target, prop, value) {
    if (prop === "age" && value < 0) {
      throw new Error("年龄不能为负数!");
    }
    console.log(`设置属性:${prop} = ${value}`);
    return Reflect.set(target, prop, value);
  }
});

console.log(proxyUser.name); // "Alice"
proxyUser.age = 25;

2. 函数调用拦截

function sum(a, b) {
  return a + b;
}

const proxySum = new Proxy(sum, {
  apply(target, thisArg, args) {
    console.log(`调用函数参数: ${args}`);
    return Reflect.apply(target, thisArg, args) * 2;
  }
});

console.log(proxySum(2, 3)); // 10

3. 构造函数拦截

class Person {
  constructor(name) {
    this.name = name;
  }
}

const ProxyPerson = new Proxy(Person, {
  construct(target, args) {
    console.log("拦截 new 操作:", args);
    return Reflect.construct(target, args);
  }
});

const p = new ProxyPerson("Bob");

六、Proxy 的典型应用场景

1. 数据校验

set 捕捉器中拦截赋值,做合法性检查。

2. 默认值/容错

通过 get 捕捉器提供默认值,避免访问不存在属性时报错。

3. 日志与调试

统一在 get/set 中打印日志,便于调试和监控。

4. 安全防护

隐藏敏感字段,或者禁止删除/修改关键属性。

5. 响应式系统

Vue3 的响应式就是通过 Proxy 拦截对象的 getset 实现的。

6. Mock API / 虚拟对象

用 Proxy 生成动态对象,返回虚拟数据,常用于前端开发和测试。

应用场景脑图

mindmap
  root((Proxy 应用场景))
    数据校验
      表单输入检查
      参数合法性
    默认值/容错
      安全访问嵌套属性
      避免 undefined 报错
    日志与调试
      调试器
      调用记录
    安全防护
      隐藏敏感属性
      防止非法修改
    响应式系统
      Vue3
      MobX
    Mock API
      虚拟对象
      自动返回值

七、Proxy vs Object.defineProperty

graph TB
    subgraph Vue2: Object.defineProperty
        A1[只能拦截已存在属性] --> A2[数组索引无法监听]
        A2 --> A3[新增/删除属性无效]
    end

    subgraph Vue3: Proxy
        B1[可拦截任意操作] --> B2[支持数组索引]
        B2 --> B3[支持新增/删除属性]
        B3 --> B4[覆盖更多场景: in/new/apply]
    end

八、注意事项

  • 性能开销比直接对象操作大。
  • 代理对象和原对象不同引用。
  • 部分内置对象(Map/Set)拦截不完整。
  • JSON.stringify(proxy) 会触发 get

九、总结

  • Proxy 是 JavaScript 的元编程能力,可拦截和自定义对象行为。
  • 搭配 Reflect 更安全可靠。
  • 在 Vue3、数据校验、安全防护等场景有广泛应用。
  • 是对 Object.defineProperty 的升级,功能更强大,但需注意性能和兼容性。

十、完整案例:实现响应式对象

const bucket = new WeakMap();
let activeEffect = null;

function effect(fn) {
  activeEffect = fn;
  fn();
  activeEffect = null;
}

function reactive(target) {
  return new Proxy(target, {
    get(obj, key) {
      if (activeEffect) {
        let depsMap = bucket.get(obj);
        if (!depsMap) bucket.set(obj, (depsMap = new Map()));
        let deps = depsMap.get(key);
        if (!deps) depsMap.set(key, (deps = new Set()));
        deps.add(activeEffect);
      }
      return Reflect.get(obj, key);
    },
    set(obj, key, value) {
      const result = Reflect.set(obj, key, value);
      const depsMap = bucket.get(obj);
      if (depsMap) {
        const effects = depsMap.get(key);
        effects && effects.forEach(fn => fn());
      }
      return result;
    }
  });
}

const state = reactive({ count: 0 });

effect(() => {
  console.log("count 发生变化:", state.count);
});

state.count++;
state.count++;

十一、响应式流程图

flowchart LR
    A[访问 reactive 对象属性] --> B[Proxy get 捕捉器]
    B -->|存在 activeEffect| C[收集依赖: 将 effect 存入 bucket]
    A2[修改 reactive 对象属性] --> B2[Proxy set 捕捉器]
    B2 --> D[查找对应依赖集合]
    D --> E[触发副作用函数重新执行]
    C --> F[副作用函数打印新值]
    E --> F