Az ECMAScript 6 olyan hatékony új API-kat tartalmaz, mint a Proxy() és hasznos társ, a Reflect. Itt szeretném megvizsgálni, hogyan használhatjuk ki a proxy-k és szimbólumok kombinációját, hogy „titkos” mix-ineket hozzunk létre az objektumaihoz!

Ha még nem használta a szimbólumok előnyeit, javaslom, hogy olvassa el őket az MDN dokumentációban. Röviden:

  • a szimbólumok primitívek
  • objektumok, például karakterláncok kulcsaként használhatók
  • egy szimbólum csak azon hivatkozáson keresztül használható, amellyel létrehozták – vagyis egy szimbólum csak önmagával egyenlő
  • az előző miatt nem szerializálhatók JSON-ba, illetve nem eleveníthetők fel onnan
  • ha nem fér hozzá a hatókörében lévő szimbólumhoz, nem tudja elolvasni!

Használhatunk szimbólumokat a meglévő objektumok kiegészítésére (vagy terjesztésére), és olyan tulajdonságokat adhatunk hozzá, amelyekről tudjuk, hogy nem ütköznek az objektum meglévő tulajdonságaival. Más szóval, a szimbólumok hatékony eszközt jelenthetnek biztonságos „mix-in” létrehozásához vagy „branding” objektumok létrehozásához.

const entityTypeSym: unique symbol = Symbol();
type WithEntityType<
  T extends object,
  S extends symbol
> = T & {
  readonly [entityTypeSym]: S;
};
const USER: unique symbol = Symbol();
type WithUserType<T extends object> =
  WithEntityType<T, typeof USER>;
function withUserType<T extends object>(
  object: T
): WithUserType<T> {
  return {
    ...object,
   [entityTypeSym]: USER,
  };
}
function isUserType<T>(value: T): value is WithUserType<T> {
  return value !== null && typeof value === "object" &&
    value[entityTypeSym] === USER;
}
const data = { username: "foo" } as const;
// data: { username: string }
const user = withUserType(data);
// user: { [entityTypeSym]: USER, username: string }

Ez az egyszerű megvalósítás meglehetősen ügyes, de van néhány mellékhatása – nevezetesen, hogy az objektum feletti iteráció felsorolja a szimbólumot, és azt szeretnénk, hogy a mix-in átláthatatlanabb legyen. Ezt megkerülhetjük Object.defineProperty()-el, de az objektumokhoz kapcsolódó bármely tulajdonság általában elérhetővé válik a Object.getOwnPropertyDescriptors() belső implementációjával.

// :(
for (const prop in user) {
  alert(prop);
  // Uncaught TypeError: Cannot convert a Symbol value to a string
}

Mi lenne, ha a tulajdonunkat elérhetővé tudnánk tenni szimbólumokkal, de anélkül, hogy az valójában kapcsolódna a tárgyhoz? Ekkor, ha a entityTypeSym kulcsunk privát marad (a mi modulunkra terjed ki), akkor csak a modulunkban lévő függvények tudják olvasni vagy írni az értékét.

Ez valójában a Proxy()-nak köszönhetően lehetséges!

function withUserType<T extends object>(
  object: T
): WithUserType<T> {
  return new Proxy(object, {
    get: (target, prop, receiver) => {
      if (prop === entityTypeSym) {
        return USER;
      }
      return Reflect.get(target, prop, receiver);
    },
  }) as WithUserType<T>;
}

A withUserType visszatérési értéke egy homlokzati objektum, amely beburkolja az adott object-et, és ténylegesen elfogja az objektumon lévő tulajdonság eléréséhez szükséges beépített viselkedést! Tehát míg a többi proxy függvény nincs csapdában, és egyszerűen továbbít az objektumra, elkapjuk a hozzáférést a privát entityTypeSym szimbólumunkhoz, és visszaadjuk a típusát.

Mivel a proxy objektumunk többi metódusa nincs csapdában, az olyan funkciók, mint a Object.getOwnPropertyDescriptors(), soha nem adják vissza a szimbólumunkat – ami azt jelenti, hogy a modulunkon kívül a kód áthaladhat a proxy objektumunkon, és nem fér hozzá a személyes adatainkhoz!

Azonban a modulunkban található minden olyan funkció, amely nem hozzáfér a szimbólumhoz, olvashatja és írhatja azt, azzal a biztossággal, hogy más kód nem manipulálhatta az értékeket.

function getType<T>(value: T): symbol | undefined {
  return value !== null && typeof value === "object" &&
    value[entityTypeSym];
}
getType(user); // Code outside this module can never read
               // entityTypeSym nor value[entityTypeSym]
               // without our getType() function

Mire használhatnánk ezt?

Van egy tárgyam. Gyakran hivatkoznom kell a kulcsok számára; ennek általános módja azonban, a Object.keys(object).length egy kissé költséges művelet, tekintve, hogy az összes felsorolható kulcsból álló tömb létrehozására van szükség, csak hogy megkapja a méretét.

Mi lenne, ha egy proxy segítségével nyomon követhetnénk egy objektumon lévő tulajdonságok számát, és egy privát szimbólumot az érték eléréséhez?

Elég ügyes, igaz?

Mondja el nekem a megjegyzésekben, hogy milyen más nagyszerű alkalmazásokat talált ki proxykhoz és szimbólumokhoz!