前置知识: JavaScript

Proxy与Reflect实际应用

00:00
3 min Advanced 2026/6/14

JavaScript Proxy与Reflect实际应用场景:响应式系统、验证、缓存、日志。

1. Proxy 基础

1.1 语法

const proxy = new Proxy(target, handler);
参数说明
target代理原始对象
handler定义拦截操作对象陷阱函数)

1.2 可拦截的操作

陷阱(Trap)拦截的操作
get读取属性 proxy.key
set设置属性 proxy.key = value
haskey in proxy
deletePropertydelete proxy.key
ownKeysObject.keys(proxy)
getOwnPropertyDescriptorObject.getOwnPropertyDescriptor
getPrototypeOfObject.getPrototypeOf(proxy)
setPrototypeOfObject.setPrototypeOf(proxy)
isExtensibleObject.isExtensible(proxy)
preventExtensionsObject.preventExtensions(proxy)
apply函数调用 proxy(...args)
constructnew proxy(...args)

2. Reflect 配合 Proxy

2.1 Reflect 的作用

Reflect 提供Proxy 陷阱一一对应静态方法,用于在陷阱执行默认行为

const proxy = new Proxy(obj, {
  get(target, key, receiver) {
    console.log(`读取 ${String(key)}`);
    return Reflect.get(target, key, receiver); // 执行默认行为
  },
  set(target, key, value, receiver) {
    console.log(`设置 ${String(key)} = ${value}`);
    return Reflect.set(target, key, value, receiver);
  },
});

2.2 为什么用 Reflect 而非直接操作

//  直接操作可能忽略属性描述符
set(target, key, value) {
  target[key] = value; // 忽略 setter、writable 等限制
  return true;
}

//  Reflect 尊重属性描述符
set(target, key, value, receiver) {
  return Reflect.set(target, key, value, receiver); // 正确触发 setter
}

3. 实际应用场景

3.1 响应式系统(Vue 3 原理)

function reactive(target) {
  const deps = new Map(); // key → Set<effect>

  const track = (key) => {
    if (activeEffect) {
      let dep = deps.get(key);
      if (!dep) {
        dep = new Set();
        deps.set(key, dep);
      }
      dep.add(activeEffect);
    }
  };

  const trigger = (key) => {
    const dep = deps.get(key);
    if (dep) {
      dep.forEach((effect) => effect());
    }
  };

  return new Proxy(target, {
    get(target, key, receiver) {
      track(key);
      const result = Reflect.get(target, key, receiver);
      // 深层响应式
      if (typeof result === 'object' && result !== null) {
        return reactive(result);
      }
      return result;
    },
    set(target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver);
      const result = Reflect.set(target, key, value, receiver);
      if (oldValue !== value) {
        trigger(key);
      }
      return result;
    },
  });
}

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

// 使用
const state = reactive({ count: 0 });
effect(() => {
  console.log('count changed:', state.count);
});
state.count++; // 自动触发: "count changed: 1"

3.2 属性验证

function validated(target, schema) {
  return new Proxy(target, {
    set(target, key, value, receiver) {
      if (schema[key]) {
        const validator = schema[key];
        if (!validator.validate(value)) {
          throw new TypeError(`Invalid value for ${String(key)}: ${value}`);
        }
      }
      return Reflect.set(target, key, value, receiver);
    },
  });
}

const user = validated(
  {},
  {
    age: { validate: (v) => typeof v === 'number' && v >= 0 && v <= 150 },
    email: { validate: (v) => typeof v === 'string' && /^[^\s@]+@[^\s@]+$/.test(v) },
  }
);

user.age = 25; //
user.age = -1; // TypeError: Invalid value for age: -1
user.email = 'a@b.com'; //
user.email = 'invalid'; // TypeError

3.3 缓存代理

function cachedFetch(target) {
  const cache = new Map();

  return new Proxy(target, {
    apply(target, thisArg, args) {
      const key = JSON.stringify(args);
      if (cache.has(key)) {
        console.log('Cache hit:', key);
        return Promise.resolve(cache.get(key));
      }
      return Reflect.apply(target, thisArg, args).then((result) => {
        cache.set(key, result);
        return result;
      });
    },
  });
}

const cachedApi = cachedFetch(async (url) => {
  const res = await fetch(url);
  return res.json();
});

// 第一次请求:实际发起
await cachedApi('/api/users');
// 第二次请求:缓存命中
await cachedApi('/api/users');

3.4 私有属性

function withPrivate(target, privateKeys) {
  return new Proxy(target, {
    get(target, key) {
      if (privateKeys.includes(key)) {
        throw new Error(`Cannot access private property: ${String(key)}`);
      }
      return Reflect.get(target, key);
    },
    set(target, key, value) {
      if (privateKeys.includes(key)) {
        throw new Error(`Cannot set private property: ${String(key)}`);
      }
      return Reflect.set(target, key, value);
    },
    ownKeys(target) {
      return Reflect.ownKeys(target).filter((key) => !privateKeys.includes(key));
    },
    getOwnPropertyDescriptor(target, key) {
      if (privateKeys.includes(key)) return undefined;
      return Reflect.getOwnPropertyDescriptor(target, key);
    },
  });
}

const obj = withPrivate({ name: 'Alice', _secret: 'password' }, ['_secret']);
obj.name; // 'Alice'
obj._secret; // Error: Cannot access private property: _secret
Object.keys(obj); // ['name']

3.5 函数日志

function withLogging(fn) {
  return new Proxy(fn, {
    apply(target, thisArg, args) {
      console.log(`调用 ${target.name}(${args.map((a) => JSON.stringify(a)).join(', ')})`);
      const start = performance.now();
      const result = Reflect.apply(target, thisArg, args);
      const duration = performance.now() - start;
      console.log(`返回: ${JSON.stringify(result)} (${duration.toFixed(2)}ms)`);
      return result;
    },
  });
}

const add = withLogging(function add(a, b) {
  return a + b;
});

add(1, 2);
// 调用 add(1, 2)
// 返回: 3 (0.01ms)

3.6 负索引数组

function negativeIndexArray(arr) {
  return new Proxy(arr, {
    get(target, key) {
      if (typeof key === 'string' && /^-\d+$/.test(key)) {
        const index = target.length + parseInt(key);
        return Reflect.get(target, index);
      }
      return Reflect.get(target, key);
    },
    set(target, key, value) {
      if (typeof key === 'string' && /^-\d+$/.test(key)) {
        const index = target.length + parseInt(key);
        return Reflect.set(target, index, value);
      }
      return Reflect.set(target, key, value);
    },
  });
}

const arr = negativeIndexArray([10, 20, 30, 40, 50]);
arr[-1]; // 50
arr[-2]; // 40

4. 可撤销代理

const { proxy, revoke } = Proxy.revocable(target, handler);

// 使用 proxy
proxy.key; // 正常工作

// 撤销代理
revoke();

// 再次使用会报错
proxy.key; // TypeError: Cannot perform 'get' on a proxy that has been revoked

适用场景:临时授权访问、API 密钥传递后立即撤销。

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式