创建型与结构型模式

共 17 题
📑 题目列表 17 题
#
★★★

1. Proxy 与装饰器模式在表单校验与权限拦截的工程取舍

在表单校验与权限拦截场景中,Proxy 代理模式与装饰器模式分别适合什么样的工程场景?两者在实现、可维护性与性能上有何取舍?

  • Proxy 代理的运行时拦截能力与动态性
  • 装饰器模式在编译期/静态层面的组合能力
  • 表单校验与权限拦截对对象拦截、函数包装的不同需求

Proxy 模式通过拦截对象的基础操作(get/set/has 等)在运行时对任意对象进行透明包装,适合对"对象整体"进行校验与权限控制,例如对 store 对象做 set 拦截实现字段校验、对敏感对象做 get 拦截实现权限访问控制;其优点是无需修改原对象的调用方,且可拦截任意属性、动态扩展。装饰器模式则通过包装函数或类来增强具体方法,适合对"方法/函数"做静态、可堆叠的增强,例如权限校验装饰器、记录日志装饰器,可组合多个装饰器形成职责链。工程取舍上:Proxy 更灵活、侵入性小但性能开销略高且语义隐晦;装饰器更直观、可组合、面向方法级切面,但需要显式标注且对对象的整体拦截能力弱。实践中常按"拦截对象 vs 拦截方法"来选型,Proxy 负责数据层与全局拦截,装饰器负责方法级与组件级切面。

两种模式本质都是"增强不改变原对象",但抽象层级不同:Proxy 是语言级元编程,拦截对象基本操作;装饰器是结构组合模式,包装对象接口。表单校验常把校验规则横切到多个字段上,用 Proxy 对 set 统一拦截更省事;权限拦截常作用在方法入口,用装饰器逐个声明更清晰。回答时点出"按对象级还是方法级、按运行时还是静态"来权衡即可。

#
★★★

2. 工厂模式(Factory Function)与抽象工厂在多 SKU 业务组件的工程价值

在多 SKU(多规格、多渠道)业务组件中,工厂函数与抽象工厂模式分别解决了什么问题?如何用它们降低组件间的耦合与重复代码?

  • 工厂函数(Factory Function)封装创建逻辑
  • 抽象工厂创建一族相关产品对象
  • 多 SKU 场景按规格/渠道动态选择组件

在多 SKU 业务中,不同 SKU 往往对应不同的展示组件、数据处理逻辑或渠道适配器。工厂函数通过一个接受"规格/类型"参数并返回对应组件或实例的函数,把"根据 SKU 选择组件"的 if/switch 逻辑集中收口,调用方无需关心具体类,从而降低耦合。抽象工厂则将"一族相关对象"(如某 SKU 的组件、校验规则、上报逻辑、样式配置)的创建封装成工厂接口,避免客户端为了构造一个完整 SKU 而异质性拼装多个对象。工程价值上,工厂能显著减少重复的 create 逻辑、方便在测试中替换 Mock 实现、便于新增 SKU 时只扩展工厂而不改动调用方,符合开闭原则。技术上常配合"注册表 + 映射表"实现,把 SKU 类型映射到工厂函数,实现按需动态创建(动态渲染)。

工厂模式的核心价值是"把实例化逻辑从使用处剥离",让调用方依赖抽象而非具体类。多 SKU 是典型的多态场景,用工厂模式可以避免业务代码里到处散落 type 判断与重复构造代码。抽象工厂更进一步,处理的是"一组相关依赖"的创建,适合 SKU 内存在多个相关联对象的场景。回答应强调"降低耦合、集中变化、便于扩展与测试"。

#
★★★

3. 原型模式(Prototype)的 Object.create 与 proto 在对象克隆的应用

在前端对象克隆场景中,原型模式如何通过 Object.create 与 proto 实现?直接赋值 proto 与 Object.create 有何区别与风险?

  • Object.create(proto) 创建以指定对象为原型的对象
  • proto 与 Object.getPrototypeOf/setPrototypeOf 的区别
  • 原型链与对象克隆、继承的实现

