Proxy (JavaScript)
Proxy – mechanizm wprowadzony w ECMAScript 6 (znanym także jako ES6), który pozwala na przechwytywanie i redefiniowanie podstawowych operacji wykonywanych na obiektach i funkcjach w języku JavaScript. Proxy jest jednym z kluczowych elementów metaprogramowania w JavaScript.
Historia
Idea tzw. „przestrzeni nazw” i możliwości podmieniania zachowań obiektów pojawiła się w Javy już w latach 80‑tych, ale dopiero wersja ES6, opublikowana w 2015 roku, wprowadziła natywny i jednolity interfejs Proxy. Projekt tego rozwiązania był realizowany pod nazwą Harmony w ramach inicjatywy TC39.
Podstawowa składnia
const handler = {
get(target, prop, receiver) {
// kod przechwytujący odczyt właściwości
},
set(target, prop, value, receiver) {
// kod przechwytujący zapis
}
};
const proxy = new Proxy(targetObject, handler);
Obiekt targetObject – znany także jako cel – jest „opakowywany” przez Proxy. Każde odwołanie do właściwości, wywołanie funkcji czy konstrukcja nowego obiektu może zostać przechwycone przez tzw. handler – zestaw trapów (pułapek).
Najważniejsze trapy
get(target, prop, receiver)– odczyt wartości właściwości.set(target, prop, value, receiver)– zapis wartości.has(target, prop)– obsługa operatorain.deleteProperty(target, prop)– usuwanie właściwości.apply(target, thisArg, argumentsList)– wywołanie funkcji.construct(target, argumentsList, newTarget)– użycie operatoranew.ownKeys(target)– zwraca listę własnych kluczy (używane m.in. przezObject.keys,for...in).getPrototypeOf(target)orazsetPrototypeOf(target, proto)– dostęp i modyfikacja łańcucha prototypów.defineProperty(target, prop, descriptor)– definiowanie własności przy pomocyObject.defineProperty.
Przykłady zastosowań
Walidacja danych
const user = { name: "Jan", age: 30 };
const validator = {
set(target, prop, value) {
if (prop === "age" && typeof value !== "number") {
throw new TypeError("Wiek musi być liczbą");
}
target[prop] = value;
return true;
}
};
const userProxy = new Proxy(user, validator);
userProxy.age = 31; // OK
userProxy.age = "trzydzieści"; // TypeError
Obsługa nieistniejących właściwości
const defaultValues = { a: 1, b: 2 };
const fallback = {
get(target, prop) {
return prop in target ? target[prop] : defaultValues[prop];
}
};
const obj = new Proxy({}, fallback);
console.log(obj.a); // 1
console.log(obj.c); // undefined (brak w defaultValues)
Transparentne logowanie
function logAccess(target) {
return new Proxy(target, {
get(t, p, r) {
console.log(`Odczyt: ${String(p)}`);
return Reflect.get(t, p, r);
},
set(t, p, v, r) {
console.log(`Zapis: ${String(p)} = ${v}`);
return Reflect.set(t, p, v, r);
}
});
}
const arr = logAccess([1, 2, 3]);
arr[0]; // konsola: Odczyt: 0
arr[1] = 5; // konsola: Zapis: 1 = 5
Powiązane obiekty i klasy
Do implementacji trapów najczęściej wykorzystuje się wbudowany obiekt Reflect, który zapewnia „domyślne” zachowanie operacji. Dzięki temu kod w trapie może najpierw wykonać dodatkową logikę, a następnie delegować do Reflect, aby nie zmienić semantyki.
Ograniczenia i pułapki
- Proxy nie może być przetransponowany – nie da się utworzyć „proxy of a proxy” w taki sposób, aby oba zachowania się nakładały; najbliższym rozwiązaniem jest łańcuchowe opakowanie.
- Nie wszystkie wbudowane operacje są przechwytywalne (np. wewnętrzne algorytmy silnika, które nie odwołują się do publicznego API).
- Użycie Proxy może wpływać na wydajność, zwłaszcza przy intensywnym odczycie w pętlach.
- Wszystkie własności, w tym symbole (Symbol), są objęte trapami, co może prowadzić do nieoczekiwanych rezultatów przy manipulacji ukrytymi własnościami.
- W środowiskach sandboxowych (np. Node.js
vm) dostęp do Proxy może być ograniczony ze względów bezpieczeństwa.
Bezpieczeństwo
Proxy umożliwia izolację i kontrolę dostępu do obiektów, co jest szczególnie przydatne w bibliotekach typu sandbox. Jednak niewłaściwe użycie może otworzyć luki, np. przy niewłaściwym przekazywaniu receiver w trapie get, co umożliwia obejście kontroli dostępu.
Podsumowanie
Proxy w JavaScript to potężny mechanizm pozwalający na dynamiczne modyfikowanie zachowań obiektów i funkcji. Dzięki trapom i obiektowi Reflect programiści mogą wprowadzać walidację, logowanie, wirtualizację danych oraz tworzyć zaawansowane API, zachowując jednocześnie pełną kontrolę nad kosztami wydajnościowymi i aspektami bezpieczeństwa.