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可以显著提升代码的灵活性和可维护性。

相关推荐
paopaokaka_luck1 小时前
基于SpringBoot+Uniapp的健身饮食小程序(协同过滤算法、地图组件)
前端·javascript·vue.js·spring boot·后端·小程序·uni-app
患得患失9492 小时前
【前端】【vscode】【.vscode/settings.json】为单个项目配置自动格式化和开发环境
前端·vscode·json
飛_2 小时前
解决VSCode无法加载Json架构问题
java·服务器·前端
YGY Webgis糕手之路4 小时前
OpenLayers 综合案例-轨迹回放
前端·经验分享·笔记·vue·web
90后的晨仔4 小时前
🚨XSS 攻击全解:什么是跨站脚本攻击?前端如何防御?
前端·vue.js
Ares-Wang4 小时前
JavaScript》》JS》 Var、Let、Const 大总结
开发语言·前端·javascript
90后的晨仔5 小时前
Vue 模板语法完全指南:从插值表达式到动态指令,彻底搞懂 Vue 模板语言
前端·vue.js
德育处主任5 小时前
p5.js 正方形square的基础用法
前端·数据可视化·canvas
烛阴5 小时前
Mix - Bilinear Interpolation
前端·webgl
90后的晨仔5 小时前
Vue 3 应用实例详解:从 createApp 到 mount,你真正掌握了吗?
前端·vue.js