原型模式的核心是"以某个原型对象为模板创建新对象"。Object.create(proto) 会创建一个以 proto 为原型的新对象,实现"原型级克隆",新对象可以共享原型上的方法,还能就地覆盖自有属性,这是实现原型继承与对象克隆的推荐方式。proto 是历史遗留的访问器属性,用于读写对象原型,但它是非标准且易被误用(会污染原型、性能差),标准做法是用 Object.getPrototypeOf 与 Object.setPrototypeOf。工程上,对象克隆分"浅克隆"(Object.assign、展开运算符)与"深克隆"(structuredClone、递归),而原型模式强调"复用原型而非复制属性",适合模板对象、样式对象、实体实例复用的场景。性能上 Object.create 比逐属性复制更省,且能保留原型链上的共享方法。

原型模式的关键是理解"原型链"这一 JS 特有的机制。回答要点出 Object.create 是标准、安全的原型创建方式,而 proto 是遗留的非标准写法,应避免在生产代码中直接使用。同时区分"原型式复用"与"深/浅拷贝"两个概念,说明原型模式解决的是"共享行为与模板复用",而非完整的数据复制。

#
★★★

4. 单例模式的多种实现(ES Module、静态属性、闭包)与场景

单例模式在前端有哪些常见实现方式?ES Module、静态属性、闭包各自的特点与适用场景是什么?

  • ES Module 天然单例(模块缓存)
  • 类静态属性保存单例实例
  • 闭包隐藏实例变量

单例模式保证一个类只有一个实例并提供全局访问点。前端常用三种实现:其一,ES Module 天然具备单例特性,模块只在首次导入时执行一次并缓存导出,后续 import 复用同一实例,是前端最推荐的实现方式;其二,类静态属性(如 static getInstance 记录实例)在类内部保存唯一实例,可通过 instance 静态字段实现,简洁直观;其三,闭包方式用 IIFE 包裹实例变量,通过返回的 getter 控制实例的创建与访问,能实现真正的懒加载。使用场景包括全局状态 store、配置对象、事件总线、日志器等。工程上需注意:单例在测试中不易隔离(需提供 reset 方法)、可能在多实例需求(如 SSR、多租户)下成为隐患,因此现代前端更倾向用模块级单例 + 依赖注入来兼顾全局性与可测试性。

回答应覆盖三种实现并对比其特点,同时点出单例的潜在问题(全局状态、测试隔离、SSR 多实例)。模块单例是 90% 场景的默认选择,因为它简单、无额外样板代码且天然缓存。静态属性与闭包则用于需要控制"何时创建"或"隐藏实例"的场景。

#
★★

5. 工厂模式在前端的应用,组件工厂与动态渲染?

工厂模式在前端如何用于组件工厂与动态渲染?请举例说明?

  • 组件工厂根据类型返回对应组件
  • 动态渲染(根据配置/数据渲染组件)
  • 与映射表、render props、动态组件结合

组件工厂模式通过一个"根据类型/配置返回对应组件"的函数,把"如何选择组件"的决策集中起来,从而实现动态渲染。例如根据后端下发的字段类型(text、select、date)选择对应的输入组件,或根据消息类型渲染不同的气泡组件。实现上常结合"组件注册表 + 映射表":const registry = { text: TextComp, select: SelectComp },再通过 registry[type] || DefaultComp 动态渲染。Vue 中的 <component :is>、React 中的 React.createElementswitch 分支返回组件都是组件工厂的体现。这样新增组件类型只需扩展注册表,无需改动调用方,符合开闭原则,也能让配置驱动的动态表单、低代码平台、消息流等场景优雅实现。

组件工厂本质是"用数据驱动创建组件",把组件选择逻辑从渲染逻辑中解耦。这是低代码、动态表单、复杂列表渲染的核心手段。回答应强调"映射表 + 动态组件"的技术组合,并说明其开闭性原则的价值。

#
★★

6. 装饰器模式与高阶组件(HOC),增强组件的模式?

装饰器模式与高阶组件(HOC)如何增强组件?它们在前端 React 体系中的关系和区别是什么?

  • 装饰器动态增加功能
  • 高阶组件(HOC)包装组件增强能力
  • 与 render props、Hooks 的对比

