Explora patrones avanzados de Proxy en JavaScript para la intercepci贸n, validaci贸n y comportamiento din谩mico de objetos. Mejora la calidad, seguridad y mantenibilidad del c贸digo con ejemplos pr谩cticos.
Patrones de Proxy en JavaScript: Intercepci贸n y Validaci贸n de Objetos Avanzada
El objeto Proxy de JavaScript es una caracter铆stica poderosa que permite interceptar y personalizar las operaciones fundamentales de los objetos. Habilita t茅cnicas avanzadas de metaprogramaci贸n, ofreciendo un mayor control sobre el comportamiento de los objetos y abriendo posibilidades para patrones de dise帽o sofisticados. Este art铆culo explora varios patrones de Proxy, mostrando sus casos de uso en validaci贸n, intercepci贸n y modificaci贸n de comportamiento din谩mico. Profundizaremos en ejemplos pr谩cticos para demostrar c贸mo los Proxies pueden mejorar la calidad del c贸digo, la seguridad y la mantenibilidad en tus proyectos de JavaScript.
Entendiendo el Proxy de JavaScript
En esencia, un objeto Proxy encapsula otro objeto (el objetivo) e intercepta las operaciones realizadas en ese objetivo. Estas intercepciones se manejan mediante trampas, que son m茅todos que definen el comportamiento personalizado para operaciones espec铆ficas como obtener una propiedad, establecer una propiedad o llamar a una funci贸n. La API de Proxy proporciona un mecanismo flexible y extensible para modificar el comportamiento predeterminado de los objetos.
Conceptos Clave
- Objetivo: El objeto original que el Proxy encapsula.
- Manejador: Un objeto que contiene los m茅todos de trampa. Cada trampa corresponde a una operaci贸n espec铆fica.
- Trampas: M茅todos dentro del manejador que interceptan y personalizan las operaciones del objeto. Las trampas comunes incluyen
get,set,applyyconstruct.
Creando un Proxy
Para crear un Proxy, usas el constructor Proxy, pasando el objeto objetivo y el objeto manejador como argumentos:
const target = {};
const handler = {
get: function(target, property, receiver) {
console.log(`Obteniendo propiedad: ${property}`);
return Reflect.get(target, property, receiver);
}
};
const proxy = new Proxy(target, handler);
proxy.name = "John"; // Registra: Obteniendo propiedad: name
console.log(proxy.name); // Registra: Obteniendo propiedad: name, luego John
Trampas Comunes de Proxy
Los Proxies ofrecen una gama de trampas para interceptar diversas operaciones. Aqu铆 est谩n algunas de las trampas m谩s com煤nmente utilizadas:
get(target, property, receiver): Intercepta el acceso a la propiedad.set(target, property, value, receiver): Intercepta la asignaci贸n de la propiedad.has(target, property): Intercepta el operadorin.deleteProperty(target, property): Intercepta el operadordelete.apply(target, thisArg, argumentsList): Intercepta las llamadas a funciones.construct(target, argumentsList, newTarget): Intercepta el operadornew.getPrototypeOf(target): Intercepta el m茅todoObject.getPrototypeOf().setPrototypeOf(target, prototype): Intercepta el m茅todoObject.setPrototypeOf().isExtensible(target): Intercepta el m茅todoObject.isExtensible().preventExtensions(target): Intercepta el m茅todoObject.preventExtensions().getOwnPropertyDescriptor(target, property): Intercepta el m茅todoObject.getOwnPropertyDescriptor().defineProperty(target, property, descriptor): Intercepta el m茅todoObject.defineProperty().ownKeys(target): Intercepta los m茅todosObject.getOwnPropertyNames()yObject.getOwnPropertySymbols().
Patrones de Proxy
Ahora, exploremos algunos patrones de Proxy pr谩cticos y sus aplicaciones:
1. Proxy de Validaci贸n
Un Proxy de Validaci贸n impone restricciones a las asignaciones de propiedades. Intercepta la trampa set para validar el nuevo valor antes de permitir que la asignaci贸n contin煤e.
Ejemplo: Validar la entrada del usuario en un formulario.
const user = {};
const validator = {
set: function(target, property, value) {
if (property === 'age') {
if (!Number.isInteger(value) || value < 0 || value > 120) {
throw new Error('Edad inv谩lida. La edad debe ser un entero entre 0 y 120.');
}
}
target[property] = value;
return true; // Indica 茅xito
}
};
const proxy = new Proxy(user, validator);
proxy.name = 'Alice';
proxy.age = 30;
console.log(user);
try {
proxy.age = 'inv谩lido'; // Lanza un error
} catch (error) {
console.error(error.message);
}
En este ejemplo, la trampa set verifica si la propiedad age es un entero entre 0 y 120. Si la validaci贸n falla, se lanza un error, impidiendo que se asigne el valor inv谩lido.
Ejemplo Global: Este patr贸n de validaci贸n es esencial para garantizar la integridad de los datos en aplicaciones globales donde la entrada del usuario puede provenir de diversas fuentes y culturas. Por ejemplo, la validaci贸n de c贸digos postales puede variar significativamente entre pa铆ses. Un proxy de validaci贸n puede adaptarse para admitir diferentes reglas de validaci贸n seg煤n la ubicaci贸n del usuario.
const address = {};
const addressValidator = {
set: function(target, property, value) {
if (property === 'postalCode') {
// Ejemplo: Asumiendo una validaci贸n simple de c贸digo postal de EE. UU.
if (!/^[0-9]{5}(?:-[0-9]{4})?$/.test(value)) {
throw new Error('C贸digo postal de EE. UU. inv谩lido.');
}
}
target[property] = value;
return true;
}
};
const addressProxy = new Proxy(address, addressValidator);
addressProxy.postalCode = "12345-6789"; // V谩lido
try {
addressProxy.postalCode = "abcde"; // Inv谩lido
} catch(e) {
console.log(e);
}
// Para una aplicaci贸n m谩s internacional, usar铆as una biblioteca de validaci贸n m谩s sofisticada
// que podr铆a validar c贸digos postales seg煤n el pa铆s del usuario.
2. Proxy de Registro
Un Proxy de Registro intercepta el acceso y la asignaci贸n de propiedades para registrar estas operaciones. Es 煤til para depuraci贸n y auditor铆a.
Ejemplo: Registrar el acceso y la modificaci贸n de propiedades.
const data = {
value: 10
};
const logger = {
get: function(target, property) {
console.log(`Obteniendo propiedad: ${property}`);
return target[property];
},
set: function(target, property, value) {
console.log(`Estableciendo propiedad: ${property} a ${value}`);
target[property] = value;
return true;
}
};
const proxy = new Proxy(data, logger);
console.log(proxy.value); // Registra: Obteniendo propiedad: value, luego 10
proxy.value = 20; // Registra: Estableciendo propiedad: value a 20
Las trampas get y set registran la propiedad a la que se accede o se modifica, proporcionando un rastro de las interacciones del objeto.
Ejemplo Global: En una corporaci贸n multinacional, los proxies de registro se pueden usar para auditar el acceso y las modificaciones de datos realizadas por empleados en diferentes ubicaciones. Esto es crucial para fines de cumplimiento y seguridad. Las zonas horarias pueden necesitar ser consideradas en la informaci贸n de registro.
const employeeData = {
name: "John Doe",
salary: 50000
};
const auditLogger = {
get: function(target, property) {
const timestamp = new Date().toISOString();
console.log(`${timestamp} - [GET] Accediendo a la propiedad: ${property}`);
return target[property];
},
set: function(target, property, value) {
const timestamp = new Date().toISOString();
console.log(`${timestamp} - [SET] Estableciendo la propiedad: ${property} a ${value}`);
target[property] = value;
return true;
}
};
const proxiedEmployee = new Proxy(employeeData, auditLogger);
proxiedEmployee.name; // Registra la marca de tiempo y el acceso a 'name'
proxiedEmployee.salary = 60000; // Registra la marca de tiempo y la modificaci贸n de 'salary'
3. Proxy de Solo Lectura
Un Proxy de Solo Lectura impide la asignaci贸n de propiedades. Intercepta la trampa set y lanza un error si se intenta modificar una propiedad.
Ejemplo: Hacer un objeto inmutable.
const config = {
apiUrl: 'https://api.example.com'
};
const readOnly = {
set: function(target, property, value) {
throw new Error(`No se puede establecer la propiedad: ${property}. El objeto es de solo lectura.`);
}
};
const proxy = new Proxy(config, readOnly);
console.log(proxy.apiUrl);
try {
proxy.apiUrl = 'https://newapi.example.com'; // Lanza un error
} catch (error) {
console.error(error.message);
}
Cualquier intento de establecer una propiedad en el proxy resultar谩 en un error, asegurando que el objeto permanezca inmutable.
Ejemplo Global: Este patr贸n es 煤til para proteger archivos de configuraci贸n que no deben modificarse en tiempo de ejecuci贸n, especialmente en aplicaciones distribuidas globalmente. Modificar accidentalmente la configuraci贸n en una regi贸n puede afectar a todo el sistema.
const globalSettings = {
defaultLanguage: "en",
currency: "USD",
timeZone: "UTC"
};
const immutableHandler = {
set: function(target, property, value) {
throw new Error(`No se puede modificar la propiedad de solo lectura: ${property}`);
}
};
const immutableSettings = new Proxy(globalSettings, immutableHandler);
console.log(immutableSettings.defaultLanguage); // muestra 'en'
// Intentar cambiar un valor lanzar谩 un error
// immutableSettings.defaultLanguage = "fr"; // lanza Error: No se puede modificar la propiedad de solo lectura: defaultLanguage
4. Proxy Virtual
Un Proxy Virtual controla el acceso a un recurso que puede ser costoso de crear o recuperar. Puede retrasar la creaci贸n del recurso hasta que realmente se necesite.
Ejemplo: Carga perezosa de una imagen.
const image = {
display: function() {
console.log('Mostrando imagen');
}
};
const virtualProxy = {
get: function(target, property) {
if (property === 'display') {
console.log('Creando imagen...');
const realImage = {
display: function() {
console.log('Mostrando imagen real');
}
};
target.display = realImage.display;
return realImage.display;
}
return target[property];
}
};
const proxy = new Proxy(image, virtualProxy);
// La imagen no se crea hasta que se llama a display.
proxy.display(); // Registra: Creando imagen..., luego Mostrando imagen real
El objeto de imagen real solo se crea cuando se llama al m茅todo display, evitando el consumo innecesario de recursos.
Ejemplo Global: Considere un sitio web de comercio electr贸nico global que sirve im谩genes de productos. Usando un Proxy Virtual, las im谩genes se pueden cargar solo cuando son visibles para el usuario, optimizando el uso del ancho de banda y mejorando los tiempos de carga de la p谩gina, especialmente para los usuarios con conexiones a Internet lentas en diferentes regiones.
const product = {
loadImage: function() {
console.log("Cargando imagen de alta resoluci贸n...");
// Simula la carga de una imagen grande
setTimeout(() => {
console.log("Imagen cargada");
this.displayImage();
}, 2000);
},
displayImage: function() {
console.log("Mostrando la imagen");
}
};
const lazyLoadProxy = {
get: function(target, property) {
if (property === "displayImage") {
// En lugar de cargar inmediatamente, retrasa la carga
console.log("Solicitud para mostrar la imagen recibida. Cargando...");
target.loadImage();
return function() { /* Intencionalmente vac铆o */ }; // Retorna una funci贸n vac铆a para evitar la ejecuci贸n inmediata
}
return target[property];
}
};
const proxiedProduct = new Proxy(product, lazyLoadProxy);
// Llamar a displayImage desencadena el proceso de carga perezosa
proxiedProduct.displayImage();
5. Proxy Revocable
Un Proxy Revocable te permite revocar el proxy en cualquier momento, haci茅ndolo inutilizable. Esto es 煤til para escenarios sensibles a la seguridad donde necesitas controlar el acceso a un objeto.
Ejemplo: Otorgar acceso temporal a un recurso.
const target = {
secret: 'Este es un secreto'
};
const handler = {
get: function(target, property) {
console.log('Accediendo a la propiedad secreta');
return target[property];
}
};
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.secret); // Registra: Accediendo a la propiedad secreta, luego Este es un secreto
revoke();
try {
console.log(proxy.secret); // Lanza un TypeError
} catch (error) {
console.error(error.message); // Registra: No se puede realizar 'get' en un proxy que ha sido revocado
}
El m茅todo Proxy.revocable() crea un proxy revocable. Llamar a la funci贸n revoke() hace que el proxy sea inutilizable, impidiendo el acceso posterior al objeto objetivo.
Ejemplo Global: En un sistema distribuido globalmente, podr铆as usar un proxy revocable para otorgar acceso temporal a datos confidenciales a un servicio que se ejecuta en una regi贸n espec铆fica. Despu茅s de un cierto tiempo, el proxy se puede revocar para evitar el acceso no autorizado.
const sensitiveData = {
apiKey: "CLAVE_SUPER_SECRETA"
};
const handler = {
get: function(target, property) {
console.log("Accediendo a datos sensibles");
return target[property];
}
};
const { proxy: dataProxy, revoke: revokeAccess } = Proxy.revocable(sensitiveData, handler);
// Permitir acceso durante 5 segundos
setTimeout(() => {
revokeAccess();
console.log("Acceso revocado");
}, 5000);
// Intento de acceder a los datos
console.log(dataProxy.apiKey); // Registra la clave API
// Despu茅s de 5 segundos, esto lanzar谩 un error
setTimeout(() => {
try {
console.log(dataProxy.apiKey); // Lanza: TypeError: No se puede realizar 'get' en un proxy que ha sido revocado
} catch (error) {
console.error(error);
}
}, 6000);
6. Proxy de Conversi贸n de Tipos
Un Proxy de Conversi贸n de Tipos intercepta el acceso a las propiedades para convertir autom谩ticamente el valor devuelto a un tipo espec铆fico. Esto puede ser 煤til para trabajar con datos de diferentes fuentes que pueden tener tipos inconsistentes.
Ejemplo: Convertir valores de cadena a n煤meros.
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); // Registra: 11.99 (n煤mero)
console.log(proxy.quantity * 2); // Registra: 10 (n煤mero)
La trampa get verifica si el valor de la propiedad es una cadena que se puede convertir en un n煤mero. Si es as铆, convierte el valor a un n煤mero antes de devolverlo.
Ejemplo Global: Al tratar con datos provenientes de API con diferentes convenciones de formato (por ejemplo, diferentes formatos de fecha o s铆mbolos de moneda), un Proxy de Conversi贸n de Tipos puede garantizar la consistencia de los datos en toda tu aplicaci贸n, independientemente de la fuente. Por ejemplo, manejar diferentes formatos de fecha y convertirlos todos al formato ISO 8601.
const apiData = {
dateUS: "12/31/2023",
dateEU: "31/12/2023"
};
const dateFormatConverter = {
get: function(target, property) {
let value = target[property];
if (property.startsWith("date")) {
// Intento de convertir los formatos de fecha de EE. UU. y la UE a 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); // Muestra: 2023-12-31
console.log(proxiedApiData.dateEU); // Muestra: 2023-12-31
Mejores Pr谩cticas para Usar Proxies
- Usa Proxies con Criterio: Los Proxies pueden agregar complejidad a tu c贸digo. 脷salos solo cuando proporcionen beneficios significativos, como una mejor validaci贸n, registro o control sobre el comportamiento de los objetos.
- Considera el Rendimiento: Las trampas de Proxy pueden introducir sobrecarga. Perfila tu c贸digo para asegurar que los Proxies no impacten negativamente en el rendimiento, especialmente en secciones cr铆ticas para el rendimiento.
- Maneja los Errores con Gracia: Aseg煤rate de que tus m茅todos de trampa manejen los errores de forma adecuada, proporcionando mensajes de error informativos cuando sea necesario.
- Usa la API Reflect: La API
Reflectproporciona m茅todos que reflejan el comportamiento predeterminado de las operaciones de objetos. Usa los m茅todosReflectdentro de tus m茅todos de trampa para delegar al comportamiento original cuando sea apropiado. Esto asegura que tus trampas no rompan la funcionalidad existente. - Documenta tus Proxies: Documenta claramente el prop贸sito y el comportamiento de tus Proxies, incluyendo las trampas que se usan y las restricciones que se imponen. Esto ayudar谩 a otros desarrolladores a entender y mantener tu c贸digo.
Conclusi贸n
Los Proxies de JavaScript son una herramienta poderosa para la manipulaci贸n e intercepci贸n avanzada de objetos. Al entender y aplicar varios patrones de Proxy, puedes mejorar la calidad del c贸digo, la seguridad y la mantenibilidad. Desde la validaci贸n de la entrada del usuario hasta el control de acceso a recursos sensibles, los Proxies ofrecen un mecanismo flexible y extensible para personalizar el comportamiento de los objetos. A medida que explores las posibilidades de los Proxies, recuerda usarlos con criterio y documentar tu c贸digo a fondo.
Los ejemplos proporcionados demuestran c贸mo usar los Proxies de JavaScript para resolver problemas del mundo real en un contexto global. Al comprender y aplicar estos patrones, puedes crear aplicaciones m谩s robustas, seguras y mantenibles que satisfagan las necesidades de una base de usuarios diversa. Recuerda siempre considerar las implicaciones globales de tu c贸digo y adaptar tus soluciones a los requisitos espec铆ficos de diferentes regiones y culturas.