Skip to content

_01_Proxy

Proxy基础API

Proxy 就像给对象加了一层"保护罩"或"中间层",所有对对象的操作(比如读属性、写属性)都会先经过这层代理。你可以在这层代理里做些额外操作,比如检查权限、记录日志,或者修改操作行为。

基本用法

创建 Proxy代理很简单,用 new Proxy() 就行,需要两个参数:

  • 第一个参数是要代理的目标对象(target)
  • 第二个参数是"处理对象"(handler),里面定义了各种拦截操作的方法
javascript
// 语法
const proxy = new Proxy(target, handler);

常用拦截方法(handler里的"钩子")

就像门卫有不同的职责(查身份证、登记访客),Proxy也有多种拦截方法,对应不同的对象操作:

  1. get(target, prop, receiver)
    拦截属性读取操作,比如 proxy.nameproxy['name']

    javascript
    const obj = { name: '小明' };
    const proxy = new Proxy(obj, {
      get(target, prop) {
        console.log(`有人读取了${prop}属性`);
        return target[prop]; // 返回属性值
      }
    });
    console.log(proxy.name); // 会先打印日志,再输出"小明"
  2. set(target, prop, value, receiver)
    拦截属性赋值操作,比如 proxy.age = 18

    javascript
    const 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; // 正常赋值
  3. has(target, prop)
    拦截 prop in proxy 这样的判断操作

    javascript
    const proxy = new Proxy({ name: '小红' }, {
      has(target, prop) {
        console.log(`有人检查是否有${prop}属性`);
        return prop in target;
      }
    });
    console.log('name' in proxy); // 先打印日志,再返回true
  4. deleteProperty(target, prop)
    拦截 delete proxy.prop 这样的删除操作

    javascript
    const 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 的作用就很明显了:

javascript
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 的设计目的主要有以下几点:

  1. 统一对象操作接口:将对象的各种操作(如 obj[prop]delete obj[prop] 等)集中到 Reflect 对象上,形成更规范的函数式接口。

  2. 与 Proxy 配合使用Reflect 的方法与 Proxy 拦截器的方法一一对应,便于在 Proxy 中实现默认行为的转发。

  3. 更合理的返回值:相比传统操作符(如 delete),Reflect 方法返回更明确的操作结果(通常是布尔值表示成功与否)。

  4. 函数式编程风格:提供函数式的对象操作方式,便于组合和传递。

常用的 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.getReflect.set 来实现默认的属性访问和赋值行为,这样可以确保操作符合 JavaScript 的默认语义,同时保持代码的规范性。

例如,使用 Reflect.set 比直接赋值更适合在 Proxy 的 set 拦截器中使用,因为它能正确处理继承关系和 receiver 绑定:

javascript
// 传统方式
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')),他就会按标准流程帮你操作,还会告诉你"拿到了"或" 没找到"。

再举几个具体例子:

  1. 以前查对象有没有某个属性:
javascript
if ('name' in obj) { ...
}

现在可以让助手查:

javascript
if (Reflect.has(obj, 'name')) { ...
}
  1. 以前给对象赋值:
javascript
obj.age = 18;

现在让助手来做(他还会告诉你成没成功):

javascript
const success = Reflect.set(obj, 'age', 18); // 成功返回true

为什么需要这个助手?

  • 他做事更规范:不管对象多特殊(比如被Proxy代理的),他都按标准流程来
  • 他会反馈结果:操作成功与否一目了然
  • 他适合和Proxy配合:Proxy就像保安,决定让不让访问;Reflect就像快递员,负责实际送货

简单说:Reflect 就是把原来用符号(indelete=)做的事,变成了用函数来做,让代码更统一、更好用。

哈哈,能听懂就好!其实很多技术概念拆开了说,就跟日常聊天似的。

比如再回头看 Proxy 和 Reflect 这俩:

  • Proxy 就像给对象雇了个"门卫",有人想拿东西、放东西,都得经过它点头。
  • 要是门卫觉得没问题,就喊 Reflect 这个"跑腿的"去实际操作(拿/放),完事还能告诉门卫"搞定了"或"没搞定"。

这么一想,是不是就特具体了~ 有啥不懂的,继续用"说人话"模式问就行!

leetcode实战:实现访问任意对象的任意属性并打印

js
// 练习题目:访问一个对象的任意属性,返回一个方法,打印此属性名称
const printObjectProperty = () => {
    return new Proxy({}, {
        get(target, property, receiver) {
            return () => property;
        }
    })
}
const proxy = printObjectProperty();
console.log(proxy['name']());

实现多层对象的代理

js
    // 使用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);