Proxy 就是给对象装个拦截器,读写属性前先过一层处理。
核心两个:
① get 拦截——读属性时拦截
② set 拦截——写属性时拦截
坑:Vue3 响应式原理就是 Proxy、Reflect 配合 Proxy 使用、this 指向问题。
没有经纪人时,你直接摸原对象,摸到啥是啥;有了经纪人后,每一次读写都先过 handler 里的规则。
// 没有代理:直接拿结果,拦不住
// 原对象,没有经纪人
let target = { name: "John" };
// undefined(什么都没有)
console.log(target.age);
// 有代理:每次读都先过 handler.get
// 经纪人的规则本
let handler = {
// 拦截"读"这个动作
get(target, prop) {
// 读 age 就直接返回 30
if (prop === "age") return 30;
// 其他属性照原样读
return target[prop];
}
};
// 包一层经纪人
let proxy = new Proxy(target, handler);
// 30(被经纪人加工了)
console.log(proxy.age);
new Proxy(原对象, 经纪人规则) 生成代理对象。以后操作一律用 proxy,不要再直接碰 target——直接碰就绕过经纪人了,拦截规则白写。
经纪人不仅拦"读",还拦"写"。往对象上塞属性时,先问一句"这合法吗"。
// 经纪人规则本
let handler = {
// 拦截"写"
set(target, prop, value) {
// 先记个日志
console.log("有人要把 " + prop + " 改成 " + value);
// 真正写进去
target[prop] = value;
// 表示放行
return true;
}
};
// 包一层
let proxy = new Proxy(target, handler);
// 写属性会先被 set 拦住
proxy.skill = "前端";
// 打印:有人要把 skill 改成 前端
最常见的实战场景:不允许 age 被设成负数。set 里判断一下,不合规就拒绝写入。
// 原对象,年龄 20
let user = { age: 20 };
// 经纪人规则本
let handler = {
// 拦截写
set(target, prop, value) {
// 要写的是 age 且是负数
if (prop === "age" && value < 0) {
// 报警
console.log("年龄不能是负数!");
// ★ 拒绝这次写入
return false;
}
// 合法才真写
target[prop] = value;
// 放行
return true;
}
};
// 包一层经纪人
let proxy = new Proxy(user, handler);
// 想写成负数,被拦下
proxy.age = -5;
// 打印:年龄不能是负数!
// 20(没写进去)
console.log(proxy.age);
set 里 return false 表示"拒绝这次写入"(严格模式下还会抛错)。Vue 3 的响应式原理就是用 Proxy 拦截读写、数据一变就自动更新界面——理解它等于摸到响应式框架的门。
① 可能在什么地方用:表单数据校验(不合法就拒写)、给对象读写加日志、Vue 3 响应式原理、实现"只读"对象、数据懒加载/默认值兜底。
② 常见的问题:创建代理后还直接用原对象导致拦截不生效;set 里忘了 return true;get 里循环引用导致死循环。
③ 解决思路:只通过 proxy 访问对象;set 放行时务必 return true;get 里要返回对象时记得返回 Reflect.get(...) 或原对象属性,别在拦截器里又触发自己。
| API | 作用 | 参数 | 返回值 | 代码示例 |
|---|---|---|---|---|
| new Proxy(obj, 处理器) | 给原对象包一层经纪人,读写先过它 | 原对象, 处理器 | 一个代理 | new Proxy(obj, h); |
| get(目标, 键) | 读属性时被拦住 | 对象, 属性名 | 读出来的值 | get(target, key) {} |
| set(目标, 键, 值) | 写属性时被拦住 | 对象, 属性名, 新值 | true 放行 | set(target, key, v) {} |