字
字节笔记本
2026年3月22日
ES6 Proxy 代理对象完全指南
这篇文章将全面介绍 ES6 Proxy 代理对象,从基本概念到实际用例,涵盖默认值处理、负索引数组、属性隐藏、缓存代理、只读视图、枚举对象、运算符重载等高级技巧。
Proxy 基本介绍
Proxy 用于修改某些操作的默认行为,可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改。
ES6 原生提供了 Proxy 构造函数:
javascript
var proxy = new Proxy(target, handler);new Proxy用来生成 Proxy 实例target是表示所要拦截的对象handler是用来定制拦截行为的对象
最简单的例子——一个总是返回 55 的 get 陷阱:
javascript
let target = { x: 10, y: 20 };
let handler = {
get: (obj, prop) => 55
};
target = new Proxy(target, handler);
target.x; // 55
target.y; // 55所有代理陷阱
Proxy 提供了十几个不同的拦截陷阱:
| 陷阱 | 触发时机 |
|---|---|
handler.get | 读取属性 |
handler.set | 设置属性 |
handler.has | in 操作符 |
handler.apply | 函数调用 |
handler.construct | new 操作符 |
handler.ownKeys | Object.getOwnPropertyNames |
handler.deleteProperty | delete 操作符 |
handler.defineProperty | Object.defineProperty |
handler.isExtensible | Object.isExtensible |
handler.preventExtensions | Object.preventExtensions |
handler.getPrototypeOf | Object.getPrototypeOf |
handler.setPrototypeOf | Object.setPrototypeOf |
handler.getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor |
默认值 / "零值"
模仿 Go 语言的零值概念,为未设置的属性提供默认值:
javascript
const withZeroValue = (target, zeroValue) =>
new Proxy(target, {
get: (obj, prop) => (prop in obj ? obj[prop] : zeroValue)
});
let pos = { x: 4, y: 19 };
console.log(pos.x, pos.y, pos.z); // 4, 19, undefined
pos = withZeroValue(pos, 0);
console.log(pos.x, pos.y, pos.z); // 4, 19, 0负索引数组
在 JavaScript 中使用负索引访问数组元素,类似 Python 的 arr[-1]:
javascript
const negativeArray = els =>
new Proxy(els, {
get: (target, propKey, receiver) =>
Reflect.get(
target,
+propKey < 0 ? String(target.length + +propKey) : propKey,
receiver
)
});
const unicorn = negativeArray(["a", "b", "c"]);
unicorn[-1]; // 'c'
unicorn[-2]; // 'b'隐藏属性
模拟私有属性,屏蔽以下划线开头的属性:
javascript
const hide = (target, prefix = "_") =>
new Proxy(target, {
has: (obj, prop) => !prop.startsWith(prefix) && prop in obj,
ownKeys: obj =>
Reflect.ownKeys(obj).filter(
prop => typeof prop !== "string" || !prop.startsWith(prefix)
),
get: (obj, prop, rec) => (prop in rec ? obj[prop] : undefined)
});
let userData = hide({
firstName: "Tom",
mediumHandle: "@tbarrasso",
_favoriteRapper: "Drake"
});
userData._favoriteRapper; // undefined
"_favoriteRapper" in userData; // false缓存代理
根据时间策略使属性过期,实现缓存效果:
javascript
const ephemeral = (target, ttl = 60) => {
const CREATED_AT = Date.now();
const isExpired = () => Date.now() - CREATED_AT > ttl * 1000;
return new Proxy(target, {
get: (obj, prop) => (isExpired() ? undefined : Reflect.get(obj, prop))
});
};
let bankAccount = ephemeral({ balance: 14.93 }, 10);
console.log(bankAccount.balance); // 14.93
// 10 秒后
console.log(bankAccount.balance); // undefined只读视图
将对象包装为完全不可修改的只读视图:
javascript
const NOPE = () => { throw new Error("Can't modify read-only view"); };
const NOPE_HANDLER = {
set: NOPE,
defineProperty: NOPE,
deleteProperty: NOPE,
preventExtensions: NOPE,
setPrototypeOf: NOPE
};
const readOnlyView = target => new Proxy(target, NOPE_HANDLER);
let config = readOnlyView({ debug: false, version: "1.0" });
config.debug = true; // Uncaught Error: Can't modify read-only view枚举视图
访问不存在的属性时抛出异常,而非返回 undefined:
javascript
const createEnum = target =>
readOnlyView(
new Proxy(target, {
get: (obj, prop) => {
if (prop in obj) {
return Reflect.get(obj, prop);
}
throw new ReferenceError(`Unknown prop "${prop}"`);
}
})
);
let SHIRT_SIZES = createEnum({ S: 10, M: 15, L: 20 });
SHIRT_SIZES.S; // 10
SHIRT_SIZES.S = 15; // Uncaught Error: Can't modify read-only view
SHIRT_SIZES.XL; // Uncaught ReferenceError: Unknown prop "XL"运算符重载
使用 handler.has 重载 in 操作符,实现范围检查:
javascript
const range = (min, max) =>
new Proxy(Object.create(null), {
has: (_, prop) => +prop >= min && +prop <= max
});
const X = 10.5;
const nums = [1, 5, X, 50, 100];
if (X in range(1, 100)) {
// true
}
nums.filter(n => n in range(1, 10)); // [1, 5]Cookie 对象代理
用 Proxy 简化 document.cookie 的操作:
javascript
const getCookieObject = () => {
const cookies = document.cookie
.split(";")
.reduce(
(cks, ck) => ({
[ck.substr(0, ck.indexOf("=")).trim()]: ck.substr(
ck.indexOf("=") + 1
),
...cks
}),
{}
);
const setCookie = (name, val) => (document.cookie = `${name}=${val}`);
const deleteCookie = name =>
(document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:01 GMT;`);
return new Proxy(cookies, {
set: (obj, prop, val) => (
setCookie(prop, val), Reflect.set(obj, prop, val)
),
deleteProperty: (obj, prop) => (
deleteCookie(prop), Reflect.deleteProperty(obj, prop)
)
});
};
let docCookies = getCookieObject();
docCookies.has_recent_activity; // "1"
docCookies.has_recent_activity = "2"; // "2"
delete docCookies["has_recent_activity"]; // true组合多个代理
Proxy 的优势在于可以轻松组合:
javascript
let docCookies = withZeroValue(
hide(readOnlyView(getCookieObject())),
"Cookie not found"
);
docCookies.has_recent_activity // "1"
docCookies.nonExistentCookie // "Cookie not found"
docCookies._ga // "Cookie not found"(隐藏的属性)
docCookies.newCookie = "1" // Error: Can't modify read-only view注意事项
性能
Proxy 的主要缺点是性能开销。对于性能敏感的代码,需要衡量代理的优势是否超过性能影响。
限制
javascript
// 目标必须是 Object,不能是原始值
new Proxy("hello", {});
// TypeError: Cannot create proxy with a non-object as target
// 无法检测对象是否是代理
// 根据 JS 规范,无法确定一个对象是否是 ProxyPolyfill
Proxy 没有完整的 polyfill,但 Google 提供了一个部分 polyfill,支持 get、set、apply 和 construct 陷阱。
Proxy vs Object.defineProperty
| 特性 | Proxy | Object.defineProperty |
|---|---|---|
| 拦截范围 | 13 种操作 | get/set |
| 动态添加属性 | 自动拦截 | 需要重新定义 |
| 数组变化检测 | 支持 | 不支持 |
| 性能 | 较低 | 较高 |
| Vue 3 | 基于 Proxy | Vue 2 基于 defineProperty |
Proxy 提供了 JavaScript 中强大的元编程能力,是 Vue 3 响应式系统的底层实现基础。
分享: