Dart 语法实战深度解析:用二叉搜索树和 Mixin 链把语法练到骨子里
Dart 语法练兵:用二叉搜索树与 Mixin 链把类、泛型、异步等语法点串起来实战。 偏手写练习,掌握后能现场写出兼具类型安全与复用性的 Dart 代码,应对语法扎实度考察。
一句话概括
语法不是看会的——通过手写二叉搜索树、Mixin 能力链和并发任务调度器三个实战练习,把 Dart 的泛型、Mixin、异步编程一次性练透,面试写白板代码不卡壳。
核心知识点
1. 泛型二叉搜索树:泛型 + 递归 + 运算符重载
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
29
30
31
class BST<T extends Comparable<T>> {
_Node<T>? _root;
void insert(T v) => _root = _ins(_root, v);
_Node<T>? _ins(_Node<T>? n, T v) {
if (n == null) return _Node(v);
final c = v.compareTo(n.val);
if (c < 0) n.l = _ins(n.l, v);
else if (c > 0) n.r = _ins(n.r, v);
return n;
}
// 运算符重载:合并两棵树
BST<T> operator +(BST<T> o) {
final r = BST<T>();
for (final v in inorder()) r.insert(v);
for (final v in o.inorder()) r.insert(v);
return r;
}
List<T> inorder() {
final res = <T>[];
_trav(_root, res);
return res;
}
void _trav(_Node<T>? n, List<T> r) {
if (n == null) return;
_trav(n.l, r); r.add(n.val); _trav(n.r, r);
}
}
class _Node<T> { T val; _Node<T>? l, r; _Node(this.val); }
面试要点:T extends Comparable<T> 保证元素可比,operator + 展示 Dart 的运算符重载能力,递归遍历是二叉树的基本功。
2. Mixin 链:线性化顺序决定一切
1
2
3
4
5
6
7
8
9
10
11
12
13
mixin CanWalk { String move() => '走路'; }
mixin CanSwim { String move() => '游泳'; }
mixin CanFly { String move() => '飞行'; }
// A: Walk → Swim → Fly → 最后声明的 Fly 覆盖前面的 → "飞行"
class CreatureA with CanWalk, CanSwim, CanFly {}
// B: Fly → Swim → Walk → Walk 最后覆盖 → "走路"
class CreatureB with CanFly, CanSwim, CanWalk {}
void main() {
print(CreatureA().move()); // 飞行
print(CreatureB().move()); // 走路
}
规则:with 后面的 Mixin 覆盖前面的同名方法。Dart 用线性化顺序(C3 算法变体)决定 super 调用链。Mixin 可以加 on 约束限制使用范围:mixin FlyLogger on HasWings { … }。
3. on 约束:限制 Mixin 的使用范围
1
2
3
4
5
6
abstract class Animal {}
mixin CanBark on Animal {
void bark() => print('汪汪!');
}
class Dog extends Animal with CanBark {} // ✅
// class Robot with CanBark {} // ❌ Robot 不是 Animal
on 不仅是约束,还让 Mixin 能调用父类方法——Mixin 里的 super 指向 on 约束的类。
4. 并发任务调度器:Future + Stream + 优先级队列
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
29
30
31
32
33
34
35
class TaskScheduler {
final int _max;
final _queue = PriorityQueue<Task>((a, b) =>
b.priority.index.compareTo(a.priority.index));
int _running = 0;
final _events = StreamController<TaskEvent>.broadcast();
Stream<TaskEvent> get events => _events.stream;
Future<T> submit<T>(Task<T> task) {
final c = Completer<T>();
_queue.add(Task(id: task.id, priority: task.priority,
work: () async {
final r = await task.work(); c.complete(r); return r;
}));
_next();
return c.future;
}
void _next() {
while (_running < _max && _queue.isNotEmpty) {
_running++;
final t = _queue.removeFirst();
_events.add(TaskStarted(t.id));
t.work().then((v) {
_events.add(TaskCompleted(t.id, v));
}).catchError((e) {
_events.add(TaskFailed(t.id, e));
}).whenComplete(() {
_running--;
_next();
});
}
}
}
核心思路:Completer 桥接回调与 Future,PriorityQueue 保证高优先级先执行,Stream 广播状态变化供 UI 订阅。
5. async 函数本质是状态机
1
2
3
4
5
6
7
8
9
10
// 你写的
Future<int> add(int a, int b) async {
await Future.delayed(Duration(seconds: 1));
return a + b;
}
// 编译器等价于
Future<int> add(int a, int b) {
return Future.delayed(Duration(seconds: 1)).then((_) => a + b);
}
每个 await 都是一个挂起点,编译器把 async 函数切成多个状态,await 后面代码是回调。这也是为什么 await 不阻塞线程——它只是暂停当前函数,事件循环继续处理其他任务。
其实你每天都在用
List.sort((a, b) => a.compareTo(b)):背后就是泛型约束<T extends Comparable<T>>with SingleTickerProviderStateMixin:Flutter 动画 Mixin,只能在State上使用,就是on约束jsonDecode()返回Map<String, dynamic>:泛型 Map,value 是 dynamic 因为 JSON 类型不固定Future.wait([...]):并发请求多个 API,跟任务调度器的 wait 是同一个原理StreamBuilder:订阅 Stream 来重建 Widget,底层就是本节写的 StreamController
常见误解(FAQ)
❌ 误区:「Mixin 就是多继承」
不是。Mixin 是线性化组合,不是树状继承。class C with A, B 等价于 C → B → A → Object,形成一条线性 super 链,而不是同时继承 A 和 B。
❌ 误区:「async 函数里的代码都是异步执行的」
async 函数在第一个 await 之前的代码是同步执行的。这也是为什么 showLoading() 放在 await 前才能先显示。
❌ 误区:「with 后面的 Mixin 越多越好」
Mixin 越多,线性化链越长,super.move() 穿透层数越多,性能有害且逻辑难以跟踪。Mixin 适合”能力”建模,不适合”数据”建模。
一句话总结
Dart 语法要练到手熟——泛型让你写出类型安全的通用代码,Mixin 让你灵活组合能力,async/await 让你优雅编排异步流程,三者合一才是 Dart 开发的完整武器库。