Dart 语言概览:定位、编译模式与空安全
Dart 的定位
Dart 是 Google 为 Flutter 量身定制的编程语言,2011 年诞生,最初目标是替代 JavaScript,后来演变为 Flutter 的首选语言。选择 Dart 的核心原因有三:
- 同时支持 JIT 和 AOT:可以在开发期热重载,发布期高性能,这是其他语言组合难以同时满足的
- 单线程事件循环:消除了竞态条件,简化并发模型
- 强类型 + 声明式友好:配合 Flutter 的 Widget 树,写起来自然
JIT 与 AOT 两种编译模式
这是 Dart 最重要的工程设计之一,也是 Flutter 开发体验良好的根本原因。
JIT(Just-In-Time,即时编译)
JIT 在运行时把 Dart 源码编译成机器码。可以把它想象成一名「现场翻译」:你说一句,他翻一句,慢一点,但能立刻反映你的修改。
开发期使用 JIT 的好处:
- 支持热重载(Hot Reload):只把修改的代码增量编译注入,毫秒级刷新
- 支持调试断点:因为代码不是提前编译的,可以动态注入调试信息
- 更快的构建速度:不需要完整编译整个应用
JIT 工作流:
Dart 源码 → Dart VM (Kernel AST) → 增量编译 → 机器码(运行时生成)
↑
热重载时只更新这部分
AOT(Ahead-Of-Time,提前编译)
AOT 在构建时把 Dart 代码编译成原生机器码。像一位「专业笔译员」:提前把整本书译完,读起来流畅,但改了原文就要重新翻译整本。
发布期使用 AOT 的好处:
- 启动速度快:无需运行时编译,直接执行机器码
- 执行性能高:可以做更激进的优化(tree-shaking、内联等)
- 体积更小:未使用的代码被完全裁剪
AOT 工作流:
Dart 源码 → dart compile → ELF 二进制 → 直接运行(无 VM 解释器)
两种模式的对比
| 维度 | JIT(开发期) | AOT(发布期) |
|---|---|---|
| 热重载 | ✅ 支持 | ❌ 不支持 |
| 启动速度 | 较慢 | 快 |
| 运行性能 | 中等 | 高 |
| 二进制体积 | 包含 VM | 精简 |
| 调试支持 | ✅ 全量 | 有限 |
flutter run 默认用 JIT,flutter build apk --release 才会用 AOT。
Sound Null Safety
Dart 2.12 引入了 Sound Null Safety(健全空安全),这是 Dart 类型系统的一次根本升级。
为什么需要空安全
空指针异常(Null Pointer Exception,NPE)是软件领域最常见的运行时崩溃原因。null 被发明者 Tony Hoare 称为「十亿美元错误」——每个对象默认可能为 null,导致必须随处防御性判空。
Sound 的含义
「Sound」的意思是类型系统的保证是可靠的。如果编译器说某个变量不为 null,那它在运行时绝对不会是 null,不存在漏网之鱼。
相比之下,Java 的 @NonNull 注解是非 Sound 的——只是个提示,运行时仍可能抛 NPE。
可空与非空类型
String name = 'Flutter'; // 非空类型,绝不能赋 null
String? nickname = null; // 可空类型,后缀 ? 表示可能为 null
// 编译器在类型层面区分这两种情况
int length = name.length; // 安全,无需判空
int? nickLen = nickname?.length; // 必须用 ?. 操作符
空安全操作符
| 操作符 | 含义 | 示例 |
|---|---|---|
? |
声明可空类型 | String? s |
?. |
安全调用(null 时返回 null) | s?.length |
?? |
空合并(null 时用默认值) | s ?? 'default' |
??= |
空合并赋值 | s ??= 'init' |
! |
非空断言(null 时抛异常) | s!.length |
late |
延迟初始化(保证使用前初始化) | late String s |
late 关键字
late 用于解决「变量不能立即初始化,但使用时保证非空」的场景:
class MyWidget extends StatefulWidget {
late final AnimationController _controller;
@override
void initState() {
super.initState();
// 在 initState 中初始化,比声明时更晚,但保证在 build 前完成
_controller = AnimationController(vsync: this);
}
}
如果 late 变量在初始化前被访问,会抛 LateInitializationError,这是运行时检查,不是编译时检查。
Dart 的运行环境
Dart 代码有三种运行方式:
- Dart VM:开发期,支持 JIT 和调试
- Flutter Engine:移动/桌面端,AOT 编译后嵌入原生应用
- dart2js:编译为 JavaScript,运行在浏览器(Flutter Web)
理解这三种运行环境,有助于解释为什么同样的 Dart 代码在 Flutter Web 和 Flutter Mobile 上行为可能有细微差异(如 dart:io 在 Web 不可用)。