0

0

javascript闭包怎样实现代理模式

星降

星降

发布时间:2025-08-12 16:06:02

|

507人浏览过

|

来源于php中文网

原创

闭包实现代理模式的核心是通过工厂函数创建代理对象,该代理利用闭包捕获并持有对真实对象及私有状态(如缓存)的引用,从而在不修改原对象的前提下,对其方法调用进行拦截和增强。1. 工厂函数接收真实对象作为参数;2. 内部定义私有状态(如cache)和代理方法;3. 返回的新对象方法通过闭包访问真实对象和私有状态,在调用前后添加额外逻辑(如缓存、日志、权限校验等);4. 每个代理实例拥有独立且持久的状态,互不干扰;5. 实现方式轻量、直观,适用于方法级别的增强,如缓存、日志、参数校验、权限控制、懒加载和重试机制;6. 与es6 proxy相比,闭包代理为方法级拦截,无法代理所有对象操作,但更简单直接,适合特定方法的非侵入式增强。因此,闭包代理模式是一种基于函数作用域和闭包机制的轻量级代理实现方案,广泛应用于性能优化和行为扩展场景。

javascript闭包怎样实现代理模式

JavaScript中,利用闭包实现代理模式,核心在于创建一个函数,这个函数返回一个新的对象(代理),而这个新对象的方法能够访问并操作原始对象(被代理对象),同时在访问前后加入额外的逻辑。简单来说,闭包在这里提供了一个私有作用域,让代理能够“记住”它所代理的真实对象,并对其行为进行拦截或增强。

javascript闭包怎样实现代理模式

解决方案

要用闭包实现代理模式,我们通常会创建一个工厂函数。这个函数接收一个“真实”的服务或对象实例作为参数,然后返回一个“代理”对象。代理对象内部的方法,通过闭包捕获了对真实实例的引用,因此可以在调用真实实例的方法之前或之后,执行额外的操作。

举个例子,假设我们有一个处理用户数据的服务

UserService
,它可能会直接与数据库交互。为了增加缓存或者日志功能,我们不想直接修改
UserService
的代码,这时就可以用代理模式:

立即学习Java免费学习笔记(深入)”;

javascript闭包怎样实现代理模式
// 真实的 UserService,它可能很“重”或者涉及到网络请求
class UserService {
    constructor() {
        console.log("UserService: 实例被创建了。");
    }

    getUserInfo(userId) {
        console.log(`UserService: 正在从数据库获取用户 ${userId} 的信息...`);
        // 模拟异步操作,比如数据库查询
        return new Promise(resolve => {
            setTimeout(() => {
                resolve({ id: userId, name: `用户-${userId}`, email: `user${userId}@example.com` });
            }, 500);
        });
    }

    updateUserInfo(userId, data) {
        console.log(`UserService: 正在更新用户 ${userId} 的信息:`, data);
        // 模拟异步操作
        return new Promise(resolve => {
            setTimeout(() => {
                console.log(`UserService: 用户 ${userId} 信息更新成功。`);
                resolve({ success: true, updatedId: userId });
            }, 300);
        });
    }
}

// 使用闭包创建 UserService 的代理
function createUserProxy(realUserService) {
    const cache = {}; // 缓存,通过闭包保持其状态
    console.log("Proxy: 代理实例被创建了。");

    return {
        getUserInfo: async function(userId) {
            if (cache[userId]) {
                console.log(`Proxy: 从缓存中获取用户 ${userId} 的信息。`);
                return cache[userId];
            }
            console.log(`Proxy: 缓存未命中,调用真实服务获取用户 ${userId} 的信息。`);
            const userInfo = await realUserService.getUserInfo(userId);
            cache[userId] = userInfo; // 缓存结果
            console.log(`Proxy: 用户 ${userId} 信息已存入缓存。`);
            return userInfo;
        },

        updateUserInfo: async function(userId, data) {
            console.log(`Proxy: 准备更新用户 ${userId} 的信息,执行前置日志记录...`);
            // 在更新前清除缓存,确保数据一致性
            if (cache[userId]) {
                delete cache[userId];
                console.log(`Proxy: 已清除用户 ${userId} 的旧缓存。`);
            }
            const result = await realUserService.updateUserInfo(userId, data);
            console.log(`Proxy: 用户 ${userId} 信息更新完成,执行后置日志记录...`);
            return result;
        }
    };
}

