JavaScript Proxy 和 Reflect

一、Proxy 基本概念

1. 什么是 Proxy

Proxy 是 ES6 引入的元编程特性,用于创建一个对象的代理,从而可以拦截和自定义对象的基本操作。

javascript 复制代码
const target = {};
const handler = {
  get(target, property) {
    return `拦截读取: ${property}`;
  }
};
const proxy = new Proxy(target, handler);

console.log(proxy.message); // "拦截读取: message"

2. 核心术语

  • target:被代理的目标对象
  • handler:包含拦截器(trap)的对象
  • trap:拦截目标对象操作的函数

二、Proxy 拦截操作

1. 常用拦截器

属性访问拦截

javascript 复制代码
const handler = {
  get(target, prop) {
    console.log(`读取属性 ${prop}`);
    return target[prop];
  },
  set(target, prop, value) {
    console.log(`设置属性 ${prop} = ${value}`);
    target[prop] = value;
    return true; // 表示设置成功
  }
};

方法调用拦截

javascript 复制代码
const handler = {
  apply(target, thisArg, argumentsList) {
    console.log(`调用函数,参数: ${argumentsList}`);
    return target.apply(thisArg, argumentsList);
  }
};

function sum(a, b) { return a + b; }
const proxySum = new Proxy(sum, handler);
proxySum(1, 2); // 输出日志后返回3

2. 完整拦截器列表

拦截器 触发时机 示例
get 读取属性 proxy.x
set 设置属性 proxy.x = 1
has in 操作符 'x' in proxy
deleteProperty delete 操作 delete proxy.x
apply 函数调用 proxy()
construct new 操作 new proxy()
ownKeys Object.keys()等 Object.keys(proxy)
getPrototypeOf Object.getPrototypeOf() Object.getPrototypeOf(proxy)
setPrototypeOf Object.setPrototypeOf() Object.setPrototypeOf(proxy, proto)
isExtensible Object.isExtensible() Object.isExtensible(proxy)
preventExtensions Object.preventExtensions() Object.preventExtensions(proxy)
getOwnPropertyDescriptor Object.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor(proxy, 'x')
defineProperty Object.defineProperty() Object.defineProperty(proxy, 'x', desc)

三、Proxy 高级应用

1. 数据验证

javascript 复制代码
const validator = {
  set(target, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value) || value < 0) {
        throw new TypeError('年龄必须是正整数');
      }
    }
    target[prop] = value;
    return true;
  }
};

const person = new Proxy({}, validator);
person.age = 30; // 正常
person.age = 'young'; // 抛出错误

2. 自动填充对象

javascript 复制代码
const autoFiller = {
  get(target, prop) {
    if (!(prop in target)) {
      target[prop] = {};
    }
    return target[prop];
  }
};

const tree = new Proxy({}, autoFiller);
tree.branch1.branch2.leaf = 'value';
console.log(tree); // { branch1: { branch2: { leaf: 'value' } } }

3. 负数组索引

javascript 复制代码
const negativeArray = arr => new Proxy(arr, {
  get(target, prop, receiver) {
    const index = parseInt(prop);
    if (index < 0) {
      prop = target.length + index;
    }
    return Reflect.get(target, prop, receiver);
  }
});

const arr = negativeArray(['a', 'b', 'c']);
console.log(arr[-1]); // "c"

四、Reflect 基本概念

1. 什么是 Reflect

Reflect 是一个内置对象,提供拦截 JavaScript 操作的方法,这些方法与 Proxy 的拦截器一一对应。

javascript 复制代码
const obj = { x: 1 };
console.log(Reflect.get(obj, 'x')); // 1

2. Reflect 的设计目的

  1. 统一操作 API :将语言内部操作(如 [[Get]][[Set]])暴露为函数
  2. 与 Proxy 配合:每个 Proxy 拦截器都有对应的 Reflect 方法
  3. 替代部分 Object 方法:更合理的返回值设计

五、Reflect 主要方法

1. 基本操作方法

方法 等价操作 区别
Reflect.get(target, prop) target[prop] 支持 receiver
Reflect.set(target, prop, value) target[prop] = value 返回布尔值
Reflect.has(target, prop) prop in target -
Reflect.deleteProperty(target, prop) delete target[prop] 返回布尔值
Reflect.ownKeys(target) Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target)) -

2. 对象扩展方法

方法 等价操作 区别
Reflect.defineProperty(target, prop, desc) Object.defineProperty(target, prop, desc) 返回布尔值
Reflect.getOwnPropertyDescriptor(target, prop) Object.getOwnPropertyDescriptor(target, prop) -
Reflect.isExtensible(target) Object.isExtensible(target) -
Reflect.preventExtensions(target) Object.preventExtensions(target) 返回布尔值

3. 原型相关方法

javascript 复制代码
const obj = {};
const proto = { x: 1 };

// 设置原型
Reflect.setPrototypeOf(obj, proto);
console.log(obj.x); // 1

// 获取原型
console.log(Reflect.getPrototypeOf(obj) === proto); // true

4. 函数调用方法

javascript 复制代码
function greet(name) {
  return `Hello, ${name}`;
}

// 函数调用
console.log(Reflect.apply(greet, null, ['Alice'])); // "Hello, Alice"

// 构造函数调用
class Person {}
const p = Reflect.construct(Person, []);
console.log(p instanceof Person); // true

六、Proxy 和 Reflect 配合使用

