Utforsk avanserte JavaScript Proxy-mønstre for objektavskjæring, validering og dynamisk atferd. Lær hvordan du forbedrer kodekvalitet, sikkerhet og vedlikeholdbarhet med praktiske eksempler.
JavaScript Proxy-mønstre: Avansert objektavskjæring og validering
JavaScript Proxy-objektet er en kraftig funksjon som lar deg avskjære og tilpasse grunnleggende objektoperasjoner. Det muliggjør avanserte metaprogrammeringsteknikker, og tilbyr større kontroll over objektatferd og åpner for muligheter for sofistikerte designmønstre. Denne artikkelen utforsker forskjellige Proxy-mønstre, og viser deres bruksområder innen validering, avskjæring og dynamisk atferdsmodifikasjon. Vi vil dykke ned i praktiske eksempler for å demonstrere hvordan Proxies kan forbedre kodekvalitet, sikkerhet og vedlikeholdbarhet i JavaScript-prosjektene dine.
Forstå JavaScript Proxy
I sin kjerne pakker et Proxy-objekt et annet objekt (målet) og avskjærer operasjoner som utføres på det målet. Disse avskjæringene håndteres av feller, som er metoder som definerer tilpasset atferd for spesifikke operasjoner som å hente en egenskap, sette en egenskap eller kalle en funksjon. Proxy API-et gir en fleksibel og utvidbar mekanisme for å endre standardatferden til objekter.
Viktige konsepter
- Mål: Det opprinnelige objektet som Proxy-en pakker.
- Handler: Et objekt som inneholder fellemetodene. Hver felle tilsvarer en spesifikk operasjon.
- Feller: Metoder i handleren som avskjærer og tilpasser objektoperasjoner. Vanlige feller inkluderer
get,set,applyogconstruct.
Opprette en Proxy
For å opprette en Proxy, bruker du Proxy-konstruktøren, og sender målobjektet og handlerobjektet som argumenter:
const target = {};
const handler = {
get: function(target, property, receiver) {
console.log(`Henter egenskap: ${property}`);
return Reflect.get(target, property, receiver);
}
};
const proxy = new Proxy(target, handler);
proxy.name = "John"; // Logger: Henter egenskap: name
console.log(proxy.name); // Logger: Henter egenskap: name, deretter John
Vanlige Proxy-feller
Proxies tilbyr en rekke feller for å avskjære forskjellige operasjoner. Her er noen av de mest brukte fellene:
get(target, property, receiver): Avskjærer tilgang til egenskap.set(target, property, value, receiver): Avskjærer egenskapstildeling.has(target, property): Avskjærerin-operatoren.deleteProperty(target, property): Avskjærerdelete-operatoren.apply(target, thisArg, argumentsList): Avskjærer funksjonskall.construct(target, argumentsList, newTarget): Avskjærernew-operatoren.getPrototypeOf(target): AvskjærerObject.getPrototypeOf()-metoden.setPrototypeOf(target, prototype): AvskjærerObject.setPrototypeOf()-metoden.isExtensible(target): AvskjærerObject.isExtensible()-metoden.preventExtensions(target): AvskjærerObject.preventExtensions()-metoden.getOwnPropertyDescriptor(target, property): AvskjærerObject.getOwnPropertyDescriptor()-metoden.defineProperty(target, property, descriptor): AvskjærerObject.defineProperty()-metoden.ownKeys(target): AvskjærerObject.getOwnPropertyNames()- ogObject.getOwnPropertySymbols()-metodene.
Proxy-mønstre
La oss nå utforske noen praktiske Proxy-mønstre og deres applikasjoner:
1. Valideringsproxy
En valideringsproxy håndhever begrensninger for egenskapstildelinger. Den avskjærer set-fellen for å validere den nye verdien før den lar tildelingen fortsette.
Eksempel: Validere brukerinndata i et skjema.
const user = {};
const validator = {
set: function(target, property, value) {
if (property === 'age') {
if (!Number.isInteger(value) || value < 0 || value > 120) {
throw new Error('Ugyldig alder. Alder må være et heltall mellom 0 og 120.');
}
}
target[property] = value;
return true; // Indikerer suksess
}
};
const proxy = new Proxy(user, validator);
proxy.name = 'Alice';
proxy.age = 30;
console.log(user);
try {
proxy.age = 'invalid'; // Kaster en feil
} catch (error) {
console.error(error.message);
}
I dette eksemplet sjekker set-fellen om age-egenskapen er et heltall mellom 0 og 120. Hvis valideringen mislykkes, kastes en feil, og hindrer at den ugyldige verdien blir tilordnet.
Globalt eksempel: Dette valideringsmønsteret er viktig for å sikre dataintegritet i globale applikasjoner der brukerinndata kan komme fra forskjellige kilder og kulturer. For eksempel kan validering av postnummer variere betydelig mellom land. En valideringsproxy kan tilpasses for å støtte forskjellige valideringsregler basert på brukerens plassering.
const address = {};
const addressValidator = {
set: function(target, property, value) {
if (property === 'postalCode') {
// Eksempel: Antar en enkel amerikansk postnummer-validering
if (!/^[0-9]{5}(?:-[0-9]{4})?$/.test(value)) {
throw new Error('Ugyldig amerikansk postnummer.');
}
}
target[property] = value;
return true;
}
};
const addressProxy = new Proxy(address, addressValidator);
addressProxy.postalCode = "12345-6789"; // Gyldig
try {
addressProxy.postalCode = "abcde"; // Ugyldig
} catch(e) {
console.log(e);
}
// For en mer internasjonal applikasjon, vil du bruke et mer sofistikert valideringsbibliotek
// som kan validere postnummer basert på brukerens land.
2. Loggingsproxy
En loggingsproxy avskjærer tilgang til og tildeling av egenskaper for å logge disse operasjonene. Det er nyttig for feilsøking og revisjon.
Eksempel: Logge tilgang til og endring av egenskaper.
const data = {
value: 10
};
const logger = {
get: function(target, property) {
console.log(`Henter egenskap: ${property}`);
return target[property];
},
set: function(target, property, value) {
console.log(`Setter egenskap: ${property} til ${value}`);
target[property] = value;
return true;
}
};
const proxy = new Proxy(data, logger);
console.log(proxy.value); // Logger: Henter egenskap: value, deretter 10
proxy.value = 20; // Logger: Setter egenskap: value til 20
get- og set-fellene logger egenskapen som blir aksessert eller endret, og gir et spor av objektinteraksjoner.
Globalt eksempel: I et multinasjonalt selskap kan loggingsproxyer brukes til å revidere datatilgang og endringer utført av ansatte på forskjellige steder. Dette er avgjørende for samsvar og sikkerhet. Tidssoner må kanskje vurderes i loggingsinformasjonen.
const employeeData = {
name: "John Doe",
salary: 50000
};
const auditLogger = {
get: function(target, property) {
const timestamp = new Date().toISOString();
console.log(`${timestamp} - [GET] Får tilgang til egenskap: ${property}`);
return target[property];
},
set: function(target, property, value) {
const timestamp = new Date().toISOString();
console.log(`${timestamp} - [SET] Setter egenskap: ${property} til ${value}`);
target[property] = value;
return true;
}
};
const proxiedEmployee = new Proxy(employeeData, auditLogger);
proxiedEmployee.name; // Logger tidsstempel og tilgang til 'name'
proxiedEmployee.salary = 60000; // Logger tidsstempel og endring av 'salary'
3. Skrivebeskyttet proxy
En skrivebeskyttet proxy hindrer egenskapstildeling. Den avskjærer set-fellen og kaster en feil hvis det gjøres et forsøk på å endre en egenskap.
Eksempel: Gjøre et objekt uforanderlig.
const config = {
apiUrl: 'https://api.example.com'
};
const readOnly = {
set: function(target, property, value) {
throw new Error(`Kan ikke sette egenskap: ${property}. Objektet er skrivebeskyttet.`);
}
};
const proxy = new Proxy(config, readOnly);
console.log(proxy.apiUrl);
try {
proxy.apiUrl = 'https://newapi.example.com'; // Kaster en feil
} catch (error) {
console.error(error.message);
}
Ethvert forsøk på å sette en egenskap på proxyen vil resultere i en feil, og sikre at objektet forblir uforanderlig.
Globalt eksempel: Dette mønsteret er nyttig for å beskytte konfigurasjonsfiler som ikke skal endres under kjøring, spesielt i globalt distribuerte applikasjoner. Å endre konfigurasjonen i en region ved et uhell kan påvirke hele systemet.
const globalSettings = {
defaultLanguage: "en",
currency: "USD",
timeZone: "UTC"
};
const immutableHandler = {
set: function(target, property, value) {
throw new Error(`Kan ikke endre skrivebeskyttet egenskap: ${property}`);
}
};
const immutableSettings = new Proxy(globalSettings, immutableHandler);
console.log(immutableSettings.defaultLanguage); // skriver ut 'en'
// Forsøk på å endre en verdi vil kaste en feil
// immutableSettings.defaultLanguage = "fr"; // kaster Error: Kan ikke endre skrivebeskyttet egenskap: defaultLanguage
4. Virtuell proxy
En virtuell proxy kontrollerer tilgangen til en ressurs som kan være kostbar å opprette eller hente. Den kan utsette opprettelsen av ressursen til den faktisk er nødvendig.
Eksempel: Lat laste inn et bilde.
const image = {
display: function() {
console.log('Viser bilde');
}
};
const virtualProxy = {
get: function(target, property) {
if (property === 'display') {
console.log('Oppretter bilde...');
const realImage = {
display: function() {
console.log('Viser ekte bilde');
}
};
target.display = realImage.display;
return realImage.display;
}
return target[property];
}
};
const proxy = new Proxy(image, virtualProxy);
// Bildet opprettes ikke før display kalles.
proxy.display(); // Logger: Oppretter bilde..., deretter Viser ekte bilde
Det virkelige bildeobjektet opprettes bare når display-metoden kalles, og unngår unødvendig ressursbruk.
Globalt eksempel: Tenk deg et globalt e-handelsnettsted som viser bilder av produkter. Ved hjelp av en virtuell proxy kan bilder lastes inn bare når de er synlige for brukeren, og optimaliserer båndbreddebruken og forbedrer sidens innlastingstider, spesielt for brukere med trege internettforbindelser i forskjellige regioner.
const product = {
loadImage: function() {
console.log("Laster inn høyoppløselig bilde...");
// Simulerer lasting av et stort bilde
setTimeout(() => {
console.log("Bilde lastet");
this.displayImage();
}, 2000);
},
displayImage: function() {
console.log("Viser bildet");
}
};
const lazyLoadProxy = {
get: function(target, property) {
if (property === "displayImage") {
// I stedet for å laste umiddelbart, forsink lastingen
console.log("Forespørsel om å vise bilde mottatt. Laster...");
target.loadImage();
return function() { /* Intentionally Empty */ }; // Returner tom funksjon for å forhindre umiddelbar utførelse
}
return target[property];
}
};
const proxiedProduct = new Proxy(product, lazyLoadProxy);
// Kall displayImage utløser den late lasteprosessen
proxiedProduct.displayImage();
5. Gjenkallelig proxy
En gjenkallelig proxy lar deg gjenkalle proxyen når som helst, noe som gjør den ubrukelig. Dette er nyttig for sikkerhetsfølsomme scenarier der du trenger å kontrollere tilgangen til et objekt.
Eksempel: Gi midlertidig tilgang til en ressurs.
const target = {
secret: 'Dette er en hemmelighet'
};
const handler = {
get: function(target, property) {
console.log('Får tilgang til hemmelig egenskap');
return target[property];
}
};
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.secret); // Logger: Får tilgang til hemmelig egenskap, deretter Dette er en hemmelighet
revoke();
try {
console.log(proxy.secret); // Kaster en TypeError
} catch (error) {
console.error(error.message); // Logger: Kan ikke utføre 'get' på en proxy som er blitt gjenkalt
}
Proxy.revocable()-metoden oppretter en gjenkallelig proxy. Å kalle revoke()-funksjonen gjør proxyen ubrukelig, og hindrer videre tilgang til målobjektet.
Globalt eksempel: I et globalt distribuert system kan du bruke en gjenkallelig proxy til å gi midlertidig tilgang til sensitive data til en tjeneste som kjører i en bestemt region. Etter en viss tid kan proxyen gjenkalles for å forhindre uautorisert tilgang.
const sensitiveData = {
apiKey: "SUPER_SECRET_KEY"
};
const handler = {
get: function(target, property) {
console.log("Får tilgang til sensitive data");
return target[property];
}
};
const { proxy: dataProxy, revoke: revokeAccess } = Proxy.revocable(sensitiveData, handler);
// Tillat tilgang i 5 sekunder
setTimeout(() => {
revokeAccess();
console.log("Tilgang gjenkalt");
}, 5000);
// Forsøk å få tilgang til data
console.log(dataProxy.apiKey); // Logger API-nøkkelen
// Etter 5 sekunder vil dette kaste en feil
setTimeout(() => {
try {
console.log(dataProxy.apiKey); // Kaster: TypeError: Kan ikke utføre 'get' på en proxy som er blitt gjenkalt
} catch (error) {
console.error(error);
}
}, 6000);
6. Typekonverteringsproxy
En typekonverteringsproxy avskjærer tilgang til egenskaper for automatisk å konvertere den returnerte verdien til en bestemt type. Dette kan være nyttig for å jobbe med data fra forskjellige kilder som kan ha inkonsekvente typer.
Eksempel: Konvertere strengverdier til tall.
const data = {
price: '10.99',
quantity: '5'
};
const typeConverter = {
get: function(target, property) {
const value = target[property];
if (typeof value === 'string' && !isNaN(Number(value))) {
return Number(value);
}
return value;
}
};
const proxy = new Proxy(data, typeConverter);
console.log(proxy.price + 1); // Logger: 11.99 (tall)
console.log(proxy.quantity * 2); // Logger: 10 (tall)
get-fellen sjekker om egenskapsverdien er en streng som kan konverteres til et tall. I så fall konverterer den verdien til et tall før den returnerer den.
Globalt eksempel: Når du arbeider med data som kommer fra APIer med forskjellige formateringskonvensjoner (f.eks. forskjellige datoformater eller valutasymboler), kan en typekonverteringsproxy sikre datakonsistens på tvers av applikasjonen din, uavhengig av kilden. For eksempel, håndtere forskjellige datoformater og konvertere dem alle til ISO 8601-format.
const apiData = {
dateUS: "12/31/2023",
dateEU: "31/12/2023"
};
const dateFormatConverter = {
get: function(target, property) {
let value = target[property];
if (property.startsWith("date")) {
// Forsøk å konvertere både amerikanske og europeiske datoformater til ISO 8601
if (property === "dateUS") {
const [month, day, year] = value.split("/");
value = `${year}-${month}-${day}`;
} else if (property === "dateEU") {
const [day, month, year] = value.split("/");
value = `${year}-${month}-${day}`;
}
return value;
}
return value;
}
};
const proxiedApiData = new Proxy(apiData, dateFormatConverter);
console.log(proxiedApiData.dateUS); // Skriver ut: 2023-12-31
console.log(proxiedApiData.dateEU); // Skriver ut: 2023-12-31
Beste praksis for bruk av Proxies
- Bruk Proxies med omhu: Proxies kan legge til kompleksitet i koden din. Bruk dem bare når de gir betydelige fordeler, for eksempel forbedret validering, logging eller kontroll over objektatferd.
- Vurder ytelse: Proxy-feller kan introdusere overhead. Profiler koden din for å sikre at Proxies ikke påvirker ytelsen negativt, spesielt i ytelseskritiske seksjoner.
- Håndter feil på en god måte: Forsikre deg om at fellemetodene dine håndterer feil på en hensiktsmessig måte, og gir informativ feilmeldinger når det er nødvendig.
- Bruk Reflect API:
ReflectAPI-et gir metoder som speiler standardatferden til objektoperasjoner. BrukReflect-metoder i fellemetodene dine for å delegere til den opprinnelige atferden når det er hensiktsmessig. Dette sikrer at fellene dine ikke bryter eksisterende funksjonalitet. - Dokumenter dine Proxies: Dokumenter tydelig formålet og atferden til dine Proxies, inkludert fellene som brukes og begrensningene som håndheves. Dette vil hjelpe andre utviklere å forstå og vedlikeholde koden din.
Konklusjon
JavaScript Proxies er et kraftig verktøy for avansert objektmanipulasjon og avskjæring. Ved å forstå og bruke forskjellige Proxy-mønstre, kan du forbedre kodekvalitet, sikkerhet og vedlikeholdbarhet. Fra å validere brukerinndata til å kontrollere tilgangen til sensitive ressurser, tilbyr Proxies en fleksibel og utvidbar mekanisme for å tilpasse objektatferd. Når du utforsker mulighetene med Proxies, husk å bruke dem med omhu og dokumentere koden din grundig.
Eksemplene som er gitt demonstrerer hvordan du bruker JavaScript Proxies til å løse virkelige problemer i en global kontekst. Ved å forstå og bruke disse mønstrene, kan du lage mer robuste, sikre og vedlikeholdbare applikasjoner som dekker behovene til en mangfoldig brukerbase. Husk alltid å vurdere de globale implikasjonene av koden din og tilpasse løsningene dine til de spesifikke kravene i forskjellige regioner og kulturer.