Multiple application scenarios of the Proxy pattern
The proxy pattern is a structural design pattern that controls access to another object by creating a proxy object. It has various application scenarios in JavaScript, playing an important role in performance optimization, security control, and feature enhancement.
Lazy Initialization (Virtual Proxy)
Virtual proxies are commonly used to delay the initialization of expensive objects until they are actually needed. This is particularly useful for resource-intensive operations.
class HeavyResource {
constructor() {
console.log('Creating heavy resource...');
// Simulate time-consuming operation
this.data = Array(1000000).fill('data');
}
process() {
console.log('Processing heavy resource');
}
}
class HeavyResourceProxy {
constructor() {
this.resource = null;
}
process() {
if (!this.resource) {
this.resource = new HeavyResource();
}
this.resource.process();
}
}
// Usage
const proxy = new HeavyResourceProxy();
// The real object hasn't been created yet
proxy.process(); // The real object is created on first call
Access Control (Protection Proxy)
Protection proxies are used to control access to sensitive objects, often for permission validation scenarios.
class SensitiveData {
getData() {
return 'Sensitive data';
}
}
class DataAccessProxy {
constructor(user) {
this.user = user;
this.sensitiveData = new SensitiveData();
}
getData() {
if (this.user.role === 'admin') {
return this.sensitiveData.getData();
}
throw new Error('Unauthorized access to sensitive data');
}
}
// Example usage
const admin = { role: 'admin' };
const user = { role: 'guest' };
const adminProxy = new DataAccessProxy(admin);
console.log(adminProxy.getData()); // Output: Sensitive data
const userProxy = new DataAccessProxy(user);
try {
console.log(userProxy.getData()); // Throws error
} catch (e) {
console.error(e.message);
}
Caching Proxy
Caching proxies can store the results of expensive operations to avoid repeated calculations.
class ExpensiveCalculation {
compute(input) {
console.log('Performing expensive calculation...');
// Simulate time-consuming computation
return input * input;
}
}
class CachingProxy {
constructor() {
this.cache = new Map();
this.calculator = new ExpensiveCalculation();
}
compute(input) {
if (this.cache.has(input)) {
console.log('Retrieving result from cache');
return this.cache.get(input);
}
const result = this.calculator.compute(input);
this.cache.set(input, result);
return result;
}
}
// Example usage
const proxy = new CachingProxy();
console.log(proxy.compute(5)); // Performs calculation
console.log(proxy.compute(5)); // Retrieves from cache
Remote Proxy
Remote proxies provide a local representative for remote objects, often used for API call encapsulation.
class RemoteAPI {
async fetchData(endpoint) {
console.log(`Fetching data from ${endpoint}...`);
const response = await fetch(endpoint);
return response.json();
}
}
class APIProxy {
constructor() {
this.cache = new Map();
this.api = new RemoteAPI();
}
async get(endpoint) {
if (this.cache.has(endpoint)) {
console.log('Retrieving API data from cache');
return this.cache.get(endpoint);
}
const data = await this.api.fetchData(endpoint);
this.cache.set(endpoint, data);
return data;
}
}
// Example usage
const apiProxy = new APIProxy();
apiProxy.get('https://api.example.com/data')
.then(data => console.log(data));
Logging Proxy
Logging proxies can add logging functionality without modifying the original object.
class Database {
query(sql) {
// Actual database query
return `Query result: ${sql}`;
}
}
class LoggingProxy {
constructor(database) {
this.database = database;
}
query(sql) {
console.log(`[${new Date().toISOString()}] Executing query: ${sql}`);
const result = this.database.query(sql);
console.log(`[${new Date().toISOString()}] Query completed`);
return result;
}
}
// Example usage
const db = new Database();
const loggedDb = new LoggingProxy(db);
console.log(loggedDb.query('SELECT * FROM users'));
Validation Proxy
Validation proxies can perform parameter validation before method invocation.
class UserService {
updateProfile(userId, data) {
// Actual update logic
return `Profile updated for user ${userId}`;
}
}
class ValidationProxy {
constructor(service) {
this.service = service;
}
updateProfile(userId, data) {
if (!userId) throw new Error('User ID cannot be empty');
if (!data || typeof data !== 'object') {
throw new Error('Data must be an object');
}
return this.service.updateProfile(userId, data);
}
}
// Example usage
const service = new UserService();
const validatedService = new ValidationProxy(service);
try {
console.log(validatedService.updateProfile(123, { name: 'John' }));
validatedService.updateProfile(null, {}); // Throws error
} catch (e) {
console.error(e.message);
}
DOM Event Proxy
In frontend development, event delegation is a classic application of the proxy pattern.
class EventProxy {
constructor(element) {
this.element = element;
this.handlers = new Map();
}
addEventListener(type, handler) {
if (!this.handlers.has(type)) {
this.handlers.set(type, new Set());
this.element.addEventListener(type, (e) => {
this.handlers.get(type).forEach(h => h(e));
});
}
this.handlers.get(type).add(handler);
}
removeEventListener(type, handler) {
if (this.handlers.has(type)) {
this.handlers.get(type).delete(handler);
}
}
}
// Example usage
const button = document.querySelector('button');
const proxy = new EventProxy(button);
const handler1 = () => console.log('Handler 1');
const handler2 = () => console.log('Handler 2');
proxy.addEventListener('click', handler1);
proxy.addEventListener('click', handler2);
// Clicking the button triggers both handlers
// Remove one handler
proxy.removeEventListener('click', handler1);
Lazy Loading Image Proxy
Lazy loading of images is a common frontend performance optimization technique that can be implemented using the proxy pattern.
class LazyImage {
constructor(placeholderSrc, realSrc) {
this.placeholder = placeholderSrc;
this.realSrc = realSrc;
this.image = new Image();
this.loaded = false;
}
load() {
if (!this.loaded) {
this.image.src = this.realSrc;
this.loaded = true;
}
}
render(element) {
const img = document.createElement('img');
img.src = this.placeholder;
img.dataset.realSrc = this.realSrc;
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.load();
img.src = this.realSrc;
observer.unobserve(img);
}
});
});
observer.observe(img);
element.appendChild(img);
}
}
// Example usage
const container = document.getElementById('image-container');
const lazyImage = new LazyImage('placeholder.jpg', 'large-image.jpg');
lazyImage.render(container);
Function Throttling/Debouncing Proxy
The proxy pattern can be used to implement throttling and debouncing control for functions.
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
fn.apply(this, args);
lastTime = now;
}
};
}
// Original function
function handleScroll() {
console.log('Handling scroll event');
}
// Create debounced proxy
const debouncedScroll = debounce(handleScroll, 200);
// Create throttled proxy
const throttledScroll = throttle(handleScroll, 200);
// Example usage
window.addEventListener('scroll', debouncedScroll);
// Or
window.addEventListener('scroll', throttledScroll);
Data Format Conversion Proxy
Proxies can perform format conversion during data transfer.
class JSONAPI {
getData() {
return '{"name":"John","age":30}';
}
}
class JSONToObjectProxy {
constructor(api) {
this.api = api;
}
getData() {
const json = this.api.getData();
return JSON.parse(json);
}
}
// Example usage
const api = new JSONAPI();
const proxy = new JSONToObjectProxy(api);
console.log(proxy.getData()); // Outputs JavaScript object
Multilingual Proxy
The proxy pattern can be used to implement multilingual support.
class Translator {
constructor(language) {
this.language = language;
this.translations = {
en: { hello: 'Hello', goodbye: 'Goodbye' },
zh: { hello: '你好', goodbye: '再见' },
fr: { hello: 'Bonjour', goodbye: 'Au revoir' }
};
}
translate(key) {
return this.translations[this.language][key] || key;
}
}
class TranslationProxy {
constructor(defaultLanguage = 'en') {
this.translator = new Translator(defaultLanguage);
}
setLanguage(language) {
this.translator.language = language;
}
t(key) {
return this.translator.translate(key);
}
}
// Example usage
const i18n = new TranslationProxy('zh');
console.log(i18n.t('hello')); // Output: 你好
i18n.setLanguage('fr');
console.log(i18n.t('hello')); // Output: Bonjour
State Management Proxy
In frontend state management, the proxy pattern can help implement controlled access to state.
class Store {
constructor() {
this.state = { count: 0 };
this.subscribers = [];
}
getState() {
return this.state;
}
dispatch(action) {
switch (action.type) {
case 'INCREMENT':
this.state.count += 1;
break;
case 'DECREMENT':
this.state.count -= 1;
break;
}
this.notify();
}
subscribe(callback) {
this.subscribers.push(callback);
}
notify() {
this.subscribers.forEach(cb => cb(this.state));
}
}
class StoreProxy {
constructor() {
this.store = new Store();
}
getState() {
return { ...this.store.getState() }; // Return copy to prevent direct modification
}
dispatch(action) {
console.log('Dispatching action:', action);
const result = this.store.dispatch(action);
console.log('New state:', this.getState());
return result;
}
subscribe(callback) {
return this.store.subscribe(callback);
}
}
// Example usage
const store = new StoreProxy();
store.subscribe(state => console.log('State updated:', state));
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'DECREMENT' });
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn