Põhjalik juhend arendajatele JavaScripti Proxy API valdamiseks. Õppige pealt kuulama ja kohandama objektide operatsioone praktiliste näidete, kasutusjuhtude ja jõudlusnippidega.
JavaScripti Proxy API: Süvitsiminek objekti käitumise muutmisel
Kaasaegse JavaScripti areneval maastikul otsivad arendajad pidevalt võimsamaid ja elegantsemaid viise andmete haldamiseks ja nendega suhtlemiseks. Kuigi funktsioonid nagu klassid, moodulid ja async/await on muutnud meie koodi kirjutamise viisi revolutsiooniliseks, on ECMAScript 2015 (ES6) versioonis tutvustatud võimas metaprogrammeerimise funktsioon, mis sageli jääb alakasutatuks: Proxy API.
Metaprogrammeerimine võib tunduda hirmutav, kuid see on lihtsalt kontseptsioon koodi kirjutamisest, mis opereerib teise koodiga. Proxy API on JavaScripti peamine tööriist selleks, võimaldades luua teise objekti jaoks 'proksi', mis suudab pealt kuulata ja uuesti defineerida selle objekti põhilisi operatsioone. See on nagu kohandatava väravavahi paigutamine objekti ette, andes teile täieliku kontrolli selle üle, kuidas sellele juurde pääsetakse ja seda muudetakse.
See põhjalik juhend aitab Proxy API müstikat hajutada. Uurime selle põhimõisteid, analüüsime selle erinevaid võimekusi praktiliste näidete abil ning arutame edasijõudnud kasutusjuhte ja jõudlusega seotud kaalutlusi. Lõpuks saate aru, miks proxyd on kaasaegsete raamistike nurgakivi ja kuidas saate neid kasutada puhtama, võimsama ja paremini hooldatava koodi kirjutamiseks.
Põhimõistete mõistmine: sihtmärk, käsitleja ja püünised
Proxy API on üles ehitatud kolmele põhikomponendile. Nende rollide mõistmine on proxy'de valdamise võti.
- Sihtmärk (Target): See on algne objekt, mida soovite mähkida. See võib olla mis tahes tüüpi objekt, sealhulgas massiivid, funktsioonid või isegi teine proxy. Proxy virtualiseerib selle sihtmärgi ja kõik operatsioonid edastatakse lõpuks (kuigi mitte tingimata) sellele.
- Käsitleja (Handler): See on objekt, mis sisaldab proxy loogikat. See on kohatäitja-objekt, mille omadused on funktsioonid, mida tuntakse 'püünistena'. Kui proxy'ga tehakse operatsioon, otsib see käsitlejast vastavat püünist.
- Püünised (Traps): Need on käsitleja meetodid, mis pakuvad juurdepääsu omadustele. Iga püünis vastab objekti põhioperatsioonile. Näiteks püünis
get
kuulab pealt omaduste lugemist ja püünisset
omaduste kirjutamist. Kui käsitlejas pole püünist defineeritud, edastatakse operatsioon lihtsalt sihtmärgile, justkui proxy't polekski olemas.
Proxy loomise süntaks on otsekohene:
const proxy = new Proxy(target, handler);
Vaatame ühte väga lihtsat näidet. Loome proxy, mis lihtsalt edastab kõik operatsioonid sihtmärk-objektile, kasutades tühja käsitlejat.
// Algne objekt
const target = {
message: "Tere, Maailm!"
};
// Tühi käsitleja. Kõik operatsioonid edastatakse sihtmärgile.
const handler = {};
// Proxy objekt
const proxy = new Proxy(target, handler);
// Omadusele juurdepääs proxy kaudu
console.log(proxy.message); // Väljund: Tere, Maailm!
// Operatsioon edastati sihtmärgile
console.log(target.message); // Väljund: Tere, Maailm!
// Omaduse muutmine proxy kaudu
proxy.anotherMessage = "Tere, Proxy!";
console.log(proxy.anotherMessage); // Väljund: Tere, Proxy!
console.log(target.anotherMessage); // Väljund: Tere, Proxy!
Selles näites käitub proxy täpselt nagu algne objekt. Tõeline võimsus ilmneb siis, kui hakkame käsitlejas püüniseid defineerima.
Proxy anatoomia: levinumate püüniste uurimine
Käsitleja objekt võib sisaldada kuni 13 erinevat püünist, millest igaüks vastab JavaScripti objektide fundamentaalsele sisemisele meetodile. Uurime kõige levinumaid ja kasulikumaid neist.
Omadustele juurdepääsu püünised
1. `get(target, property, receiver)`
See on vaieldamatult kõige kasutatavam püünis. See käivitub, kui proxy omadust loetakse.
target
: Algne objekt.property
: Omaduse nimi, millele juurde pääsetakse.receiver
: Proxy ise või objekt, mis sellest pärineb.
Näide: Vaikimisi väärtused olematutele omadustele.
const user = {
firstName: 'John',
lastName: 'Doe',
age: 30
};
const userHandler = {
get(target, property) {
// Kui omadus on sihtmärgis olemas, tagasta see.
// Vastasel juhul tagasta vaike-sõnum.
return property in target ? target[property] : `Omadust '${property}' ei eksisteeri.`;
}
};
const userProxy = new Proxy(user, userHandler);
console.log(userProxy.firstName); // Väljund: John
console.log(userProxy.age); // Väljund: 30
console.log(userProxy.country); // Väljund: Omadust 'country' ei eksisteeri.
2. `set(target, property, value, receiver)`
Püünis set
kutsutakse välja, kui proxy omadusele omistatakse väärtus. See on ideaalne valideerimiseks, logimiseks või ainult lugemiseks mõeldud objektide loomiseks.
value
: Uus väärtus, mis omadusele määratakse.- Püünis peab tagastama tõeväärtuse:
true
, kui määramine õnnestus, jafalse
vastasel juhul (mis viskab rangetes režiimisTypeError
vea).
Näide: Andmete valideerimine.
const person = {
name: 'Jane Doe',
age: 25
};
const validationHandler = {
set(target, property, value) {
if (property === 'age') {
if (typeof value !== 'number' || !Number.isInteger(value)) {
throw new TypeError('Vanus peab olema täisarv.');
}
if (value <= 0) {
throw new RangeError('Vanus peab olema positiivne arv.');
}
}
// Kui valideerimine õnnestub, määra väärtus sihtmärk-objektile.
target[property] = value;
// Märgi õnnestumist.
return true;
}
};
const personProxy = new Proxy(person, validationHandler);
personProxy.age = 30; // See on kehtiv
console.log(personProxy.age); // Väljund: 30
try {
personProxy.age = 'kolmkümmend'; // Visatakse TypeError
} catch (e) {
console.error(e.message); // Väljund: Vanus peab olema täisarv.
}
try {
personProxy.age = -5; // Visatakse RangeError
} catch (e) {
console.error(e.message); // Väljund: Vanus peab olema positiivne arv.
}
3. `has(target, property)`
See püünis kuulab pealt in
operaatorit. See võimaldab teil kontrollida, millised omadused tunduvad objektil eksisteerivat.
Näide: 'Privaatsete' omaduste peitmine.
JavaScriptis on levinud tava lisada privaatsete omaduste ette allkriips (_). Saame kasutada has
püünist, et peita need in
operaatori eest.
const secretData = {
_apiKey: 'xyz123abc',
publicKey: 'pub456def',
id: 1
};
const hidingHandler = {
has(target, property) {
if (property.startsWith('_')) {
return false; // Teeskle, et seda pole olemas
}
return property in target;
}
};
const dataProxy = new Proxy(secretData, hidingHandler);
console.log('publicKey' in dataProxy); // Väljund: true
console.log('_apiKey' in dataProxy); // Väljund: false (kuigi see on sihtmärgil olemas)
console.log('id' in dataProxy); // Väljund: true
Märkus: See mõjutab ainult in
operaatorit. Otsene juurdepääs nagu dataProxy._apiKey
toimiks endiselt, kui te ei implementeeri ka vastavat get
püünist.
4. `deleteProperty(target, property)`
See püünis käivitatakse, kui omadus kustutatakse operaatoriga delete
. See on kasulik oluliste omaduste kustutamise vältimiseks.
Püünis peab tagastama true
õnnestunud kustutamise korral või false
ebaõnnestunud kustutamise korral.
Näide: Omaduste kustutamise vältimine.
const immutableConfig = {
databaseUrl: 'prod.db.server',
port: 8080
};
const deletionGuardHandler = {
deleteProperty(target, property) {
if (property in target) {
console.warn(`Katse kustutada kaitstud omadust: '${property}'. Operatsioon keelatud.`);
return false;
}
return true; // Omadust niikuinii ei eksisteerinud
}
};
const configProxy = new Proxy(immutableConfig, deletionGuardHandler);
delete configProxy.port;
// Konsooli väljund: Katse kustutada kaitstud omadust: 'port'. Operatsioon keelatud.
console.log(configProxy.port); // Väljund: 8080 (Seda ei kustutatud)
Objekti loetlemise ja kirjeldamise püünised
5. `ownKeys(target)`
See püünis käivitatakse operatsioonide puhul, mis hangivad objekti enda omaduste loendi, nagu Object.keys()
, Object.getOwnPropertyNames()
, Object.getOwnPropertySymbols()
ja Reflect.ownKeys()
.
Näide: Võtmete filtreerimine.
Kombineerime selle meie eelmise 'privaatse' omaduse näitega, et need täielikult peita.
const secretData = {
_apiKey: 'xyz123abc',
publicKey: 'pub456def',
id: 1
};
const keyHidingHandler = {
has(target, property) {
return !property.startsWith('_') && property in target;
},
ownKeys(target) {
return Reflect.ownKeys(target).filter(key => !key.startsWith('_'));
},
get(target, property, receiver) {
// Takista ka otsest juurdepääsu
if (property.startsWith('_')) {
return undefined;
}
return Reflect.get(target, property, receiver);
}
};
const fullProxy = new Proxy(secretData, keyHidingHandler);
console.log(Object.keys(fullProxy)); // Väljund: ['publicKey', 'id']
console.log('publicKey' in fullProxy); // Väljund: true
console.log('_apiKey' in fullProxy); // Väljund: false
console.log(fullProxy._apiKey); // Väljund: undefined
Pange tähele, et me kasutame siin Reflect
objekti. Reflect
objekt pakub meetodeid pealtkuulatavate JavaScripti operatsioonide jaoks ning selle meetoditel on samad nimed ja signatuurid kui proxy püünistel. Parim praktika on kasutada Reflect
objekti algse operatsiooni edastamiseks sihtmärgile, tagades vaikekäitumise korrektse säilimise.
Funktsiooni ja konstruktori püünised
Proxy'd ei piirdu ainult tavaliste objektidega. Kui sihtmärk on funktsioon, saate pealt kuulata kutseid ja konstruktsioone.
6. `apply(target, thisArg, argumentsList)`
See püünis kutsutakse välja, kui funktsiooni proxy't käivitatakse. See kuulab pealt funktsioonikutset.
target
: Algne funktsioon.thisArg
: Kutsethis
kontekst.argumentsList
: Funktsioonile edastatud argumentide loend.
Näide: Funktsioonikutsete ja nende argumentide logimine.
function sum(a, b) {
return a + b;
}
const loggingHandler = {
apply(target, thisArg, argumentsList) {
console.log(`Kutsutakse funktsiooni '${target.name}' argumentidega: ${argumentsList}`);
// Käivita algne funktsioon õige konteksti ja argumentidega
const result = Reflect.apply(target, thisArg, argumentsList);
console.log(`Funktsioon '${target.name}' tagastas: ${result}`);
return result;
}
};
const proxiedSum = new Proxy(sum, loggingHandler);
proxiedSum(5, 10);
// Konsooli väljund:
// Kutsutakse funktsiooni 'sum' argumentidega: 5,10
// Funktsioon 'sum' tagastas: 15
7. `construct(target, argumentsList, newTarget)`
See püünis kuulab pealt new
operaatori kasutamist klassi või funktsiooni proxy'l.
Näide: Singleton mustri implementeerimine.
class MyDatabaseConnection {
constructor(url) {
this.url = url;
console.log(`Ühendun aadressiga ${this.url}...`);
}
}
let instance;
const singletonHandler = {
construct(target, argumentsList) {
if (!instance) {
console.log('Luuakse uus eksemplar.');
instance = Reflect.construct(target, argumentsList);
}
console.log('Tagastatakse olemasolev eksemplar.');
return instance;
}
};
const ProxiedConnection = new Proxy(MyDatabaseConnection, singletonHandler);
const conn1 = new ProxiedConnection('db://primary');
// Konsooli väljund:
// Luuakse uus eksemplar.
// Ühendun aadressiga db://primary...
// Tagastatakse olemasolev eksemplar.
const conn2 = new ProxiedConnection('db://secondary'); // URL-i ignoreeritakse
// Konsooli väljund:
// Tagastatakse olemasolev eksemplar.
console.log(conn1 === conn2); // Väljund: true
console.log(conn1.url); // Väljund: db://primary
console.log(conn2.url); // Väljund: db://primary
Praktilised kasutusjuhud ja edasijõudnud mustrid
Nüüd, kui oleme üksikud püünised läbi käinud, vaatame, kuidas neid saab kombineerida reaalsete probleemide lahendamiseks.
1. API abstraktsioon ja andmete teisendamine
API-d tagastavad sageli andmeid formaadis, mis ei vasta teie rakenduse tavadele (nt snake_case
vs. camelCase
). Proxy saab selle teisenduse läbipaistvalt käsitleda.
function snakeToCamel(s) {
return s.replace(/(_\w)/g, (m) => m[1].toUpperCase());
}
// Kujutage ette, et see on meie toorandmestik API-st
const apiResponse = {
user_id: 123,
first_name: 'Alice',
last_name: 'Wonderland',
account_status: 'active'
};
const camelCaseHandler = {
get(target, property) {
const camelCaseProperty = snakeToCamel(property);
// Kontrolli, kas camelCase versioon on otse olemas
if (camelCaseProperty in target) {
return target[camelCaseProperty];
}
// Varuvariant on algne omaduse nimi
if (property in target) {
return target[property];
}
return undefined;
}
};
const userModel = new Proxy(apiResponse, camelCaseHandler);
// Nüüd saame omadustele ligi camelCase'i abil, kuigi need on salvestatud snake_case'ina
console.log(userModel.userId); // Väljund: 123
console.log(userModel.firstName); // Väljund: Alice
console.log(userModel.accountStatus); // Väljund: active
2. Jälgitavad (Observables) ja andmesidumine (Data Binding) (kaasaegsete raamistike tuum)
Proxy'd on mootoriks reaktiivsussüsteemide taga kaasaegsetes raamistikes nagu Vue 3. Kui muudate proksitud olekuobjekti omadust, saab set
püünist kasutada uuenduste käivitamiseks kasutajaliideses või rakenduse muudes osades.
Siin on väga lihtsustatud näide:
function createObservable(target, callback) {
const handler = {
set(obj, prop, value) {
const result = Reflect.set(obj, prop, value);
callback(prop, value); // Käivita tagasikutse muudatuse korral
return result;
}
};
return new Proxy(target, handler);
}
const state = {
count: 0,
message: 'Tere'
};
function render(prop, value) {
console.log(`MUUDATUS TUVASTATUD: Omadus '${prop}' seati väärtusele '${value}'. Kasutajaliidese uuesti renderdamine...`);
}
const observableState = createObservable(state, render);
observableState.count = 1;
// Konsooli väljund: MUUDATUS TUVASTATUD: Omadus 'count' seati väärtusele '1'. Kasutajaliidese uuesti renderdamine...
observableState.message = 'Hüvasti';
// Konsooli väljund: MUUDATUS TUVASTATUD: Omadus 'message' seati väärtusele 'Hüvasti'. Kasutajaliidese uuesti renderdamine...
3. Negatiivsed massiiviindeksid
Klassikaline ja lõbus näide on natiivse massiivi käitumise laiendamine negatiivsete indeksite toetamiseks, kus -1
viitab viimasele elemendile, sarnaselt keeltega nagu Python.
function createNegativeArrayProxy(arr) {
const handler = {
get(target, property) {
const index = Number(property);
if (!Number.isNaN(index) && index < 0) {
// Teisenda negatiivne indeks positiivseks, lugedes lõpust
property = String(target.length + index);
}
return Reflect.get(target, property);
}
};
return new Proxy(arr, handler);
}
const originalArray = ['a', 'b', 'c', 'd', 'e'];
const proxiedArray = createNegativeArrayProxy(originalArray);
console.log(proxiedArray[0]); // Väljund: a
console.log(proxiedArray[-1]); // Väljund: e
console.log(proxiedArray[-2]); // Väljund: d
console.log(proxiedArray.length); // Väljund: 5
Jõudlusega seotud kaalutlused ja parimad praktikad
Kuigi proxy'd on uskumatult võimsad, ei ole nad imerohi. On oluline mõista nende mõju.
Jõudluse lisakulu
Proxy lisab kaudse kihi. Iga proksitud objektiga tehtav operatsioon peab läbima käsitleja, mis lisab väikese hulga lisakulu võrreldes otseoperatsiooniga tavalisel objektil. Enamiku rakenduste jaoks (nagu andmete valideerimine või raamistiku tasemel reaktiivsus) on see lisakulu tühine. Kuid jõudluskriitilises koodis, näiteks tihedas tsüklis, mis töötleb miljoneid elemente, võib see muutuda pudelikaelaks. Tehke alati võrdlusteste, kui jõudlus on esmatähtis.
Proxy invariandid
Püünis ei saa sihtmärk-objekti olemuse kohta täielikult valetada. JavaScript jõustab reeglite kogumit, mida nimetatakse 'invariantideks', mida proxy püünised peavad järgima. Invariandi rikkumine põhjustab TypeError
vea.
Näiteks deleteProperty
püüdmise invariant on see, et see ei saa tagastada väärtust true
(mis näitab õnnestumist), kui vastav omadus sihtmärk-objektil ei ole konfigureeritav. See takistab proxy'l väitmast, et ta kustutas omaduse, mida ei saa kustutada.
const target = {};
Object.defineProperty(target, 'unbreakable', { value: 10, configurable: false });
const handler = {
deleteProperty(target, prop) {
// See rikub invarianti
return true;
}
};
const proxy = new Proxy(target, handler);
try {
delete proxy.unbreakable; // See viskab vea
} catch (e) {
console.error(e.message);
// Väljund: 'deleteProperty' on proxy: returned true for non-configurable property 'unbreakable'
}
Millal kasutada proxy'sid (ja millal mitte)
- Sobib hästi: Raamistike ja teekide ehitamiseks (nt olekuhaldus, ORM-id), silumiseks ja logimiseks, robustsete valideerimissüsteemide implementeerimiseks ja võimsate API-de loomiseks, mis abstraheerivad aluseks olevaid andmestruktuure.
- Kaaluge alternatiive: Jõudluskriitiliste algoritmide jaoks, lihtsate objektilaienduste jaoks, kus piisaks klassist või tehasefunktsioonist, või kui peate toetama väga vanu brausereid, millel puudub ES6 tugi.
Tühistatavad proxy'd
Olukordadeks, kus teil võib tekkida vajadus proxy 'välja lülitada' (nt turvakaalutlustel või mäluhalduseks), pakub JavaScript Proxy.revocable()
. See tagastab objekti, mis sisaldab nii proxy't kui ka revoke
funktsiooni.
const target = { data: 'tundlik' };
const handler = {};
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.data); // Väljund: tundlik
// Nüüd tühistame proxy juurdepääsu
revoke();
try {
console.log(proxy.data); // See viskab vea
} catch (e) {
console.error(e.message);
// Väljund: Cannot perform 'get' on a proxy that has been revoked
}
Proxy'd vs. teised metaprogrammeerimise tehnikad
Enne proxy'de tulekut kasutasid arendajad sarnaste eesmärkide saavutamiseks teisi meetodeid. On kasulik mõista, kuidas proxy'd nendega võrreldes toimivad.
`Object.defineProperty()`
Object.defineProperty()
muudab objekti otse, defineerides getterid ja setterid konkreetsete omaduste jaoks. Proxy'd seevastu ei muuda algset objekti üldse; nad mähivad selle.
- Ulatus: `defineProperty` töötab omadusepõhiselt. Peate defineerima getteri/setteri iga omaduse jaoks, mida soovite jälgida. Proxy
get
jaset
püünised on globaalsed, püüdes kinni operatsioone mis tahes omadusega, sealhulgas hiljem lisatud uutega. - Võimekused: Proxy'd suudavad pealt kuulata laiemat valikut operatsioone, nagu
deleteProperty
,in
operaator ja funktsioonikutsed, mida `defineProperty` teha ei suuda.
Kokkuvõte: Virtualiseerimise jõud
JavaScripti Proxy API on enamat kui lihtsalt nutikas funktsioon; see on fundamentaalne nihe selles, kuidas me saame objekte disainida ja nendega suhelda. Võimaldades meil pealt kuulata ja kohandada põhilisi operatsioone, avavad proxy'd ukse võimsate mustrite maailma: alates sujuvast andmete valideerimisest ja teisendamisest kuni reaktiivsete süsteemideni, mis on kaasaegsete kasutajaliideste jõuallikaks.
Kuigi nendega kaasneb väike jõudluskulu ja hulk reegleid, mida tuleb järgida, on nende võime luua puhtaid, lahtisidestatud ja võimsaid abstraktsioone võrratu. Objekte virtualiseerides saate ehitada süsteeme, mis on vastupidavamad, hooldatavamad ja väljendusrikkamad. Järgmine kord, kui seisate silmitsi keerulise väljakutsega, mis hõlmab andmehaldust, valideerimist või jälgitavust, kaaluge, kas proxy on selle töö jaoks õige tööriist. See võib olla just kõige elegantsem lahendus teie tööriistakastis.