Metaprogramación en JavaScript: API Reflect y Patrón Observador con Proxy

La API Reflect fue introducida en ECMAScript 2015 (ES6) para proporcionar métodos estáticos que permiten interceptar y manipular operaciones de objetos en JavaScript. A diferencia de otros objetos globales, Reflect no es un constructor ni tiene una instancia; actúa como un contenedor de funciones que reflejan las operaciones internas de los objetos. Además, sus métodos están diseñados para alinearse perfectamente con las trampas (traps) del objeto Proxy.

Lectura de Propiedades con Reflect.get

El método Reflect.get permite leer propiedades de un objeto. Su principal ventaja sobre la notación de punto o corchetes es la capacidad de especificar un receiver personalizado, lo cual es crucial cuando se trabaja con getters que dependen del contexto this.

const userProfile = {
    firstName: "Alice",
    lastName: "Smith",
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    }
};

console.log(Reflect.get(userProfile, 'firstName')); // "Alice"

const contextOverride = {
    firstName: "Bob",
    lastName: "Jones"
};

// El contexto 'this' dentro del getter se enlaza a contextOverride
console.log(Reflect.get(userProfile, 'fullName', contextOverride)); // "Bob Jones"

Escritura de Propiedades con Reflect.set

Para asignar valores, Reflect.set devuelve un valor booleano que indica si la operación fue exitosa. Al igual que con get, permite redefinir el contexto this mediante el parámetro receiver cuando se invoca un setter.

const inventory = {
    item: "Laptop",
    stock: 50,
    set updateStock(quantity) {
        this.stock = quantity;
    }
};

console.log(Reflect.set(inventory, 'stock', 45)); // true
console.log(inventory.stock); // 45

const alternateContext = { stock: 0 };
// El setter se ejecuta en el contexto de alternateContext
Reflect.set(inventory, 'updateStock', 10, alternateContext);
console.log(alternateContext.stock); // 10

Verificación de Existencia con Reflect.has

Este método funciona como el equivalente funcional del operador in, devolviendo true si la propiedad existe en el objeto o en su cadena de prototipos.

const config = { theme: 'dark', lang: 'es' };
console.log(Reflect.has(config, 'theme')); // true
console.log(Reflect.has(config, 'version')); // false

Integración de Reflect y Proxy

Los métodos de Reflect mapean directamente las trampas de Proxy. Esto permite invocar el comportamiento predeterminado del objeto dantro del manejador del proxy, facilitando la adición de lógica personalizada sin romper la funcionalidad nativa.

const targetObj = { status: 'active', level: 1 };

const handler = {
    get(target, prop, receiver) {
        console.log(`Leyendo la propiedad: ${prop}`);
        return Reflect.get(target, prop, receiver);
    },
    set(target, prop, value, receiver) {
        console.log(`Modificando ${prop} a ${value}`);
        return Reflect.set(target, prop, value, receiver);
    }
};

const monitoredObj = new Proxy(targetObj, handler);
monitoredObj.level = 2; 
console.log(monitoredObj.status);

Implementación del Patrón Observador

Combinando Proxy y Reflect, es posible implementar el patrón de diseño Observador de manera reactiva. Al interceptar las operaciones de escritura, podemos notificar automáticamente a los suscriptores cuando el estado del objeto cambia.

const listeners = new Set();

const subscribe = (callback) => listeners.add(callback);

const createObservable = (target) => {
    return new Proxy(target, {
        set(obj, prop, value, receiver) {
            const success = Reflect.set(obj, prop, value, receiver);
            if (success) {
                listeners.forEach(listener => listener(obj, prop, value));
            }
            return success;
        }
    });
};

const appState = createObservable({
    user: 'Guest',
    isLoggedIn: false
});

const logger = (state, key, val) => {
    console.log(`Estado actualizado: ${key} = ${val}`);
};

subscribe(logger);

// La modificación dispara automáticamente la función logger
appState.isLoggedIn = true; 
appState.user = 'Admin';

Etiquetas: JavaScript ES6 Reflect proxy metaprogramming

Publicado el 7-7 02:21