JavaScript 代理的 7 个用例

2025-05-25

JavaScript 代理的 7 个用例

JavaScript 的Proxy对象是一个实用的工具,它开启了无限可能,让你能够在应用程序中创建一些真正有用的行为。与 TypeScript 结合使用时,Proxy 可以增强你管理和操作对象及函数的能力,让你能够以意想不到的方式进行操作。在本文中,我们将通过实际示例探索 Proxy 的强大实用性。

什么是代理?

JavaScript 中的代理对另一个对象(目标)的包装,它允许您拦截并重新定义该对象的基本操作,例如属性查找、赋值、枚举和函数调用。这意味着您可以在获取或设置属性时添加自定义逻辑。这对于处理验证、通知甚至自动数据绑定非常有用。

创建一个简单的代理

让我们开始学习如何创建代理。如果你之前没见过代理,我们先从一个非常基础的例子开始。

type MessageObject = {
    message: string;
};

let target: MessageObject = {
    message: "Hello, world!"
};

let handler: ProxyHandler<MessageObject> = {
    get: (obj, prop) => {
        return `Property ${String(prop)} is: ${obj[prop]}`;
    }
};

let proxy: MessageObject = new Proxy(target, handler);
console.log(proxy.message);  // Output: Property message is: Hello, world!
Enter fullscreen mode Exit fullscreen mode

在这个例子中,每当访问代理上的属性时,处理程序的 get 方法都会被调用,这使我们能够修改简单访问属性的行为。我相信您可以想象这会带来各种可能性。

现在让我们来看看另外 7 个有用的例子!

1. 自动填充属性

代理可以在访问时动态填充对象属性,这对于按需处理或复杂对象的初始化很有用。

type LazyProfile = {
    firstName: string;
    lastName: string;
    fullName?: string;
};

let lazyProfileHandler = {
    get: (target: LazyProfile, property: keyof LazyProfile) => {
        if (property === "fullName" && !target[property]) {
            target[property] = `${target.firstName} ${target.lastName}`;
        }
        return target[property];
    }
};

let profile: LazyProfile = new Proxy({ firstName: "John", lastName: "Doe" }, lazyProfileHandler);
console.log(profile.fullName);  // Output: John Doe
Enter fullscreen mode Exit fullscreen mode

2. 操作计数

使用代理来统计对某个对象执行某些操作的次数。这对于调试、监控或分析应用程序性能特别有用。

type Counter = {
    [key: string]: any;
    _getCount: number;
};

let countHandler = {
    get: (target: Counter, property: keyof Counter) => {
        if (property === "_getCount") {
            return target[property];
        }
        target._getCount++;
        return target[property];
    }
};

let counter: Counter = new Proxy({ a: 1, b: 2, _getCount: 0 }, countHandler);
counter.a;
counter.b;
console.log(counter._getCount);  // Output: 2
Enter fullscreen mode Exit fullscreen mode

3. 不可变对象

使用代理通过拦截并阻止对象创建后的任何更改来创建真正不可变的对象。

function createImmutable<T extends object>(obj: T): T {
    return new Proxy(obj, {
        set: () => {
            throw new Error("This object is immutable");
        }
    });
}

const immutableObject = createImmutable({ name: "Jane", age: 25 });
// immutableObject.age = 26;  // Throws error
Enter fullscreen mode Exit fullscreen mode

4. 方法链和流畅接口

通过使用代理构建流畅的接口来增强方法链,其中每个方法调用都会返回一个代理以支持进一步的调用。

type FluentPerson = {
    setName(name: string): FluentPerson;
    setAge(age: number): FluentPerson;
    save(): void;
};

function FluentPerson(): FluentPerson {
    let person: any = {};

    return new Proxy({}, {
        get: (target, property) => {
            if (property === "save") {
                return () => { console.log(person); };
            }
            return (value: any) => {
                person[property] = value;
                return target;
            };
        }
    }) as FluentPerson;
}

const person = FluentPerson();
person.setName("Alice").setAge(30).save();  // Output: { setName: 'Alice', setAge: 30 }
Enter fullscreen mode Exit fullscreen mode

5.智能缓存

这是我最喜欢的用例之一。实现智能缓存机制,按需获取或计算数据,然后存储起来,以便后续快速访问。

function smartCache<T extends object>(obj: T, fetcher: (key: keyof T) => any): T {
    const cache: Partial<T> = {};
    return new Proxy(obj, {
        get: (target, property: keyof T) => {
            if (!cache[property]) {
                cache[property] = fetcher(property);
            }
            return cache[property];
        }
    });
}

const userData = smartCache({ userId: 1 }, (prop) => {
    console.log(`Fetching data for ${String(prop)}`);
    return { name: "Bob" };  // Simulated fetch
});

console.log(userData.userId);  // Output: Fetching data for userId, then returns { name: "Bob" }
Enter fullscreen mode Exit fullscreen mode

6.动态属性验证

代理可以动态强制执行属性赋值的规则。以下是如何确保在更改属性之前满足某些条件的方法:

let user = {
  age: 25
};

let validator = {
  set: (obj, prop, value) => {
    if (prop === 'age' && (typeof value !== 'number' || value < 18)) {
      throw new Error("User must be at least 18 years old.");
    }
    obj[prop] = value;
    return true;  // Indicate success
  }
};

let userProxy = new Proxy(user, validator);
userProxy.age = 30;  // Works fine
console.log(userProxy.age);  // Output: 30
// userProxy.age = 'thirty';  // Throws error
// userProxy.age = 17;  // Throws error
Enter fullscreen mode Exit fullscreen mode

7. 关注变化

代理的一个常见用例是创建可监视的对象,当发生变化时通知您。

function onChange(obj, onChange) {
  const handler = {
    set: (target, property, value, receiver) => {
      onChange(`Property ${String(property)} changed to ${value}`);
      return Reflect.set(target, property, value, receiver);
    }
  };
  return new Proxy(obj, handler);
}

const person = { name: "John", age: 30 };
const watchedPerson = onChange(person, console.log);

watchedPerson.age = 31;  // Console: Property age changed to 31
Enter fullscreen mode Exit fullscreen mode

使用代理的缺点

虽然代理非常有用,但也有一些注意事项:

  1. 性能:代理可能会带来性能开销,尤其是在高频操作中,因为代理上的每个操作都必须经过处理程序。
  2. 复杂性:功能强大,复杂性也随之增加。代理使用不当可能会导致难以调试的问题和可维护性问题。
  3. 兼容性:代理无法针对不支持 ES6 功能的旧版浏览器进行 polyfill,从而限制了它们在需要广泛兼容性的环境中的使用。

结束

JavaScript 中的代理,尤其是在与 TypeScript 结合使用时,提供了一种灵活的对象交互方式。它们支持验证、观察和绑定等功能。无论您是构建复杂的用户界面、开发游戏,还是处理服务器端逻辑,理解和使用代理都能让您在代码中拥有更深层次的控制力和复杂性。感谢您的阅读,希望您能有所收获!🎓

另外,我还要毫不掩饰地打个广告🔌。如果你在敏捷开发团队工作,并且使用工具进行在线会议,比如计划扑克或回顾会议,不妨看看我的免费工具Kollabe

文章来源:https://dev.to/mattlewandowski93/7-use-cases-for-javascript-proxies-3b29
PREV
Array.reduce() 被 Goated 了🐐✨
NEXT
我经常使用的网站