// 使用示例
const realService = new UserService();
const userServiceProxy = createUserProxy(realService);

// 第一次获取,会走真实服务并缓存
userServiceProxy.getUserInfo(101).then(data => console.log("获取到用户:", data));

// 再次获取,会走缓存
setTimeout(() => {
    userServiceProxy.getUserInfo(101).then(data => console.log("再次获取到用户:", data));
}, 700);

// 更新信息,会清除缓存并走真实服务
setTimeout(() => {
    userServiceProxy.updateUserInfo(101, { name: "新名字", email: "new@example.com" })
        .then(result => console.log("更新结果:", result));
}, 1500);

// 更新后再次获取,又会走真实服务
setTimeout(() => {
    userServiceProxy.getUserInfo(101).then(data => console.log("更新后再次获取用户:", data));
}, 2000);

在这个例子里,

createUserProxy
函数就是那个工厂,它内部的
cache
对象和返回的
{ getUserInfo, updateUserInfo }
形成了闭包,使得
getUserInfo
updateUserInfo
方法可以持续访问并操作
cache
realUserService
,即便
createUserProxy
函数已经执行完毕。

为什么选择闭包实现代理模式?

我觉得,用闭包来搞定代理模式,这事儿挺有意思的,而且在某些场景下,它确实是个非常自然的选择。在我看来,主要有几个点:

javascript闭包怎样实现代理模式

首先,封装性特别好。当你在

createUserProxy
里面定义
cache
变量或者直接引用
realUserService
时,这些东西就都被“锁”在了闭包的作用域里,外部是访问不到的。这意味着代理内部的实现细节,比如那个缓存逻辑,可以很好地隐藏起来,不会污染全局作用域,也不会被不小心修改。这让代码更健壮,也更容易维护。

其次,状态持久化和独立性。闭包最核心的特性之一就是它能让内部函数记住并访问外部函数的变量,即使外部函数已经执行完了。对于代理模式来说,这意味着每个代理实例可以拥有自己独立的、持久的状态。比如上面例子中的

cache
,每个
userServiceProxy
实例都有自己的缓存,互不干扰。这对于实现像缓存、节流、防抖这样的功能来说,简直是量身定制。你不需要把这些状态挂到全局对象上,也不用担心多个代理实例之间会相互影响。

再者,实现起来相对直观和轻量。对于一些相对简单的拦截需求,比如只是在方法调用前后加点日志、做个缓存,或者做个简单的权限校验,用闭包实现代理模式,代码结构非常清晰,也很好理解。你不需要引入新的语法或者复杂的API(比如ES6的

Proxy
对象),直接用JavaScript本身的作用域链和函数特性就能搞定。这对于快速实现功能或者在老旧浏览器环境下,是个不错的选择。

最后,可以无侵入地增强现有对象。你不需要修改

UserService
类的源代码,就能给它加上缓存、日志等功能。这在很多时候非常重要,尤其当你使用的库或者框架不允许你直接修改其内部代码时。闭包代理提供了一种“外挂式”的增强方案,保持了原有代码的纯净性。

当然,它也有它的局限性,比如只能代理你明确定义的方法,无法像ES6

Proxy
那样拦截所有操作(属性读取、设置、删除等等),但对于特定方法的增强,闭包代理已经足够优雅和强大了。

闭包代理模式与ES6 Proxy API有何不同?

说实话,这俩都是实现代理模式的利器,但它们的哲学和能力范围还是挺不一样的。理解它们之间的差异,能帮助你更好地选择在什么场景下用哪种。

迷你天猫商城
迷你天猫商城

