啥是JS的Proxy,带你瞧一瞧

462 阅读3分钟

JavaScript的Proxy对象是一个非常有用的工具,它打开了无限的可能性,让你能够在应用程序中创建一些非常有用的行为。当与TypeScript结合使用时,Proxy增强了你管理和操作对象和函数的能力,这些能力你可能之前从未想过能够实现。在本文中,我们将通过实际示例探索Proxy的强大实用性。

什么是Proxy?

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

创建一个简单的Proxy 让我们来看看如何创建一个Proxy。我们从一个非常基本的示例开始,以防你之前没有见过Proxy。

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);  // 输出: Property message is: Hello, world!

在这个例子中,每当访问代理对象上的属性时,都会调用处理程序的get方法,这使我们能够修改简单访问属性的行为。我相信你可以想象出许多不同的可能性。

现在让我们深入探讨7个更加实用的示例!

  1. 自动填充属性 当访问时,Proxy可以动态填充对象属性,这对于按需处理或初始化复杂对象非常有用。
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);  // 输出: John Doe
  1. 操作计数 使用Proxy来统计对象上执行某些操作的次数。这对于调试、监控或分析应用程序性能特别有用。
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);  // 输出: 2
  1. 不可变对象 通过使用Proxy拦截并阻止对象创建后的任何更改,可以创建真正的不可变对象。
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;  // 抛出错误
  1. 方法链和流畅接口 通过使用Proxy增强方法链,创建流畅接口,其中每个方法调用返回一个Proxy,从而允许进一步调用。
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();  // 输出: { setName: 'Alice', setAge: 30 }
  1. 智能缓存 这是我最喜欢的使用场景之一。实现智能缓存机制,其中数据在需要时获取或计算,并且在之后快速访问时存储起来。
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" };  // 模拟获取
});

console.log(userData.userId);  // 输出: Fetching data for userId, then returns { name: "Bob" }
  1. 动态属性验证 Proxy可以动态强制执行属性赋值的规则。以下是如何确保在更改属性之前满足特定条件:
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;  // 表示成功
  }
};

let userProxy = new Proxy(user, validator);
userProxy.age = 30;  // 工作正常
console.log(userProxy.age);  // 输出: 30
// userProxy.age = 'thirty';  // 抛出错误
// userProxy.age = 17;  // 抛出错误
  1. 监听变化 Proxy的一个常见用例是创建可监听的对象,在发生更改时通知你。
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;  // 控制台输出: Property age changed to 31

使用Proxy的缺点

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

性能:Proxy可能会引入性能开销,特别是在高频操作中,因为每个代理对象的操作都必须经过处理程序。

复杂性:伴随强大功能而来的是更大的复杂性。错误使用Proxy可能导致难以调试的问题和可维护性问题。

兼容性:不能为不支持ES6特性的旧版浏览器提供Proxy的polyfill,这限制了它在需要广泛兼容性的环境中的使用。