Flutter深度面试题集合

本文部分小节配有 Mermaid 流程图 / 时序图(GitHub、VS Code Mermaid 插件、Hexo 站点均可渲染)。

目录

一、Dart语言基础与进阶

1. Dart的异步编程模型

问题:

  • 请详细解释Dart的事件循环机制(Event Loop)
  • FutureStreamCompleter的区别和使用场景
  • async/await的底层实现原理
  • 如何处理多个异步任务的并发和依赖关系?

参考答案:

事件循环机制:

Dart 在单个 Isolate 内是单线程模型:同一时刻只执行一段 Dart 代码。异步与回调由 事件循环(Event Loop) 调度;典型实现里有两条队列(文档里偶见与 macrotask 概念对照,Dart 侧一般称 Event 队列):

  • Microtask(微任务)队列:优先级更高。scheduleMicrotask、以及多数 Future.then 链上注册的回调会进入该队列。语义是:在当前同步片段跑完后、处理下一个「外部事件」之前,先把微任务清空
  • Event(事件)队列Future(() { ... }) 传入的回调、Timerdart:io 完成回调、平台异步结果等多进此队列。每一轮通常只从该队列取出 一个 任务执行;该任务执行期间产生的同步代码与新的微任务,仍遵循「先同步、再微任务耗尽、再下一个 Event」的规则。

一轮循环里的大致顺序: 同步代码执行到调用栈空 → 反复执行直至 Microtask 队列为空 → 取 一个 Event 任务执行 → 再进入下一轮。

为什么要有微任务? 用于在「同一轮逻辑」里尽快跑完轻量后续(例如 Future 链的衔接),避免被某个排在队首的 Timer / I/O 任务插队打断;但若微任务里无限 scheduleMicrotask,会 饿死 Event 队列(Timer、I/O 迟迟得不到执行),面试中可主动提到这一陷阱。

与浏览器「一帧」的关系(对照本站《Life of frame》):

站内文章 《Life of frame》 描述的是 浏览器里一帧约 16.6ms 内 的阶段顺序:输入 → JavaScript → Begin Frame → requestAnimationFrame → Layout → Paint → Idle,与 渲染管线、刷新率 强相关。
Dart 的事件循环描述的是 同一 Isolate 内 如何交替执行同步代码与两类异步队列,并不等同于浏览器的一帧;二者是不同层面的调度:

对照点 浏览器一帧(Life of frame) Dart 事件循环(单 Isolate)
时间尺度 与屏幕刷新相关(如 ~60fps → 约一帧 16.6ms) 无固定「帧长」,以「任务」为单位轮转
核心目标 输入响应、JS、样式/布局/绘制、空闲回调 调度 Future/Timer/I/O、微任务、Flutter 框架任务
与绘制关系 强绑定:Layout/Paint 在帧内阶段执行 Flutter 中真正「画一帧」由引擎 VSync / SchedulerBinding 驱动;Dart 循环负责执行 build/layout/paint提交上来的任务,概念上可理解为「帧驱动 UI」与「事件循环执行 Dart 代码」配合,而不是把浏览器那张阶段图直接套在 VM 上
Flutter Web 由浏览器承载;Dart 编译为 JS 后在浏览器线程模型上跑,与 Life of frame 同属浏览器生态,但仍要先分清 JS/Dart 任务渲染阶段 两层 移动端 / 桌面端 非 Web 时,没有浏览器那一套 rAF/Layout 阶段图,事件循环仍是 VM 语义

图示 1:一轮循环(与上文文字一致)

flowchart TD
  A[执行当前同步代码至栈空] --> B[依次执行 Microtask 直至队列为空]
  B --> C[从 Event 队列取一个任务并执行]
  C --> A

图示 2:微任务「插队」示意(同一轮内先清空 Microtask)

flowchart LR
  subgraph turn["同一轮内"]
    S[同步代码结束] --> MT[清空 Microtask 队列]
    MT --> EV[执行下一个 Event 任务]
  end

图示 3:任务大致入队位置(面试常问)

flowchart TB
  F1["Future(() { ... }) 、 Timer 、 I/O 回调"] --> Q1[Event 队列]
  F2["Future.microtask 、 多数 Future.then 回调"] --> Q2[Microtask 队列]

图示 4:与「一帧」概念的并列对照(概念层,非执行顺序一一对应)

flowchart LR
  subgraph browser["浏览器一帧 约 16.6ms"]
    B1["输入 / JS / BeginFrame"] --> B2["rAF"]
    B2 --> B3["Layout / Paint / Idle"]
  end
  subgraph dart["Dart 单 Isolate"]
    D1["同步 + Microtask 耗尽"] --> D2["一个 Event 任务"]
    D2 --> D1
  end

图示 4 仅帮助建立 心智模型:左侧是 渲染与输入的帧内阶段(见《Life of frame》),右侧是 Dart VM 事件循环;Flutter 里一帧的 build 往往由引擎在合适时机通过事件循环触发,不要简单把两张图合并成一条时间线。

Future vs Stream vs Completer:

  • Future:表示一个可能还没完成的异步操作的结果,只能产生一个值或错误
  • Stream:可以产生多个异步事件的序列,适合处理连续的数据流
  • Completer:可以手动控制Future的完成,适用于需要手动触发异步完成的场景

async/await原理(语法糖 → Future + 状态机):

结论先说清async 函数立刻返回一个 Future<T>;函数体不会「阻塞 OS 线程」,而是在每个 await 处把后续代码变成 Future 完成后的回调链(概念上等价于层层 Future.then)。编译器(VM / dart2js / dart2wasm 等)在编译期把源码 降低(lower) 为:一个状态机 + 若干 continuation,而不是为每次调用保留完整调用栈到异步结束。

1. 三个关键点

  • **async**:把函数体改写;返回值统一包成 Future(若写的是 async void,则对应 Future<void> 的副作用形式,面试里常问「async void 与 Future 返回值」的区别,此处不展开)。
  • **await ee 须为 Future 或可被包装为 Future 的值。语义是:先求值 e 得到 Future f;当前这段同步代码执行到此为止;把「await 之后的所有语句」编译成在 f 完成(value 或 error)之后执行的逻辑。完成时通过 **Future.then / 内部等价路径 调度到事件循环(成功走 then,失败走 catchError / onError,对应 try/catch around await)。
  • 状态机:每一个 await 把函数体切成一段 状态(continuation)。状态 0 从入口跑到第一个 await 前;状态 1 在第一个 Future 完成后从第一个 await 后继续,直到第二个 await;以此类推。await 仍要活的局部变量不会留在原调用栈帧里,而是由编译器生成的 闭包捕获状态对象字段 保存(因此可以 await 很多次而不会「栈无限增长」)。

2. 与手写 Future.then 的对应(心智模型,非逐字 IR)

下面左为 async/await,右为概念上等价的链式写法(真实 lowering 会生成具名状态类与辅助方法,结构类似):

1
2
3
4
5
6
7
8
// async/await 写法
Future<int> calc() async {
var x = 1;
final a = await fetchA(); // 挂起点 1
x += a;
final b = await fetchB(); // 挂起点 2
return x + b;
}
1
2
3
4
5
6
7
8
9
10
// 概念等价:连续 then,局部变量由闭包/状态保存
Future<int> calc() {
var x = 1;
return fetchA().then((a) {
x += a;
return fetchB();
}).then((b) {
return x + b;
});
}

