_01_Proxy
Proxy基础API
Proxy
就像给对象加了一层"保护罩"或"中间层",所有对对象的操作(比如读属性、写属性)都会先经过这层代理。你可以在这层代理里做些额外操作,比如检查权限、记录日志,或者修改操作行为。
基本用法
创建 Proxy代理很简单,用 new Proxy()
就行,需要两个参数:
- 第一个参数是要代理的目标对象(target)
- 第二个参数是"处理对象"(handler),里面定义了各种拦截操作的方法
// 语法
const proxy = new Proxy(target, handler);
常用拦截方法(handler里的"钩子")
就像门卫有不同的职责(查身份证、登记访客),Proxy也有多种拦截方法,对应不同的对象操作:
get(target, prop, receiver)
拦截属性读取操作,比如proxy.name
或proxy['name']
javascriptconst obj = { name: '小明' }; const proxy = new Proxy(obj, { get(target, prop) { console.log(`有人读取了${prop}属性`); return target[prop]; // 返回属性值 } }); console.log(proxy.name); // 会先打印日志,再输出"小明"
set(target, prop, value, receiver)
拦截属性赋值操作,比如proxy.age = 18
javascriptconst proxy = new Proxy({}, { set(target, prop, value) { if (prop === 'age' && (value < 0 || value > 150)) { console.log('年龄值不合理!'); return false; // 阻止赋值 } target[prop] = value; return true; // 表示赋值成功 } }); proxy.age = 200; // 打印"年龄值不合理!" proxy.age = 20; // 正常赋值
has(target, prop)
拦截prop in proxy
这样的判断操作javascriptconst proxy = new Proxy({ name: '小红' }, { has(target, prop) { console.log(`有人检查是否有${prop}属性`); return prop in target; } }); console.log('name' in proxy); // 先打印日志,再返回true
deleteProperty(target, prop)
拦截delete proxy.prop
这样的删除操作javascriptconst proxy = new Proxy({ id: 1 }, { deleteProperty(target, prop) { if (prop === 'id') { console.log('id属性不能删除!'); return false; // 阻止删除 } delete target[prop]; return true; } }); delete proxy.id; // 打印"id属性不能删除!"
核心特点
- 非侵入式:不需要修改原对象,就能对它的操作进行拦截
- 灵活可控:可以选择拦截哪些操作,不拦截的会按默认行为执行
- 嵌套代理需手动实现:Proxy默认只代理一层对象,如果要代理嵌套对象,需要像前面例子那样用递归(就像剥洋葱,每层都要加代理)
简单说,Proxy就是对象的"代理人",所有对对象的"请求"都要先经过它,它可以决定放行、拒绝,或者加工一下再处理。
receiver
可以理解成"实际触发操作的那个对象",或者说"谁在调用这个属性"。
举个生活例子:你托朋友(Proxy)向老板(target)要工资。这里:
- target 是老板(原始对象)
- property 是"工资"(要访问的属性)
- receiver 就是你(真正发起请求的人)
具体场景:
当对象有继承关系时,receiver
的作用就很明显了:
const parent = {
name: "老王",
get fullName() {
// 这里的 this 会指向 receiver
return this.name + "同志";
}
};
const child = new Proxy({}, {
get(target, prop, receiver) {
// 如果 child 没有这个属性,就去 parent 里找
if (!(prop in target)) {
return parent[prop];
}
return target[prop];
}
});
// 设置 child 的 name
child.name = "小王";
// 访问 fullName 时,parent 里的 this 会指向 child(receiver)
console.log(child.fullName); // 输出 "小王同志" 而不是 "老王同志"
这里的关键是:parent.fullName
里的 this
指向了 receiver
(也就是 child),而不是 parent 本身。
为什么需要在意 receiver?
在 Proxy 的 get
拦截器里,如果你用 Reflect.get(target, prop, receiver)
而不是直接 target[prop]
,就能保证这种"上下文正确传递"。
简单说:receiver
就是为了让被访问的属性知道"到底是谁在访问我",确保操作的上下文不出错。
Refelct基础API
Reflect
是 JavaScript 中的一个内置对象,它提供了一组与对象操作相关的静态方法,这些方法对应了对象的各种内部行为(如属性访问、赋值、函数调用等)。
Reflect
的设计目的主要有以下几点:
统一对象操作接口:将对象的各种操作(如
obj[prop]
、delete obj[prop]
等)集中到Reflect
对象上,形成更规范的函数式接口。与 Proxy 配合使用:
Reflect
的方法与 Proxy 拦截器的方法一一对应,便于在 Proxy 中实现默认行为的转发。更合理的返回值:相比传统操作符(如
delete
),Reflect
方法返回更明确的操作结果(通常是布尔值表示成功与否)。函数式编程风格:提供函数式的对象操作方式,便于组合和传递。
常用的 Reflect
方法包括:
Reflect.get(target, prop, receiver)
:获取对象属性值,类似target[prop]
Reflect.set(target, prop, value, receiver)
:设置对象属性值,类似target[prop] = value
Reflect.has(target, prop)
:检查属性是否存在,类似prop in target
Reflect.deleteProperty(target, prop)
:删除属性,类似delete target[prop]
Reflect.construct(target, args)
:创建实例,类似new target(...args)
Reflect.apply(target, thisArg, args)
:调用函数,类似target.apply(thisArg, args)
在之前的 Proxy 示例中,我们使用 Reflect.get
和 Reflect.set
来实现默认的属性访问和赋值行为,这样可以确保操作符合 JavaScript 的默认语义,同时保持代码的规范性。
例如,使用 Reflect.set
比直接赋值更适合在 Proxy 的 set
拦截器中使用,因为它能正确处理继承关系和 receiver
绑定:
// 传统方式
target[prop] = value;
// 使用 Reflect
Reflect.set(target, prop, value, receiver);
总的来说,Reflect
提供了一套更可靠、更函数式的对象操作 API,尤其在与 Proxy 配合时能发挥重要作用。
可以把 Reflect
理解成"对象操作的工具人",专门帮你干那些和对象打交道的脏活累活。
打个比方:如果把对象比作一个工具箱,里面的属性就是各种工具(螺丝刀、锤子等)。
以前你得自己动手拿工具(obj.prop
)、放工具(obj.prop = 1
)、扔工具(delete obj.prop
);而 Reflect
就像一个助手,你只需要告诉他"去拿螺丝刀"(Reflect.get(obj, 'prop')
),他就会按标准流程帮你操作,还会告诉你"拿到了"或" 没找到"。
再举几个具体例子:
- 以前查对象有没有某个属性:
if ('name' in obj) { ...
}
现在可以让助手查:
if (Reflect.has(obj, 'name')) { ...
}
- 以前给对象赋值:
obj.age = 18;
现在让助手来做(他还会告诉你成没成功):
const success = Reflect.set(obj, 'age', 18); // 成功返回true
为什么需要这个助手?
- 他做事更规范:不管对象多特殊(比如被Proxy代理的),他都按标准流程来
- 他会反馈结果:操作成功与否一目了然
- 他适合和Proxy配合:Proxy就像保安,决定让不让访问;Reflect就像快递员,负责实际送货
简单说:Reflect
就是把原来用符号(in
、delete
、=
)做的事,变成了用函数来做,让代码更统一、更好用。
哈哈,能听懂就好!其实很多技术概念拆开了说,就跟日常聊天似的。
比如再回头看 Proxy 和 Reflect 这俩:
- Proxy 就像给对象雇了个"门卫",有人想拿东西、放东西,都得经过它点头。
- 要是门卫觉得没问题,就喊 Reflect 这个"跑腿的"去实际操作(拿/放),完事还能告诉门卫"搞定了"或"没搞定"。
这么一想,是不是就特具体了~ 有啥不懂的,继续用"说人话"模式问就行!
leetcode
实战:实现访问任意对象的任意属性并打印
// 练习题目:访问一个对象的任意属性,返回一个方法,打印此属性名称
const printObjectProperty = () => {
return new Proxy({}, {
get(target, property, receiver) {
return () => property;
}
})
}
const proxy = printObjectProperty();
console.log(proxy['name']());
实现多层对象的代理
// 使用JS实现对象的深层代理
const deepProxy = (obj) => {
if (obj === null || obj === undefined || typeof obj !== 'object') {
return obj;
}
return new Proxy(obj, {
get(target, property, receiver) {
console.log('访问属性', property);
const value = Reflect.get(target, property, receiver);
return deepProxy(value);
}, set(target, property, value, receiver) {
console.log('设置属性', property);
return Reflect.set(target, property, value, receiver);
}
})
}
const obj = {
a: {
b: {
c: {
d: 1
}
}
}
}
const obj2 = deepProxy(obj);
console.log(obj2.a.b.c.d);
obj2.a.b.c.d = 2;
console.log(obj2.a.b.c.d);