迷你天猫商城是一个基于Spring Boot的综合性B2C电商平台,需求设计主要参考天猫商城的购物流程:用户从注册开始,到完成登录,浏览商品,加入购物车,进行下单,确认收货,评价等一系列操作。 作为迷你天猫商城的核心组成部分之一,天猫数据管理后台包含商品管理,订单管理,类别管理,用户管理和交易额统计等模块,实现了对整个商城的一站式管理和维护。所有页面均兼容IE10及以上现代浏览器。部署方式1、项目

下载

ES6 Proxy API,我觉得它更像是一个“元编程”级别的工具。它提供了一个拦截所有对象操作的能力,包括属性的读取 (

get
)、设置 (
set
)、方法的调用 (
apply
)、构造函数调用 (
construct
),甚至是
in
操作符、
delete
操作符等等。你可以通过定义一系列的“陷阱”(
trap
)来拦截这些操作。它的强大之处在于,你可以创建一个非常通用的代理,几乎可以拦截任何对目标对象的操作,这让它在实现像Vue 3响应式系统、ORM框架等底层机制时显得非常强大和灵活。它直接作用于对象本身,而不是某个特定的方法。

闭包代理模式,它更像是“方法级别”的代理。你通过闭包创建的代理,通常是针对目标对象上的某个或某几个特定方法进行拦截和增强。比如上面的

getUserInfo
updateUserInfo
。如果你想拦截
UserService
实例上所有可能的属性访问(比如
userService.someProperty
),或者拦截
new UserService()
这样的构造行为,闭包代理就显得力不从心了。它依赖于你手动为每个需要代理的方法编写包装逻辑。

简单来说:

  • 拦截粒度: ES6 Proxy 是对象级别的,能拦截所有低层操作;闭包代理是方法级别的,只能拦截你显式包装的方法。
  • 实现复杂性: ES6 Proxy 提供了更强大的能力,但也意味着你需要理解更多的“陷阱”概念,相对而言学习曲线稍陡峭一些。闭包代理则基于JavaScript最基础的函数和作用域概念,对于简单场景来说更直观。
  • 适用场景:
    • ES6 Proxy 更适合需要对对象进行全面监控、修改其底层行为,或者构建通用框架和库的场景,比如实现响应式数据、数据绑定、虚拟化对象等。
    • 闭包代理 更适合对特定方法进行功能增强、性能优化(如缓存)、日志记录、权限控制等,当你只关心少数几个方法的行为时,它显得更轻量、更直接。
  • 性能: 对于非常频繁的底层操作,ES6 Proxy 可能会引入一些额外的开销,因为它需要通过内部机制来处理所有的陷阱。但对于大多数应用来说,这种开销通常可以忽略不计。闭包代理由于其针对性强,如果实现得当,性能损耗可能非常小。

所以,选择哪个,真的要看你的具体需求。如果只是想给某个函数加个缓存或日志,闭包代理可能更简单直接;如果想实现一个像Vue那样的数据响应系统,那ES6 Proxy就是不二之选。

闭包代理模式在实际开发中的应用场景

