本工具使用的開源套件

本工具程式碼中捆綁了 1 個開源套件。

Dart 速查 — 簡明參考

Dart 3 語法、類型系統、異步與最常用標準庫速查手冊,覆蓋約 80% 日常場景。

D

Dart Dart 3

Dart SDK · OO · 泛型 · 函數式 · 異步優先 · 靜態(健全空安全)

學習路徑

先學會 dart create/run 與 pubspec 包結構 → 掌握變量、類型與流程控制 → 深入函數、字符串與集合 → 理解類、mixin 與錯誤處理 → 用 isolate 與 async/await 寫併發 → 再按需學網絡、時間、進程、正則與構建測試。FAQ 節適合回頭避坑。

1.Hello World 與運行環境

最小程序、dart create/run 與項目結構。

最小程序

每個 Dart 程序從 void main() 入口函數開始。dart run 會執行它,print 輸出到標準輸出。

1
2
3
4
5
6
7
8
9
void main() {
print('Hello, world!');
}
// 運行方式:
// dart run hello.dart
// 或先編譯再執行:
// dart compile exe hello.dart -o hello
// main 必須叫 main 才會被當作入口
// 返回類型 void 表示不返回值

創建與運行

dart create 生成標準項目骨架,dart run 直接運行入口文件。開發期 JIT 熱重載,發佈時 AOT 編譯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 創建控制台項目:
// dart create -t console myapp
// 創建 Flutter 項目:
// flutter create myapp
// 進入項目目錄:
// cd myapp
// 運行入口文件:
// dart run
// 指定文件運行:
// dart run bin/main.dart
// 編譯為可執行文件:
// dart compile exe bin/main.dart
// 靜態分析:
// dart analyze

項目結構

pubspec.yaml 聲明包名、依賴與 SDK 版本。代碼放在 lib/ 與 bin/,測試放在 test/。

1
2
3
4
5
6
7
8
9
10
11
12
// pubspec.yaml 示例:
// name: myapp
// environment:
// sdk: '>=3.0.0 <4.0.0'
// dependencies:
// http: ^1.1.0
// 目錄約定:
// lib/main.dart 庫代碼
// bin/main.dart 可執行入口
// test/ 單元測試
// 安裝依賴:
// dart pub get

打印輸出

print 輸出到 stdout 並追加換行。$ 插值把變量嵌入字符串,${表達式} 用於複雜形式。

1
2
3
4
5
6
7
8
9
10
void main() {
print('Hello, Dart!');
final name = 'Rex';
print('Hi, $name'); // 變量插值
final age = 5;
print('Age is ${age + 1}'); // 表達式插值
print('Pi = ${3.14159.toStringAsFixed(2)}');
}
// stdout.write 不換行
// stderr.writeln 輸出到錯誤流

命令行參數

main 可接收 List<String> args 參數。args[0] 起是命令行傳入的實參,不含程序名。

1
2
3
4
5
6
7
8
9
10
11
void main(List<String> args) {
print('參數個數: ${args.length}');
for (final arg in args) {
print('參數: $arg');
}
}
// 運行:
// dart run hello.dart a b c
// args == ['a', 'b', 'c']
// 程序名本身不在 args 裏
// 需要程序路徑用 Platform.script

異步主函數

main 可以是 Future<void>,程序會等待所有 await 完成後再退出。異步操作常用於耗時任務。

1
2
3
4
5
6
7
8
9
Future<void> main() async {
print('開始');
await Future.delayed(Duration(seconds: 1));
print('一秒後執行');
}
// Future.delayed 模擬耗時任務
// await 會阻塞到 Future 完成
// 返回 Future<void> 讓運行時等待
// 不等待則程序可能提前退出

註釋

// 單行註釋、/// 文檔註釋(生成 API 文檔)、/* */ 塊註釋。註釋不影響運行。

1
2
3
4
5
6
7
8
9
// 單行註釋
/// 文檔註釋:供 dart doc 生成 API 文檔
/* 塊註釋:單行閉合即可 */
void main() {
// 行尾註釋
print('OK'); // 解釋這行做了什麼
}
// 文檔註釋放在聲明前
// 用 dart doc 生成 HTML 文檔

頂層成員

Dart 允許頂層函數與變量(不在類裏)。import 導入庫,頂層變量在文件間共享。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:math';
// 頂層變量:
final appName = 'demo';
// 頂層函數:
int square(int x) => x * x;
void main() {
print(appName);
print(square(4));
print(max(1, 9));
}
// 頂層成員整個庫可見
// 變量建議 final 避免全局可變
// 私有成員以下劃線開頭:_helper
// import 放在文件最頂部

2.變量與常量

變量聲明、final/const、空安全與解構。

var 與 final

var 聲明可重新賦值但類型推斷固定。final 聲明只能賦值一次,賦值後不可改。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
var counter = 0; // 推斷為 int
counter = 1; // 可重新賦值
final name = 'Rex'; // 只能賦值一次
// name = 'New'; // 錯誤:final 不可改
final now = DateTime.now(); // 運行時確定
print('$counter $name $now');
}
// var 一旦推斷類型就固定
// 類型不同會報錯:counter = 'x'
// final 只約束賦值次數
// 對象內部仍可修改

const 常量

const 是編譯期常量,必須在編譯時能確定值。const 值會規範化,同一常量只存一份。

1
2
3
4
5
6
7
8
9
10
11
void main() {
const pi = 3.14159;
const greeting = 'Hello';
const list = [1, 2, 3]; // 編譯期列表
const double factor = 0.5;
print('$pi $greeting $list $factor');
}
// const 的值必須在編譯時已知
// DateTime.now() 不能用於 const
// const 構造創建編譯期常量
// 優先用 const 提升性能

顯式類型

可顯式標註類型聲明變量。靜態類型幫助編譯期檢查,公共 API 建議顯式標註。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
int count = 42;
double ratio = 3.14;
String name = 'Dart';
bool enabled = true;
List<int> nums = <int>[1, 2, 3];
print('$count $ratio $name $enabled $nums');
}
// 類型寫在變量名前面
// 顯式類型讓意圖更清楚
// 局部變量可用 var 讓推斷
// 參數與返回值建議寫類型

可空變量

健全空安全下默認不可為 null。類型後加 ? 聲明可空,使用前判空或用 ?? 給默認值。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
String? maybe = null; // 可空
String name = maybe ?? '匿名'; // 空合併默認值
if (maybe != null) {
print('有值: $maybe');
}
print(name);
}
// String? 可以賦 null
// String 不能賦 null(編譯錯誤)
// ?? 在左側為 null 時用右側
// 判空後 Dart 會做類型提升

late 延遲初始化

late 延遲到首次訪問時才初始化。late final 只能賦值一次,適合依賴運行時環境的字段。

1
2
3
4
5
6
7
8
9
10
11
12
class Config {
late final String apiKey = loadKey();
}
String loadKey() => 'abc-123';
void main() {
final config = Config();
print(config.apiKey); // 首次訪問才加載
}
// late 聲明時不用立即賦值
// 首次使用前必須賦值否則報錯
// late final 保證只初始化一次
// 常用於單例、懶加載配置

解構賦值

Dart 3 支持記錄與模式解構,把集合元素一次性賦給多個變量。解構必須匹配形狀。

1
2
3
4
5
6
7
8
9
10
11
void main() {
final (x, y) = (10, 20);
final (name: n, age: a) = (name: 'Rex', age: 5);
final [first, second, ...] = [1, 2, 3];
print('$x $y $n $a $first $second');
}
// 多餘元素用 _ 忽略:
// final [_, second2] = [1, 2];
// 無 rest 時列表模式長度必須匹配
// 也適用於 map 鍵值
// for (final MapEntry(k: k, v: v) in entries)

作用域與遮蔽

變量在聲明它的塊內可見,內層可以遮蔽外層同名變量。頂層變量整個庫可見。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final top = '全局';
void main() {
final inner = '函數內';
if (true) {
final block = '塊內';
print(block);
print(inner); // 可見外層
print(top); // 可見全局
}
// print(block); // 錯誤:超出作用域
}
// 塊作用域與大括號一致
// 內層可遮蔽外層同名變量
// 同作用域重複聲明報錯
// 建議避免深層遮蔽影響可讀性

dynamic 與 Object

dynamic 跳過靜態檢查,類型在運行時決定。Object 要求靜態類型檢查,需顯式轉換。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
dynamic d = 42;
d = '變成字符串'; // 運行時任意類型
Object obj = 42;
// obj.length; // 錯誤:Object 無此成員
if (obj is String) {
print(obj.length); // 類型提升後可訪問
}
print(d);
}
// dynamic 不做靜態檢查,慎用
// Object 是所有類的父類型
// is 判斷後觸發類型提升
// 優先用明確類型或泛型

3.數據類型

內置類型、集合類型、記錄與枚舉。

數值類型

int 是整數,double 是浮點數,num 是二者的父類型。整數字面量可賦給 double。

1
2
3
4
5
6
7
8
9
10
11
void main() {
int i = 42;
double d = 3.14;
num n = 42; // num 可容納整數
double whole = 42; // int 可直接賦給 double
print('$i $d $n $whole');
}
// int 在原生平台是 64 位
// double 是 64 位 IEEE 754
// 數值運算結果類型看操作數
// 類型判斷用 is:if (n is int)

字符串類型

String 是不可變 UTF-16 序列。單引號雙引號等價,支持插值與多行文本。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
String a = '單引號';
String b = "雙引號"; // 等價
String multi = '''第一行
第二行''';
print('$a $b');
print(multi);
}
// 字符串不可修改
// 修改操作都返回新串
// 一種引號內可直接包含另一種
// 需要同種引號時用反斜槓轉義

布爾類型

bool 只有 true 和 false。條件表達式必須返回 bool,不會隱式轉換數字。

1
2
3
4
5
6
7
8
9
10
11
void main() {
bool ok = true;
bool done = false;
if (ok) print('成功');
final result = ok && !done;
print(result);
}
// && 邏輯與 || 邏輯或
// ! 邏輯非
// 三元:ok ? '是' : '否'
// Dart 沒有 truthy/falsy 概念

列表類型

List 是有序集合,支持泛型。字面量 [a, b] 創建列表,默認可增長。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
List<int> nums = [1, 2, 3];
nums.add(4);
nums.remove(2);
final first = nums.first;
final len = nums.length;
print('$first $len $nums');
}
// List<String> 泛型限定元素類型
// 索引訪問 nums[0],越界拋錯
// isEmpty / isNotEmpty 判空
// first / last 取首尾元素

