JavaScript 的Proxy物件是一個有用的工具,它開啟了一個充滿可能性的世界,讓您在應用程式中建立一些真正有用的行為。當與 TypeScript 結合使用時,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!

在此範例中,每當存取代理程式上的屬性時,都會呼叫處理程序的 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

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

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

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 }

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" }

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

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

使用代理的缺點

雖然代理非常有用,但它們有一些注意事項:

  1. 效能:代理程式會帶來效能開銷,尤其是在高頻操作中,因為代理程式上的每個操作都必須經過處理程序。

  2. 複雜性:能力越大,複雜度越高。代理的不正確使用可能會導致難以除錯的問題和可維護性問題。

  3. 相容性:代理程式無法為不支援 ES6 功能的舊版瀏覽器進行多填充,這限制了它們在需要廣泛相容性的環境中的使用。

結束

JavaScript 中的代理,尤其是與 TypeScript 一起使用時,提供了一種與物件互動的靈活方式。它們支援驗證、觀察和綁定等功能。無論您是建立複雜的使用者介面、開發遊戲還是處理伺服器端邏輯,理解和利用代理程式都可以為您提供更深層的控制和程式碼的複雜性。感謝您的閱讀,希望您學到新東西! 🎓

還有無恥的插頭🔌。如果您在敏捷開發團隊中工作並使用線上會議工具(例如規劃撲克或回顧),請查看我的免費工具Kollabe


原文出處:https://dev.to/mattlewandowski93/7-use-cases-for-javascript-proxies-3b29


共有 0 則留言