← JavaScript Map values() 方法 JavaScript handler.apply()拦截函数调用 →

JavaScript Proxy 处理器

原创 2026-03-24 JavaScript 已有人查阅

JavaScript Proxy 处理器方法全解析:13 个与技巧

在 JavaScript 中,Proxy 是一个极为强大的元编程特性。它允许我们创建一个对象的代理,从而拦截并重新定义该对象的基本操作。而这些操作的拦截逻辑,正是通过 handler 对象 上的处理器方法(handler methods) 来实现的。

你列出的这 13 个方法,涵盖了从属性读取、赋值、删除,到原型链操作、对象扩展性控制等方方面面。理解它们,就等于掌握了 Proxy 的核心。下面,我们逐一剖析每个方法的作用、使用场景以及我个人的一些实践经验。

13 个 handler 方法速览

方法 触发的操作
apply() 拦截函数调用 proxy(...args)proxy.call()proxy.apply()
construct() 拦截 new proxy(...args)
defineProperty() 拦截 Object.defineProperty()
deleteProperty() 拦截 delete proxy[prop]
get() 拦截属性读取 proxy[prop]
getOwnPropertyDescriptor() 拦截 Object.getOwnPropertyDescriptor()
getPrototypeOf() 拦截 Object.getPrototypeOf()__proto__instanceof
has() 拦截 in 操作符 prop in proxy
isExtensible() 拦截 Object.isExtensible()
ownKeys() 拦截 Object.keys()Object.getOwnPropertyNames()Reflect.ownKeys()
preventExtensions() 拦截 Object.preventExtensions()
set() 拦截属性赋值 proxy[prop] = value
setPrototypeOf() 拦截 Object.setPrototypeOf()

核心方法详解与实践

1. get() —— 属性读取的守门人

get() 是最常用的处理器方法。它会在读取对象属性时被触发,适合做数据校验、日志记录、默认值返回等操作。

<script>
// 代码号:学习编程,用 get 拦截器实现安全的属性访问
const user = {
    name: "李明",
    age: 28
};

const userProxy = new Proxy(user, {
    get(target, prop, receiver) {
        if (prop === "age" && target[prop] < 18) {
            return "未成年人信息受保护";
        }
        // 不存在的属性返回友好提示
        return prop in target ? target[prop] : `属性 "${prop}" 不存在`;
    }
});

console.log(userProxy.name);  // 李明
console.log(userProxy.age);   // 28(正常返回,因为大于18)
console.log(userProxy.email); // 属性 "email" 不存在
</script>

个人见解:使用 get 做数据验证比在业务代码中到处写 if 判断要干净得多。我在一个用户信息管理系统里,用 get 统一处理敏感字段的访问权限,代码维护量大幅降低。

2. set() —— 赋值时的校验员

set() 在给属性赋值时触发,非常适合做数据格式校验、取值范围限制、只读属性保护

<script>
// 代码号:学习编程,用 set 拦截器保护数据完整性
const product = {
    price: 0,
    name: ""
};

const productProxy = new Proxy(product, {
    set(target, prop, value, receiver) {
        if (prop === "price") {
            if (typeof value !== "number" || value < 0) {
                console.error("价格必须是正数");
                return false; // 赋值失败
            }
        }
        if (prop === "name") {
            if (typeof value !== "string" || value.trim() === "") {
                console.error("名称不能为空");
                return false;
            }
        }
        target[prop] = value;
        return true;
    }
});

productProxy.price = 99.9;   // 正常
productProxy.name = "机械键盘"; // 正常
productProxy.price = -10;    // 价格必须是正数(赋值被阻止)
console.log(productProxy.price); // 仍然是 99.9
</script>

3. apply() —— 函数的中间件

apply() 拦截函数的调用,可以用来做性能监控、参数校验、缓存结果等。我常用它来做 API 请求的缓存层。

<script>
// 代码号:学习编程,用 apply 拦截器实现函数调用缓存
function expensiveCalculation(n) {
    console.log("正在计算...");
    return n * 2;
}

const cachedProxy = new Proxy(expensiveCalculation, {
    cache: new Map(),
    apply(target, thisArg, args) {
        const key = JSON.stringify(args);
        if (this.cache.has(key)) {
            console.log("从缓存返回结果");
            return this.cache.get(key);
        }
        const result = target.apply(thisArg, args);
        this.cache.set(key, result);
        return result;
    }
});

console.log(cachedProxy(5)); // 正在计算... 10
console.log(cachedProxy(5)); // 从缓存返回结果 10
</script>

4. construct() —— 构造函数的守卫

construct() 拦截 new 操作符,可以用来做单例模式、参数转换、实例校验

<script>
// 代码号:学习编程,用 construct 实现单例模式
let instance = null;

class DatabaseConnection {
    constructor(config) {
        this.config = config;
    }
}

const ConnectionProxy = new Proxy(DatabaseConnection, {
    construct(target, args) {
        if (!instance) {
            instance = new target(...args);
        }
        return instance;
    }
});

const conn1 = new ConnectionProxy({ host: "localhost" });
const conn2 = new ConnectionProxy({ host: "remote" });

console.log(conn1 === conn2); // true,始终返回同一个实例
console.log(conn1.config);    // { host: "localhost" },第二次参数被忽略
</script>

5. deleteProperty() —— 删除操作的控制