可见:每一个 await 对应一层「先等 Future,再执行后面」;异常与 try/catch 会映射为 catchErrorFuture 的错误传播,面试可答「await 本质是让编译器帮你拆 then 链并接好错误边界」。

3. 状态转移(示意)

stateDiagram-v2
  direction LR
  [*] --> S0: 入口到第一个 await 前
  S0 --> S1: fetchA 完成后
  S1 --> S2: fetchB 完成后
  S2 --> [*]: return

4. 执行线程与「暂停」的含义

  • 暂停的是 Dart 协程式执行,不是阻塞底层线程(单 Isolate 内仍是一条事件循环线程)。await 之后直到 Future 完成,这段间隙里线程可以去跑别的 微任务 / Event
  • **async 不写 await**:函数体仍可能同步跑完,返回的 Future 可能已是 已完成的 FutureFuture.value 语义)。
  1. async / Stream 的区别(防混淆)**
  • **async* + yield**:生成的是 Stream,由另一类状态机实现(StreamController / pull & push),和「单值 Future + await」不是同一套 lowering,面试点到即可。

并发处理:

1
2
3
4
5
6
7
// 并发执行
Future.wait([future1, future2, future3])

// 顺序执行
for (var task in tasks) {
await task();
}

2. Dart的内存管理

问题:

  • Dart的垃圾回收机制是怎样的?
  • 什么是”孤岛”现象?如何避免内存泄漏?
  • 解释Dart中的强引用和弱引用

参考答案:

垃圾回收机制:
Dart使用分代垃圾回收:

  • 新生代(New Generation):使用半空间复制算法,适合短生命周期对象
  • 老年代(Old Generation):使用标记-清除算法,适合长生命周期对象

孤岛现象:
当一组对象相互引用但不再被根对象引用时,形成”孤岛”。这些对象无法被回收,导致内存泄漏。

避免方法:

  • 及时取消订阅Stream
  • 避免闭包捕获不必要的变量
  • 使用WeakReference(弱引用)
  • 在dispose中清理资源

强引用 vs 弱引用:

  • 强引用:默认引用类型,阻止垃圾回收
  • 弱引用(WeakReference):不阻止垃圾回收,适合缓存场景

3. Dart的类型系统

问题:

  • Dart是强类型语言吗?解释其类型推断机制
  • dynamicObjectObject?的区别
  • 泛型的协变和逆变在Dart中如何体现?

参考答案:

类型系统:
Dart是强类型语言,支持类型推断。变量声明时可以省略类型,编译器会自动推断。

dynamic vs Object vs Object?:

  • dynamic:关闭静态类型检查,运行时检查,可能抛出运行时异常
  • Object:所有非空类型的基类,需要类型转换才能使用特定方法
  • Object?:所有类型的基类(包括null),是Dart 2.12+的顶层类型

泛型协变和逆变:

1
2
3
4
// 协变(Covariance):子类型可以赋值给父类型
List<Animal> animals = List<Dog>();

// Dart默认不支持逆变,但可以通过类型参数约束实现

二、Flutter框架核心

4. Widget、Element、RenderObject三棵树

问题:

  • 请详细解释三棵树的关系和作用
  • Widget为什么设计成不可变的?
  • Element的生命周期是怎样的?
  • 什么情况下会触发树的重建、更新和卸载?

参考答案:

三棵树关系:

  • Widget树:配置信息,轻量级,不可变
  • Element树:Widget的实例化对象,管理生命周期,持有Widget和RenderObject引用
  • RenderObject树:负责布局和渲染,计算大小、位置、绘制

Widget不可变的原因:

  • 性能优化:可以频繁创建销毁,内存开销小
  • 状态管理:状态与配置分离,便于管理
  • 复用性:相同配置的Widget可以复用Element

Element生命周期:

  1. mount:插入树中
  2. update:Widget配置更新
  3. activate:从非活动状态恢复
  4. deactivate:移到非活动状态
  5. unmount:从树中移除

触发条件:

  • 重建:父Widget重建,子Widget类型或Key改变
  • 更新:Widget配置改变但类型和Key相同
  • 卸载:Widget从树中移除

三棵树关系示意:

flowchart LR
  W["Widget 树\n配置、不可变"]
  E["Element 树\n实例、生命周期"]
  R["RenderObject 树\n布局与绘制"]
  W --> E
  E --> R

5. Flutter的渲染管线

问题:

  • 从用户交互到屏幕显示的完整流程
  • buildlayoutpaint三个阶段的执行顺序和优化点
  • 什么是RepaintBoundary?如何使用它优化性能?
  • 解释Layer树的作用

参考答案:

完整流程:

  1. 用户触发手势 → GestureBinding处理
  2. 触发setState → 标记Element为dirty
  3. Build阶段:重建Widget树
  4. Layout阶段:计算RenderObject的大小和位置
  5. Paint阶段:生成Layer树
  6. Composite阶段:合成Layer
  7. Skia渲染到GPU
  8. 显示到屏幕

从触发到上屏(简化管线):

flowchart TD
  G[手势 / 调度 / setState] --> B[Build:Widget 树]
  B --> L[Layout:约束与尺寸]
  L --> P[Paint:Layer / 绘制指令]
  P --> C[Composite 合成]
  C --> S[Skia → GPU → 屏幕]

三个阶段优化:

  • Build优化:减少重建范围,使用const Widget
  • Layout优化:避免不必要的layout,使用SizedBox限制大小
  • Paint优化:使用RepaintBoundary隔离重绘区域

RepaintBoundary:
创建独立的Layer,当子树重绘时不影响父树,适用于:

  • 频繁更新的动画区域
  • 复杂的静态背景
  • 列表项

Layer树:

  • 记录绘制指令
  • 支持Layer复用
  • 用于合成和光栅化

6. State管理

问题:

  • StatefulWidget的State生命周期
  • setState的执行机制和性能影响
  • 为什么需要状态管理方案?比较Provider、Riverpod、Bloc、GetX的优缺点
  • 如何设计一个全局状态管理方案?

参考答案:

State生命周期:

  1. createState()
  2. initState()
  3. didChangeDependencies()
  4. build()
  5. didUpdateWidget()
  6. setState()
  7. deactivate()
  8. dispose()

StatefulWidget 状态生命周期(简化):

stateDiagram-v2
  [*] --> createState
  createState --> initState
  initState --> didChangeDependencies
  didChangeDependencies --> inTree: build 首帧后进入树
  inTree --> inTree: setState / didUpdateWidget 再 build
  inTree --> deactivate: 从树移除
  deactivate --> dispose
  dispose --> [*]

setState机制:

  • 标记Element为dirty
  • 触发下一帧重建
  • 性能影响:重建整个子树

状态管理方案对比:

方案 优点 缺点 适用场景
Provider 简单易用,官方推荐 需要BuildContext 中小型项目
Riverpod 编译时安全,无需Context 学习曲线较陡 大型项目
Bloc 清晰的状态流转,可测试 代码量大 企业级应用
GetX 功能全面,代码简洁 过度封装,难以调试 快速开发

全局状态管理设计:

1
2
3
4
5
6
7
8
9
// 使用InheritedWidget
class AppState extends InheritedWidget {
final UserModel user;
final ThemeMode theme;

static AppState of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<AppState>();
}
}

三、性能优化

7. Flutter性能分析