装饰器模式允许在不修改原对象的前提下,动态地给对象附加职责。在前端 React 中,高阶组件(HOC)是装饰器模式的典型实现:HOC 是一个接收组件并返回新组件的函数,通过包装为原组件注入 props、添加生命周期、包裹权限校验、记录日志等能力,例如 withRouterwithLoading。同样的思路也应用于 Vue 的 mixin 与包装组件。HOC 的优点是组合复用、逻辑集中,但存在 props 命名冲突、组件嵌套层级过深、调试困难等缺点,因此现代 React 更推荐用自定义 Hooks 或 render props 替代部分 HOC 场景。回答时还应说明装饰器语法(@)在 JS 中尚未完全标准化的现状,以及其在方法/类层面的应用。

HOC 是函数式组合的装饰器表达,核心是"包装并增强"。回答应既说明 HOC 作为装饰器模式在前端的落地,也指出其局限(props 冲突、层级嵌套)与 Hooks 替代方案,体现对模式演进的理解。

#
★★

7. 享元模式(Flyweight)在长列表与表格中复用单元格、图标等轻量对象的工程价值,与 React key/Virtual DOM 复用的关系?

享元模式如何通过复用轻量对象优化长列表与表格的性能?它与 React 的 key 与 Virtual DOM 复用有何关系?

  • 享元模式共享可复用对象、分离固有与外部状态
  • 长列表/表格中单元格、图标的复用
  • React key 与 Virtual DOM 节点复用

享元模式通过"共享可复用对象"减少对象创建数量,把"固有状态"(共享、不变)与"外部状态"(随上下文变化)分离,从而以少量对象服务大量逻辑实例。在长列表与表格中,图标、单元格模板、样式对象、格式化函数等轻量对象可被大量复用,避免为每一行重复创建,从而降低内存与 GC 压力。它与 React 的复用机制互为表里:React 的 key 用于标识 Virtual DOM 节点,使 diff 能对相同 key 的节点进行复用而非重建,从而减少 DOM 操作;而非受控复用(如把高开销的节点/组件缓存起来)则是享元思想在框架层的体现。工程上,享元模式的价值在于"减少重复创建",与 React 的 key 复用、虚拟 DOM 的 diff 复用、以及 CSS 类名复用等共同提升长列表性能。但需注意过度设计,简单对象直接创建即可,共享只在对象创建/存储成本高时才值得。

本题要打通"设计模式"与"框架机制"两个层面。享元模式关注"对象级复用",React key 与 Virtual DOM 关注"节点级复用",两者目标一致(减少昂贵创建与销毁)。回答要说明享元分离内外状态的思想,并映射到 React 的 diff 复用机制,体现对性能优化的整体理解。

#
★★

8. 单例模式在全局事件总线、Store、Modal Manager 的多实例风险

单例模式用于全局事件总线、Store、Modal Manager 时,可能带来哪些多实例风险?如何规避?

  • 刷新/多标签页/SSR 下的单例失效
  • 意外创建多个实例导致事件/状态不同步
  • 测试隔离与 reset 机制

单例模式在事件总线、Store、Modal Manager 等全局设施中常见,但存在多实例风险:其一,模块在多个入口/打包环境下可能被重复打包为多个实例,导致事件总线收发不互通、状态不一致;其二,SSR(服务端渲染)场景下每个请求共享模块级单例,会造成数据串扰;其三,Micro-frontend(微前端)与多实例挂载(如多个 React 根)可能各自创建同一单例;其四,热更新(HMR)与测试中重新加载模块会重置或产生新实例。规避手段包括:使用模块级单例 + 全局挂载(如 window.__store)防止重复打包创建多实例、在 SSR 中按请求隔离用工厂而非单例、为单例提供 reset/clear 方法便于测试与热更新、用依赖注入替代裸单例便于替换。工程上应认识到"单例隐式共享全局状态",在需要多实例的场景(SSR、微前端、多租户)应避免使用裸单例。

