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

相关推荐
coding随想38 分钟前
JavaScript ES6 解构:优雅提取数据的艺术
前端·javascript·es6
年老体衰按不动键盘42 分钟前
快速部署和启动Vue3项目
java·javascript·vue
小小小小宇42 分钟前
一个小小的柯里化函数
前端
灵感__idea1 小时前
JavaScript高级程序设计(第5版):无处不在的集合
前端·javascript·程序员
小小小小宇1 小时前
前端双Token机制无感刷新
前端
小小小小宇1 小时前
重提React闭包陷阱
前端
小小小小宇1 小时前
前端XSS和CSRF以及CSP
前端
UFIT1 小时前
NoSQL之redis哨兵
java·前端·算法
超级土豆粉1 小时前
CSS3 的特性
前端·css·css3
星辰引路-Lefan1 小时前
深入理解React Hooks的原理与实践
前端·javascript·react.js