Last updated on December 19, 2025 am
今天是JavaScript进阶系列文章的第9篇文章了,世界就是一个巨大的草台班子,而每个班子里总是少不了诸如“黄牛”、“中间商”这样的角色,今天我们来讲讲ES6中JavaScript中的“中间商”–代理(Proxy)。
一、Proxy的本质定义:对象操作的“拦截器”
Proxy是ES6引入的元编程工具,译为“代理”,其核心功能是在目标对象与外界操作之间架设拦截层,所有对目标对象的访问必须经过该层筛选与改写。这种机制打破了对象操作的默认行为,让开发者能以极低侵入性的方式实现逻辑增强。
与传统的Object.defineProperty相比,Proxy的核心优势体现在三方面:
- 拦截粒度更全面:可监听对象的13种基本操作(如
get/set/delete/in等),而非单一属性的读写;
- 动态性更强:无需提前声明拦截属性,新增属性自动纳入监听范围;
- 支持更多对象类型:可代理数组、函数甚至类构造器。
所以,不难理解Vue3为什么使用Proxy来重写响应式系统,毕竟,是真的香啊。
二、 Proxy的核心写法:构造器与拦截陷阱
1. 基础语法结构
Proxy通过构造函数创建实例,语法固定为:
1
| const proxy = new Proxy(target, handler);
|
target:被代理的目标对象(可是对象、数组、函数等);
handler:拦截规则对象,包含各类“陷阱函数”(Trap),未定义的陷阱将默认透传操作至目标对象。
2. 常用拦截陷阱实战
(1)属性读写拦截(get/set)
最常用的陷阱组合,可实现数据校验、默认值填充等功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const userValidator = { set(target, prop, value, receiver) { if (prop === 'age' && (!Number.isInteger(value) || value < 0 || value > 120)) { throw new Error(`无效年龄:${value}`); } return Reflect.set(target, prop, value, receiver); }, get(target, prop, receiver) { return prop in target ? Reflect.get(target, prop, receiver) : '未设置'; } };
const user = new Proxy({}, userValidator); user.name = 'Alice'; user.age = 130; console.log(user.gender);
|
(2)函数调用拦截(apply)
专门用于代理函数,可实现调用日志、参数校验等:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const calcProxy = new Proxy((a, b) => a + b, { apply(target, thisArg, args) { console.log(`执行加法:${args.join('+')}`); if (!args.every(Number.isFinite)) { throw new Error('参数必须为数字'); } return target.apply(thisArg, args); } });
calcProxy(2, 3); calcProxy(2, '3');
|
(3)构造器拦截(construct)
拦截new操作,可控制实例创建过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const UserProxy = new Proxy(class { constructor(name) { this.name = name; } }, { construct(target, args) { console.log(`创建用户:${args[0]}`); const instance = new target(...args); instance.createTime = Date.now(); return instance; } });
const user = new UserProxy('Bob');
|
三、Proxy的典型使用场景
数据校验与格式化
通过set陷阱实现强类型校验,广泛用于表单处理、配置项验证等场景。例如限制商品价格必须为正数:
1 2 3 4 5 6 7 8 9 10 11 12
| const priceHandler = { set(target, prop, value) { if (prop.includes('price') && (typeof value !== 'number' || value <= 0)) { throw new Error(`无效${prop}:${value}`); } Reflect.set(target, prop, value); } };
const product = new Proxy({ name: '手机' }, priceHandler); product.salePrice = 3999; product.salePrice = -100;
|
响应式编程基石
Vue 3的响应式系统核心基于Proxy实现,通过拦截属性读写触发依赖收集与视图更新。简化原理如下:
1 2 3 4 5 6 7 8 9 10 11 12
| function reactive(target) { return new Proxy(target, { get(target, prop) { track(target, prop); return Reflect.get(target, prop); }, set(target, prop, value) { Reflect.set(target, prop, value); trigger(target, prop); } }); }
|
相较于Vue 2的Object.defineProperty,此实现天然支持数组索引修改、新增属性等场景。
日志与调试增强
通过拦截操作自动记录日志,无需侵入业务代码。例如监控对象操作轨迹:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function createLogger(target, name) { return new Proxy(target, { get: logOperation('读取'), set: logOperation('修改'), deleteProperty: logOperation('删除') });
function logOperation(type) { return (target, prop) => { console.log(`[${name}]${type}属性${prop}`); return Reflect[type === '删除' ? 'deleteProperty' : type](target, prop); }; } }
const user = createLogger({ name: 'Alice' }, '用户数据'); user.age = 25; delete user.age;
|
虚拟化与权限控制
创建“虚拟对象”隐藏真实数据结构,同时实现权限校验。例如限制私有属性访问:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function createSecureObject(data) { const privateKeys = new Set(['_password']); return new Proxy(data, { get(target, prop) { if (privateKeys.has(prop)) { throw new Error('无权访问私有属性'); } return prop === 'name' ? target[prop].toUpperCase() : target[prop]; } }); }
const user = createSecureObject({ name: 'bob', _password: '123' }); console.log(user.name); console.log(user._password);
|
四、使用注意事项
虽然Proxy在诸多场景中表现很出色,但依然存在着一些需要再使用中注意的问题:
- 兼容性限制:不支持IE浏览器,现代浏览器需考虑降级方案(如Vue 3的IE兼容版仍用
Object.defineProperty,哈哈,你们就偷着乐吧);
- this指向问题:陷阱函数中的
this指向handler而非目标对象,需用Reflect确保上下文正确;
- 性能权衡:代理层会带来轻微性能损耗,高频操作场景需评估是否使用,不过这种损耗在现代计算机性能前可以忽略不计;
- 不可代理场景:某些原生对象的内部属性(如
Date的[[PrimitiveValue]])无法拦截,这个属于冷知识了。
【往期精彩】