本题考察对单例模式"坑"的认知。单例假设"全局唯一",但前端环境(多标签页、SSR、微前端、HMR、打包分离)天然可能破坏唯一性。回答应列举这些风险并给出对策(全局挂载、按请求隔离、reset、依赖注入),体现工程成熟度。

#
★★

9. 桥接模式(Bridge)在抽象与实现分离(如图表库适配多端渲染)的应用

桥接模式如何实现抽象与实现的分离?在前端图表库适配多端渲染等场景中如何应用?

  • 抽象层与实现层解耦,各自独立演化
  • 多端渲染(Canvas/SVG/WebGL/服务端)适配
  • 接口与具体实现分离

桥接模式的核心是把"抽象部分"与"实现部分"分离,让它们可以独立变化,通过组合而非继承连接。在前端图表库中,抽象层定义图表的统一 API(如 render()getData()),实现层则分散到不同渲染后端(Canvas、SVG、WebGL、服务端 SVG 等),桥接层根据端环境选择对应的实现。这样新增一种渲染方式只需新增实现类,无需改动图表抽象;新增一种图表类型也只需扩展抽象,无需改动渲染后端。这种"抽象 × 实现"的矩阵被桥接模式拆解为两个独立维度,避免了组合爆炸。工程上,多端适配(如 Web、小程序、Node 服务端渲染图表)正是桥接模式的典型应用,ECharts 等库的渲染器抽象即是该思想的体现。

桥接模式解决的是"多个维度变化"的耦合问题,用组合替代多层继承。回答应以"抽象(图表模型)+ 实现(渲染后端)"这一典型结构说明,并强调其开闭价值——新增维度的一方时不动另一方。多端渲染是绝佳例证。

#
★★

10. 装饰器模式在高阶组件与中间件的应用

装饰器模式如何应用于高阶组件与中间件?两者在实现上有什么共同思想?

  • 装饰器动态叠加功能
  • 高阶组件(HOC)包装增强
  • 中间件(Middleware)链式包装

装饰器模式的核心是"在不修改原对象的前提下,以包装方式动态叠加职责"。高阶组件(HOC)是装饰器在 React 中的体现:withAuth(Component)withLogger(Component) 通过包装返回新组件,叠加权限、日志、数据加载等能力。中间件(Middleware)同样是装饰器思想的体现:Express/Koa 中每个中间件接收请求并决定是否传给下一个,Redux 的 applyMiddleware 用一个函数包装 store 的 dispatch,层层叠加日志、异步、持久化等能力。两者的共同点是"洋葱式/链式包装",通过组合而非修改原逻辑来增强功能,具有可插拔、可复用、关注点分离的优点。区别在于 HOC 面向组件渲染,中间件面向请求/数据流处理流程。理解这一共同思想有助于写出可组合、可扩展的增强代码。

本题把装饰器模式的两个典型载体(HOC 与中间件)放在一起考察。回答要提炼"链式包装、组合增强"这一共性,并分别说明 HOC 在组件层、中间件在请求/数据流层的应用,展现对"横切关注点"统一处理的理解。

#
★★

11. 适配器模式在 API 兼容与多端统一的应用

适配器模式如何解决 API 兼容与多端统一问题?请举例说明?

  • 适配器将不兼容接口转换为目标接口
  • 兼容旧 API、第三方库、多端差异
  • 统一调用层隔离差异

适配器模式把一个类的接口转换为客户端期望的另一个接口,使原本不兼容的类可以协作。在前端,适配器广泛用于:其一,兼容旧 API——当后端接口或 SDK 升级、字段变更时,用适配器把新接口映射为旧调用方期望的结构,避免大规模改动;其二,多端统一——web、小程序、Node 端对 API 的实现差异用适配器统一(如封装 request 适配 axios/fetch/uni.request),上层代码只依赖统一接口;其三,第三方库接入——把库的 API 转换成业务约定的接口。工程上,适配器提供"隔离层",让调用方与具体实现解耦,便于替换实现、平滑迁移、统一异常处理。实现通常是一个包装函数或类,转发调用并做结构转换。