集合與映射

Set 是無序不重複集合,Map 是鍵值對映射。二者都有泛型版本與字面量語法。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
Set<String> tags = {'a', 'b', 'a'}; // 自動去重
tags.add('c');
Map<String, int> ages = {'alice': 30};
ages['bob'] = 25;
print(tags); // {a, b, c}
print(ages['bob']); // 25
print(ages['nobody']); // null
}
// Set 判斷成員用 contains
// Map 訪問不存在的鍵返回 null
// Map 遍歷順序為插入順序
// Set 適合去重與成員判斷

記錄類型

Dart 3 的 record 是無名字的輕量聚合類型。可帶字段名,可配合模式解構。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
var point = (x: 1, y: 2); // 命名記錄
var pair = ('left', 3); // 位置記錄
print(point.x);
print(pair.$1); // 位置取值
final (a, b) = ('first', 2);
print('$a $b');
}
// 記錄自動獲得 == 與 hashCode
// 記錄類型即形狀:(int, int)
// 適合返回多個值代替小類
// 記錄是不可變的

枚舉

enum 定義有限的命名常量集合。增強枚舉(Dart 2.17+)可帶字段與方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enum Status { pending, approved, rejected }
enum Color {
red(255), green(0), blue(0);
const Color(this.value);
final int value;
}
void main() {
final s = Status.approved;
print(s.name); // approved
print(Color.red.value);
for (final c in Status.values) {
print(c.name);
}
}
// .name 得到枚舉名
// .values 遍歷所有成員
// switch 處理枚舉默認窮盡
// 增強枚舉可附加數據與行為

類型轉換

int.parse / double.parse 把字符串轉數值,toString 轉回字符串。as 做運行時類型轉換。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main() {
int n = int.parse('42');
double d = double.parse('3.14');
String s = 42.toString();
Object obj = 'hello';
if (obj is String) {
final len = obj.length; // 提升後安全
print(len);
}
print('$n $d $s');
}
// parse 失敗會拋 FormatException
// toStringAsFixed(2) 保留兩位小數
// as 強轉失敗會拋 TypeError
// 推薦用 is + 類型提升代替 as

4.引用與空安全

對象引用、null 安全、深拷貝與原生內存。

對象引用

Dart 沒有裸指針,變量保存的是對象引用。賦值只複製引用,多個變量可指向同一對象。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final list1 = [1, 2, 3];
final list2 = list1; // 複製引用
list2.add(4);
print(list1); // [1, 2, 3, 4]
final copy = List.of(list1); // 真正複製
copy.add(5);
print(list1); // 不受影響
}
// 引用類型賦值共享對象
// List.of / [...list] 做淺拷貝
// 基本類型是不可變值類型
// 共享可變對象時要留意副作用

空安全

健全空安全讓 null 只能出現在可空類型 String? 中。編譯器在編譯期保證非空安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
String name = 'Rex'; // 非空
String? nickname; // 可空,默認 null
nickname = '小雷';
if (nickname != null) {
print('$name 的暱稱 $nickname');
}
print(nickname);
}
// 非空類型賦值 null 編譯報錯
// 判空後變量自動提升為非空
// ?? 提供默認值避免判空
// ?. 安全訪問:nickname?.length

非空斷言 !

在可空表達式後加 ! 斷言其非空,斷言失敗拋出空指針異常。只在確實非空時用。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
String? maybe = '有值';
final len = maybe!.length; // 斷言非空
print(len);
String? empty;
final safe = empty?.length; // null
print(safe);
}
// ! 繞過空安全檢查
// 斷言失敗拋 Null check error
// 優先用判空與類型提升
// ?. 安全訪問返回 null 而非拋錯

空合併

?? 在左側為 null 時取右側默認值。??= 在變量為 null 時才賦值。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
String? user;
final name = user ?? '匿名'; // 空合併
user ??= '遊客'; // 為 null 才賦值
int? count;
count ??= 0;
count++;
print('$name $user $count');
}
// ?? 只關心左側是否為 null
// ??= 等價於 count = count ?? 0
// 可串聯:a ?? b ?? c ?? '默認'
// ?? 也用於集合默認:list ?? []

late 與 const

final 運行時一次性賦值,const 編譯期常量。late 延遲初始化適合大對象與循環依賴。

1
2
3
4
5
6
7
8
9
10
11
void main() {
final now = DateTime.now(); // 運行時
const pi = 3.14159; // 編譯期
late final big = createBig(); // 首次訪問
print('$now $pi ${big.length}');
}
List<int> createBig() => List<int>.generate(100, (i) => i);
// final 可在構造器或運行時賦值
// const 值必須在編譯期可確定
// late 延遲到首次訪問才執行
// 頂層與靜態變量默認懶初始化

引用與相等

== 默認比較引用,identical() 判斷是否同一對象。需要結構相等時用工具函數。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final a = [1, 2];
final b = [1, 2];
print(a == b); // false:不同對象
print(identical(a, b)); // false:不同引用
final c = a;
print(identical(a, c)); // true:同一引用
print(a.length == b.length); // true
}
// 字符串與數字有值語義,== 比較內容
// List/Map 默認按引用比較
// 結構比較用 listEquals(package:collection)
// 自定義類可重寫 == 與 hashCode

深淺拷貝

淺拷貝只複製頂層,深層對象仍共享。深拷貝需逐層複製,不可變集合天然安全共享。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final nested = [[1], [2]];
final shallow = List.of(nested); // 淺拷貝
shallow[0].add(9);
print(nested[0]); // [1, 9]
final deep = nested.map((e) => List.of(e)).toList();
deep[0].add(8);
print(nested[0]); // [1, 9] 不變
}
// 淺拷貝共享嵌套對象
// 深拷貝每層都新建對象
// Map.of / Set.of 同理
// 不可變集合避免共享副作用

原生內存

dart:ffi 提供 Pointer 與 calloc 訪問原生內存,用於與 C 庫互操作。指針生命週期需開發者管理。

1
2
3
4
5
6
7
8
9
10
11
12
// dart:ffi 提供原生內存訪問:
// Pointer<Int32> 表示 C int 指針
// 分配與釋放:
// final p = calloc<Int32>(1); // package:ffi
// p.value = 42;
// calloc.free(p);
// 與 C 庫互操作:
// final lib = DynamicLibrary.open('libc.so')
// final fn = lib.lookupFunction(...)
// 字符串用 Pointer<Utf8>
// 僅原生平台可用,Web 不可用
// 指針生命週期需開發者管理

5.流程控制

if、循環、switch 與模式匹配。

if / else

if/else if/else 按條件分支執行。條件必須是 bool,可空判斷會觸發類型提升。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main() {
int score = 85;
if (score >= 90) {
print('優秀');
} else if (score >= 60) {
print('及格');
} else {
print('不及格');
}
}
// 條件必須返回 bool
// 大括號可省略但建議保留
// 判空後自動類型提升:
// if (name != null) print(name.length)
// 無 else 時條件不滿足則跳過

三元表達式

條件 ? 真值 : 假值 是單表達式分支。與 ?? 配合處理可空默認值。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final n = 7;
final sign = n > 0 ? '正' : '非正';
String? maybe;
final label = maybe ?? '默認'; // 空合併
print(sign);
print(label);
}
// 三元兩個分支類型應一致
// 嵌套三元可讀性差,少用
// ?? 專門處理 null 默認值
// 複雜分支用 if/else 更清晰

for 循環

for 經典三段式循環,for-in 遍歷可迭代對象。Dart 3 支持循環內模式解構。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void main() {
for (var i = 0; i < 3; i++) {
print('i=$i');
}
final names = ['Rex', 'Ada'];
for (final name in names) {
print(name);
}
for (final (i, name) in names.indexed) {
print('$i: $name');
}
}
// i++ 等價於 i = i + 1
// for-in 可遍歷 Iterable 與 Map
// .indexed 生成 (index, value) 記錄
// 修改被遍歷的集合要謹慎

while 循環

while 先判斷再執行,do-while 至少執行一次再判斷。適合未知次數的循環。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void main() {
var n = 3;
while (n > 0) {
print('n=$n');
n--;
}
var count = 0;
do {
print('至少執行一次');
count++;
} while (count < 1);
}
// while 條件為 false 則一次都不執行
// do-while 至少執行一次
// 條件必須是 bool
// 注意更新循環變量防止死循環

switch 表達式

Dart 3 的 switch 表達式返回一個值。case 後是模式,=> 給出結果,無需 break。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final status = 404;
final label = switch (status) {
200 => '成功',
404 => '未找到',
_ => '其他',
};
print(label);
}
// 末尾必須窮盡或用 _ 兜底
// 表達式版 case 用 => 而非 :
// 支持模式:switch (value) { int n => ... }
// 比 if 鏈更簡潔,結果可賦值

switch 語句

switch 語句按 case 執行分支。支持空 case 穿透、守衞與模式。每個非空分支要 break。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void main() {
final day = 3;
switch (day) {
case 1:
case 2:
case 3:
print('工作日');
break;
default:
print('週末');
}
switch (day) {
case int n when n > 3:
print('大於 3');
break;
default:
print('其他');
}
}
// 空 case 自動向下穿透
// 非空分支需 break 或 return
// when 添加額外條件守衞
// 模式匹配時比 if 鏈更強

模式匹配

if-case 與 switch 可對值做結構匹配。對象模式、記錄模式與守衞組合使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Circle {
const Circle(this.radius);
final double radius;
}
void main() {
final shape = Circle(5);
if (shape case Circle(radius: final r)) {
print('半徑 $r');
}
final value = (name: 'Rex', age: 5);
final msg = switch (value) {
(name: final n, age: 0) => '$n 剛出生',
(name: final n, age: final a) => '$n $a 歲',
_ => '未知',
};
print(msg);
}
// if-case 匹配成功才進入分支
// 對象模式按字段名解構
// 記錄模式按位置或名字匹配
// 窮盡 switch 讓編譯器保證覆蓋

break 與 continue

break 退出當前循環,continue 跳到下一輪。標籤可控制嵌套循環的跳轉目標。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void main() {
for (var i = 0; i < 5; i++) {
if (i == 1) continue; // 跳過 i=1
if (i == 4) break; // 退出循環
print('i=$i'); // 0 2 3
}
outer:
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
if (j == 1) break outer; // 退出外層
print('$i,$j');
}
}
}
// continue 只跳本輪剩餘代碼
// break 徹底結束循環
// 標籤寫在循環前,用標籤名引用
// 標籤適合深層嵌套跳出

