代理模式的优缺点是什么?
什么是代理模式?
代理模式(Proxy Pattern)是一种结构型设计模式,它通过创建代理对象来控制对原始对象的访问。
这种模式在前端开发中广泛应用,特别是在需要控制对象访问、添加额外逻辑或优化性能的场景中。
核心思想:在客户端代码和真实对象之间添加中间层,这个中间层(代理)可以:
- 控制对原始对象的访问权限
- 添加预处理/后处理逻辑
- 实现延迟加载
- 缓存昂贵操作的结果
- 记录日志或监控行为
代理模式实现方式
1. 虚拟代理(延迟加载)
// 原始图片加载器
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. 缓存代理(记忆函数)
// 原始计算函数
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. 保护代理(访问控制)
// 原始用户服务
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实现
// 原始对象
const database = {users: {1: { name: 'Alice', email: 'alice@example.com' }},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); // 抛出错误
代理模式优缺点分析
优点:
- 访问控制:实现精细的权限管理
// 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);}
}
- 性能优化:通过缓存和延迟加载提升性能
// 图片懒加载代理
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'; // 实际加载延迟到图片可见时
- 职责分离:保持核心逻辑的纯净性
// 原始表单验证
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;}
}
缺点:
- 复杂性增加:可能引入额外抽象层
// 过度设计的代理示例(不推荐)
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;}
}
- 性能损耗:额外的代理调用开销
// 性能敏感的原始类
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];}
});// 在需要高性能计算的场景中,这种代理会产生明显开销
- 调试困难:调用堆栈变深
// 多层代理导致的调试问题
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. 表单验证代理
// 原始表单对象
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: 'user@example.com', password: '12345'
}; // 自动触发验证并显示错误
2. API请求代理
// 请求代理工厂
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);
注意事项
- 接口一致性:代理必须保持与原对象相同的接口
// 错误的代理实现(接口不一致)
class BadProxy {constructor(file) {this.file = file;}// 遗漏了原始对象的save方法read() {return this.file.read();}
}
- 避免深层代理嵌套
// 不推荐的深层嵌套
const proxy1 = new Proxy(obj, handler1);
const proxy2 = new Proxy(proxy1, handler2);
const proxy3 = new Proxy(proxy2, handler3);
// 应尽量保持代理层级扁平
- 注意内存管理
// 代理导致的闭包内存泄漏
function createLeakyProxy() {const hugeData = new Array(1000000).fill('data');return new Proxy({}, {get(target, prop) {// 无意中持有hugeData的引用return hugeData[prop];}});
}
- 性能关键路径慎用
// 在动画循环中避免使用复杂代理
function animate() {// 直接访问对象属性element.x += velocity.x;element.y += velocity.y;// 而不是:// proxiedElement.x += velocity.x;requestAnimationFrame(animate);
}
- 与装饰器模式区分
// 装饰器模式(增强功能)
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增强类型安全
- 配合工厂模式创建复杂代理
正确使用代理模式可以提升代码的可维护性和扩展性,但需要警惕模式滥用带来的复杂性。
建议结合具体需求场景,在代码清晰度和功能需求之间找到平衡点。
相关文章:
代理模式的优缺点是什么?
什么是代理模式? 代理模式(Proxy Pattern)是一种结构型设计模式,它通过创建代理对象来控制对原始对象的访问。 这种模式在前端开发中广泛应用,特别是在需要控制对象访问、添加额外逻辑或优化性能的场景中。 核心…...
基于LangChain和通义(Tongyi)实现NL2SQL的智能检索(无需训练)
在数据驱动的时代,如何高效地从数据库中获取信息成为了一个重要的挑战。自然语言到SQL(NL2SQL)技术提供了一种便捷的解决方案,使用户能够用自然语言查询数据库,而无需深入了解SQL语法。本文将探讨如何利用LangChain和通义(Tongyi)实现NL2SQL的智能检索,具体步骤如下: …...
Spring Boot 集成 Redis 对哈希数据的详细操作示例,涵盖不同结构类型(基础类型、对象、嵌套结构)的完整代码及注释
以下是 Spring Boot 集成 Redis 对哈希数据的详细操作示例,涵盖不同结构类型(基础类型、对象、嵌套结构)的完整代码及注释: 1. 集成步骤 1.1 添加依赖 在 pom.xml 中添加以下依赖: <dependency><groupId&g…...
ROS云课三分钟-差动移动机器人巡逻报告如何撰写-中等报告
评语: 成绩中等(70/100),具体如下: 1. 摘要部分 问题描述: 内容空洞:摘要过于简短,仅简要概述了研究内容和实现方法,未突出研究的创新点或重要性。缺乏细节࿱…...
Java8+Spring Boot + Vue + Langchain4j 实现阿里云百炼平台 AI 流式对话对接
1. 引言 在本文中,我们将介绍如何使用 Spring Boot、Vue.js 和 Langchain4j,实现与 阿里云百炼平台 的 AI 流式对话对接。通过结合这些技术,我们将创建一个能够实时互动的 AI 聊天应用。 这是一个基于 Spring Boot Vue.js Langchain4j 的智…...
可发1区的超级创新思路(python 实现):一种轻量化的动态稀疏门控网络
首先声明,该模型为原创!原创!原创!且该思路还未有成果发表,感兴趣的小伙伴可以借鉴! 一、应用领域 视频异常检测、生成视频检测。 二、模型解析 该模型由1.关键帧动态选择机制、2.关键帧动态选择机制以及3.关键帧动态选择机制三大核心组件构成,形成端到端的视频异常…...
【Kafka基础】单机安装与配置指南,从零搭建环境
学习Kafka,掌握Kafka的单机部署是理解其分布式特性的第一步。本文将手把手带你完成Kafka单机环境的安装、配置及基础验证,涵盖常见问题排查技巧。 1 环境准备 1.1 系统要求 操作系统:CentOS 7.9依赖组件:JDK 8(Kafka …...
Scala 转义字符
Scala 转义字符 引言 Scala作为一种多范式编程语言,拥有丰富的字符处理能力。在Scala编程中,转义字符的使用非常频繁,它们可以用来处理字符串中的特殊字符,使得字符串的表示更加直观和符合预期。本文将详细探讨Scala中的转义字符…...
TCP/IP五层协议
目录 1. 五层模型结构 2. 各层核心功能与协议 (1) 应用层(Application Layer) (2) 传输层(Transport Layer) (3) 网络层(Network Layer) (4) 数据链路层(Data Link Layer) (5…...
Dify接口api对接,流式接收流式返回(.net)
试了好多种方法除了Console.WriteLine()能打印出来,试了好些方法都不行,不是报错就是打印只有一行,要么就是接收完才返回...下面代码实现调用api接收流式数据,并进行流式返回给前端: using Furion.HttpRemote; using …...
微信小程序开发前端培训课程
大前端培训课程 1.HTML课程: 1.HTML标签基础 2.布局DIVspan 3.表单标签 4.多媒体标签 5.Table使用 2.CSS课程: 1.Box 盒子模型,列表布局(一行两列,一行多列) 2.单行文字,多行文字 3.文…...
代码随想录算法训练营第五十二天|图论专题: 101. 孤岛的总面积、102. 沉没孤岛、103. 水流问题、104. 建造最大岛屿
101. 孤岛的总面积 本题要求找到不靠边的陆地面积,那么我们只要从周边找到陆地然后 通过 dfs或者bfs 将周边靠陆地且相邻的陆地都变成海洋,然后再去重新遍历地图 统计此时还剩下的陆地就可以了。 1、从左边和后边向中间遍历 2、从上边和下边向中间遍历…...
仿modou库one thread one loop式并发服务器
源码:田某super/moduo 目录 SERVER模块: Buffer模块: Socket模块: Channel模块: Connection模块: Acceptor模块: TimerQueue模块: Poller模块: EventLoop模块&a…...
MNIST 数据集 与 TFOD API
此处给出我在进行毕业设计过程中写的三份脚本,作为demo 展示模型的预处理,输出信息提取和TFOD API的应用。 script1 加载本地的MNIST模型,对本地的手写数字进行推理 # test the validation of the saved file and the camera import cv2 i…...
SpringSecurity6.0 通过JWTtoken进行认证授权
之前写过一个文章,从SpringSecurity 5.x升级到6.0,当时是为了配合公司的大版本升级做的,里面的各项配置都是前人留下来的,其实没有花时间进行研究SpringSecurity的工作机制。现在新东家有一个简单的系统要搭建,用户的认…...
【Java】Maven
一、概念 是一个项目管理和构建工具,它基于项目对象模型(POM)的概念,通过一小段描述信息来管理项目的构建。 二、Maven坐标 <groupId>com.itheima</groupId><artifactId>maven-project01</artifactId>&…...
第十五届蓝桥杯PythonC组题解
A题:拼正方形 问题描述 给定一定数量的 22 和 11 的方块,求能拼出的最大正方形边长。 解题思路 二分法:将奇数和偶数边长分开处理,通过二分法寻找最大满足条件的边长。面积验证:总方块面积需大于等于目标正方形面积…...
MATLAB中plot函数的详细参数表
LineSpec - 线型、标记和颜色 线型说明-实线--虚线:点线-.点划线 标记说明o圆圈加号*星号.点x叉号_水平线条|垂直线条s方形d菱形^上三角v下三角>右三角<左三角p五角形h六角形 颜色说明 y 黄色 m 品红色 c 青蓝色 r 红色 g 绿色 b 蓝色 w 白色 k 黑色 MarkerFaceColor…...
R语言赋能气象水文科研:从多维数据处理到学术级可视化
全球气候变化加剧了极端天气与水文事件的复杂性,气象卫星、雷达、地面观测站及水文传感器每天产生TB级时空异质数据。传统研究常面临四大瓶颈: 数据清洗低效:缺失值、异常值处理耗时;时空分析模型构建复杂࿱…...
虚拟试衣间-云尚衣橱小程序-衣橱管理实现
衣橱管理实现 目标 (Goal): 用户 (User): 能通过 UniApp 小程序上传衣服图片。 后端 (Backend): 接收图片,存到云存储,并将图片信息(URL、用户ID等)存入数据库。 用户 (User): 能在小程序里看到自己上传的所有衣服图片列表。 技术栈细化 (Refined Tech Stack for this Pha…...
BGP路由协议之属性2
Orgin 起源 公认必遵属性 起源名称标记描述IGPi如果路由是由始发的 BGP 路由器使用 network 命令注入到 BGP 的,那么该 BGP 路由的 origin 属性为 IGPEGPe如果路由是通过 EGP 学习到的,那么该 BGP 路由的 Origin 属性为 EGPIncomplete?如果路由是通过…...
纯个人整理,蓝桥杯使用的算法模板day2(0-1背包问题),手打个人理解注释,超全面,且均已验证成功(附带详细手写“模拟流程图”,全网首个
算法索引 01背包优化前空间优化版(使用一维数组)优化后的模拟流程图为何优化后,j不能使用正序遍历模拟流程图 代码对应实现案例 01背包 优化前 /*** 0-1背包问题解法(与下方代码表格示例对应,已模拟验证)*…...
算法与数据结构线性表之栈和队列
Hello大家好! 很高兴与大家见面! 给生活添点快乐,开始今天的编程之路。 我的博客:<但愿. 我的专栏:C语言、题目精讲、算法与数据结构、C 欢迎点赞,关注 一 栈 1概念:栈是⼀种特殊的线性表,其只允许…...
python应用之使用pdfplumber 解析pdf文件内容
目录标题 一. 通过 pdfplumber.open() 解析复杂PDF:1-2. 报错:V2 : 1-3. v3 使用tk 库,弹框选择文件运行环境准备完整代码保存运行测试步骤方式二:命令行方式(适用于自动化) 测试用例示例常见问…...
laravel update报In PackageManifest.php line 122:Undefined index: name 错误的解决办法
用 composer 更新 laravel依赖包时报错 > Illuminate\Foundation\ComposerScripts::postAutoloadDump > Illuminate\Foundation\ComposerScripts::postAutoloadDump > php artisan package:discover --ansiIn PackageManifest.php line 122:Undefined index: nameScr…...
Vue中使用antd-table组件实现数据选择、禁用、已选择禁用-demo
实现案例 实现过程 表格代码 关键代码 :row-selection="rowSelection" <div><div class="flex items-center justify-between pt-[24px] pb-[16px]"><p>已选:{{ keysNum }}</p><a-input-search v-model:value="productN…...
C语言--统计输入字符串中的单词个数
输入 输入:大小写字母以及空格,单词以空格分隔 输出:单词个数 代码 如果不是空格且inWord0说明是进入单词的第一个字母,则单词总数加一。 如果是空格,证明离开单词,inWord 0。 #include <stdio.h&g…...
Kubernetes 集群搭建(三):使用dashboard用户界面(需要访问外网获取yaml)
(一)简介 K8s Dashboard是Kubernetes提供的一种基于Web的用户界面工具,用于可视化地管理和监控Kubernetes集群 主要功能: 资源查看与管理: 查看Kubernetes集群中的各种资源,如节点、Pod、服务、部署等。 对…...
Debian 12 服务器搭建Beego环境
一、Debian 12系统准备 1.更新系统 #apt update && apt upgrade -y 2.安装基础工具 #apt install -y git curl wget make gcc 二、安装Go环境 Go语言的镜像官网:https://golang.google.cn/ 1.下载go最新版 #cd /usr/local/src #wget -o https://golang.go…...
游戏引擎学习第208天
运行游戏并回顾我们的情况 今天,我们将继续完成之前中断的调试输出工作。最近的工作偏离了一些,展示了如何进行元编程的实践,主要涉及了一个小的解析器。尽管这个解析器本身是一个玩具,但它展示了如何完成一个完整的循环…...
