JavaScript Proxy 完全指南:从拦截到实战的保姆级教程

前端开发者每天都要和各种对象打交道,但你是否想过给对象装上"监控摄像头"?Proxy 就是这样一个神奇的存在------它能让你在对象的一举一动上安装监听器,实现真正的"上帝视角"。本文将用 5 个真实场景 + 7 个核心用法,带你彻底掌握这个 ES6 的终极武器。


一、初识 Proxy:对象世界的"智能管家"

1.1 什么是 Proxy?

想象你有一个贴身管家(Proxy),每当有人要接触你的物品(对象)时,管家都会先询问你的意见。Proxy 就是这样一个中介层,允许你拦截并自定义对象的底层操作。

javascript 复制代码
const person = { name: '小明' };

const guardian = new Proxy(person, {
  get(target, property) {
    console.log(`有人想获取 ${property} 属性`);
    return target[property];
  }
});

console.log(guardian.name); // 输出:有人想获取 name 属性 → 小明

1.2 创建基础 Proxy

Proxy 构造函数接收两个参数:

javascript 复制代码
const proxy = new Proxy(target, handler);
  • target: 要代理的目标对象
  • handler: 定义拦截行为的"陷阱"对象

二、Proxy 的七大核心陷阱

2.1 属性访问拦截(get)

javascript 复制代码
const bankAccount = new Proxy({ balance: 1000 }, {
  get(target, prop) {
    if (prop === 'balance') {
      console.warn('余额查询需授权');
      return '***';
    }
    return target[prop];
  }
});

console.log(bankAccount.balance); // 输出警告并返回***

2.2 属性设置拦截(set)

javascript 复制代码
const validator = new Proxy({}, {
  set(target, prop, value) {
    if (prop === 'age' && !Number.isInteger(value)) {
      throw new Error('年龄必须是整数');
    }
    target[prop] = value;
    return true; // 必须返回true表示成功
  }
});

validator.age = 30; // 正常
validator.age = '三十'; // 抛出错误

这个Proxy就是保证对应基本类型。

2.3 函数调用拦截(apply)

javascript 复制代码
const logger = new Proxy(function sum(a, b) {
  return a + b;
}, {
  apply(target, thisArg, args) {
    console.log(`正在计算 ${args.join('+')}`);
    return target(...args);
  }
});

logger(2, 3); // 输出日志 → 5

这个 Proxy 对象拦截了对 sum 函数的调用,在调用函数之前输出了一条日志信息,然后执行原函数并返回结果。通过这种方式,我们可以在不修改原始函数代码的情况下,为函数添加额外的功能,比如日志记录、性能监控等。

2.4 其他常用陷阱


三、Proxy 的五大实战场景

3.1 数据验证器

javascript 复制代码
const userValidator = new Proxy({}, {
  set(target, prop, value) {
    const rules = {
      username: v => v.length >= 3,
      password: v => /[A-Z]/.test(v),
      email: v => v.includes('@')
    };
    
    if (rules[prop] && !rules[prop](value)) {
      throw new Error(`${prop} 格式错误`);
    }
    
    target[prop] = value;
    return true;
  }
});

3.2 自动缓存系统

javascript 复制代码
function createCache(fn) {
  const cache = new Map();
  
  return new Proxy(fn, {
    apply(target, thisArg, args) {
      const key = JSON.stringify(args);
      
      if (cache.has(key)) {
        console.log('命中缓存');
        return cache.get(key);
      }
      
      const result = target(...args);
      cache.set(key, result);
      return result;
    }
  });
}

3.3 表单自动保存

javascript 复制代码
const formData = new Proxy({}, {
  set(target, prop, value) {
    target[prop] = value;
    autoSaveToLocalStorage();
    return true;
  }
});

// 每次修改自动触发保存
formData.username = 'Alice';

四、Proxy 的进阶技巧

4.1 链式操作拦截

javascript 复制代码
const chain = new Proxy({}, {
  get(target, prop) {
    return (...args) => {
      console.log(`执行 ${prop} 操作,参数:${args}`);
      return new Proxy({}, this); // 返回新代理实现链式调用
    };
  }
});

chain.add(5).subtract(3).multiply(2);

4.2 不可变对象

javascript 复制代码
function createImmutable(obj) {
  return new Proxy(obj, {
    set() { throw new Error('不可修改'); },
    deleteProperty() { throw new Error('不可删除'); }
  });
}

五、Proxy 的注意事项

  1. 性能损耗:频繁操作时需谨慎使用
  2. 浏览器兼容:IE 全系不支持,移动端需注意
  3. 无法代理特殊对象 :如 Date, Map, Set 等需要特殊处理
  4. 代理的代理:多层代理可能产生意外行为

六、Proxy vs Object.defineProperty

特性 Proxy Object.defineProperty
拦截范围 13种操作 仅属性读写
数组处理 完美支持 需要hack处理
动态属性 自动捕获 需要预先定义
代码侵入性 需要修改对象
性能 稍慢 更快

掌握 Proxy 就像获得了一把打开元编程大门的钥匙。建议读者从简单的数据验证开始实践,逐步探索更复杂的应用场景。记住:能力越大责任越大,合理使用才能发挥其真正价值。