问题:

  • 如何使用Flutter DevTools进行性能分析?
  • 什么是”jank”?如何定位和解决?
  • 解释Flutter的”帧率”概念,如何保持60fps?
  • Profile模式和Release模式的区别

参考答案:

DevTools性能分析:

  1. Performance面板:查看帧率、CPU使用率
  2. Flutter Frames:分析每帧的耗时
  3. CPU Profiler:定位性能瓶颈
  4. Memory面板:分析内存使用

Jank(卡顿):

  • 定义:帧渲染时间超过16.67ms(60fps)
  • 定位:使用Performance Overlay查看
  • 解决:优化build/layout/paint阶段

保持60fps:

  • 每帧耗时 < 16.67ms
  • 避免在build方法中执行耗时操作
  • 使用compute进行耗时计算
  • 优化列表性能

Profile vs Release:

  • Profile:保留调试信息,性能接近Release
  • Release:无调试信息,性能最优

8. 列表优化

问题:

  • ListViewGridView的懒加载机制
  • 如何优化长列表的性能?
  • ListView.builderListView的区别
  • 什么情况下使用ListView.separated

参考答案:

懒加载机制:

  • 只构建可见区域的Widget
  • 滚动时动态创建和销毁Widget
  • 使用Viewport实现

长列表优化:

  1. 使用ListView.builder
  2. 设置itemExtent(固定高度)
  3. 使用const Widget
  4. 避免复杂的item布局
  5. 使用AutomaticKeepAliveClientMixin(必要时)

ListView.builder vs ListView:

  • ListView:一次性构建所有子Widget,适合少量固定内容
  • ListView.builder:按需构建,适合长列表

ListView.separated:
用于需要分隔线的列表,自动管理分隔Widget的生命周期。


9. 图片和资源优化

问题:

  • 图片缓存机制是怎样的?
  • 如何处理大图片加载导致的内存问题?
  • 解释Image组件的各种构造函数的使用场景

参考答案:

图片缓存机制:

  • 内存缓存:PaintingBinding.instance.imageCache
  • 默认缓存100张图片或100MB
  • 可自定义缓存大小

大图片处理:

  1. 使用cacheWidth/cacheHeight限制解码大小
  2. 使用ResizeImage
  3. 分块加载大图
  4. 使用Native图片加载

Image构造函数:

  • Image.asset:加载assets图片
  • Image.network:加载网络图片
  • Image.file:加载本地文件
  • Image.memory:加载内存数据
  • ImageStream:自定义图片加载

四、平台交互

10. Platform Channels

问题:

  • MethodChannelEventChannelBasicMessageChannel的区别
  • 如何实现Flutter与原生平台的双向通信?
  • Platform Channel的数据序列化机制
  • 如何处理异步的Platform调用?

参考答案:

三种Channel区别:

  • MethodChannel:方法调用,一次性通信
  • EventChannel:事件流,持续通信
  • BasicMessageChannel:消息传递,双向通信

双向通信实现:

1
2
3
4
5
6
7
8
9
10
// Flutter端
final channel = MethodChannel('com.example/channel');
channel.setMethodCallHandler((call) async {
if (call.method == 'nativeCall') {
// 处理原生调用
}
});

// 原生端调用Flutter
await channel.invokeMethod('flutterMethod', args);

MethodChannel 调用时序(示意):

sequenceDiagram
  participant D as Dart 业务层
  participant C as MethodChannel
  participant E as Flutter Engine
  participant N as 原生 Android / iOS
  D->>C: invokeMethod(name, args)
  C->>E: 序列化 StandardMessageCodec
  E->>N: 平台通道分发
  N-->>E: 返回值 / 错误
  E-->>C: 反序列化
  C-->>D: Future 完成

数据序列化:

  • 使用StandardMessageCodec
  • 支持基本类型:int、double、String、List、Map
  • 自定义类型需要转换

异步处理:

  • Platform调用是异步的
  • 使用async/await处理
  • 注意线程切换

11. Platform Views

问题:

  • 什么是PlatformView?使用场景是什么?
  • AndroidViewUiKitView的实现原理
  • Platform View的性能问题和解决方案

参考答案:

Platform View用途:

  • 嵌入原生视图(地图、WebView、相机预览等)
  • 复用原生组件
  • 性能敏感的UI

实现原理:

  • AndroidView:使用Virtual Display或Hybrid Composition
  • UiKitView:使用UIKit集成

性能问题:

  • 内存占用高
  • 渲染性能差
  • 手势冲突

解决方案:

  • 限制Platform View数量
  • 使用Hybrid Composition(Android)
  • 优化原生视图

五、架构设计

12. 应用架构

问题:

  • 你如何设计一个大型Flutter应用的架构?
  • MVVM、Clean Architecture在Flutter中的实践
  • 如何实现模块化和组件化?
  • 依赖注入在Flutter中的实现方式

参考答案:

大型应用架构:

1
2
3
4
5
6
7
8
9
10
11
12
13
lib/
├── core/ # 核心功能
│ ├── network/
│ ├── storage/
│ └── utils/
├── features/ # 功能模块
│ ├── auth/
│ ├── home/
│ └── profile/
├── shared/ # 共享组件
│ ├── widgets/
│ └── models/
└── main.dart

Clean Architecture 分层依赖方向:

flowchart TB
  subgraph presentation[Presentation UI]
    UI[Widgets / State]
  end
  subgraph domain[Domain]
    UC[UseCase / Entity]
  end
  subgraph data[Data]
    REPO[Repository 实现]
    DS[数据源 API DB]
  end
  UI --> UC
  UC --> REPO
  REPO --> DS

Clean Architecture:

  • Presentation层:UI、State管理
  • Domain层:业务逻辑、UseCase
  • Data层:数据源、Repository

模块化实现:

  • 使用package分离模块
  • 定义清晰的接口
  • 使用依赖注入解耦

依赖注入:

  • get_it
  • injectable
  • provider

13. 导航和路由

问题:

  • Navigator 1.0 vs Navigator 2.0的区别
  • 如何实现深层链接(Deep Link)?
  • 路由守卫的实现
  • 如何管理复杂的导航栈?

参考答案:

Navigator 1.0 vs 2.0:

  • Navigator 1.0:命令式,简单易用
  • Navigator 2.0:声明式,支持复杂场景

Deep Link实现:

1
2
3
4
5
6
7
// Android: AndroidManifest.xml
// iOS: Info.plist
// Flutter: uni_links包

getLinksStream().listen((link) {
// 处理深度链接
});

路由守卫:

1
2
3
4
5
6
class AuthGuard extends RouteGuard {
@override
Future<bool> canNavigate(RouteSettings settings) async {
return await checkAuth();
}
}

复杂导航栈:

  • 使用Navigator 2.0
  • 维护路由栈状态
  • 使用RouterDelegate

六、测试与质量

14. 测试策略

问题:

  • Flutter中的单元测试、Widget测试、集成测试
  • 如何提高测试覆盖率?
  • Mock和Stub在测试中的应用
  • 如何测试异步代码?

参考答案:

三种测试:

  • 单元测试:测试函数、类
  • Widget测试:测试UI组件
  • 集成测试:测试完整流程

提高覆盖率:

  • 使用flutter test --coverage
  • 重点测试业务逻辑
  • 使用Mock隔离依赖

Mock和Stub:

1
2
3
4
5
6
// 使用mockito
class MockApi extends Mock implements Api {}

test('test', () {
when(mockApi.fetch()).thenAnswer((_) async => 'data');
});