适配器是"接口转换器",核心价值是隔离变化、统一接口。回答应结合"旧 API 兼容"与"多端统一"两个典型场景,说明适配器如何让上层代码不感知底层差异,并强调其为迁移与多端开发带来的便利。

#
★★

12. 建造者模式在复杂配置对象(请求配置、组件 props)的应用

建造者模式如何用于构建复杂配置对象,如请求配置与组件 props?它的价值是什么?

  • 建造者分步构建复杂对象
  • 将对象构造与表示分离
  • 请求配置、组件 props 的链式构建

建造者模式将"复杂对象的构造过程"与"对象的表示"分离,通过一步步设置参数最后生成完整对象,适合构造参数多、可选字段多、有默认值的对象。前端常见应用:请求配置——用链式 API 构建 URL、method、params、headers、超时等,避免构造调用出现大量参数;组件 props——用工厂/建造器生成风格统一、带默认值的 props 对象;复杂表单配置、图表配置等同样适用。价值在于:可读性好(链式可读)、参数灵活(可选与默认)、构造逻辑集中、便于复用与测试,且能避免"过长的参数列表"与"构造过程散落各处"。实现上常配合 fluent 链式调用与默认值合并。需注意对象较简单时用默认参数或对象展开即可,不必强行引入建造者。

建造者模式适合"参数多且有默认值"的对象。回答应说明其"分步构造、集中默认值、链式可读"的价值,并点出边界——简单对象用默认参数即可,避免过度设计,体现对模式适用性的判断。

#

13. 组合模式(Composite)在表单字段树与 Tree 数据结构的工程价值

组合模式如何统一处理表单字段树与 Tree 数据结构?其工程价值是什么?

  • 组合模式将部分与整体一致对待(树形结构)
  • 表单字段树、组件树、文件树
  • 递归处理与统一接口

组合模式把"部分"与"整体"统一成一致的结构,让客户端可以一致地处理叶子节点与容器节点,典型结构是树(Tree)。在前端,表单字段树、组件树、菜单树、文件树都是组合模式的体现:一个字段可以是叶子(输入框)也可以是容器(分组、数组字段),处理时统一遍历。工程价值在于:统一的遍历/校验接口(如对整棵树递归做校验、遍渲染、序列化)、递归处理让代码简洁、便于配置驱动。React 的组件树、Vue 的嵌套组件天然是组合结构,diff 与渲染都递归进行。实现上需注意叶子与容器提供一致的接口,容器负责组合子节点。组合模式能显著提升对树形数据的处理一致性,但需注意避免过度抽象导致接口复杂。

组合模式的核心是"树形结构与递归统一处理"。回答应结合表单字段树、组件树等前端典型场景,说明"部分与整体一致对待"如何让递归遍历、校验、渲染变得统一,并点出其配置驱动的价值。

#

14. 外观模式(Facade)在复杂子系统封装的应用

外观模式如何封装复杂子系统,为调用方提供简单统一的接口?请举例说明?

  • 外观模式提供统一的高层接口
  • 隐藏子系统复杂性
  • 降低调用方与子系统的耦合

外观模式为复杂子系统提供一个统一的、简化的高层接口,隐藏内部细节,让调用方通过简单入口即可完成任务。前端典型应用:封装浏览器 API——把 fetch、XHR、WebSocket、Cookie 等一系列操作封装成统一的 request/api 模块,调用方只需调用 api.getUser();封装多个第三方库——把图表、上传、埋点等封装成统一门面;封装 DOM 操作——统一 $ 选择器与事件绑定。价值在于:简化调用、隔离复杂度、降低耦合、便于统一修改与维护(子系统内部变化不影响调用方)。但需注意外观不应过度"透传",否则失去意义;它是一种"简化入口",与适配器(转换接口)关注点不同。

外观模式是"提供统一入口"的门面。回答应结合浏览器 API 封装、多库封装等场景,说明其如何简化调用与隔离复杂度,并区分外观(简化入口)与适配器(接口转换)的不同。

#

15. 单例模式与模块缓存,状态管理的单例设计?

