Proxy și Reflect (Metaprogramare și Sisteme Reactive) în JavaScript

Proxy, Reflect și Symbol ECMAScript 2015+

Cum funcționează reactivitatea în Vue 3: interceptarea citirii și scrierii pe obiecte prin trap-uri get/set în Proxy și delegarea operațiunilor native prin Reflect.

Sintaxă
const proxy = new Proxy(target, { get(t, p) { return Reflect.get(t, p); } });

Obiectul Proxy permite învelirea (wrapping-ul) unui obiect țintă pentru a intercepta și personaliza operațiunile fundamentale efectuate asupra acestuia: citirea de proprietăți (get), scrierea de valori (set), ștergerea (deleteProperty), verificarea existenței (has) sau apelarea ca funcție (apply).

Împreună cu obiectul însoțitor Reflect (care conține metodele native corespunzătoare pentru delegarea acțiunii implicite), Proxy stă la baza celor mai populare sisteme de reactivitate modernă (inclusiv motorul de reactivitate din Vue 3 și MobX).

Construirea unui mini-sistem de Reactivitate și Validare cu Proxy
const dateUtilizator = {
    nume: 'Maria',
    varsta: 25
};

// Creăm un Proxy care validează datele la scriere și declanșează actualizarea interfeței:
const utilizatorReactiv = new Proxy(dateUtilizator, {
    // Interceptăm citirea de proprietăți (get)
    get(target, proprietate, receiver) {
        console.log(`[GET] S-a citit proprietatea '${proprietate}'`);
        return Reflect.get(target, proprietate, receiver);
    },

    // Interceptăm modificarea sau adăugarea de proprietăți (set)
    set(target, proprietate, valoare, receiver) {
        if (proprietate === 'varsta') {
            if (typeof valoare !== 'number' || valoare <= 0) {
                throw new TypeError("Vârsta trebuie să fie un număr pozitiv valid!");
            }
        }
        
        console.log(`[SET] Proprietatea '${proprietate}' s-a schimbat în: ${valoare}`);
        
        // Delegăm scrierea efectivă către Reflect
        const succes = Reflect.set(target, proprietate, valoare, receiver);
        
        // Simulăm declanșarea re-randării interfeței utilizator:
        if (succes) {
            document.title = `Profil: ${target.nume} (${target.varsta} ani)`;
        }
        return succes;
    }
});

// Utilizare:
console.log(utilizatorReactiv.nume); // Afișează: [GET] S-a citit proprietatea 'nume' -> Maria
utilizatorReactiv.varsta = 26;       // Afișează: [SET] Proprietatea 'varsta' s-a schimbat în: 26

// Tentativă de setare invalidă:
// utilizatorReactiv.varsta = -5;    // Aruncă TypeError: Vârsta trebuie să fie un număr pozitiv valid!

Cele Mai Frecvente Trap-uri Proxy

Trap Proxy Operațiune interceptată Echivalent Reflect
get(target, prop, receiver) Citirea valorii: obj.prop Reflect.get(...)
set(target, prop, val, receiver) Scrierea valorii: obj.prop = val Reflect.set(...)
has(target, prop) Operatorul in: 'prop' in obj Reflect.has(...)
deleteProperty(target, prop) Operatorul delete: delete obj.prop Reflect.deleteProperty(...)