拦截 delete 操作,可以防止误删重要属性。

<script>
// 代码号:学习编程,用 deleteProperty 保护关键属性
const config = {
    apiKey: "12345",
    endpoint: "https://api.example.com"
};

const protectedConfig = new Proxy(config, {
    deleteProperty(target, prop) {
        if (prop === "apiKey") {
            console.error("apiKey 不允许删除");
            return false;
        }
        delete target[prop];
        return true;
    }
});

delete protectedConfig.apiKey;    // apiKey 不允许删除
delete protectedConfig.endpoint;   // 成功
console.log(protectedConfig);      // { apiKey: "12345" }
</script>

6. has() —— in 操作符的过滤器

拦截 prop in proxy 操作,可以用来隐藏某些属性

<script>
// 代码号:学习编程,用 has 拦截器隐藏内部属性
const userData = {
    _password: "secret123",
    username: "alice"
};

const safeUser = new Proxy(userData, {
    has(target, prop) {
        // 以下划线开头的属性对 in 操作符不可见
        if (prop.startsWith("_")) {
            return false;
        }
        return prop in target;
    }
});

console.log("username" in safeUser);    // true
console.log("_password" in safeUser);   // false
</script>

7. ownKeys() —— 枚举属性的过滤

拦截 Object.keys()Reflect.ownKeys() 等操作,控制哪些属性会被枚举。

<script>
// 代码号:学习编程,用 ownKeys 控制属性枚举
const data = {
    id: 1,
    name: "报表",
    _internal: "敏感数据"
};

const filteredProxy = new Proxy(data, {
    ownKeys(target) {
        return Object.keys(target).filter(key => !key.startsWith("_"));
    }
});

console.log(Object.keys(filteredProxy)); // ['id', 'name']
// 注意:_internal 在枚举中消失了,但直接访问 filteredProxy._internal 依然可以
</script>

8. defineProperty() 与 getOwnPropertyDescriptor()

这两个方法分别拦截属性定义和属性描述符的获取,配合使用可以实现动态属性行为

<script>
// 代码号:学习编程,动态控制属性描述符
const dynamicObj = new Proxy({}, {
    defineProperty(target, prop, descriptor) {
        console.log(`正在定义属性: ${prop}`);
        return Reflect.defineProperty(target, prop, descriptor);
    },
    getOwnPropertyDescriptor(target, prop) {
        console.log(`正在获取属性描述符: ${prop}`);
        return Reflect.getOwnPropertyDescriptor(target, prop);
    }
});

Object.defineProperty(dynamicObj, "version", {
    value: "1.0.0",
    writable: false
});
// 输出: 正在定义属性: version

const desc = Object.getOwnPropertyDescriptor(dynamicObj, "version");
// 输出: 正在获取属性描述符: version
console.log(desc.value); // 1.0.0
</script>

9. 原型相关:getPrototypeOf() 与 setPrototypeOf()

拦截原型读取和设置,可以伪造原型链阻止原型修改

<script>
// 代码号:学习编程,控制原型访问
const base = { type: "base" };
const target = { name: "target" };

const protoProxy = new Proxy(target, {
    getPrototypeOf() {
        return base; // 假装原型是 base
    },
    setPrototypeOf() {
        console.log("禁止修改原型");
        return false;
    }
});

console.log(Object.getPrototypeOf(protoProxy) === base); // true
Object.setPrototypeOf(protoProxy, {}); // 禁止修改原型
</script>

10. 扩展性控制:isExtensible() 与 preventExtensions()

这两个方法控制对象的扩展性行为,可以覆盖默认的冻结逻辑

<script>
// 代码号:学习编程,自定义扩展性判断
const sealed = new Proxy({}, {
    isExtensible() {
        return false; // 告诉外界对象不可扩展
    },
    preventExtensions() {
        console.log("尝试阻止扩展");
        return true; // 表示操作成功
    }
});

console.log(Object.isExtensible(sealed)); // false
Object.preventExtensions(sealed); // 尝试阻止扩展
</script>

本节课程知识要点

  1. 善用 Reflect:在 handler 方法内部,推荐使用 Reflect 对象上的同名方法来执行原始操作。这样既保持了默认行为,又能在其基础上添加自定义逻辑。例如 Reflect.get(target, prop, receiver)

  2. 返回值很重要set()defineProperty()deleteProperty() 等方法需要返回布尔值来表示操作是否成功。返回 false 或在严格模式下抛出异常,可以阻止操作。

  3. ownKeys() 与枚举的局限:通过 ownKeys() 过滤掉的属性,在 Object.keys() 中不会出现,但 Reflect.ownKeys() 返回的是过滤后的结果,且直接属性访问依然有效。如需隐藏,还需配合 get() 和 has()

  4. 性能考量:Proxy 会带来额外的性能开销。在性能敏感的场景(如循环内高频访问)需谨慎使用。我通常在配置层、权限控制层、缓存层使用 Proxy,而在核心计算循环中避免。

这 13 个 handler 方法共同构成了 JavaScript 元编程的基石。掌握它们,你就能在对象层面实现日志、校验、缓存、权限控制、数据隐藏等高级功能。代码号学习编程的过程中,建议从 get 和 set 开始练习,逐步扩展到其他方法,你会发现 Proxy 带来的设计自由度远超预期。

← JavaScript Map values() 方法 JavaScript handler.apply()拦截函数调用 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号