异步测试:

1
2
3
4
5
6
test('async test', () async {
await expectLater(
stream,
emitsInOrder([1, 2, 3])
);
});

15. 代码质量

问题:

  • 如何在团队中推行代码规范?
  • Flutter中的静态分析工具
  • 如何设计可测试的代码?

参考答案:

代码规范:

  • 使用analysis_options.yaml
  • 配置lint规则
  • 代码审查流程

静态分析工具:

  • dart analyze
  • flutter analyze
  • dart_code_metrics

可测试代码设计:

  • 依赖注入
  • 单一职责
  • 接口抽象

七、实战问题

16. 复杂场景处理

问题:

  • 如何实现一个自定义的滑动删除效果?
  • 如何优化首屏启动时间?
  • 如何处理复杂的表单验证?
  • 如何实现国际化(i18n)?

参考答案:

滑动删除:

1
2
3
4
5
6
7
Dismissible(
key: Key(item.id),
onDismissed: (direction) {
// 删除逻辑
},
child: ListTile(...),
)

首屏优化:

  1. 延迟加载非关键资源
  2. 优化main方法
  3. 使用预加载
  4. 减少首屏Widget复杂度

表单验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
Form(
key: formKey,
child: Column(
children: [
TextFormField(
validator: (value) {
if (value.isEmpty) return '请输入';
return null;
},
),
],
),
)

国际化:

1
2
3
4
5
6
7
8
9
MaterialApp(
localizationsDelegates: [
AppLocalizationsDelegate(),
],
supportedLocales: [
Locale('en'),
Locale('zh'),
],
)

17. 错误处理

问题:

  • Flutter中的错误捕获机制
  • 如何实现全局错误处理?
  • 如何上报和分析线上错误?

参考答案:

错误捕获:

1
2
3
4
5
6
7
8
9
10
11
// Flutter错误
FlutterError.onError = (details) {
// 处理Flutter错误
};

// Dart错误
runZonedGuarded(() {
runApp(MyApp());
}, (error, stack) {
// 处理Dart错误
});

全局错误处理:

  • 使用FlutterError.onError
  • 使用runZonedGuarded
  • 使用PlatformDispatcher.instance.onError

错误上报:

  • Sentry
  • Firebase Crashlytics
  • Bugly

八、进阶问题

20. Flutter引擎

问题:

  • Flutter引擎的架构是怎样的?
  • Skia渲染引擎的作用
  • Dart VM的工作原理

参考答案:

Flutter引擎架构:

  • Framework层:Dart代码
  • Engine层:C++代码,包括Skia、Dart VM
  • Embedder层:平台特定代码

Skia作用:

  • 2D图形渲染库
  • 将Layer树渲染到GPU
  • 支持多种后端(OpenGL、Vulkan、Metal)

Dart VM:

  • JIT编译(开发模式)
  • AOT编译(发布模式)
  • 垃圾回收
  • 异步支持

21. 热重载原理

问题:

  • Flutter的热重载是如何实现的?
  • 热重载的限制是什么?
  • 为什么有些改动需要完全重启?

参考答案:

热重载原理:

  1. 修改代码
  2. Dart VM增量编译
  3. 注入更新代码到Dart VM
  4. 触发Widget树重建
  5. 保持应用状态

限制:

  • 不能修改main方法
  • 不能修改全局变量初始化
  • 不能修改枚举类型
  • 不能修改泛型类型

需要重启的情况:

  • 修改了枚举
  • 修改了泛型
  • 修改了main方法
  • 修改了全局变量初始值

22. 包体积优化

问题:

  • 如何减小Flutter应用的包体积?
  • Tree Shaking在Flutter中的应用
  • 如何拆分APK/IPA?

参考答案:

包体积优化:

  1. 使用–split-debug-info
  2. 压缩图片资源
  3. 移除未使用的代码
  4. 使用延迟加载
  5. 优化第三方库

Tree Shaking:

  • Dart编译器自动移除未使用代码
  • 需要避免动态调用
  • 使用const优化

APK/IPA拆分:

1
2
flutter build apk --split-per-abi
flutter build ios --split-debug-info

九、开放性问题

23. 技术选型

问题:

  • 什么情况下你会选择Flutter而不是原生开发?
  • Flutter的局限性是什么?
  • 你对Flutter的未来发展有什么看法?

参考答案:

选择Flutter的场景:

  • 跨平台需求(iOS/Android/Web/Desktop)
  • 快速迭代
  • UI为主的应用
  • 团队熟悉Dart

Flutter局限性:

  • 包体积较大
  • Platform View性能
  • 原生功能需要桥接
  • 不适合AR/VR等高性能场景

未来发展:

  • Impeller渲染引擎
  • Web性能提升
  • Desktop支持完善
  • 生态持续丰富

24. 问题解决

问题:

  • 请分享一个你遇到的最复杂的Flutter问题,以及解决过程
  • 你如何学习和跟进Flutter的最新技术?

参考答案:

问题解决思路:

  • 问题定位
  • 分析原因
  • 查阅文档
  • 寻求社区帮助
  • 总结经验

学习方法:

  • 官方文档
  • GitHub issues
  • Flutter社区
  • 技术博客
  • 实践项目

十、实战编程题

编程题1:实现一个带缓存的图片加载组件

要求:

  • 支持内存缓存和磁盘缓存
  • 显示加载占位符
  • 处理加载失败情况
  • 支持图片压缩

时间: 30分钟

示例答案: 生产环境常用 **cached_network_image**(磁盘缓存由 flutter_cache_manager 完成,内存由框架缓存解码结果)。memCacheWidth / maxWidthDiskCache 可限制解码尺寸,起到省内存与「压缩」效果。

1
2
3
4
5
6
7
8
9
10
11
import 'package:cached_network_image/cached_network_image.dart';

Widget buildImage(String url) {
return CachedNetworkImage(
imageUrl: url,
memCacheWidth: 400,
maxWidthDiskCache: 800,
placeholder: (_, __) => const Center(child: CircularProgressIndicator()),
errorWidget: (_, __, ___) => const Icon(Icons.broken_image_outlined),
);
}

编程题2:实现一个高性能的无限滚动列表

要求:

  • 支持下拉刷新
  • 支持上拉加载更多
  • 优化滚动性能
  • 处理异常情况

时间: 40分钟

示例答案: ListView.builder 只构建可见项;**RefreshIndicator** 下拉刷新;**ScrollController** 监听接近底部触发加载;错误用 SnackBar 或底部占位重试。

1
2
3
4
5
6
7
8
9
10
11
12
RefreshIndicator(
onRefresh: () async => reload(),
child: ListView.builder(
controller: scrollController,
itemCount: items.length + (loadingMore ? 1 : 0),
itemBuilder: (c, i) {
if (i == items.length) return const Center(child: CircularProgressIndicator());
return ListTile(title: Text(items[i]));
},
),
);
// scrollController 在 listener 中:pixels >= maxScrollExtent - 200 时 loadMore()

编程题3:实现一个表单验证系统

要求:

  • 支持多种验证规则
  • 实时验证
  • 显示错误信息
  • 支持异步验证

时间: 35分钟

示例答案: **Form + GlobalKey<FormState>** 做整表校验;**autovalidateMode** 控制实时;同步规则写在 validator;异步(如用户名占用)在提交按钮里先通过同步校验再 await 接口,失败则 setState 写入字段 errorText 或单独 Text