在我日常的开发实践中,闭包代理模式虽然不像ES6 Proxy那样“包罗万象”,但在很多特定、常见的场景下,它依然是我的首选,因为它足够简单、直接,而且有效。

  1. 方法缓存 (Caching):这大概是最经典也最常用的场景了,就像上面示例中展示的那样。当某个函数的计算成本很高,或者涉及到频繁的网络请求时,你可以用闭包创建一个代理,在第一次调用时执行实际操作并将结果存储在一个闭包变量(比如

    Map
    或普通对象)中,后续调用时直接从缓存返回,大大提升性能。这对于一些数据不经常变动,但查询频繁的接口特别有用。

  2. 日志记录与性能监控 (Logging & Performance Monitoring):想象一下,你想要知道某个关键业务逻辑函数被调用了多少次,每次调用花费了多长时间,或者它的输入输出是什么。你完全可以用闭包代理来包装这个函数。在代理内部,你可以记录函数开始执行的时间,调用原始函数,然后记录结束时间,计算耗时,并将这些信息发送到你的日志系统或监控平台。这样,你既不侵入原始业务逻辑,又能获得宝贵的运行数据。

  3. 参数校验与预处理 (Validation & Pre-processing):在调用一个核心业务逻辑函数之前,你可能需要对传入的参数进行严格的校验,或者进行一些格式转换。比如,确保某个ID是数字,某个字符串不是空的,或者将日期字符串转换为

    Date
    对象。通过闭包代理,你可以在调用原始函数之前,先执行这些校验和预处理逻辑。如果校验失败,可以直接抛出错误,避免无效数据进入核心逻辑;如果成功,则传递处理后的参数。

  4. 权限控制与安全检查 (Access Control & Security):假设你的应用中有些操作只有特定角色或权限的用户才能执行。你可以创建一个代理,在调用这些敏感操作之前,先检查当前用户的权限。如果权限不足,直接拒绝操作并返回错误信息,而不是让请求触达真实的服务。这在前端需要进行一些预校验时非常实用,当然,最终的权限校验还是要在后端进行。

  5. 延迟加载/懒加载 (Lazy Loading):对于一些初始化成本很高,但并非立即需要的对象,你可以用闭包代理来“延迟”它们的创建。代理对象在被创建时并不立即实例化真实的重量级对象,而是在其某个方法被第一次调用时,才去创建并初始化真实对象。这样可以加快应用的启动速度,并节省不必要的资源消耗。

  6. 错误处理与重试机制 (Error Handling & Retry):当某个操作(尤其是网络请求)可能因为临时性问题而失败时,你可以用闭包代理来包装它,在代理内部实现一个简单的重试逻辑。如果原始操作失败,代理可以尝试再次调用几次,直到成功或达到最大重试次数。同时,你也可以在这里捕获并统一处理错误,例如向用户展示友好的错误信息,而不是直接抛出未经处理的异常。

这些场景都体现了闭包代理的“非侵入性增强”能力。它就像一个“中间人”,在不改动被代理对象代码的前提下,为其添加了新的行为或控制了访问方式,这在构建模块化、可维护的应用时显得尤为重要。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
es6新特性
es6新特性

es6新特性有:1、块级作用域变量;2、箭头函数;3、模板字符串;4、解构赋值;5、默认参数;6、 扩展运算符;7、 类和继承;8、Promise。本专题为大家提供es6新特性的相关的文章、下载、课程内容,供大家免费下载体验。

100

2023.07.17

es6新特性有哪些
es6新特性有哪些

es6的新特性有:1、块级作用域;2、箭头函数;3、解构赋值;4、默认参数;5、扩展运算符;6、模板字符串;7、类和模块;8、迭代器和生成器;9、Promise对象;10、模块化导入和导出等等。本专题为大家提供es6新特性的相关的文章、下载、课程内容,供大家免费下载体验。

192

2023.08.04

JavaScript ES6新特性
JavaScript ES6新特性

ES6是JavaScript的根本性升级,引入let/const实现块级作用域、箭头函数解决this绑定问题、解构赋值与模板字符串简化数据处理、对象简写与模块化提升代码可读性与组织性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

221

2025.12.24

scripterror怎么解决
scripterror怎么解决

scripterror的解决办法有检查语法、文件路径、检查网络连接、浏览器兼容性、使用try-catch语句、使用开发者工具进行调试、更新浏览器和JavaScript库或寻求专业帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

208

2023.10.18

500error怎么解决
500error怎么解决

500error的解决办法有检查服务器日志、检查代码、检查服务器配置、更新软件版本、重新启动服务、调试代码和寻求帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

295

2023.10.25

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

298

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

212

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1498

2023.10.24

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

9

2026.01.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

相关下载

更多

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Vue 教程
Vue 教程

共42课时 | 7.3万人学习

Vue3.x 工具篇--十天技能课堂
Vue3.x 工具篇--十天技能课堂

共26课时 | 1.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号