什么是代理模式?
代理模式(Proxy Pattern)是一种结构型设计模式,它通过创建代理对象来控制对原始对象的访问。
这种模式在前端开发中广泛应用,特别是在需要控制对象访问、添加额外逻辑或优化性能的场景中。
核心思想:在客户端代码和真实对象之间添加中间层,这个中间层(代理)可以:
- 控制对原始对象的访问权限
- 添加预处理/后处理逻辑
- 实现延迟加载
- 缓存昂贵操作的结果
- 记录日志或监控行为
代理模式实现方式
1. 虚拟代理(延迟加载)
javascript
// 原始图片加载器
class ImageLoader {
constructor(url) {
this.url = url;
}
load() {
console.log(`Loading image from ${this.url}`);
return `<img src="${this.url}" />`;
}
}
// 虚拟代理 - 延迟加载
class ImageProxy {
constructor(url) {
this.url = url;
this.imageLoader = null; // 延迟初始化
}
load() {
if (!this.imageLoader) {
this.imageLoader = new ImageLoader(this.url);
// 添加占位逻辑
const placeholder = document.createElement('div');
placeholder.style.width = '300px';
placeholder.style.height = '200px';
placeholder.style.background = '#eee';
document.body.appendChild(placeholder);
// 延迟实际加载
setTimeout(() => {
document.body.removeChild(placeholder);
document.body.innerHTML += this.imageLoader.load();
}, 2000);
}
return 'Image loading initiated...';
}
}
// 使用代理
const image = new ImageProxy('https://example.com/large-image.jpg');
console.log(image.load()); // 立即返回占位符,2秒后加载真实图片
2. 缓存代理(记忆函数)
javascript
// 原始计算函数
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 缓存代理
function createCachedProxy(fn) {
const cache = new Map();
return function(n) {
if (cache.has(n)) {
console.log(`Cache hit for n=${n}`);
return cache.get(n);
}
const result = fn(n);
cache.set(n, result);
console.log(`Calculated for n=${n}`);
return result;
};
}
// 使用代理
const cachedFib = createCachedProxy(fibonacci);
console.log(cachedFib(35)); // 长时间计算
console.log(cachedFib(35)); // 立即返回缓存结果
3. 保护代理(访问控制)
javascript
// 原始用户服务
class UserService {
constructor() {
this.users = new Map([[1, { id: 1, name: 'Admin', role: 'admin' }]]);
}
deleteUser(id) {
this.users.delete(id);
return `User ${id} deleted`;
}
}
// 保护代理
class UserServiceProxy {
constructor(user) {
this.userService = new UserService();
this.currentUser = user;
}
deleteUser(id) {
if (this.currentUser.role !== 'admin') {
throw new Error('Permission denied');
}
return this.userService.deleteUser(id);
}
}
// 使用代理
const adminProxy = new UserServiceProxy({ role: 'admin' });
console.log(adminProxy.deleteUser(1)); // 成功
const userProxy = new UserServiceProxy({ role: 'user' });
userProxy.deleteUser(1); // 抛出权限错误
4. ES6 Proxy实现
javascript
// 原始对象
const database = {
users: {
1: { name: 'Alice', email: '[email protected]' }
},
getEmail: function(userId) {
return this.users[userId]?.email;
}
};
// 创建代理
const protectedDatabase = new Proxy(database, {
get(target, prop) {
// 权限验证
if (prop === 'users') {
throw new Error('Direct access to users denied');
}
return target[prop];
},
set(target, prop, value) {
// 写操作限制
if (prop === 'users') {
throw new Error('User modification requires admin privileges');
}
target[prop] = value;
return true;
}
});
console.log(protectedDatabase.getEmail(1)); // 正常访问
protectedDatabase.users = {}; // 抛出错误
console.log(protectedDatabase.users); // 抛出错误
代理模式优缺点分析
优点:
- 访问控制:实现精细的权限管理
javascript
// API请求代理示例
class ApiProxy {
constructor(api) {
this.api = api;
}
async request(endpoint) {
if (isRateLimited(endpoint)) {
throw new Error('API rate limit exceeded');
}
trackRequest(endpoint);
return this.api.request(endpoint);
}
}
- 性能优化:通过缓存和延迟加载提升性能
javascript
// 图片懒加载代理
const lazyImage = new Proxy(new Image(), {
set(target, prop, value) {
if (prop === 'src') {
// 延迟到元素可见时加载
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
target.src = value;
observer.unobserve(target);
}
});
});
observer.observe(target);
return true;
}
return Reflect.set(...arguments);
}
});
document.body.appendChild(lazyImage);
lazyImage.src = 'https://example.com/large-image.jpg'; // 实际加载延迟到图片可见时
- 职责分离:保持核心逻辑的纯净性
javascript
// 原始表单验证
class FormValidator {
validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
}
// 验证代理添加日志
class LoggingValidatorProxy {
constructor(validator) {
this.validator = validator;
}
validateEmail(email) {
const result = this.validator.validateEmail(email);
console.log(`Email validation result for ${email}: ${result}`);
return result;
}
}
缺点:
- 复杂性增加:可能引入额外抽象层
javascript
// 过度设计的代理示例(不推荐)
class OverEngineeredProxy {
constructor(service) {
this.service = service;
this.logger = new Logger();
this.cache = new Cache();
this.validator = new Validator();
}
async getData() {
this.logger.logStart();
if (!this.validator.validate()) {
throw new Error('Validation failed');
}
const data = await this.cache.get('data') || this.service.getData();
this.logger.logEnd();
return data;
}
}
- 性能损耗:额外的代理调用开销
javascript
// 性能敏感的原始类
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
add(other) {
return new Vector(this.x + other.x, this.y + other.y);
}
}
// 添加日志代理可能影响性能
const loggedVector = new Proxy(new Vector(1,2), {
get(target, prop) {
if (typeof target[prop] === 'function') {
return function(...args) {
console.log(`Calling ${prop} with`, args);
return target[prop].apply(target, args);
};
}
return target[prop];
}
});
// 在需要高性能计算的场景中,这种代理会产生明显开销
- 调试困难:调用堆栈变深
javascript
// 多层代理导致的调试问题
const original = {
method() { console.log('Original method'); }
};
const proxy1 = new Proxy(original, {
get(target, prop) {
console.log('Proxy1 handler');
return target[prop];
}
});
const proxy2 = new Proxy(proxy1, {
get(target, prop) {
console.log('Proxy2 handler');
return target[prop];
}
});
proxy2.method(); // 调用链:proxy2 -> proxy1 -> original
工程实践建议
1. 表单验证代理
javascript
// 原始表单对象
const form = {
values: {},
errors: {},
submit() {
console.log('Submitting:', this.values);
}
};
// 验证代理
const validatedForm = new Proxy(form, {
set(target, prop, value) {
if (prop === 'values') {
// 自动触发验证
target.errors = validateForm(value);
if (Object.keys(target.errors).length === 0) {
target.submit();
}
}
return Reflect.set(...arguments);
}
});
function validateForm(values) {
const errors = {};
if (!values.email?.includes('@')) errors.email = 'Invalid email';
if (values.password?.length < 6) errors.password = 'Password too short';
return errors;
}
// 使用
validatedForm.values = {
email: '[email protected]',
password: '12345'
}; // 自动触发验证并显示错误
2. API请求代理
javascript
// 请求代理工厂
function createApiProxy(api, config = {}) {
return new Proxy(api, {
get(target, prop) {
const originalMethod = target[prop];
if (typeof originalMethod !== 'function') return originalMethod;
return async function(...args) {
// 请求拦截
if (config.beforeRequest) {
args = config.beforeRequest(...args) || args;
}
try {
const response = await originalMethod.apply(target, args);
// 响应拦截
return config.afterResponse
? config.afterResponse(response)
: response;
} catch (error) {
// 错误处理
if (config.errorHandler) {
return config.errorHandler(error);
}
throw error;
}
};
}
});
}
// 使用示例
const rawApi = {
async getUser(id) {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
};
const enhancedApi = createApiProxy(rawApi, {
beforeRequest: (id) => {
console.log(`Requesting user ${id}`);
return [id]; // 可以修改参数
},
afterResponse: (data) => {
console.log('Received response');
return { ...data, fullName: `${data.firstName} ${data.lastName}` };
},
errorHandler: (error) => {
console.error('API Error:', error);
return { error: true, message: error.message };
}
});
// 调用方式保持一致
enhancedApi.getUser(123).then(console.log);
注意事项
- 接口一致性:代理必须保持与原对象相同的接口
javascript
// 错误的代理实现(接口不一致)
class BadProxy {
constructor(file) {
this.file = file;
}
// 遗漏了原始对象的save方法
read() {
return this.file.read();
}
}
- 避免深层代理嵌套
javascript
// 不推荐的深层嵌套
const proxy1 = new Proxy(obj, handler1);
const proxy2 = new Proxy(proxy1, handler2);
const proxy3 = new Proxy(proxy2, handler3);
// 应尽量保持代理层级扁平
- 注意内存管理
javascript
// 代理导致的闭包内存泄漏
function createLeakyProxy() {
const hugeData = new Array(1000000).fill('data');
return new Proxy({}, {
get(target, prop) {
// 无意中持有hugeData的引用
return hugeData[prop];
}
});
}
- 性能关键路径慎用
javascript
// 在动画循环中避免使用复杂代理
function animate() {
// 直接访问对象属性
element.x += velocity.x;
element.y += velocity.y;
// 而不是:
// proxiedElement.x += velocity.x;
requestAnimationFrame(animate);
}
- 与装饰器模式区分
javascript
// 装饰器模式(增强功能)
function withLogging(fn) {
return function(...args) {
console.log('Calling function');
return fn(...args);
};
}
// 代理模式(控制访问)
const proxiedFn = new Proxy(fn, {
apply(target, thisArg, args) {
if (!validate(args)) throw new Error('Invalid arguments');
return Reflect.apply(target, thisArg, args);
}
});
代理模式是前端架构中的重要模式,适用于:
- 需要访问控制的场景(权限验证、流量控制)
- 性能优化需求(缓存、延迟加载)
- 增强监控能力(日志记录、性能跟踪)
- 实现智能引用(自动清理、加载)
在实际工程中建议:
- 优先使用ES6 Proxy实现简单代理逻辑
- 对性能敏感模块谨慎使用
- 保持代理接口与原始对象一致
- 使用TypeScript增强类型安全
- 配合工厂模式创建复杂代理
正确使用代理模式可以提升代码的可维护性和扩展性,但需要警惕模式滥用带来的复杂性。
建议结合具体需求场景,在代码清晰度和功能需求之间找到平衡点。