1
2
3
4
5
6
7
8
9
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (v) {
if (v == null || v.isEmpty) return '必填';
if (!v.contains('@')) return '邮箱格式';
return null;
},
);
// 提交:if (formKey.currentState!.validate()) { await asyncCheck(); }

编程题4:实现一个简单的状态管理库

要求:

  • 支持状态存储
  • 支持状态订阅
  • 支持状态更新通知
  • 避免不必要的重建

时间: 45分钟

示例答案:**ChangeNotifier** 存状态,notifyListeners() 通知;UI 侧 **ListenableBuilder(listenable: store, builder: ...)** 只重建订阅该 notifier 的子树;向下分发可用 **InheritedNotifier** 包一层,等价于迷你 Provider。

1
2
3
4
5
6
7
8
9
class MiniStore extends ChangeNotifier {
int count = 0;
void inc() {
count++;
notifyListeners();
}
}

// ListenableBuilder(listenable: store, builder: (_, __) => Text('${store.count}'))

编程题5:实现一个路由管理系统

要求:

  • 支持命名路由
  • 支持路由守卫
  • 支持路由参数传递
  • 支持深层链接

时间: 40分钟

示例答案: 推荐 **go_routerGoRoute 声明路径与命名、**redirect 做登录守卫、路径参数用 :id;深层链接由系统 URL 交给同一套 GoRouter 解析。纯 Navigator 时可用 **onGenerateRoute** 根据 RouteSettings.name 解析并返回 MaterialPageRoute

1
2
3
4
5
6
7
8
GoRouter(
redirect: (ctx, state) =>
state.matchedLocation.startsWith('/user') && !isLoggedIn ? '/login' : null,
routes: [
GoRoute(path: '/login', builder: (_, __) => const LoginPage()),
GoRoute(path: '/detail/:id', builder: (_, s) => DetailPage(id: s.pathParameters['id']!)),
],
);

编程题6:实现一个网络请求封装

要求:

  • 支持GET/POST等常用方法
  • 支持请求拦截器
  • 支持响应拦截器
  • 支持错误处理
  • 支持缓存

时间: 45分钟

示例答案: 使用 **dioInterceptorsWrapper 实现请求/响应拦截(加 Token、统一错误码);**dio_cache_interceptor 或自定义 Interceptor 按 URL 做 GET 缓存。

1
2
3
4
5
6
7
8
9
10
11
final dio = Dio();
dio.interceptors.add(InterceptorsWrapper(
onRequest: (o, h) {
o.headers['Authorization'] = 'Bearer $token';
return h.next(o);
},
onError: (e, h) {
if (e.response?.statusCode == 401) { /* 刷新 token / 登出 */ }
return h.next(e);
},
));

编程题7:实现一个动画组件库

要求:

  • 实现淡入淡出动画
  • 实现滑动动画
  • 实现缩放动画
  • 支持动画组合
  • 支持自定义曲线

时间: 40分钟

示例答案: **AnimationController + CurvedAnimation**FadeTransition / SlideTransition / ScaleTransition 嵌套实现组合;曲线用 **Curves.easeOutCubic** 等或 **Cubic(0.42, 0, 0.58, 1)** 自定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
late final AnimationController _c = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
late final Animation<double> _t = CurvedAnimation(parent: _c, curve: Curves.easeOut);

@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _t,
child: SlideTransition(
position: Tween<Offset>(begin: const Offset(0, 0.08), end: Offset.zero).animate(_t),
child: ScaleTransition(scale: Tween<double>(begin: 0.96, end: 1).animate(_t), child: child),
),
);
}

编程题8:实现一个主题切换系统

要求:

  • 支持多主题切换
  • 支持主题持久化
  • 支持动态主题
  • 支持局部主题覆盖

时间: 35分钟

示例答案:**MaterialApp.theme / darkTheme / themeMode;持久化 **SharedPreferences 保存 light|dark|system;动态主色可用 **ThemeData(colorSchemeSeed: seed)**;局部覆盖在子树再包 **Theme(data: Theme.of(context).copyWith(...), child: ...)**

1
2
3
4
5
6
7
8
9
10
11
12
// 持久化 themeMode 后:
MaterialApp(
theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue)),
darkTheme: ThemeData.dark(),
themeMode: savedMode, // ThemeMode.system / light / dark
);

// 局部:
Theme(
data: Theme.of(context).copyWith(colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal)),
child: const SomeChild(),
);

十一、特定技术栈深度问题

Riverpod深度问题

问题1:Riverpod的核心概念

  • Provider的本质是什么?
  • ProviderScope的作用是什么?
  • ProviderContainer如何管理Provider?

问题2:Riverpod的状态管理

  • StateProvider vs StateNotifierProvider的区别
  • 如何处理异步状态?
  • 如何实现Provider之间的依赖?

问题3:Riverpod的性能优化

  • 如何避免不必要的重建?
  • select方法的作用是什么?
  • 如何使用ProviderScope进行性能隔离?

问题4:Riverpod的测试

  • 如何在测试中覆盖Provider?
  • 如何Mock Provider?
  • 如何测试异步Provider?

Bloc深度问题

问题1:Bloc的核心概念

  • Bloc和Cubit的区别是什么?
  • BlocProvider的作用是什么?
  • BlocBuilder vs BlocListener vs BlocConsumer的区别

问题2:Bloc的状态管理

  • 如何设计状态类?
  • 如何处理复杂的状态流转?
  • 如何实现Bloc之间的通信?

问题3:Bloc的测试

  • 如何测试Bloc?
  • bloc_test包的使用
  • 如何Mock依赖?

问题4:Bloc的最佳实践

  • 如何组织Bloc代码?
  • 如何避免Bloc过于复杂?
  • 如何处理导航逻辑?

Clean Architecture深度问题

问题1:Clean Architecture的层级

  • Entity、UseCase、Repository的作用是什么?
  • 为什么需要这么多层级?
  • 如何避免层级之间的耦合?

问题2:Clean Architecture的实现

  • 如何在Flutter中实现Clean Architecture?
  • 如何处理数据映射?
  • 如何处理错误?

问题3:Clean Architecture的测试

  • 如何测试每一层?
  • 如何Mock依赖?
  • 如何保证测试覆盖率?

问题4:Clean Architecture的权衡

  • Clean Architecture的优缺点是什么?
  • 什么情况下不适合使用?
  • 如何简化Clean Architecture?

GetX深度问题

问题1:GetX的核心功能

  • GetX的状态管理原理是什么?
  • GetX的路由管理如何实现?
  • GetX的依赖注入如何工作?

问题2:GetX的优缺点

  • GetX的优点是什么?
  • GetX的缺点是什么?
  • 什么情况下适合使用GetX?

问题3:GetX的性能

  • GetX的性能如何?
  • 如何优化GetX的性能?
  • GetX的内存管理如何?

问题4:GetX的最佳实践

  • 如何组织GetX代码?
  • 如何避免GetX的陷阱?
  • 如何测试GetX代码?

自定义渲染深度问题

问题1:RenderObject

  • 如何自定义RenderObject?
  • RenderObject的布局协议是什么?
  • 如何优化RenderObject的性能?

问题2:CustomPainter

  • CustomPainter的使用场景是什么?
  • 如何实现复杂的自定义绘制?
  • 如何优化CustomPainter的性能?

问题3:Layer

  • Layer的作用是什么?
  • 如何自定义Layer?
  • 如何使用Layer优化性能?

