FORMA

代理与反射

ProxyReflect 用于拦截并自定义对象基本操作,是 Vue 3 等响应式系统的底层机制之一。

一、Proxy:拦截与自定义操作

Proxy 用于创建一个对象的代理,从而可以拦截和重新定义该对象的基本操作(如属性读取、赋值、函数调用等)。

1. 基本语法

js
const proxy = new Proxy(target, handler);
  • target:被代理的目标对象(可以是普通对象、数组、函数、甚至另一个代理)。
  • handler:定义拦截行为的对象,内部包含捕获器(trap)方法。

2. 常用捕获器

捕获器触发时机
get(target, prop, receiver)读取属性:proxy[prop]Object.create
set(target, prop, value, receiver)设置属性:proxy[prop] = value
has(target, prop)in 运算符:prop in proxy
deleteProperty(target, prop)delete proxy[prop]
apply(target, thisArg, argsList)函数调用:proxy(...args)call/apply
construct(target, argsList, newTarget)new proxy(...args)
ownKeys(target)Object.getOwnPropertyNamesObject.keys
getOwnPropertyDescriptor(target, prop)Object.getOwnPropertyDescriptor
defineProperty(target, prop, descriptor)Object.defineProperty
preventExtensions(target)Object.preventExtensions
isExtensible(target)Object.isExtensible
getPrototypeOf(target)Object.getPrototypeOf__proto__instanceof
setPrototypeOf(target, proto)Object.setPrototypeOf

3. 示例:getset 拦截

js
const user = { name: "Alice", age: 30 };
const proxy = new Proxy(user, {
    get(target, prop, receiver) {
        console.log(`GET ${String(prop)}`);
        return prop in target ? target[prop] : "default";
    },
    set(target, prop, value, receiver) {
        console.log(`SET ${String(prop)} = ${value}`);
        target[prop] = value;
        return true; // 表示设置成功
    },
});
console.log(proxy.name); // GET name → Alice
proxy.age = 31; // SET age = 31
console.log(proxy.xxx); // GET xxx → 'default'

4. 可撤销 Proxy

使用 Proxy.revocable() 创建一个可以手动撤销的代理。撤销后任何操作都会抛出 TypeError

js
const target = { msg: "hello" };
const { proxy, revoke } = Proxy.revocable(target, {});
console.log(proxy.msg); // 'hello'
revoke();
// console.log(proxy.msg); // TypeError: Cannot perform 'get' on a proxy that has been revoked

二、Reflect:反射 API

Reflect 是一个内置对象,提供了一组与 Proxy 捕获器一一对应的静态方法。它的主要用途是与 Proxy 协同工作,实现默认行为。

1. 常用 Reflect 方法

Proxy 捕获器对应的 Reflect 方法
getReflect.get(target, prop, receiver)
setReflect.set(target, prop, value, receiver)
hasReflect.has(target, prop)
deletePropertyReflect.deleteProperty(target, prop)
applyReflect.apply(target, thisArg, argsList)
constructReflect.construct(target, argsList, newTarget)
ownKeysReflect.ownKeys(target)
… 共 13 个方法Proxy 捕获器完全对应

2. 与 Proxy 协同:保持默认行为

Proxy 的捕获器中,通常需要调用 Reflect 的方法来完成默认操作,同时可以添加额外逻辑。

js
const proxy = new Proxy(target, {
    set(target, prop, value, receiver) {
        // 自定义验证
        if (prop === "age" && (value < 0 || value > 150)) {
            throw new Error("Invalid age");
        }
        // 调用 Reflect 执行默认赋值
        return Reflect.set(target, prop, value, receiver);
    },
});

3. 替代 Object 部分方法的现代 API

Reflect 的方法相比于 Object 同名方法(如 definePropertygetPrototypeOf)有以下优势:

  • 返回布尔值而不是抛出异常(例如 Reflect.defineProperty 返回 true/false,而 Object.defineProperty 失败时抛错)。
  • 更合理的 this 绑定Reflect.get 可以传入 receiver 来正确绑定 getter 中的 this
  • Proxy 保持一致,更符合函数式风格。
js
// 旧方式(容易抛异常)
try {
    Object.defineProperty(obj, "prop", { value: 1 });
} catch (e) {}

// 新方式(返回值更可控)
if (Reflect.defineProperty(obj, "prop", { value: 1 })) {
    // 成功
} else {
    // 失败
}

三、应用场景

1. 属性验证(数据校验)

set 捕获器中对赋值进行校验。

js
const user = {};
const validatedUser = new Proxy(user, {
    set(target, prop, value) {
        if (prop === "age" && (typeof value !== "number" || value < 0)) {
            throw new TypeError("Age must be a positive number");
        }
        target[prop] = value;
        return true;
    },
});
validatedUser.age = 25; // OK
// validatedUser.age = -5; // Error

2. 数据绑定与响应式系统(Vue 3 核心原理)

Vue 3 的响应式基于 Proxy,能够拦截对对象的所有访问和修改,并触发依赖更新。

js
function reactive(target) {
    const handler = {
        get(target, key, receiver) {
            const result = Reflect.get(target, key, receiver);
            // 收集依赖(track)
            console.log(`GET ${String(key)}`, result);
            return typeof result === "object" ? reactive(result) : result;
        },
        set(target, key, value, receiver) {
            const oldValue = target[key];
            const result = Reflect.set(target, key, value, receiver);
            // 触发更新(trigger)
            if (oldValue !== value) {
                console.log(`SET ${String(key)}: ${oldValue} -> ${value}`);
            }
            return result;
        },
    };
    return new Proxy(target, handler);
}

const state = reactive({ count: 0 });
state.count++; // GET count → SET count: 0 -> 1

3. 日志记录与性能测试

记录属性访问、函数调用耗时等。

js
function performanceMonitor(fn) {
    return new Proxy(fn, {
        apply(target, thisArg, args) {
            console.time("fn");
            const result = Reflect.apply(target, thisArg, args);
            console.timeEnd("fn");
            return result;
        },
    });
}
const slowFunc = n => {
    let sum = 0;
    for (let i = 0; i < n; i++) sum += i;
    return sum;
};
const monitored = performanceMonitor(slowFunc);
monitored(1e7); // 输出执行时间

4. 隐藏私有属性(模拟私有字段)

拦截 gethas,阻止以 _ 开头的属性被外部访问。

js
const privateProxy = obj =>
    new Proxy(obj, {
        get(target, prop) {
            if (prop.startsWith("_")) {
                throw new Error("Private property");
            }
            return Reflect.get(target, prop);
        },
        has(target, prop) {
            if (prop.startsWith("_")) return false;
            return Reflect.has(target, prop);
        },
    });
const data = { _secret: 123, public: 456 };
const pData = privateProxy(data);
console.log(pData.public); // 456
// console.log(pData._secret); // Error

5. 默认属性值(“虚拟”属性)

拦截 get,当属性不存在时返回默认值,而不实际添加该属性。

js
const withDefault = (obj, defaultVal) =>
    new Proxy(obj, {
        get(target, prop) {
            return prop in target ? target[prop] : defaultVal;
        },
    });
const person = { name: "Bob" };
const p = withDefault(person, "unknown");
console.log(p.name); // 'Bob'
console.log(p.age); // 'unknown'

6. 数组负索引支持(语法糖)

js
const createArray = arr =>
    new Proxy(arr, {
        get(target, prop) {
            const index = Number(prop);
            if (!isNaN(index) && index < 0) {
                return target[target.length + index];
            }
            return Reflect.get(target, prop);
        },
    });
const arr = createArray([1, 2, 3, 4]);
console.log(arr[-1]); // 4

总结

概念核心要点
Proxy拦截对象底层操作,提供 13 种捕获器;Proxy.revocable 创建可撤销代理。
ReflectProxy 一一对应的静态方法,用于实现默认行为;更安全、函数式的替代 Object 某些方法。
应用场景数据校验、响应式系统(Vue 3)、日志/性能、隐藏私有属性、默认值、数组负索引等。

Proxy 配合 Reflect 赋予了 JavaScript 强大的元编程能力,使得我们可以安全、灵活地控制对象行为,是现代框架和库的重要基础。

参考文献

以下链接在编写时均可正常访问:

资料说明
MDN:Proxy代理
MDN:Reflect反射 API
ECMA-262:Proxy Objects规范(英文)

Series

js

12 / 14