文章

Dart 语法实战深度解析:用二叉搜索树和 Mixin 链把语法练到骨子里

Dart 语法练兵:用二叉搜索树与 Mixin 链把类、泛型、异步等语法点串起来实战。 偏手写练习,掌握后能现场写出兼具类型安全与复用性的 Dart 代码,应对语法扎实度考察。

Dart 语法实战深度解析:用二叉搜索树和 Mixin 链把语法练到骨子里

一句话概括

语法不是看会的——通过手写二叉搜索树、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 开发的完整武器库。

本文由作者按照 CC BY 4.0 进行授权