6.函數與 Lambda

函數定義、可選參數、閉包與異步函數。

函數定義

函數由返回類型、名稱、參數與函數體構成。無返回值的函數用 void。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int add(int a, int b) {
return a + b;
}
void greet(String name) {
print('你好,$name');
}
void main() {
print(add(1, 2));
greet('Rex');
}
// 返回類型在函數名前面
// 參數必須寫類型
// 沒有返回值用 void
// 函數是對象,可賦給變量

箭頭函數

單表達式函數體可用 => 簡寫,返回該表達式的值。適合純函數與回調。

1
2
3
4
5
6
7
8
9
10
11
12
int square(int x) => x * x;
bool isEven(int n) => n % 2 == 0;
void main() {
print(square(4));
print(isEven(3));
final doubled = [1, 2, 3].map((n) => n * 2).toList();
print(doubled);
}
// => 函數體必須是單個表達式
// 與 { return expr; } 等價
// 常用於回調與高階函數
// void 返回:() => print('x')

可選位置參數

方括號 [] 聲明可選位置參數,未傳時用默認值或為 null。默認值必須是編譯期常量。

1
2
3
4
5
6
7
8
9
10
11
12
String join(String a, [String b = '無', int times = 1]) {
return '$a $b 重複 $times 次';
}
void main() {
print(join('你好'));
print(join('你好', '世界'));
print(join('你好', '世界', 3));
}
// [] 內參數按位置可選
// 默認值在參數後寫 =
// 無默認值則類型為可空
// 可空可選參數可用 ?? 給默認

命名參數

花括號 {} 聲明命名參數,調用時按名傳值。required 強制必須傳入。

1
2
3
4
5
6
7
8
9
10
11
12
13
String describe(String name,
{int age = 0, String? city, required bool married}) {
return '$name 年齡 $age 城市 $city 已婚 $married';
}
void main() {
final s = describe('Rex',
age: 5, city: '上海', married: false);
print(s);
}
// 命名參數順序無關
// 不傳時用默認值或為 null
// required 參數必須顯式傳入
// 長調用建議換行增強可讀性

高階函數

函數是一等公民,可作參數傳入或作為返回值。map/where/fold 是集合的常用高階函數。

1
2
3
4
5
6
7
8
9
10
11
void main() {
final nums = [1, 2, 3, 4];
final doubled = nums.map((n) => n * 2).toList();
final evens = nums.where((n) => n.isEven).toList();
final sum = nums.fold(0, (acc, n) => acc + n);
print('$doubled $evens $sum');
}
// map 逐元素變換
// where 按條件過濾
// fold 累積成單個結果
// 結果是惰性 Iterable,用 toList 固化

閉包

閉包捕獲定義環境中的變量。即使外層函數已返回,閉包仍能訪問與修改這些變量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Function makeCounter() {
var count = 0;
return () => ++count;
}
void main() {
final counter = makeCounter();
print(counter()); // 1
print(counter()); // 2
final counter2 = makeCounter();
print(counter2()); // 1:獨立狀態
}
// 閉包保存環境與狀態
// 每次調用 makeCounter 生成獨立閉包
// 變量在閉包間共享還是獨立看位置
// 常用來實現狀態封裝

異步函數

async 函數返回 Future,內部用 await 等待異步結果。await 只能用於 async 函數。

1
2
3
4
5
6
7
8
9
10
11
12
13
Future<String> fetchData() async {
await Future.delayed(Duration(milliseconds: 100));
return '數據';
}
Future<void> main() async {
print('開始');
final data = await fetchData();
print(data);
}
// async 函數隱式返回 Future<T>
// await 掛起直到 Future 完成
// 異常會傳回 await 處可 try/catch
// 忘記 await 會拿到 Future 對象本身

生成器

sync* 生成惰性 Iterable,async* 生成 Stream。yield 每次產出一個值,yield* 委託另一個生成器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Iterable<int> countUp(int max) sync* {
for (var i = 0; i < max; i++) {
yield i;
}
}
Stream<int> streamCount(int max) async* {
for (var i = 0; i < max; i++) {
await Future.delayed(Duration(milliseconds: 10));
yield i;
}
}
void main() {
print(countUp(3).toList());
}
// sync* 逐次生成不佔內存
// async* 配合 await 生成流
// yield* 展開子序列:yield* countUp(2)
// 生成器按需執行,首次訪問才跑

7.字符串

字面量、插值、子串、編碼與格式化。

字符串字面量

單引號與雙引號都表示字符串。可用反斜槓轉義特殊字符,r 前綴表示原始字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
String s1 = '單引號';
String s2 = "雙引號";
String esc = '換行符\n製表符\t';
String raw = r'不轉義\n直接顯示';
print('$s1 $s2');
print(esc);
print(raw);
}
// \n 換行 \t 製表符 \\ 反斜槓
// r'...' 原始字符串不處理轉義
// 適合正則表達式、Windows 路徑
// 引號內可使用另一種引號避免轉義

字符串插值

$變量 與 ${表達式} 把值嵌入字符串。任何對象都會調用 toString。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final name = 'Rex';
final age = 5;
print('$name 今年 $age 歲');
print('十年後 ${age + 10} 歲');
final list = [1, 2];
print('共 ${list.length} 個元素');
}
// $name 直接插入變量
// ${...} 插入表達式與調用
// 對象自動 toString 轉換
// 比字符串拼接更清晰高效

多行字符串