单例模式与 ES Module 模块缓存有何关系?它是如何支撑状态管理的单例设计的?

  • ES Module 模块缓存机制
  • 状态管理中共享单一 store 的需求
  • 模块级单例与 store 实例

ES Module 通过"模块缓存"机制保证同一模块只被求值一次,之后的 import 复用同一份模块实例,因此模块顶层导出的对象天然是单例。状态管理(如 Redux、Vuex、Pinia)正是利用这一特性:store 在模块顶层创建并导出,任意组件 import 得到的是同一个 store 实例,从而共享全局状态。这种"模块级单例"实现简单、无需手动保证仅一个实例。但工程上需注意:模块单例在 SSR、测试、多实例挂载场景下可能不满足"按请求隔离"或"多 store 并存"的需求,此时应使用工厂函数按需创建 store,而非依赖模块单例。因此状态管理通常提供"创建 store 的工厂 + 全局单例"两种模式,兼具灵活性与默认单例的便利。

本题打通"模块缓存"与"状态管理单例"两个概念。回答要说明 ES Module 的单例特性如何天然支撑全局 store,同时指出在 SSR/多实例场景下需要工厂化以规避单例局限,体现对"单例"适用边界的理解。

#

16. 适配器与外观,浏览器 API 的统一封装?

适配器模式与外观模式在统一封装浏览器 API 时各自起什么作用?两者有何区别?

  • 适配器转换接口
  • 外观提供统一入口
  • 两者在浏览器 API 封装中的配合与区别

在封装浏览器 API 时,适配器与外观常配合使用但职责不同:适配器负责"接口转换"——把不同浏览器/平台的 API 差异(如旧版 XMLHttpRequestfetchwindow.requestAnimationFrame 的兼容)映射为统一接口;外观负责"简化入口"——把 fetch、XHR、WebSocket、Cookie、localStorage 等众多能力封装成一个简洁的门面模块(如 api.get()storage.get())。区别在于:适配器解决"不兼容接口之间的对接",目标是让原本不同的东西可协作;外观解决"复杂系统的简化"与"统一入口",目标是让调用方更简单。工程上常用"外观(统一入口)+ 适配器(内部转换差异)"的组合,让上层代码稳定、底层差异被隔离。

本题对比适配器与外观两个易混淆的模式。回答要清晰区分"转换接口"(适配器)与"简化入口"(外观),并说明在浏览器 API 统一封装中两者如何协同——外观提供统一 API,适配器在内部消化平台差异。

#

17. 对象池模式(Object Pool)在频繁创建销毁的重对象(WebGL 纹理、Worker、连接)场景的工程应用与手动归还(release)的陷阱?

对象池模式如何处理 WebGL 纹理、Worker、连接等频繁创建销毁的重对象?手动归还(release)有哪些陷阱?

  • 对象池缓存复用对象、减少创建销毁
  • 重对象(WebGL 纹理、Worker、连接池)场景
  • 手动归还(release)的陷阱与泄漏风险

对象池模式预先创建一批对象放进池中,使用时从池中取出、用毕归还,避免频繁创建与销毁高开销对象。前端典型场景:WebGL 纹理/缓冲、Worker 实例、数据库或网络连接、频繁重用的节点对象。价值在于显著降低 GC 压力与创建开销,提升帧率与响应性。但手动归还的模型存在诸多陷阱:其一,忘记归还导致池中对象不足、最终创建新对象失去池的意义;其二,归还后仍被外部引用(stale reference)导致对象被重复使用或状态被篡改;其三,归还前未重置对象状态,导致脏数据复用;其四,池尺寸管理不当(过大浪费内存、过小频繁扩容);其五,异常路径(try/catch)下未归还导致泄漏。工程上应封装"借出/归还"的受控接口,结合自动重置与最终归还(finalizer)机制,并妥善处理并发与异常。对象池是"以内存换性能"的手段,仅在对象创建/销毁成本高时值得引入。

本题考察对象池的工程实践与"手动归还"的痛点。回答要强调对象池适用高成本对象场景,并系统列举归还的泄漏/脏数据/异常路径陷阱,给出"受控接口 + 自动重置 + 保证归还"的应对,体现对内存与资源管理的理解。