问题4:Sliver

  • Sliver的原理是什么?
  • 如何自定义Sliver?
  • 如何实现复杂的滚动效果?

Platform Channels深度问题

问题1:Platform Channel的原理

  • Platform Channel的通信机制是什么?
  • 如何优化Platform Channel的性能?
  • 如何处理Platform Channel的线程问题?

问题2:Platform Channel的类型

  • MethodChannel、EventChannel、BasicMessageChannel的区别
  • 如何选择合适的Channel类型?
  • 如何实现双向通信?

问题3:Platform Channel的序列化

  • Platform Channel如何序列化数据?
  • 如何传递自定义类型?
  • 如何处理大数据传输?

问题4:Platform Channel的错误处理

  • 如何处理Platform Channel的错误?
  • 如何保证Platform Channel的稳定性?
  • 如何测试Platform Channel?

面试建议

技术深度考察重点:

  1. 三棵树的理解程度
  2. 性能优化的实战经验
  3. 状态管理方案的选择理由
  4. 平台交互的实现能力

建议面试流程:

  1. 先问基础概念,确认基本功
  2. 再问实战场景,考察解决问题能力
  3. 最后问开放性问题,了解思维方式
  4. 编程题考察实际编码能力
  5. 架构设计题考察系统设计能力

参考资源

设计模式全解:原理、UML 与项目实战

系统梳理 GoF 23 种设计模式,结合前端与 Node.js 实战代码,配 UML 类图与时序示意图辅助理解。


目录

  1. 什么是设计模式
  2. 创建型模式(5 种)
  3. 结构型模式(7 种)
  4. 行为型模式(11 种)
  5. 速查总表

一、什么是设计模式

设计模式(Design Pattern) 是软件工程中反复出现问题的通用可复用解决方案。1994 年,Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides(即「四人组 GoF」)在《设计模式:可复用面向对象软件的基础》中总结了 23 种经典模式,按目的分为三类:

1
2
3
创建型(Creational)  ——  解决对象的创建问题
结构型(Structural) —— 解决类/对象的组合问题
行为型(Behavioral) —— 解决对象间职责与通信问题

设计原则(SOLID)

原则 说明
S ingle Responsibility 一个类只做一件事
O pen/Closed 对扩展开放,对修改关闭
L iskov Substitution 子类可替换父类而不破坏程序
I nterface Segregation 接口最小化,不强迫实现不需要的方法
D ependency Inversion 依赖抽象,而非具体实现

二、创建型模式(5 种)

2.1 单例模式(Singleton)

意图:保证一个类只有一个实例,并提供全局访问点。