三個單引號 ''' 創建多行字符串,保留換行與縮進。適合長文本與模板。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main() {
String poem = '''
牀前明月光,
疑是地上霜。''';
print(poem);
String html = '''
<div>
<p>你好</p>
</div>''';
print(html);
}
// 多行串保留實際換行
// 首尾換行會被包含
// 可與插值組合使用
// 適合 SQL、HTML、模板文本

常用方法

contains/hasPrefix 判斷包含,replaceAll 替換,toUpperCase/toLowerCase 轉換大小寫。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final text = 'Hello Dart';
print(text.contains('Dart')); // true
print(text.startsWith('Hello')); // true
print(text.toLowerCase()); // hello dart
print(text.replaceAll('Dart', 'Flutter'));
print(text.replaceFirst('l', 'L')); // HeLlo Dart
print(text.length);
}
// 字符串方法返回新串,不修改原串
// trim() 去除首尾空白
// isEmpty / isNotEmpty 判空
// 方法可以鏈式調用

子串與查找

substring 按索引截取,indexOf 查找子串位置,split 按分隔符拆分成列表。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final text = 'hello world';
print(text.substring(0, 5)); // hello
print(text.substring(6)); // world
print(text.indexOf('world')); // 6
print(text.indexOf('x')); // -1 未找到
print(text.split(' ')); // [hello, world]
}
// substring 參數為起始(含)與結束(不含)
// indexOf 找不到返回 -1
// lastIndexOf 從後往前找
// split 結果不含分隔符本身

字符與編碼

Dart 字符串是 UTF-16 序列,codeUnits 給編碼單元,runes 給碼點。處理 emoji 需 runes。

1
2
3
4
5
6
7
8
9
10
11
void main() {
final s = 'A中😀';
print(s.length); // 4 個 UTF-16 單元
print(s.codeUnits); // 編碼單元列表
print(s.runes.toList()); // 碼點列表
print(String.fromCharCodes(s.runes));
}
// 中文與 emoji 佔多個 UTF-16 單元
// length 返回編碼單元數不是字數
// runes 返回 Unicode 碼點
// 需要真實字數時用 runes.length

高效拼接

循環拼接字符串會創建大量臨時對象。StringBuffer 先累積再一次性轉成字符串。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final buffer = StringBuffer();
for (var i = 0; i < 1000; i++) {
buffer.write('第 $i 行\n');
}
final result = buffer.toString();
print('${result.length} 個字符');
}
// write 追加不換行
// writeln 追加並換行
// toString 一次性產出字符串
// 比 + 拼接高效得多

格式化與解析

toStringAsFixed 固定小數位,padLeft 補位,int.parse/double.parse 解析數字字符串。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final pi = 3.14159;
print(pi.toStringAsFixed(2)); // 3.14
print(7.toString().padLeft(3, '0')); // 007
final n = int.parse('42');
final d = double.parse('3.14');
print('$n $d');
}
// toStringAsFixed 四捨五入
// padLeft/padRight 補字符
// parse 失敗拋 FormatException
// 指定進制:int.parse('ff', radix: 16)

8.集合

列表、映射、集合運算與排序。

列表操作

add 追加、insert 插入、remove 刪除、sort 排序。默認 List 可變且可增長。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final nums = [3, 1, 2];
nums.add(4); // [3, 1, 2, 4]
nums.insert(0, 0); // [0, 3, 1, 2, 4]
nums.remove(3); // 移除元素 3
nums.sort(); // 升序
print(nums);
print(nums.reversed.toList());
}
// remove 移除首個相等元素
// removeAt 按索引移除
// contains 判斷是否包含
// indexOf 查找首個匹配下標

列表高階

map 變換、where 過濾、reduce/fold 聚合、expand 展平。返回的 Iterable 多為惰性。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final nums = [1, 2, 3, 4];
final doubled = nums.map((n) => n * 2).toList();
final evens = nums.where((n) => n.isEven).toList();
final sum = nums.reduce((a, b) => a + b);
final flat = [[1, 2], [3]].expand((e) => e).toList();
print('$doubled $evens $sum $flat');
}
// map/where 返回惰性 Iterable
// 惰性序列只在遍歷時計算
// reduce 要求列表非空
// fold 可提供初始值更安全

展開運算符

... 展開一個集合的元素併入字面量。...? 在源為 null 時安全跳過。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final a = [1, 2];
final b = [3, 4];
final merged = [...a, ...b, 5]; // [1,2,3,4,5]
List<int>? maybe;
final safe = [...a, ...?maybe]; // 空安全
final copy = [...a]; // 淺拷貝
print('$merged $safe $copy');
}
// 展開運算符併入元素而非子列表
// ...? 自動處理 null 源
// 也可用於 Map 與 Set 字面量
// 常用於合併與拷貝集合

集合內 if/for

集合字面量內可用 if 條件包含元素,用 for 批量生成元素。Dart 3 還支持模式解構。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void main() {
final includeZero = true;
final nums = [
1,
if (includeZero) 0,
for (var i = 2; i < 4; i++) i,
];
print(nums); // [1, 0, 2, 3]
final pairs = {
for (var i = 0; i < 3; i++) 'k$i': i,
};
print(pairs);
}
// if 不滿足則省略該元素
// for 循環體產出多個元素
// 同樣適用於 Set 與 Map
// 讓數據描述更聲明式

映射操作

Map 的鍵、值、條目可迭代。putIfAbsent 惰性填充,update 更新,remove 刪除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
final ages = {'alice': 30, 'bob': 25};
ages['carol'] = 22; // 新增
ages.update('bob', (v) => v + 1); // 更新
ages.putIfAbsent('dave', () => 40);
ages.remove('alice');
for (final entry in ages.entries) {
print('${entry.key}: ${entry.value}');
}
}
// 訪問不存在的鍵返回 null
// putIfAbsent 僅在缺失時計算
// entries 提供鍵值對
// containsKey 判斷鍵是否存在

集合操作

Set 保證元素唯一。union/intersection/difference 做集合運算,toSet 從列表去重。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
final a = {1, 2, 3};
final b = {3, 4, 5};
print(a.union(b)); // {1,2,3,4,5}
print(a.intersection(b)); // {3}
print(a.difference(b)); // {1,2}
final nums = [1, 1, 2, 3];
final unique = nums.toSet();
print(unique);
}
// Set 無序、元素唯一
// contains 判斷成員高效
// 可用 Set 做去重與成員檢查
// add 已存在元素則無操作

排序與查找

sort 默認升序,可傳比較器。indexOf 線性查找,contains 判斷存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void main() {
final names = ['bob', 'alice', 'carol'];
names.sort(); // 字母序
print(names);
final people = [
('bob', 30),
('alice', 20),
];
people.sort((x, y) => x.$2.compareTo(y.$2));
print(people);
print(names.indexOf('bob'));
}
// 默認按 Comparable 順序
// 比較器返回負數/零/正數
// sort 原地修改列表
// 需要原順序先拷貝再排序

只讀集合

List.unmodifiable 創建只讀列表,修改拋 UnsupportedError。List.of 複製可變列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final frozen = List.unmodifiable([1, 2, 3]);
// frozen.add(4); // 拋 UnsupportedError
final editable = List.of([1, 2, 3]); // 可變副本
editable.add(4);
print(editable);
final constList = const [1, 2, 3]; // 編譯期常量
print(frozen.length);
}
// unmodifiable 運行時只讀
// const 編譯期常量且規範化
// 傳給 API 時只讀更安全
// Map.unmodifiable / Set.unmodifiable 同理

9.內存與性能

垃圾回收、const 規範化與緩衝複用。

垃圾回收

Dart VM 自動垃圾回收,無需手動釋放內存。對象不再被引用後會被回收。

1
2
3
4
5
6
7
8
9
10
void main() {
// 對象失去引用後成為回收候選:
var data = List<int>.generate(10000, (i) => i);
data = [1, 2, 3]; // 舊列表不再被引用
print(data);
}
// 無引用對象由 GC 自動清理
// 不需要也不能手動 delete
// 短生命週期對象多不擔心
// Flutter 使用分代式 GC

const 規範化

const 值會被規範化,相同常量只存在一份。使用 const 減少重複分配。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
const a = [1, 2, 3];
const b = [1, 2, 3];
print(identical(a, b)); // true:同一實例
final c = [1, 2, 3];
final d = [1, 2, 3];
print(identical(c, d)); // false
}
// const 相同值規範化共享
// 非 const 每次都是新對象
// 熱路徑儘量用 const 字面量
// 編譯期常量不會重複構建

惰性初始化

頂層與靜態變量是懶初始化,首次訪問才賦值。late 提供同樣的延遲語義。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
print('開始');
print(cache); // 首次訪問觸發計算
print(cache); // 複用已有值
}
final cache = buildCache();
String buildCache() {
print('構建緩存中');
return 'heavy-data';
}
// 頂層變量首次訪問才初始化
// 之後複用同一實例
// late 字段同理
// 避免啓動時不必要的開銷

列表容量

可變列表按需擴容,頻繁 add 會多次重新分配。已知大小建議預分配。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final nums = <int>[];
for (var i = 0; i < 100; i++) {
nums.add(i); // 自動擴容
}
final sized = List<int>.filled(100, 0);
final growable = List<int>.generate(100, (i) => i);
print('${nums.length} ${sized.length} ${growable.length}');
}
// 默認列表可增長,擴容有代價
// filled 固定長度初始化
// generate 用函數生成元素
// 頻繁追加時批量構造更高效

緩衝複用

Uint8List 等 typed data 適合二進制緩衝。複用緩衝避免反覆分配,StringBuffer 拼接字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:typed_data';
void main() {
final bytes = Uint8List(4); // 固定大小
bytes[0] = 255;
final buffer = StringBuffer();
for (var i = 0; i < 10; i++) {
buffer.write(i);
}
print('$bytes ${buffer.toString()}');
}
// Uint8List 按字節訪問高效
// 大二進制用 Uint8List 而非 List<int>
// 複用一個緩衝減少 GC 壓力
// 大數據流式處理別整段載入

弱關聯

Expando 在不修改對象的前提下附加數據,鍵是弱引用。WeakReference 不阻止對象回收。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final tags = Expando<String>();
final obj = Object();
tags[obj] = '元數據'; // 關聯數據
print(tags[obj]); // 元數據
final weak = WeakReference(obj);
print(weak.target == obj); // true
}
// Expando 鍵是弱引用
// 對象回收後關聯數據一併消失
// 適合緩存、調試、框架擴展
// 不佔用對象本身的字段

對象生命週期

對象在 isolate 獨立堆中創建。isolate 間無法共享可變對象,只能複製或消息傳遞。

1
2
3
4
5
6
7
8
9
10
11
void main() {
// 每個 isolate 有獨立堆
final local = [1, 2, 3];
// 發送給其它 isolate 的是副本:
// final result = await Isolate.run(() => local);
print(local);
}
// isolate 間不共享內存
// 共享狀態需消息傳遞或端口
// 全局可變對象危險,慎用
// 適合數據處理的獨立任務

原生內存

與 C 互操作時用 dart:ffi 分配原生內存。指針必須手動釋放,否則內存泄漏。

1
2
3
4
5
6
7
8
9
10
import 'dart:ffi';
// 聲明指針類型:
// typedef NativeBuf = Pointer<Uint8>;
// 分配原生內存:
// final p = calloc<Uint8>(1024); // package:ffi
// 使用 p[i] 讀寫字節
// 釋放:
// calloc.free(p);
// 忘記釋放會造成泄漏
// ffi 僅原生平台可用

10.面向對象

類、繼承、mixin 與接口。

類與構造

class 定義對象藍圖。構造器與類同名,this 參數把參數直接賦給字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Animal {
String name;
int age;
Animal(this.name, this.age); // this 參數賦值
void speak() {
print('$name 叫了一聲');
}
}
void main() {
final dog = Animal('旺財', 3);
dog.speak();
print('${dog.name} ${dog.age} 歲');
}
// this.name 把構造參數賦給字段
// 字段默認非空必須初始化
// 方法可訪問實例字段
// 實例化不需要 new 關鍵字

繼承

extends 繼承父類,@override 重寫方法,super 調用父類構造器與方法。Dart 單繼承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Animal {
Animal(this.name);
final String name;
String sound() => '...';
}
class Dog extends Animal {
Dog(String name) : super(name);
@override
String sound() => '汪汪';
}
void main() {
final dog = Dog('旺財');
print(dog.sound());
}
// 子類繼承父類字段與方法
// @override 標記重寫
// super(name) 傳參給父類構造
// 一個類只能有一個父類

mixin

mixin 是可複用的行為片段,用 with 混入。比繼承更靈活,避免單一繼承限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mixin Swimmer {
void swim() => print('游泳');
}
mixin Flyer {
void fly() => print('飛行');
}
class Duck with Swimmer, Flyer {}
void main() {
final duck = Duck();
duck.swim();
duck.fly();
}
// with 關鍵字混入多個 mixin
// mixin 不能單獨實例化
// 可限定父類:mixin X on BaseClass {}
// 組合行為優於深層繼承

抽象類

abstract class 不能被實例化,只定義契約。子類必須實現抽象方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class Shape {
double area();
}
class Circle extends Shape {
Circle(this.r);
final double r;
@override
double area() => 3.14159 * r * r;
}
void main() {
final c = Circle(2);
print(c.area().toStringAsFixed(2));
}
// 抽象方法沒有方法體
// 抽象類可含具體實現
// implements 實現接口也可
// 統一類型後可按父類使用

接口

每個類都隱式是一個接口,用 implements 實現。implements 要求重寫所有成員。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Greeter {
String greet(String name) => '你好,$name';
}
class EnglishGreeter implements Greeter {
@override
String greet(String name) => 'Hello, $name';
}
void main() {
final g = EnglishGreeter();
print(g.greet('Rex'));
}
// implements 不繼承實現
// 接口的所有成員必須重寫
// 一個類可實現多個接口
// 用 implements 表達類型契約

訪問器

get 定義只讀屬性訪問,set 定義可寫屬性。訪問器調用時無需括號,隱藏內部存儲。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Rectangle {
Rectangle(this.width, this.height);
double width;
double height;
double get area => width * height;
set scale(double factor) {
width *= factor;
height *= factor;
}
}
void main() {
final r = Rectangle(2, 3);
print(r.area); // 6.0
r.scale = 2;
print(r.area); // 24.0
}
// get 像字段一樣讀取
// set 像字段一樣賦值
// 可在 get/set 中加校驗邏輯
// 計算屬性用 get 派生

靜態成員

static 成員屬於類本身而非實例。static 方法不能訪問實例成員。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MathUtils {
static const pi = 3.14159;
static int square(int x) => x * x;
static int _counter = 0; // 私有靜態
static int get count => _counter;
}
void main() {
print(MathUtils.pi);
print(MathUtils.square(5));
}
// 通過類名直接訪問靜態成員
// static 不能訪問實例字段
// 私有成員下劃線開頭,庫內可見
// static 常量常用於共享配置

sealed 類

sealed 類限定子類必須在同一庫內,switch 可窮盡匹配而無需 default。Dart 3 特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sealed class Shape {}
class Circle extends Shape {}
class Square extends Shape {}
String describe(Shape shape) => switch (shape) {
Circle() => '圓',
Square() => '方',
};
void main() {
print(describe(Circle()));
print(describe(Square()));
}
// sealed 子類限同庫聲明
// 窮盡 switch 無需 default
// 新增子類會觸發編譯警告
// 配合模式匹配很安全

11.錯誤處理

try/catch、自定義異常與異步錯誤。

try / catch

try 包裹可能拋異常的代碼,catch 捕獲異常。catch (e) 取異常對象。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
try {
final n = int.parse('abc');
print(n);
} catch (e) {
print('解析失敗: $e');
}
}
// catch (e) 捕獲所有異常
// 異常拋出後 try 內剩餘代碼跳過
// 未捕獲異常會導致程序崩潰
// catch (e, st) 可拿堆棧 st

on 子句

on 指定只捕獲特定類型的異常,與 catch 組合使用。按異常類型分流處理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main() {
try {
throw FormatException('格式錯誤');
} on FormatException catch (e) {
print('格式: ${e.message}');
} on ArgumentError {
print('參數錯誤');
} catch (e) {
print('其他: $e');
}
}
// on 類型 捕獲匹配的異常
// 不帶 catch 的 on 分支不取對象
// 順序匹配,前面的優先
// 兜底 catch 捕獲其餘異常

finally 與 rethrow

finally 無論是否異常都會執行,適合清理資源。rethrow 重新拋出保留原堆棧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void main() {
try {
outer();
} catch (e) {
print('最終處理: $e');
}
}
void outer() {
try {
throw Exception('原始錯誤');
} catch (e) {
print('中間捕獲');
rethrow; // 保留堆棧繼續拋
} finally {
print('清理資源');
}
}
// finally 總會執行
// rethrow 必須位於 catch 內
// rethrow 保留原始堆棧
// 常用於關閉資源與連接

拋出異常

throw 關鍵字拋出異常對象。可拋任意對象,但慣例是拋 Exception 或 Error 子類。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void main() {
try {
checkAge(-1);
} catch (e) {
print('拒絕: $e');
}
}
void checkAge(int age) {
if (age < 0) {
throw ArgumentError('年齡不能為負');
}
}
// throw 中斷當前控制流
// 異常可以是任意對象
// 慣例用 Exception 或 Error
// 不滿足業務條件建議拋異常

自定義異常

implements Exception 定義業務異常。重寫 toString 提供友好信息,可攜帶字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ValidationError implements Exception {
ValidationError(this.field, this.message);
final String field;
final String message;
@override
String toString() => '字段 $field 無效: $message';
}
void main() {
try {
throw ValidationError('email', '格式錯誤');
} on ValidationError catch (e) {
print(e.field); // email
print(e);
}
}
// implements Exception 標記為異常
// 可攜帶結構化字段
// on 子句精確捕獲自定義類型
// 便於上層按類型分別處理

異步錯誤

async 函數中 await 拋出的異常可被 try/catch 捕獲。未處理的 Future 錯誤會靜默丟失。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Future<void> load() async {
throw Exception('加載失敗');
}
Future<void> main() async {
try {
await load();
} catch (e) {
print('異步捕獲: $e');
}
}
// 不 await 的錯誤很難捕獲
// 用 catchError 鏈式處理:
// load().catchError(print);
// 未處理錯誤可能進入 zone
// 建議統一錯誤處理策略

流錯誤

Stream 監聽可傳 onError 回調處理錯誤。StreamController.addError 向流注入錯誤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import 'dart:async';
void main() {
final controller = StreamController<int>();
controller.stream.listen(
(data) => print('數據: $data'),
onError: (e) => print('流錯誤: $e'),
onDone: () => print('流結束'),
);
controller.add(1);
controller.addError('意外錯誤');
controller.close();
}
// addError 注入錯誤到流
// onError 回調處理流錯誤
// onDone 在關閉後觸發
// 流錯誤不會中斷其它數據

斷言

assert 在開發模式檢查條件,失敗拋 AssertionError。發佈模式斷言被移除。

1
2
3
4
5
6
7
8
9
void main() {
final score = 85;
assert(score >= 0, '分數不能為負'); // 通過
print('斷言通過');
}
// 條件為 false 拋 AssertionError
// 第二個參數是失敗消息
// 發佈模式 dart compile 移除斷言
// 適合檢查不變量,非用户輸入

12.文件與 I/O

文件讀寫、JSON 與標準輸入輸出。

讀取文件

dart:io 的 File 類讀寫文件。readAsString 讀整個文本文件,需 import 'dart:io'。

1
2
3
4
5
6
7
8
9
10
11
import 'dart:io';
Future<void> main() async {
final text = await File('data.txt').readAsString();
print(text);
final exists = await File('data.txt').exists();
print(exists);
}
// readAsString 一次性讀入內存
// 大文件用流式讀取
// 路徑不存在拋 FileSystemException
// exists 返回布爾判斷存在

寫入文件

writeAsString 寫入文本,writeAsBytes 寫入二進制。追加模式用 mode 參數。

1
2
3
4
5
6
7
8
9
10
11
12
import 'dart:io';
Future<void> main() async {
await File('out.txt').writeAsString('Hello\n');
await File('out.txt').writeAsString('World\n',
mode: FileMode.append); // 追加
final content = await File('out.txt').readAsString();
print(content);
}
// 默認模式覆蓋原文件
// FileMode.append 追加到末尾
// FileMode.writeOnly 只寫覆蓋
// 中文寫入默認 UTF-8 編碼

逐行讀取

readAsLines 把文件按行拆成列表。大文件用 openRead 流式逐行處理,避免整文件載入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'dart:io';
import 'dart:convert';
Future<void> main() async {
final lines = await File('log.txt').readAsLines();
for (final line in lines) {
print(line);
}
final stream = File('big.log').openRead()
.transform(utf8.decoder)
.transform(const LineSplitter());
await for (final line in stream) {
print(line);
}
}
// readAsLines 適合小文件
// 大文件用 openRead 流式處理
// LineSplitter 按行切分
// 流式處理不佔滿內存

JSON 處理

dart:convert 的 jsonEncode/jsonDecode 序列化與反序列化。JSON 數字解析為 int 或 double。

1
2
3
4
5
6
7
8
9
10
11
12
import 'dart:convert';
void main() {
final data = {'name': 'Rex', 'age': 5};
final encoded = jsonEncode(data);
print(encoded);
final decoded = jsonDecode(encoded) as Map<String, dynamic>;
print(decoded['name']);
}
// jsonDecode 返回 dynamic,需轉型
// 數字解析為 int 或 double
// 未知字段保留在 Map 中
// 複雜對象需手動映射

標準輸入輸出

stdin.readLineSync 同步讀一行輸入,stdout.writeln 輸出。適合命令行交互程序。

1
2
3
4
5
6
7
8
9
10
import 'dart:io';
void main() {
stdout.write('請輸入姓名: ');
final name = stdin.readLineSync() ?? '';
stdout.writeln('你好,$name');
}
// readLineSync 讀一行返回 String?
// 輸入結束返回 null,需默認值
// stdout.write 不換行
// stderr.writeln 輸出到錯誤流

目錄操作

Directory.list 遍歷目錄內容,create 建目錄,delete 刪除。返回 FileSystemEntity。

1
2
3
4
5
6
7
8
9
10
11
12
import 'dart:io';
Future<void> main() async {
await Directory('data/sub').create(recursive: true);
await for (final entity in Directory('.').list()) {
print('${entity.path} ${entity is Directory ? '目錄' : '文件'}');
}
await Directory('data').delete(recursive: true);
}
// create recursive 創建多級目錄
// list 惰性迭代目錄內容
// delete recursive 遞歸刪除
// entity 統一處理文件與目錄

二進制字節

readAsBytes 讀二進制,Uint8List 表示字節。writeAsBytes 寫入。適合圖片、音頻等。

1
2
3
4
5
6
7
8
9
10
11
12
13
import 'dart:io';
import 'dart:typed_data';
Future<void> main() async {
final bytes = await File('image.png').readAsBytes();
print('大小: ${bytes.length} 字節');
final copy = Uint8List.fromList(bytes);
await File('copy.png').writeAsBytes(copy);
print('已複製');
}
// readAsBytes 返回 Uint8List
// 字節適合非文本數據
// fromList 複製字節序列
// 大文件用 openWrite 流式寫

路徑操作

File/Directory 的 path 屬性給完整路徑。absolute 解析絕對路徑,uri 轉 file:// 形式。

1
2
3
4
5
6
7
8
9
10
11
12
import 'dart:io';
void main() {
final file = File('a/b/data.txt');
print(file.path); // 完整路徑
print(file.absolute.path); // 絕對路徑
print(file.uri); // file:// URI
print(Platform.pathSeparator);
}
// path 屬性返回完整路徑字符串
// absolute 解析為絕對路徑
// uri 轉為 file:// URI
// Windows 與 Unix 分隔符不同

13.常見坑

Dart 日常開發最容易踩的坑與正確寫法。

濫用非空斷言

用 ! 繞過空檢查容易運行時崩潰。判空、?? 與類型提升更安全。

1
2
3
4
5
6
7
8
9
10
11
// BAD:用 ! 掩蓋可空設計
String? name = 'Rex';
final len = name!.length; // 依賴運行時斷言
print(len);
// GOOD:利用類型提升
String? nick = '小雷';
if (nick != null) {
print(nick.length);
}
// GOOD:?? 提供默認值
print(name?.toUpperCase() ?? '未知');

集合 == 比較

List/Map 的 == 比較引用而非內容。相同元素的列表不相等。需要結構比較用工具函數。

1
2
3
4
5
6
7
8
9
// BAD:== 比較的是引用
final a = [1, 2];
final b = [1, 2];
print(a == b); // false
// GOOD:比較內容而非引用
print(a.join(',') == b.join(',')); // true
// GOOD:結構比較推薦
// listEquals(a, b) package:collection
// 字符串與數字直接 == 比較內容

const 與 final

const 必須是編譯期常量,final 運行時確定。把運行時值當 const 寫會編譯失敗。

1
2
3
4
5
6
7
8
9
// BAD:運行時值不能作 const
// const now = DateTime.now(); // 編譯錯誤
// GOOD:運行時值用 final
final now = DateTime.now();
// GOOD:編譯期常量用 const
const pi = 3.14159;
// const 值會規範化共享
// final 只是不能重新賦值
// 集合元素也可用 const

惰性 Iterable

map/where 返回惰性 Iterable,遍歷時重新計算,底層變化會反映到結果。需要快照用 toList。

1
2
3
4
5
6
7
8
9
10
11
// BAD:惰性 Iterable 每次遍歷重新計算
final nums = [1, 2, 3];
final lazy = nums.where((n) => n.isOdd);
nums.add(5);
print(lazy.toList()); // [1, 3, 5] 含新元素
// GOOD:需要快照用 toList
final snapshot = nums.where((n) => n.isOdd).toList();
nums.add(7);
print(snapshot); // 仍是 [1, 3, 5]
// 底層變化不影響已固化結果
// 流(Stream)只能監聽一次

忘寫 await

調用 async 函數不 await 會拿到 Future 而不是結果。併發任務用 Future.wait。

1
2
3
4
5
6
7
8
9
10
11
12
// BAD:忘記 await 得到 Future 對象
Future<String> fetch() async => '數據';
Future<void> main() async {
final result = fetch(); // Future 非字符串
print(result);
// GOOD:await 後才是結果
final text = await fetch();
print(text);
// 併發等待多個:
final all = await Future.wait([fetch(), fetch()]);
print(all);
}

級聯返回原對象

.. 級聯對同一對象連續調用並返回該對象,不是最後表達式的結果。需要返回值時別用級聯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User {
String name = '';
int age = 0;
}
void main() {
// BAD:級聯返回的是接收者
final result = [1, 2]..add(3);
print(result); // [1, 2, 3] 而非 3
// GOOD:級聯就地修改並返回原對象
final user = User()
..name = 'Rex'
..age = 5;
print(user.name);
// 需要函數結果用普通調用
print('abc'.toUpperCase());
}

遍歷時修改

遍歷集合時增刪元素會拋 ConcurrentModificationError。用 removeWhere 或收集後處理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// BAD:遍歷時移除元素會拋錯
final items = [1, 2, 3, 4];
// for (final n in items) { items.remove(n); }
// 拋 ConcurrentModificationError
// GOOD:removeWhere 安全過濾
final nums = [1, 2, 3, 4];
nums.removeWhere((n) => n.isEven);
print(nums); // [1, 3]
// GOOD:先收集再統一處理
final keep = <int>[];
for (final n in nums) {
if (n.isOdd) keep.add(n);
}
print(keep);

字符串按編碼單元

length 返回 UTF-16 單元數,中文與 emoji 會多於字符數。按字符處理要遍歷 runes。

1
2
3
4
5
6
7
8
9
10
11
12
// BAD:用 length 數中文字符
final s = '你好';
print(s.length); // 2 個單元
// emoji 情況:
final emoji = '👍';
print(emoji.length); // 2!一個符號
// GOOD:按 runes 數碼點
print(emoji.runes.length); // 1
// GOOD:按 runes 逐字處理
for (final r in emoji.runes) {
print(String.fromCharCode(r));
}

14.併發與異步

isolate、Future、Stream 與事件循環。

isolate 併發

isolate 是 Dart 併發單元,各自有獨立內存與事件循環。Isolate.run 或 Isolate.spawn 啓動。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'dart:isolate';
Future<void> main() async {
final result = await Isolate.run(() {
var sum = 0;
for (var i = 0; i < 100000; i++) {
sum += i;
}
return sum;
});
print('結果: $result');
}
// Isolate.run 返回 Future<結果>
// 隔離內存,不共享可變狀態
// 適合 CPU 密集計算
// 計算完成自動銷燬

spawn 與端口

Isolate.spawn 以入口函數啓動新 isolate,SendPort 發送消息,ReceivePort 接收消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'dart:isolate';
void entry(SendPort sendPort) {
sendPort.send('來自新 isolate');
}
Future<void> main() async {
final receive = ReceivePort();
await Isolate.spawn(entry, receive.sendPort);
final msg = await receive.first;
print(msg);
receive.close();
}
// spawn 的入口須是頂層函數或靜態方法
// 消息通過端口傳遞
// receive.first 等待第一條消息
// 用完記得 close 端口

Future 基礎

Future 表示將來完成的結果。Future.value 直接成功,await 等待完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Future<int> addLater(int a, int b) async {
await Future.delayed(Duration(milliseconds: 10));
return a + b;
}
Future<void> main() async {
final future = Future.value(42);
print(await future);
final result = await addLater(1, 2);
print(result);
}
// async 函數返回 Future
// await 掛起直到 Future 完成
// Future.delayed 定時延遲
// Future.value 包裝立即結果

async / await

async 標記異步函數,await 等待異步操作。await 只能在 async 函數內使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Future<String> fetchUser() async {
await Future.delayed(Duration(milliseconds: 50));
return 'Rex';
}
Future<void> main() async {
print('開始加載');
final name = await fetchUser();
print('加載到: $name');
final results = await Future.wait([
fetchUser(),
fetchUser(),
]);
print(results);
}
// await 暫停當前流程但主線程不阻塞
// 多個 await 默認串行
// Future.wait 併發等待多個
// 異常會傳到 await 處可捕獲

流 Stream

Stream 是異步事件序列。listen 訂閲事件,await for 逐個處理。與 Future 的單次不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'dart:async';
Stream<int> tick(int max) async* {
for (var i = 0; i < max; i++) {
await Future.delayed(Duration(milliseconds: 10));
yield i;
}
}
Future<void> main() async {
await for (final n in tick(3)) {
print('tick $n');
}
tick(2).listen((n) => print('listen $n'));
await Future.delayed(Duration(milliseconds: 100));
}
// await for 順序消費流事件
// listen 註冊監聽回調
// 流只產生一次,不能重放
// 每事件依次到達

StreamController

StreamController 手動控制流,add 發數據,addError 發錯誤,close 關閉。broadcast 支持多訂閲。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import 'dart:async';
void main() {
final controller = StreamController<int>();
controller.stream.listen((n) => print('收到 $n'));
controller.add(1);
controller.add(2);
controller.close();
final broadcast = StreamController<int>.broadcast();
broadcast.stream.listen((n) => print('A $n'));
broadcast.stream.listen((n) => print('B $n'));
broadcast.add(5);
broadcast.close();
}
// 默認流只允許一個監聽者
// broadcast 允許多個監聽
// add 向流推送數據
// 記得 close 釋放資源

等待多個 Future

Future.wait 併發等待全部完成,Future.any 等待最先完成的結果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Future<int> slow() async {
await Future.delayed(Duration(milliseconds: 30));
return 1;
}
Future<int> fast() async {
await Future.delayed(Duration(milliseconds: 10));
return 2;
}
Future<void> main() async {
final all = await Future.wait([slow(), fast()]);
print(all); // [1, 2]
final first = await Future.any([slow(), fast()]);
print(first); // 2
}
// wait 全部成功後返回結果列表
// 任一失敗默認傳播錯誤
// any 返回最先完成的
// eagerError 控制是否立即失敗

Completer

Completer 手動控制 Future 完成時機,適合把回調風格 API 轉成 async/await 風格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import 'dart:async';
Future<String> fromCallback() {
final completer = Completer<String>();
Future.delayed(Duration(milliseconds: 20), () {
completer.complete('回調結果');
});
return completer.future;
}
Future<void> main() async {
final result = await fromCallback();
print(result);
}
// complete 讓 Future 成功
// completeError 讓 Future 失敗
// 只能 complete 一次
// 用於包裝第三方回調

15.網絡

HTTP 請求、WebSocket 與 TCP 套接字。

HTTP 客户端

dart:io 的 HttpClient 發起 HTTP 請求。getUrl 得到請求對象,關閉後讀取響應體。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'dart:io';
import 'dart:convert';
Future<void> main() async {
final client = HttpClient();
final request = await client.getUrl(Uri.parse('https://example.com'));
final response = await request.close();
final body = await response.transform(utf8.decoder).join();
print('狀態碼: ${response.statusCode}');
print(body.substring(0, 50));
client.close();
}
// HttpClient 底層且需手動關閉
// 響應體是字節流,用 utf8.decoder
// statusCode 200 表示成功
// 簡單場景優先用 package:http

package:http

package:http 封裝常用請求。http.get/post 返回 Response,body 直接是字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final response = await http.get(
Uri.parse('https://example.com'),
);
print('狀態碼: ${response.statusCode}');
print(response.body.substring(0, 50));
final post = await http.post(
Uri.parse('https://example.com/api'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'name': 'Rex'}),
);
print(post.statusCode);
}
// 需在 pubspec 添加 http 依賴
// Response.body 已解碼字符串
// headers 傳請求頭
// 狀態碼判斷成功與否

URI 解析

Uri.parse 解析 URL,queryParameters 讀取查詢參數,Uri.http 構建請求地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final uri = Uri.parse('https://api.com/users?id=5&page=2');
print(uri.scheme); // https
print(uri.host); // api.com
print(uri.path); // /users
print(uri.queryParameters); // {id: 5, page: 2}
final built = Uri.http('api.com', '/users', {'id': '5'});
print(built);
}
// queryParameters 自動解碼
// Uri.https 走 TLS
// queryParametersAll 處理重複鍵
// replace 可派生新 URI

JSON API

請求 JSON 接口並用 jsonDecode 解析響應體。結合 http 包與 Future 等待數據。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final response = await http.get(
Uri.parse('https://api.example.com/users'),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body) as List;
for (final item in data) {
final user = item as Map<String, dynamic>;
print(user['name']);
}
}
}
// 檢查狀態碼再解析
// JSON 數組轉 List,對象轉 Map
// 字段不存在返回 null
// 真實項目可用模型類封裝

WebSocket

WebSocket 全雙工通信。WebSocket.connect 連接,add 發送,流式接收消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'dart:io';
Future<void> main() async {
final ws = await WebSocket.connect('wss://example.com/ws');
ws.add('你好');
await for (final message in ws) {
print('收到: $message');
if (message == 'bye') {
await ws.close();
}
}
}
// connect 建立連接
// add 發送消息
// 流式接收返回消息
// close 優雅關閉連接

TCP 套接字

Socket.connect 建立 TCP 連接,寫入請求,讀取響應。適合底層協議與自定義服務。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:io';
Future<void> main() async {
final socket = await Socket.connect('example.com', 80);
socket.write('GET / HTTP/1.0\r\n\r\n');
await for (final data in socket) {
final text = String.fromCharCodes(data);
if (text.contains('HTTP')) print(text);
}
socket.destroy();
}
// Socket 提供字節流
// write 發送請求內容
// 流式讀取響應分片
// destroy 立即關閉

HTTP 服務端

HttpServer.bind 啓動本地服務,監聽請求並返回響應。適合開發調試與工具。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:io';
Future<void> main() async {
final server = await HttpServer.bind('localhost', 8080);
print('監聽 8080 端口');
await for (final request in server) {
request.response.headers.set('Content-Type', 'text/plain; charset=utf-8');
request.response.write('你好,路徑 ${request.uri.path}');
await request.response.close();
}
}
// bind 綁定地址與端口
// 每個請求進入 await for
// response 對象返回內容
// close 結束本次響應

超時與錯誤

網絡請求可能超時或失敗。timeout 設置超時,異常用 try/catch 捕獲並處理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'dart:async';
import 'package:http/http.dart' as http;
Future<void> main() async {
try {
final response = await http
.get(Uri.parse('https://example.com'))
.timeout(const Duration(seconds: 5));
print(response.statusCode);
} on TimeoutException {
print('請求超時');
} catch (e) {
print('網絡錯誤: $e');
}
}
// timeout 超時後拋 TimeoutException
// 網絡異常多為 SocketException
// 按錯誤類型分別處理
// 生產環境可加指數退避重試

16.時間與日期

DateTime、Duration、格式化與時間戳。

當前時間

DateTime.now() 獲取當前本地時間。各字段可直接訪問,毫秒部分也可取。

1
2
3
4
5
6
7
8
9
10
11
void main() {
final now = DateTime.now();
print('${now.year}-${now.month}-${now.day}');
print('${now.hour}:${now.minute}:${now.second}');
print(now.weekday); // 週一=1 週日=7
print(now.millisecondsSinceEpoch);
}
// now 是本地時區時間
// month/day 從 1 開始
// weekday 1-7,週一為 1
// 各字段都是 int

構造時間

DateTime(年, 月, 日, ...) 構造指定時間。DateTime.utc 構造 UTC 時間。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final birthday = DateTime(2020, 1, 15);
print(birthday);
final launch = DateTime(2026, 8, 2, 10, 30);
print(launch);
final utc = DateTime.utc(2026, 1, 1);
print(utc.isUtc); // true
}
// 未傳的部分默認 0 或 1
// 月日不合法會自動進位
// UTC 構造用 DateTime.utc
// 比較用 isBefore/isAfter

時長

Duration 表示一段時間,支持時、分、秒與微秒。可加減、比較、轉各時間單位。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final halfHour = Duration(minutes: 30);
final fiveMin = Duration(minutes: 5);
print(halfHour.inSeconds); // 1800
print(halfHour + fiveMin); // 35 分鐘
print(halfHour > fiveMin); // true
final d = Duration(days: 1, hours: 2);
print('${d.inHours} 小時'); // 26
}
// inSeconds/inMinutes 等轉單位
// 支持 + - * / 運算
// 可比較大小
// 常用 Duration(milliseconds: n) 做延遲

格式化輸出

Dart 沒有內置 strftime,用 padLeft 補零拼格式,或用 intl 包的 DateFormat。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main() {
final now = DateTime.now();
final y = now.year.toString();
final m = now.month.toString().padLeft(2, '0');
final d = now.day.toString().padLeft(2, '0');
print('$y-$m-$d');
print(now.toIso8601String());
// intl 包提供日期模板:
// import 'package:intl/intl.dart';
// print(DateFormat('yyyy-MM-dd').format(now));
}
// padLeft 補足位數
// toIso8601String 輸出 ISO 格式
// intl 支持本地化日期模板
// 簡單場景手寫拼接即可

解析時間

DateTime.parse 解析 ISO 格式字符串。toIso8601String 生成標準格式便於解析與傳輸。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
final parsed = DateTime.parse('2026-08-02 10:30:00');
print(parsed.year); // 2026
final iso = DateTime.parse('2026-08-02T10:30:00Z');
print(iso.isUtc); // true
final parts = '2026-08-02'.split('-');
final d = DateTime(
int.parse(parts[0]), int.parse(parts[1]), int.parse(parts[2]));
print(d.day);
}
// 支持多種 ISO 形式
// 無時區默認本地時間
// 含 Z 表示 UTC
// 格式不合法拋 FormatException

時間運算

add/subtract 加減時長,difference 計算兩時間差,返回 Duration。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final start = DateTime(2026, 8, 2, 10);
final later = start.add(Duration(hours: 5));
print(later.hour); // 15
final earlier = start.subtract(const Duration(days: 1));
print(earlier.day); // 1
final diff = later.difference(start);
print(diff.inHours); // 5
}
// add/subtract 返回新對象
// 原對象不可變
// difference 總是非負
// 需要帶方向判斷用 isBefore

時間戳

毫秒/微秒時間戳便於存儲與排序。fromMillisecondsSinceEpoch 從時間戳還原。

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
final now = DateTime.now();
final ms = now.millisecondsSinceEpoch;
print(ms);
final back = DateTime.fromMillisecondsSinceEpoch(ms);
print(back.toIso8601String());
final day = DateTime(now.year, now.month, now.day);
print(day.millisecondsSinceEpoch);
}
// 時間戳是自 1970-01-01 起的毫秒數
// 存儲用 UTC 時間戳避免時區混亂
// fromMicrosecondsSinceEpoch 對應微秒
// 相同時間戳還原的時間相等

時區

DateTime 默認本地時區。isUtc 判斷,toUtc/toLocal 轉換。存儲建議用 UTC。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final now = DateTime.now();
print(now.isUtc); // false
final utc = now.toUtc();
print(utc.isUtc); // true
final local = utc.toLocal();
print(local == now); // 時間一致
}
// toUtc/toLocal 互轉不改變時刻
// DateTime 沒有自帶時區名稱
// 需要 IANA 時區用 package:timezone
// 持久化推薦存 UTC

計時器

Stopwatch 測量代碼執行耗時。start/stop/reset 控制,elapsed 給出耗時。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
final watch = Stopwatch()..start();
var sum = 0;
for (var i = 0; i < 1000000; i++) {
sum += i;
}
watch.stop();
print('耗時 ${watch.elapsedMilliseconds} ms');
print('微秒 ${watch.elapsedMicroseconds}');
}
// start 開始計時
// elapsed 返回 Duration
// elapsedMilliseconds 毫秒數
// reset 清零後重新開始

17.進程與環境

子進程、環境變量、標準流與信號。

運行子進程

Process.run 運行外部命令並等待結果。result.stdout 與 stderr 捕獲輸出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'dart:io';
Future<void> main() async {
final result = await Process.run('dart', ['--version']);
print('退出碼: ${result.exitCode}');
print('輸出: ${result.stdout}');
final ls = await Process.run(
Platform.isWindows ? 'cmd' : 'ls',
Platform.isWindows ? ['/c', 'dir'] : ['-la'],
);
print(ls.stdout);
}
// run 等待進程結束返回結果
// stdout/stderr 是字符串或字節
// exitCode 0 表示成功
// 需要交互用 Process.start

流式進程

Process.start 啓動進程立即返回,可實時讀 stdout 與寫入 stdin。適合長時運行任務。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'dart:io';
import 'dart:convert';
Future<void> main() async {
final process = await Process.start('dart', ['--version']);
process.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((line) => print('子進程輸出: ${line.trim()}'));
final exitCode = await process.exitCode;
print('退出碼: $exitCode');
}
// start 不等待進程結束
// stdout 是流可實時監聽
// 需要交互時用 stdin 寫入
// 可用 environment 定製環境

環境變量

Platform.environment 讀取環境變量,返回只讀 Map。不存在返回 null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import 'dart:io';
void main() {
final env = Platform.environment;
final path = env['PATH'] ?? '未設置';
print('PATH: $path');
print('有 HOME: ${env.containsKey('HOME')}');
for (final entry in env.entries) {
if (entry.key.startsWith('FLUTTER')) {
print('${entry.key}=${entry.value}');
}
}
}
// environment 是隻讀 Map
// 不存在返回 null
// containsKey 判斷存在
// 創建進程時傳 environment 可注入

退出碼

exitCode 設置進程退出碼。0 成功,非 0 失敗。命令行程序據此判斷結果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:io';
void main(List<String> args) {
if (args.isEmpty) {
stderr.writeln('用法: app <參數>');
exitCode = 1;
return;
}
print('處理 ${args[0]}');
exitCode = 0; // 成功
}
// exitCode 0 表示成功
// 非 0 表示錯誤類型
// exit() 立即結束進程
// 腳本依賴退出碼判斷結果

參數與腳本

args 是命令行參數,Platform.script 給出腳本路徑。Directory.current 給當前目錄。

1
2
3
4
5
6
7
8
9
10
11
12
13
import 'dart:io';
void main(List<String> args) {
print('參數個數: ${args.length}');
for (var i = 0; i < args.length; i++) {
print('參數 $i: ${args[i]}');
}
print('腳本路徑: ${Platform.script}');
print('當前目錄: ${Directory.current.path}');
}
// args 不含程序名
// Platform.script 是入口文件路徑
// Directory.current 當前工作目錄
// 複雜參數可用 args 包解析

標準流

stdout/stderr 輸出,stdin 輸入。flush 刷新緩衝,stderr 不參與管道正常輸出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:io';
void main() {
stdout.write('請輸入:');
stdout.flush();
final input = stdin.readLineSync() ?? '';
stdout.writeln('你輸入了: $input');
if (input.isEmpty) {
stderr.writeln('警告:輸入為空');
}
}
// stdout.write 不換行
// flush 立即輸出緩衝
// stderr 輸出到錯誤流
// 管道處理時 stdout 被重定向

文件系統

File/Directory 處理文件系統。create 建目錄,delete 刪,rename 移動,exists 判斷存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'dart:io';
Future<void> main() async {
final dir = Directory('data/sub');
await dir.create(recursive: true);
final file = File('data/note.txt');
await file.writeAsString('內容');
print(await file.exists()); // true
print(file.lengthSync()); // 字節數
await file.rename('data/new.txt');
await Directory('data').delete(recursive: true);
}
// create recursive 建多級目錄
// exists 判斷是否存在
// lengthSync 同步取大小
// rename 移動或改名

信號處理

ProcessSignal 響應系統信號(SIGINT/SIGTERM)。用於優雅關閉服務或清理資源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:io';
Future<void> main() async {
ProcessSignal.sigint.watch().listen((_) {
print('收到中斷,清理退出');
exit(0);
});
print('運行中,按 Ctrl+C 退出');
await Future.delayed(Duration(seconds: 30));
}
// sigint 是 Ctrl+C
// sigterm 是 kill 默認信號
// watch 返回流監聽信號
// 處理後調用 exit 結束
// Windows 僅部分信號可用

18.正則表達式

RegExp 的匹配、替換、分割與分組。

創建與判斷

RegExp 表示正則表達式。hasMatch 判斷是否匹配。r 前綴避免轉義。

1
2
3
4
5
6
7
8
9
10
11
void main() {
final digits = RegExp(r'\d+');
print(digits.hasMatch('abc123')); // true
print(digits.hasMatch('abc')); // false
final email = RegExp(r'^[\w.-]+@[\w-]+\.\w+$');
print(email.hasMatch('[email protected]'));
}
// r 前綴表示原始字符串
// \d 數字 \w 單詞字符 \s 空白
// ^ 行首 $ 行尾
// hasMatch 只判斷是否存在

首個匹配

firstMatch 返回第一個匹配對象。group 取分組,start/end 給位置,無匹配返回 null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
final re = RegExp(r'\d+');
final match = re.firstMatch('編號 42,編號 99');
if (match != null) {
print(match.group(0)); // 42
print(match.start); // 匹配起點
print(match.end); // 匹配終點
print(match.matched); // 42
}
}
// firstMatch 返回第一個匹配
// 無匹配返回 null
// group(0) 是整個匹配
// group(n) 是第 n 個分組

全部匹配

allMatches 返回所有匹配的迭代器。配合模式可提取所有符合條件的內容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void main() {
final re = RegExp(r'\d+');
final matches = re.allMatches('a1 b22 c333');
final numbers = <String>[];
for (final m in matches) {
numbers.add(m.group(0)!);
}
print(numbers); // [1, 22, 333]
final list = RegExp(r'\d+')
.allMatches('a1 b22')
.map((m) => m.group(0)!)
.toList();
print(list);
}
// 遍歷每個匹配對象
// group(0) 取整段匹配
// map 組合提取更簡潔
// 惰性迭代按需計算

替換

replaceAll 替換所有匹配,replaceFirst 只換第一個。回調形式按匹配內容動態生成替換。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
final re = RegExp(r'\d+');
print(re.replaceAll('a1b2', '#')); // a#b#
print(re.replaceFirst('a1b2', '#')); // a#b2
final result = re.replaceAllMapped('a1b2', (m) {
final n = int.parse(m.group(0)!);
return '${n + 1}';
});
print(result); // a2b3
}
// replaceAll 全部替換
// replaceFirst 只換首個
// 回調模式可按內容計算
// 原字符串不變,返回新串

分割

String.split 可接受 RegExp 按模式分割。分隔符不會進入結果列表。

1
2
3
4
5
6
7
8
9
10
11
void main() {
final text = 'a, b, c';
final parts = text.split(RegExp(r',\s*'));
print(parts); // [a, b, c]
print('one two three'.split(RegExp(r'\s+')));
print(text.replaceAll(RegExp(r',\s*'), ' | '));
}
// split 按匹配處切分
// 分隔符不進入結果
// 複雜分隔用正則
// 簡單分隔直接傳字符串

分組捕獲

圓括號定義捕獲組。group(n) 取分組,命名組用 (?<name>...) 且按名訪問。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void main() {
final re = RegExp(r'(\d{4})-(\d{2})-(\d{2})');
final m = re.firstMatch('日期 2026-08-02');
if (m != null) {
print(m.group(1)); // 2026
print(m.group(2)); // 08
print(m.group(3)); // 02
}
final named = RegExp(r'(?<year>\d{4})');
final nm = named.firstMatch('2026');
print(nm?.namedGroup('year'));
}
// group(1) 起是捕獲組
// 非捕獲組 (?:...) 不佔編號
// 命名組提高可讀性
// namedGroup 按名取值

標誌參數

RegExp 構造器第二個參數設標誌:caseSensitive 忽略大小寫,multiLine 多行匹配。

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
final re = RegExp('dart', caseSensitive: false);
print(re.hasMatch('Hello DART')); // true
final multiline = RegExp('^a', multiLine: true);
print(multiline.hasMatch('x\na\nb')); // true
final dotAll = RegExp('a.b', dotAll: true);
print(dotAll.hasMatch('a\nb')); // true
}
// caseSensitive: false 忽略大小寫
// multiLine 讓 ^ 匹配每行開頭
// dotAll 讓 . 匹配換行
// unicode: true 啓用 Unicode 模式

常用模式

常見驗證模式:郵箱、手機號、URL、IP。寫成可複用常量便於維護。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
final email = RegExp(r'^[\w.+-]+@[\w-]+\.\w+$');
final phone = RegExp(r'^1[3-9]\d{9}$');
final url = RegExp(r'^https?://\S+$');
final ip = RegExp(r'^\d{1,3}(\.\d{1,3}){3}$');
print(email.hasMatch('[email protected]'));
print(phone.hasMatch('13800138000'));
print(url.hasMatch('https://dart.dev'));
print(ip.hasMatch('192.168.1.1'));
}
// 郵箱:常規校驗
// 手機號:中國 11 位
// URL:http/https
// IP:四段點分十進制

19.構建與調試

pub、靜態檢查、格式化與測試。

pubspec 配置

pubspec.yaml 是包配置文件,聲明名稱、版本、SDK 與依賴。dart pub get 解析依賴。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// pubspec.yaml 示例:
// name: myapp
// description: 示例應用
// version: 1.0.0
// environment:
// sdk: '>=3.0.0 <4.0.0'
// dependencies:
// http: ^1.1.0
// dev_dependencies:
// test: ^1.24.0
// 版本約束:
// ^1.1.0 表示 >=1.1.0 <2.0.0
// 安裝依賴:
// dart pub get

pub 命令

dart pub get 安裝、upgrade 升級、outdated 查看過期、add/remove 管理依賴。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 安裝依賴:
// dart pub get
// 升級所有依賴:
// dart pub upgrade
// 查看過期依賴:
// dart pub outdated
// 添加依賴:
// dart pub add http
// 移除依賴:
// dart pub remove http
// 搜索包:
// dart pub search json
// 發佈:
// dart pub publish
// 鎖定版本在 pubspec.lock

靜態檢查

dart analyze 靜態分析代碼,報告錯誤與警告。運行在 CI 中保證代碼質量。

1
2
3
4
5
6
7
8
9
10
11
12
// 分析當前包:
// dart analyze
// 只分析指定文件:
// dart analyze lib/main.dart
// 常見檢查項:
// 未使用的導入
// 類型錯誤
// 不推薦的寫法
// 應用 lint 修復:
// dart fix --apply
// CI 集成:
// dart analyze && dart test

格式化

dart format 統一代碼風格。自動整理縮進、引號與換行,團隊協作必備。

1
2
3
4
5
6
7
8
9
10
11
// 格式化當前目錄:
// dart format .
// 格式化指定文件:
// dart format lib/main.dart
// 檢查不修改:
// dart format --output=none --set-exit-if-changed .
// 風格約定:
// 2 空格縮進
// 優先單引號
// 行尾不空格
// CI 檢查格式一致性

單元測試

package:test 提供 test() 與 expect 編寫單元測試。dart test 運行。測試放 test/ 目錄。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// test/calculator_test.dart:
import 'package:test/test.dart';
int add(int a, int b) => a + b;
void main() {
test('加法正確', () {
expect(add(1, 2), 3);
expect(add(-1, 1), 0);
});
group('邊界情況', () {
test('大數相加', () {
expect(add(1000000, 1), 1000001);
});
});
}
// 運行測試:
// dart test
// 只跑單個文件:
// dart test test/calculator_test.dart
// 名稱過濾:
// dart test -n 加法

編譯部署

dart compile 生成可執行文件。exe 原生可執行,js 編譯到 JS,aot-snapshot 快照。

1
2
3
4
5
6
7
8
9
10
11
12
// 編譯原生可執行文件:
// dart compile exe bin/main.dart
// 指定輸出:
// dart compile exe bin/main.dart -o myapp
// 編譯到 JS(瀏覽器):
// dart compile js lib/main.dart
// AOT 快照:
// dart compile aot-snapshot bin/main.dart
// 編譯核:
// dart compile kernel bin/main.dart
// 編譯後無需 Dart SDK 運行
// 原生 exe 啓動快

lint 規則

analysis_options.yaml 配置 lint 規則。lints 包提供推薦規則集,約束代碼風格。

1
2
3
4
5
6
7
8
9
10
11
12
13
// analysis_options.yaml 示例:
// include: package:lints/recommended.yaml
// linter:
// rules:
// - prefer_final_locals
// - avoid_print
// analyzer:
// language:
// strict-casts: true
// 推薦規則集:
// package:lints/recommended.yaml
// Flutter 項目用 flutter_lints
// dart analyze 會應用這些規則

調試

print 打點調試最常用。assert 檢查不變量,IDE 斷點支持逐步執行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void main() {
final total = calculate(3, 4);
print('調試: total=$total');
assert(total > 0, '結果應為正');
print(total);
}
int calculate(int a, int b) {
final sum = a + b;
// dart:developer 的 log 支持分級:
// log('中間值 $sum', name: 'calc');
return sum;
}
// print 輸出到控制台
// dart:developer 的 log 支持分級
// 斷言在發佈模式被移除
// 斷點調試支持逐步執行
// debugPrint 用於 Flutter 大量日誌

官方鏈接

直達官方文檔與資源。

關於本速查

本頁是 Dart 3 的自包含速查手冊,覆蓋語言核心與最常用標準庫在真實項目中約 80% 的常見用法。內容偏向現代慣用法:健全空安全、record 與 pattern、switch 表達式、sealed class、級聯運算符,以及基於 isolate 的併發模型。Dart 由 Google 設計,是 Flutter 的底層語言,支持 JIT 快速開發與 AOT 高效發佈兩種執行方式。權威參考見官方 Dart 語言指南與 Effective Dart。 19 個章節各自聚焦一個主題——從第一個程序到 isolate、常見誤區與構建測試。每節拆成 8 個帶示例的小節(每個 5–15 行),共約 152 個主題。代碼片段刻意短小、自解釋,註釋用中文便於對照學習。 所有處理都在瀏覽器中完成——無上傳、無追蹤。本頁是 GuruToolkit 免費開發者工具集的一部分;代碼片段可自由使用,無任何擔保。

版本 2.1.0