1. 默认转发模式

javascript 复制代码
const proxy = new Proxy(target, {
  get(target, prop, receiver) {
    console.log(`GET ${prop}`);
    return Reflect.get(target, prop, receiver);
  },
  set(target, prop, value, receiver) {
    console.log(`SET ${prop}=${value}`);
    return Reflect.set(target, prop, value, receiver);
  }
});

2. 实现观察者模式

javascript 复制代码
function createObservable(target, observer) {
  return new Proxy(target, {
    set(target, prop, value, receiver) {
      const oldValue = target[prop];
      const result = Reflect.set(target, prop, value, receiver);
      if (result && oldValue !== value) {
        observer(prop, oldValue, value);
      }
      return result;
    }
  });
}

const data = createObservable({}, (prop, oldVal, newVal) => {
  console.log(`属性 ${prop} 从 ${oldVal} 变为 ${newVal}`);
});

data.x = 1; // 输出: "属性 x 从 undefined 变为 1"
data.x = 2; // 输出: "属性 x 从 1 变为 2"

七、实际应用场景

1. API 客户端封装

javascript 复制代码
function createAPI(baseUrl) {
  return new Proxy({}, {
    get(target, endpoint) {
      return async function(params) {
        const url = `${baseUrl}/${endpoint}`;
        const response = await fetch(url, {
          method: 'POST',
          body: JSON.stringify(params)
        });
        return response.json();
      };
    }
  });
}

const api = createAPI('https://api.example.com');
const userData = await api.users({ id: 123 });

2. 不可变数据

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

const immutable = createImmutable({ x: 1 });
immutable.x = 2; // 抛出错误

3. 自动缓存

javascript 复制代码
function createCached(fn) {
  const cache = new Map();
  return new Proxy(fn, {
    apply(target, thisArg, args) {
      const key = JSON.stringify(args);
      if (cache.has(key)) {
        return cache.get(key);
      }
      const result = Reflect.apply(target, thisArg, args);
      cache.set(key, result);
      return result;
    }
  });
}

const heavyCompute = createCached(x => {
  console.log('计算中...');
  return x * x;
});

heavyCompute(2); // 输出"计算中..."后返回4
heavyCompute(2); // 直接返回4,不输出

八、注意事项与最佳实践

1. Proxy 限制

  1. 目标对象不变:Proxy 不会修改目标对象本身
  2. 严格相等proxy === target 为 false
  3. 内部插槽:某些内置对象(如 Date、Map)的内部插槽无法被拦截

2. 性能考虑

  1. 代理有开销:简单操作用原生方式更快
  2. 避免多层代理:代理的代理会显著降低性能
  3. 热路径优化:关键性能路径避免使用代理

3. 最佳实践

  1. 透明代理:尽量保持代理行为与目标对象一致
  2. 合理使用 Reflect:在拦截器中使用对应 Reflect 方法
  3. 明确文档:记录代理的自定义行为
  4. 错误处理:在拦截器中妥善处理异常

九、浏览器兼容性

1. 支持情况

  • Proxy:主流浏览器及 Node.js 6+
  • Reflect:主流浏览器及 Node.js 6+

2. 不支持的解决方案

javascript 复制代码
// 简单的Proxy polyfill检查
if (typeof Proxy === 'undefined') {
  console.warn('Proxy not supported, falling back to direct access');
  Proxy = function(target) { return target; };
}

十、总结

Proxy 和 Reflect 为 JavaScript 提供了强大的元编程能力:

  1. Proxy

    • 创建对象的代理,拦截基本操作
    • 实现数据绑定、验证、观察等高级功能
    • 自定义对象基本行为
  2. Reflect

    • 提供操作对象的统一API
    • 与Proxy拦截器一一对应
    • 改进Object方法的返回值设计

两者结合使用可以实现许多传统JavaScript难以实现的高级模式,如:

  • 透明的数据观察
  • 自动化的资源管理
  • 动态接口适配
  • 领域特定语言(DSL)

这些特性在现代框架和库中有广泛应用,如Vue 3的响应式系统就基于Proxy实现。合理使用Proxy和Reflect可以显著提升代码的灵活性和可维护性。

相关推荐
影i几秒前
Vue 3 踩坑实录:如何优雅地把“上古”第三方插件关进 Iframe 小黑屋
前端
小明记账簿_微信小程序1 分钟前
vue项目中使用echarts做词云图
前端
浪浪山_大橙子3 分钟前
Trae SOLO 生成 TensorFlow.js 手势抓取物品太牛了 程序员可以退下了
前端·javascript
出征10 分钟前
Pnpm的进化进程
前端
屿小夏22 分钟前
openGauss020-openGauss 向量数据库深度解析:从存储到AI的全栈优化
前端
Y***985134 分钟前
【学术会议论文投稿】Spring Boot实战:零基础打造你的Web应用新纪元
前端·spring boot·后端
T***u33339 分钟前
JavaScript在Node.js中的流处理大
开发语言·javascript·node.js
q***33371 小时前
SpringMVC新版本踩坑[已解决]
android·前端·后端
Croa-vo1 小时前
TikTok 数据工程师三轮 VO 超详细面经:技术深挖 + 建模推导 + 压力测试全记录
javascript·数据结构·经验分享·算法·面试
亿元程序员1 小时前
做了十年游戏,我才意识到:程序员最该投资的,是一台专业的编程显示器
前端