场景:全局状态管理(Store)、日志记录器、配置管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Store {
static #instance = null;

#state = {};

static getInstance() {
if (!Store.#instance) {
Store.#instance = new Store();
}
return Store.#instance;
}

getState() { return this.#state; }
setState(patch) { Object.assign(this.#state, patch); }
}

const a = Store.getInstance();
const b = Store.getInstance();
console.log(a === b); // true

2.2 工厂方法模式(Factory Method)

意图:定义创建对象的接口,让子类决定实例化哪个类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Button {
render() { throw new Error('abstract'); }
}
class PrimaryButton extends Button {
render() { return `<button class="btn-primary">OK</button>`; }
}
class DangerButton extends Button {
render() { return `<button class="btn-danger">Delete</button>`; }
}

function createButton(type) {
const map = { primary: PrimaryButton, danger: DangerButton };
const Ctor = map[type];
if (!Ctor) throw new Error(`Unknown type: ${type}`);
return new Ctor();
}

createButton('primary').render(); // <button class="btn-primary">OK</button>

2.3 抽象工厂模式(Abstract Factory)

意图:提供一个接口,用于创建一系列相关或依赖对象,而不指定具体类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const lightFactory = {
createButton: () => ({ type: 'button', theme: 'light' }),
createInput: () => ({ type: 'input', theme: 'light' }),
};
const darkFactory = {
createButton: () => ({ type: 'button', theme: 'dark' }),
createInput: () => ({ type: 'input', theme: 'dark' }),
};

function buildUI(factory) {
return {
button: factory.createButton(),
input: factory.createInput(),
};
}
buildUI(darkFactory); // { button: {theme:'dark'}, input: {theme:'dark'} }

2.4 建造者模式(Builder)

意图:将复杂对象的构造表示分离,同一构建过程可创建不同表示。

场景:SQL 查询构建、配置对象构造、链式调用 API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class QueryBuilder {
#table = '';
#conditions = [];
#fields = ['*'];
#limit = null;

from(table) { this.#table = table; return this; }
select(...f) { this.#fields = f; return this; }
where(cond) { this.#conditions.push(cond); return this; }
limit(n) { this.#limit = n; return this; }

build() {
let sql = `SELECT ${this.#fields.join(', ')} FROM ${this.#table}`;
if (this.#conditions.length) sql += ` WHERE ${this.#conditions.join(' AND ')}`;
if (this.#limit !== null) sql += ` LIMIT ${this.#limit}`;
return sql;
}
}

new QueryBuilder()
.from('users')
.select('id', 'name')
.where('age > 18')
.where('active = 1')
.limit(10)
.build();
// SELECT id, name FROM users WHERE age > 18 AND active = 1 LIMIT 10

2.5 原型模式(Prototype)

意图:通过复制(克隆)已有实例来创建新对象,而不是通过 new

1
2
3
4
5
6
7
8
9
10
11
12
13
const userProto = {
greet() { return `Hi, I'm ${this.name}`; },
clone() { return Object.create(this); },
};

const alice = Object.create(userProto);
alice.name = 'Alice';

const bob = alice.clone();
bob.name = 'Bob';

alice.greet(); // Hi, I'm Alice
bob.greet(); // Hi, I'm Bob

三、结构型模式(7 种)

3.1 适配器模式(Adapter)

意图:将一个接口转换成调用方期望的另一个接口——解决不兼容问题。

场景:旧接口兼容、第三方 SDK 包装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 旧日志库,接口为 log(msg)
class OldLogger {
log(msg) { console.log('[OLD]', msg); }
}

// 新系统要求 info / warn / error 三个方法
class LoggerAdapter {
constructor(logger) { this.logger = logger; }
info(msg) { this.logger.log(`INFO ${msg}`); }
warn(msg) { this.logger.log(`WARN ${msg}`); }
error(msg) { this.logger.log(`ERROR ${msg}`); }
}

const logger = new LoggerAdapter(new OldLogger());
logger.info('server started'); // [OLD] INFO server started

3.2 桥接模式(Bridge)

意图:将抽象部分与实现部分分离,使二者可以独立变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 实现部分:渲染引擎
const canvasRenderer = { draw: (shape) => `Canvas: draw ${shape}` };
const svgRenderer = { draw: (shape) => `SVG: draw ${shape}` };

// 抽象部分:形状
class Shape {
constructor(renderer) { this.renderer = renderer; }
}
class Circle extends Shape {
draw() { return this.renderer.draw('circle'); }
}
class Square extends Shape {
draw() { return this.renderer.draw('square'); }
}

new Circle(svgRenderer).draw(); // SVG: draw circle
new Square(canvasRenderer).draw(); // Canvas: draw square

3.3 组合模式(Composite)

意图:将对象组织成树形结构,使单个对象和组合对象的使用方式一致。

场景:文件系统、组件树、权限树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class File {
constructor(name, size) { this.name = name; this.size = size; }
getSize() { return this.size; }
}
class Folder {
constructor(name) { this.name = name; this.children = []; }
add(child) { this.children.push(child); return this; }
getSize() { return this.children.reduce((s, c) => s + c.getSize(), 0); }
}

const root = new Folder('root')
.add(new File('a.js', 10))
.add(
new Folder('src')
.add(new File('index.js', 20))
.add(new File('utils.js', 15))
);

root.getSize(); // 45

3.4 装饰器模式(Decorator)

意图:在不改变原对象的前提下,动态地为其添加功能。

场景:中间件(Koa/Express)、日志增强、缓存包装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function withLogging(fn) {
return function (...args) {
console.log(`[CALL] ${fn.name}(${args})`);
const result = fn.apply(this, args);
console.log(`[RET] ${result}`);
return result;
};
}

function add(a, b) { return a + b; }
const loggedAdd = withLogging(add);
loggedAdd(2, 3);
// [CALL] add(2,3)
// [RET] 5

3.5 外观模式(Facade)

意图:为子系统提供统一的高层接口,隐藏内部复杂性。

场景:SDK 封装、初始化流程统一入口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class AuthService   { login(u, p)    { return { token: 'tok_' + u }; } }
class ProfileService { fetch(token) { return { name: 'Alice' }; } }
class LogService { record(action) { console.log('[LOG]', action); } }

class AppFacade {
#auth = new AuthService();
#profile = new ProfileService();
#log = new LogService();

async signIn(username, password) {
const { token } = this.#auth.login(username, password);
const profile = this.#profile.fetch(token);
this.#log.record(`login:${username}`);
return { token, profile };
}
}

new AppFacade().signIn('alice', 'pw');

3.6 享元模式(Flyweight)

意图:共享细粒度对象以节省内存,将对象内在状态与外在状态分离。

场景:字符渲染、粒子系统、大量相似节点池。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class CharStyle {
constructor(font, size, color) {
this.font = font; this.size = size; this.color = color;
}
}

class CharStylePool {
#pool = new Map();
get(font, size, color) {
const key = `${font}-${size}-${color}`;
if (!this.#pool.has(key)) this.#pool.set(key, new CharStyle(font, size, color));
return this.#pool.get(key);
}
size() { return this.#pool.size; }
}

const pool = new CharStylePool();
// 渲染 1000 个字符,只需要极少的 CharStyle 对象
const chars = Array.from('Hello World x1000').map(ch => ({
char: ch,
style: pool.get('Arial', 14, '#333'),
}));
console.log(pool.size()); // 1(所有字符共享同一样式对象)

3.7 代理模式(Proxy)

意图:为另一个对象提供代理以控制对它的访问。

场景:懒加载、权限控制、缓存、数据校验(ES Proxy)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function createValidator(target, rules) {
return new Proxy(target, {
set(obj, prop, value) {
if (rules[prop] && !rules[prop](value)) {
throw new TypeError(`Invalid value for "${prop}": ${value}`);
}
obj[prop] = value;
return true;
},
});
}

const user = createValidator({}, {
age: v => typeof v === 'number' && v >= 0 && v <= 150,
email: v => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
});

user.age = 25; // OK
user.email = 'a@b.com'; // OK
// user.age = -1; // TypeError: Invalid value for "age": -1

四、行为型模式(11 种)

4.1 责任链模式(Chain of Responsibility)

意图:将请求沿着处理者链传递,直到某个处理者处理它为止。

场景:中间件管道(Koa)、审批流程、事件冒泡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Handler {
setNext(handler) { this.next = handler; return handler; }
handle(req) { return this.next?.handle(req); }
}

class AuthHandler extends Handler {
handle(req) {
if (!req.token) return '401 Unauthorized';
return super.handle(req);
}
}
class RoleHandler extends Handler {
handle(req) {
if (req.role !== 'admin') return '403 Forbidden';
return super.handle(req);
}
}
class BusinessHandler extends Handler {
handle(req) { return `200 OK: Hello ${req.user}`; }
}

const chain = new AuthHandler();
chain.setNext(new RoleHandler()).setNext(new BusinessHandler());

chain.handle({ token: 'tok', role: 'admin', user: 'Alice' }); // 200 OK
chain.handle({ token: 'tok', role: 'guest' }); // 403 Forbidden
chain.handle({}); // 401 Unauthorized

4.2 命令模式(Command)

意图:将操作封装为对象,支持撤销、重做、队列调度。

场景:编辑器操作历史、任务队列、宏录制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class TextEditor {
#text = '';
#history = [];

execute(command) {
this.#text = command.execute(this.#text);
this.#history.push(command);
return this;
}
undo() {
const cmd = this.#history.pop();
if (cmd) this.#text = cmd.undo(this.#text);
return this;
}
value() { return this.#text; }
}

const appendCmd = (str) => ({
execute: (t) => t + str,
undo: (t) => t.slice(0, -str.length),
});

const ed = new TextEditor();
ed.execute(appendCmd('Hello')).execute(appendCmd(', World'));
ed.value(); // Hello, World
ed.undo();
ed.value(); // Hello

4.3 解释器模式(Interpreter)

意图:为语言定义文法,并实现解释器来解释该语言的句子。

场景:模板引擎、DSL 解析、规则引擎。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 极简四则运算解释器
function interpret(expr) {
expr = expr.trim();
const addIdx = expr.lastIndexOf('+');
const subIdx = expr.lastIndexOf('-');
const pivot = Math.max(addIdx, subIdx);
if (pivot > 0) {
const left = interpret(expr.slice(0, pivot));
const right = interpret(expr.slice(pivot + 1));
return expr[pivot] === '+' ? left + right : left - right;
}
return parseFloat(expr);
}

interpret('1 + 2 + 3'); // 6
interpret('10 - 3 + 2'); // 9

4.4 迭代器模式(Iterator)

意图:提供顺序访问集合元素的方法,而不暴露其内部表示。

JavaScript 内置迭代器协议(Symbol.iterator)即此模式的语言级实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Range {
constructor(start, end, step = 1) {
this.start = start; this.end = end; this.step = step;
}
[Symbol.iterator]() {
let cur = this.start;
return {
next: () => cur <= this.end
? { value: cur += this.step, done: false }
: { done: true },
};
}
}

[...new Range(1, 10, 2)]; // [3, 5, 7, 9, 11] — 每步 +2
for (const n of new Range(0, 5)) console.log(n); // 1 2 3 4 5

4.5 中介者模式(Mediator)

意图:用中介对象封装一组对象的交互,减少对象间直接引用。

场景:聊天室、事件总线、机场调度。

1
2
3
4
5
6
7
8
9
10
11
12
13
class EventBus {
#listeners = new Map();
on(event, fn) { (this.#listeners.get(event) ?? this.#listeners.set(event, new Set()).get(event)).add(fn); }
off(event, fn) { this.#listeners.get(event)?.delete(fn); }
emit(event, data) { this.#listeners.get(event)?.forEach(fn => fn(data)); }
}

const bus = new EventBus();
bus.on('login', ({ user }) => console.log(`Welcome, ${user}`));
bus.on('login', ({ user }) => console.log(`Log: ${user} logged in`));
bus.emit('login', { user: 'Alice' });
// Welcome, Alice
// Log: Alice logged in

4.6 备忘录模式(Memento)

意图:在不破坏封装的前提下,捕获并外部化对象的内部状态,以便恢复。

场景:撤销/重做、草稿保存、游戏存档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class FormState {
#snapshots = [];
#current = {};

set(patch) { this.#current = { ...this.#current, ...patch }; }
snapshot() { this.#snapshots.push({ ...this.#current }); }
restore() { if (this.#snapshots.length) this.#current = this.#snapshots.pop(); }
get() { return { ...this.#current }; }
}

const form = new FormState();
form.set({ name: 'Alice' });
form.snapshot();
form.set({ name: 'Bob', age: 30 });
form.get(); // { name: 'Bob', age: 30 }
form.restore();
form.get(); // { name: 'Alice' }

4.7 观察者模式(Observer)

意图:对象间一对多依赖,当一个对象状态变化时,自动通知所有依赖对象。

场景:Vue/React 响应式、DOM 事件、数据绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Subject {
#observers = new Set();
#state;

subscribe(fn) { this.#observers.add(fn); }
unsubscribe(fn) { this.#observers.delete(fn); }

setState(val) {
this.#state = val;
this.#observers.forEach(fn => fn(val));
}
getState() { return this.#state; }
}

const counter = new Subject();
counter.subscribe(v => console.log('A sees:', v));
counter.subscribe(v => console.log('B sees:', v));
counter.setState(42);
// A sees: 42
// B sees: 42

4.8 状态模式(State)

意图:允许对象在内部状态改变时改变其行为,使对象看起来修改了它的类。

场景:订单状态机、红绿灯、播放器状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const states = {
idle: { fetch: (ctx) => ctx.transition('loading') },
loading: {
resolve: (ctx, data) => { ctx.data = data; ctx.transition('success'); },
reject: (ctx, err) => { ctx.error = err; ctx.transition('error'); },
},
success: { reset: (ctx) => ctx.transition('idle') },
error: { reset: (ctx) => ctx.transition('idle') },
};

class AsyncMachine {
#state = 'idle';
data = null;
error = null;

transition(s) { this.#state = s; console.log('→', s); }
dispatch(action, payload) {
const handler = states[this.#state]?.[action];
if (!handler) return console.warn(`No "${action}" in state "${this.#state}"`);
handler(this, payload);
}
getState() { return this.#state; }
}

const m = new AsyncMachine();
m.dispatch('fetch'); // → loading
m.dispatch('resolve', { id: 1 }); // → success
m.dispatch('reset'); // → idle

4.9 策略模式(Strategy)

意图:定义一系列算法,封装每一个并使它们可互换,独立于使用者而变化。

场景:排序算法、支付方式、表单验证、折扣计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const sortStrategies = {
bubble: (arr) => {
const a = [...arr];
for (let i = 0; i < a.length; i++)
for (let j = 0; j < a.length - i - 1; j++)
if (a[j] > a[j+1]) [a[j], a[j+1]] = [a[j+1], a[j]];
return a;
},
quick: (arr) => {
if (arr.length <= 1) return arr;
const pivot = arr[0];
const left = arr.slice(1).filter(x => x <= pivot);
const right = arr.slice(1).filter(x => x > pivot);
return [...sortStrategies.quick(left), pivot, ...sortStrategies.quick(right)];
},
};

class Sorter {
constructor(strategy = 'quick') { this.strategy = strategy; }
sort(arr) { return sortStrategies[this.strategy](arr); }
}

new Sorter('bubble').sort([3, 1, 4, 1, 5]); // [1, 1, 3, 4, 5]
new Sorter('quick').sort([3, 1, 4, 1, 5]); // [1, 1, 3, 4, 5]

4.10 模板方法模式(Template Method)

意图:在基类定义算法骨架,将某些步骤的实现延迟到子类,不改变结构只改变细节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class DataExporter {
// 模板方法:算法骨架
export(data) {
const processed = this.process(data);
const formatted = this.format(processed);
this.save(formatted);
}

process(data) { return data.filter(Boolean); } // 默认实现
format(data) { throw new Error('abstract'); } // 子类实现
save(content) { console.log('saving:', content.slice(0, 40)); }
}

class CSVExporter extends DataExporter {
format(data) { return data.map(r => Object.values(r).join(',')).join('\n'); }
}
class JSONExporter extends DataExporter {
format(data) { return JSON.stringify(data, null, 2); }
}

const rows = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
new CSVExporter().export(rows); // saving: 1,Alice\n2,Bob
new JSONExporter().export(rows); // saving: [\n {\n "id": 1,

4.11 访问者模式(Visitor)

意图:在不改变元素类的前提下,为其定义作用于这些元素的新操作。

场景:AST 遍历、报表统计、编译器优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class NumberNode  { constructor(v)    { this.value = v; } accept(v) { return v.visitNumber(this); } }
class AddNode { constructor(l, r) { this.left = l; this.right = r; } accept(v) { return v.visitAdd(this); } }
class MulNode { constructor(l, r) { this.left = l; this.right = r; } accept(v) { return v.visitMul(this); } }

// 求值访问者
const evalVisitor = {
visitNumber: (n) => n.value,
visitAdd: (n) => n.left.accept(evalVisitor) + n.right.accept(evalVisitor),
visitMul: (n) => n.left.accept(evalVisitor) * n.right.accept(evalVisitor),
};

// 打印访问者
const printVisitor = {
visitNumber: (n) => `${n.value}`,
visitAdd: (n) => `(${n.left.accept(printVisitor)} + ${n.right.accept(printVisitor)})`,
visitMul: (n) => `(${n.left.accept(printVisitor)} * ${n.right.accept(printVisitor)})`,
};

// (2 + 3) * 4
const ast = new MulNode(new AddNode(new NumberNode(2), new NumberNode(3)), new NumberNode(4));
ast.accept(evalVisitor); // 20
ast.accept(printVisitor); // ((2 + 3) * 4)

五、速查总表

分类 模式 核心意图 常见场景
创建型 单例 唯一实例 Store、Logger、Config
创建型 工厂方法 子类决定实例化 组件工厂、解析器
创建型 抽象工厂 相关对象族 主题 UI、跨平台组件
创建型 建造者 分步构建复杂对象 QueryBuilder、配置
创建型 原型 克隆已有实例 对象池、深拷贝
结构型 适配器 接口转换 旧库兼容、SDK 包装
结构型 桥接 抽象与实现分离 渲染引擎、驱动层
结构型 组合 树形统一接口 文件系统、组件树
结构型 装饰器 动态扩展功能 中间件、HOC、缓存
结构型 外观 统一高层接口 SDK 初始化入口
结构型 享元 共享细粒度对象 字符渲染、粒子池
结构型 代理 控制对象访问 懒加载、权限、校验
行为型 责任链 链式处理请求 中间件管道、审批流
行为型 命令 操作对象化 撤销/重做、任务队列
行为型 解释器 语言文法解析 模板引擎、DSL
行为型 迭代器 统一遍历接口 for…of、Generator
行为型 中介者 集中对象交互 EventBus、聊天室
行为型 备忘录 状态快照/恢复 撤销历史、草稿
行为型 观察者 自动通知订阅者 Vue 响应式、事件
行为型 状态 状态驱动行为 订单流、状态机
行为型 策略 算法可互换 支付、排序、折扣
行为型 模板方法 固定骨架/可变步骤 导出流程、生命周期
行为型 访问者 不改类增加操作 AST 遍历、报表