Dart 语言基础与特性

Dart 语言完整入门:从变量声明到空安全,从异步编程到函数式特性,系统掌握 Flutter 开发的核心语言基础。

开篇:为什么选 Dart?

Dart 是 Google 于 2011 年发布的编程语言,最初定位为替代 JavaScript 的 Web 语言。虽然这一目标未能实现,但 Dart 凭借简洁的语法、高效的 JIT/AOT 编译能力以及出色的开发工具链,在 2017 年随着 Flutter 的推出迎来了新生。如今,Dart 不仅是 Flutter 框架的唯一开发语言,更通过其现代化的类型系统和异步模型,成为移动、Web、桌面跨平台开发的重要工具。

与其他语言相比,Dart 的设计取各家之长:语法接近 TypeScript 和 Java,空安全借鉴了 Kotlin 和 Swift,异步模型参考了 C# 的 async/await。对于前端开发者,Dart 的学习曲线相当平缓;对于原生 Android/iOS 开发者,Dart 的类型系统和面向对象特性也相当熟悉。


一、核心语法速览

1.1 变量声明

Dart 支持显式类型和类型推断两种方式:

// 显式类型声明
String name = 'Flutter';
int count = 42;
double price = 19.99;
bool isActive = true;

// 类型推断(使用 var)
var inferredName = 'Dart';  // 推断为 String
var inferredCount = 100;     // 推断为 int

// final:运行时常量,只能赋值一次
final currentTime = DateTime.now();

// const:编译时常量,必须能在编译期确定值
const pi = 3.14159;
const appName = 'MyApp';

finalconst 的区别至关重要:final 在运行时确定值,适合接收 API 返回的数据;const 在编译期确定值,适合定义配置项和 UI 常量。

// const List 是深度不可变的
const numbers = [1, 2, 3];  // List<int>
// numbers.add(4);  // ❌ 编译错误

// final List 可以修改内容
final items = ['a', 'b'];
items.add('c');  // ✅ 可以

一句话总结var 用于类型推断,final 运行时常量,const 编译时常量——三者覆盖了变量声明的全部场景。

1.2 空安全(Null Safety)

Dart 2.12 引入的空安全是其最重要的语言特性之一。核心原则是默认非空

// 非空类型:默认不能为 null
String name = 'Alice';
// name = null;  // ❌ 编译错误

// 可空类型:使用 ? 后缀标记
String? nullableName = 'Bob';
nullableName = null;  // ✅ 可以

// 延迟初始化:使用 late
late String description;
void setup() {
  description = 'Initialized later';
}

空安全引入了两个关键运算符:

// ?. 空感知属性访问
String? email;
int? length = email?.length;  // email 为 null 时返回 null

// ?? 空合并运算符
String displayName = name ?? 'Anonymous';

// ??= 空感知赋值
String? cache;
cache ??= 'default value';  // 仅当 cache 为 null 时赋值

// ! 空断言运算符(慎用)
String confirmed = nullableName!;  // 断言不为 null,运行时若为 null 则抛异常

一句话总结:空安全通过编译期检查消除了 NullPointerException,是 Dart 走向生产级语言的关键里程碑。

1.3 字符串与插值

Dart 的字符串支持单引号、双引号和多行字符串:

// 字符串插值
var user = 'Alice';
var greeting = 'Hello, $user!';  // Hello, Alice!
var calculation = '2 + 3 = ${2 + 3}';  // 2 + 3 = 5

// 多行字符串
var sqlQuery = '''
  SELECT * FROM users
  WHERE status = 'active'
  ORDER BY created_at DESC
''';

// 原始字符串(不转义)
var path = r'C:\Users\name\file.txt';

一句话总结:Dart 字符串插值语法简洁直观,多行和原始字符串减少了转义字符的冗余。


二、函数与高阶函数

2.1 函数声明与参数

// 基本函数
int add(int a, int b) {
  return a + b;
}

// 箭头函数(单行表达式)
int multiply(int a, int b) => a * b;

// 命名参数(推荐,增加可读性)
void createUser({
  required String name,
  required int age,
  String? email,
  bool isActive = true,
}) {
  print('Creating user: $name, $age, $email, $isActive');
}

// 位置可选参数(用 [] 包裹)
void log(String message, [String level = 'INFO', DateTime? timestamp]) {
  print('[${timestamp ?? DateTime.now()}] $level: $message');
}

// 调用
void main() {
  createUser(name: 'Alice', age: 30);
  createUser(name: 'Bob', age: 25, email: 'bob@example.com');
  
  log('Start');
  log('Error', 'ERROR');
}

Dart 推荐优先使用命名参数{}),尤其是对于参数较多的函数。required 关键字表示必须传入。

一句话总结:命名参数使函数调用像配置对象一样清晰,是 Dart 代码可读性的重要保障。

2.2 高阶函数与闭包

// 函数作为参数
void processItems<T>(List<T> items, void Function(T) action) {
  for (var item in items) {
    action(item);
  }
}

// 函数作为返回值
Function(int) makeMultiplier(int factor) {
  return (int value) => value * factor;
}

// 闭包捕获外部变量
void main() {
  var counter = 0;
  var increment = () => counter++;
  
  increment();
  increment();
  print(counter);  // 2
  
  // 集合高阶函数
  var numbers = [1, 2, 3, 4, 5];
  var doubled = numbers.map((n) => n * 2).toList();
  var evens = numbers.where((n) => n.isEven).toList();
  var sum = numbers.reduce((a, b) => a + b);
  
  print(doubled);  // [2, 4, 6, 8, 10]
  print(evens);    // [2, 4]
  print(sum);      // 15
}

一句话总结:高阶函数让 Dart 具备了函数式编程的能力,集合操作如 mapwherereduce 大幅提升了代码简洁度。

2.3 typedef 与函数类型

// 定义函数类型别名
typedef JsonMap = Map<String, dynamic>;
typedef Validator<T> = String? Function(T value);

// 使用
class FormField<T> {
  final String label;
  final T value;
  final List<Validator<T>> validators;
  
  FormField(this.label, this.value, this.validators);
  
  List<String> validate() {
    return validators
        .map((v) => v(value))
        .where((error) => error != null)
        .cast<String>()
        .toList();
  }
}

// 实际验证函数
String? validateEmail(String? value) {
  if (value == null || value.isEmpty) return '邮箱不能为空';
  if (!value.contains('@')) return '邮箱格式不正确';
  return null;
}

String? validateMinLength(String value, int min) {
  if (value.length < min) return '至少需要 $min 个字符';
  return null;
}

一句话总结:typedef 不仅为函数类型提供语义化命名,还可用于简化复杂类型的声明。


三、面向对象编程

3.1 类与构造函数

class User {
  final String id;
  final String name;
  final String? email;
  final DateTime createdAt;
  
  // 主构造函数
  User({
    required this.id,
    required this.name,
    this.email,
    DateTime? createdAt,
  }) : createdAt = createdAt ?? DateTime.now();
  
  // 命名构造函数
  User.anonymous() : this(id: 'anon', name: '匿名用户');
  
  User.fromJson(Map<String, dynamic> json)
      : id = json['id'] as String,
        name = json['name'] as String,
        email = json['email'] as String?,
        createdAt = DateTime.parse(json['createdAt'] as String);
  
  // 工厂构造函数(可返回缓存实例或子类)
  factory User.cached(String id) {
    return _cache.putIfAbsent(id, () => User(id: id, name: 'User_$id'));
  }
  
  static final Map<String, User> _cache = {};
  
  // 方法
  String get displayName => name.isNotEmpty ? name : '未命名';
  
  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
    'email': email,
    'createdAt': createdAt.toIso8601String(),
  };
  
  // 重写操作符
  @override
  bool operator ==(Object other) =>
      identical(this, other) || other is User && other.id == id;
  
  @override
  int get hashCode => id.hashCode;
  
  @override
  String toString() => 'User(id: $id, name: $name)';
}

构造函数初始化列表(: createdAt = ...)在构造函数体执行之前运行,适合初始化 final 字段和进行参数校验。

一句话总结:Dart 的构造函数系统丰富而灵活,主构造函数、命名构造函数、工厂构造函数覆盖了各种对象创建场景。

3.2 混入(Mixin)

Mixin 是 Dart 解决多重继承限制的优雅方案——它允许在不形成继承链的情况下复用代码:

// 定义 Mixin
mixin Loggable {
  void log(String message) {
    print('[${DateTime.now()}] $message');
  }
  
  void logError(String error) {
    log('ERROR: $error');
  }
}

mixin Serializable {
  Map<String, dynamic> toJson();
  
  String toJsonString() => jsonEncode(toJson());
}

// 使用 Mixin
class Product with Loggable, Serializable {
  final String id;
  final String name;
  final double price;
  
  Product({required this.id, required this.name, required this.price});
  
  @override
  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
    'price': price,
  };
}

// 限制 Mixin 的使用范围(仅用于特定类或接口)
mixin Comparable<T> on Object {
  bool compareTo(T other);
}

Mixin 与继承的区别:继承表达"是一个"的关系,Mixin 表达"能做某事"的能力。

一句话总结:Mixin 让 Dart 在单继承的限制下实现了代码的横向复用,是构建可组合行为的关键工具。

3.3 扩展方法(Extension)

Dart 2.7 引入的扩展方法允许为已有类型添加新功能:

// 为 String 添加扩展
extension StringExtensions on String {
  String get reversed => split('').reversed.join('');
  
  String truncate(int maxLength, {String suffix = '...'}) {
    if (length <= maxLength) return this;
    return substring(0, maxLength - suffix.length) + suffix;
  }
  
  bool get isValidEmail {
    final regex = RegExp(r'^[\w.-]+@[\w.-]+\.\w+$');
    return regex.hasMatch(this);
  }
}

// 为 List 添加扩展
extension ListExtensions<T> on List<T> {
  T? get firstOrNull => isEmpty ? null : first;
  
  T? firstWhereOrNull(bool Function(T) test) {
    for (var element in this) {
      if (test(element)) return element;
    }
    return null;
  }
  
  List<T> distinct() => {...this}.toList();
}

// 为 nullable 类型添加扩展
extension NullableExtensions<T> on T? {
  R? let<R>(R Function(T) transform) {
    if (this == null) return null;
    return transform(this!);
  }
  
  T orDefault(T defaultValue) => this ?? defaultValue;
}

// 使用
void main() {
  print('Flutter'.reversed);  // rettulF
  print('Hello, World!'.truncate(8));  // Hello...
  print('test@example.com'.isValidEmail);  // true
  
  var list = [1, 2, 2, 3, 3, 3];
  print(list.distinct());  // [1, 2, 3]
  
  String? maybeName;
  print(maybeName.orDefault('默认名称'));  // 默认名称
  print(maybeName?.let((n) => n.toUpperCase()));  // null
}

一句话总结:扩展方法让开发者能为标准库和第三方类型注入定制行为,极大提升了代码表达力和可读性。

3.4 抽象类与接口

Dart 中没有 interface 关键字——每个类都隐式定义了一个接口:

// 抽象类定义接口契约
abstract class Repository<T> {
  Future<T?> getById(String id);
  Future<List<T>> getAll();
  Future<void> save(T item);
  Future<void> delete(String id);
}

// 实现接口(可以同时实现多个接口)
class UserRepository implements Repository<User> {
  final List<User> _users = [];
  
  @override
  Future<User?> getById(String id) async {
    return _users.firstWhereOrNull((u) => u.id == id);
  }
  
  @override
  Future<List<User>> getAll() async => List.unmodifiable(_users);
  
  @override
  Future<void> save(User item) async {
    _users.removeWhere((u) => u.id == item.id);
    _users.add(item);
  }
  
  @override
  Future<void> delete(String id) async {
    _users.removeWhere((u) => u.id == id);
  }
}

// 枚举增强(Dart 2.17+)
enum TaskStatus {
  pending('待处理', Icons.hourglass_empty),
  inProgress('进行中', Icons.autorenew),
  completed('已完成', Icons.check_circle),
  cancelled('已取消', Icons.cancel);
  
  final String label;
  final IconData icon;
  
  const TaskStatus(this.label, this.icon);
}

一句话总结:Dart 独特的"类即接口"设计让类型契约的表达既简洁又强大,枚举增强更是将 Dart 推向现代语言的行列。


四、集合与数据操作

4.1 三大核心集合

// List(有序,可重复)
var fruits = ['apple', 'banana', 'orange'];
fruits.add('grape');
fruits.insert(0, 'mango');
var first = fruits[0];

// Map(键值对)
var scores = {
  'Alice': 95,
  'Bob': 87,
  'Charlie': 92,
};
scores['David'] = 88;
var aliceScore = scores['Alice'];  // 95

// Set(无序,唯一)
var tags = {'flutter', 'dart', 'mobile'};
tags.add('flutter');  // 已存在,不重复
var allTags = tags.union({'web', 'desktop'});

4.2 集合运算符

// Spread 展开
var list1 = [1, 2, 3];
var list2 = [0, ...list1, 4, 5];  // [0, 1, 2, 3, 4, 5]

// Null-aware Spread
var nullable = null;
var list3 = [0, ...?nullable, 4];  // [0, 4]

// Collection If(条件集合)
var isAdmin = true;
var permissions = [
  'read',
  'write',
  if (isAdmin) 'delete',
  if (isAdmin) 'manage',
];

// Collection For(推导式)
var numbers = [1, 2, 3];
var doubled = [
  for (var n in numbers) n * 2,
];  // [2, 4, 6]

// 模式匹配(Dart 3.0+)
var record = (1, 'one');
var (id, name) = record;

一句话总结:Dart 的集合运算符(展开、条件、推导)让集合构造从命令式变为声明式,代码更加紧凑易读。


五、异步编程

5.1 Future 与 async/await

// 基本异步操作
Future<String> fetchUserData(String userId) async {
  await Future.delayed(Duration(seconds: 1));  // 模拟网络请求
  return 'User data for $userId';
}

// 并行执行多个 Future
Future<List<String>> fetchMultipleUsers(List<String> ids) async {
  var futures = ids.map((id) => fetchUserData(id));
  return await Future.wait(futures);
}

// 错误处理
try {
  var result = await fetchUserData('invalid-id');
} on NetworkException catch (e) {
  print('网络错误: ${e.message}');
} on FormatException catch (e) {
  print('格式错误: ${e.message}');
} catch (e) {
  print('未知错误: $e');
} finally {
  print('清理资源');
}

// 超时控制
var result = await fetchUserData('123').timeout(
  Duration(seconds: 5),
  onTimeout: () => '超时默认数据',
);

一句话总结:async/await 让异步代码像同步代码一样阅读和编写,错误处理也遵循熟悉的 try/catch 模式。

5.2 Stream 流式编程

import 'dart:async';

// 创建 Stream
Stream<int> countStream(int max) async* {
  for (var i = 1; i <= max; i++) {
    await Future.delayed(Duration(milliseconds: 100));
    yield i;  // 产生值
  }
}

// 使用 Stream
void main() async {
  await for (var count in countStream(5)) {
    print(count);  // 1, 2, 3, 4, 5(每 100ms)
  }
  
  // 流变换
  var stream = countStream(10);
  await stream
    .where((n) => n.isEven)
    .map((n) => n * 2)
    .listen((data) => print(data));  // 4, 8, 12, 16, 20
  
  // StreamController(手动控制)
  var controller = StreamController<String>.broadcast();
  
  controller.stream.listen(
    (data) => print('收到: $data'),
    onError: (e) => print('错误: $e'),
    onDone: () => print('流已关闭'),
  );
  
  controller.add('消息 1');
  controller.add('消息 2');
  await controller.close();
}

Broadcast Stream 允许多个订阅者,适合事件总线场景:

class EventBus {
  final _controller = StreamController<AppEvent>.broadcast();
  Stream<AppEvent> get stream => _controller.stream;
  
  void emit(AppEvent event) => _controller.add(event);
  void dispose() => _controller.close();
}

sealed class AppEvent {}
class UserLoggedIn extends AppEvent {}
class SettingsChanged extends AppEvent {}

一句话总结:Stream 让 Dart 具备了响应式编程的能力,配合 async* / yield 可以优雅地处理异步数据序列。


六、异常处理

// 自定义异常
class ValidationException implements Exception {
  final String field;
  final String message;
  
  ValidationException(this.field, this.message);
  
  @override
  String toString() => 'ValidationException($field): $message';
}

class NetworkException implements Exception {
  final int statusCode;
  final String? body;
  
  NetworkException(this.statusCode, {this.body});
}

// 异常转换
try {
  await http.get(url);
} on SocketException {
  throw NetworkException(0);  // 无网络
} on FormatException catch (e) {
  throw ValidationException('response', '响应格式错误: ${e.message}');
} on HttpException catch (e) {
  throw NetworkException(e.uri.hashCode);
}

// Result 类型(函数式错误处理)
sealed class Result<T, E> {}
class Success<T, E> extends Result<T, E> {
  final T value;
  Success(this.value);
}
class Failure<T, E> extends Result<T, E> {
  final E error;
  Failure(this.error);
}

Result<int, String> divide(int a, int b) {
  if (b == 0) return Failure('不能除以零');
  return Success(a ~/ b);
}

一句话总结:Dart 的异常处理继承 Java 风格,配合自定义 Result 类型可实现函数式错误处理。


七、元数据与代码生成

// 常用注解
@deprecated
void oldMethod() {}

@override
String toString() => '...';

// 自定义注解
class ApiEndpoint {
  final String path;
  final String method;
  const ApiEndpoint(this.path, {this.method = 'GET'});
}

@ApiEndpoint('/users', method: 'GET')
Future<List<User>> getUsers() async {
  // ...
}

// 代码生成器(如 freezed, json_serializable 使用 build_runner)
import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart';  // 生成文件

@JsonSerializable()
class UserModel {
  final String id;
  final String name;
  
  UserModel({required this.id, required this.name});
  
  factory UserModel.fromJson(Map<String, dynamic> json) =>
      _$UserModelFromJson(json);
  
  Map<String, dynamic> toJson() => _$UserModelToJson(this);
}

一句话总结:元数据注解配合 build_runner 的代码生成,让 Dart 开发在保持类型安全的同时避免了大量样板代码。


八、Dart 与 JavaScript 互操作

// dart:js_interop(Dart 3.2+)
import 'dart:js_interop';

// 调用 JS 函数
@JS('console.log')
external void consoleLog(JSString message);

// 暴露 Dart 函数给 JS
@JSExport()
class FlutterBridge {
  void sendMessage(String message) {
    print('来自 JS: $message');
  }
}

// 原有 js 包(pkg:js)
@JS('JSON.parse')
external dynamic jsonParse(String text);

一句话总结:js_interop 为 Flutter Web 和其他需要 JS 互操作的场景提供了类型安全的桥接方案。


FAQ

Q1: Dart 的空安全可以关闭吗?

不可以。从 Dart 3.0 开始,空安全是强制特性。所有新代码都必须在空安全环境下运行。迁移旧项目时,需要逐步将 ? 标记添加到可能为 null 的类型上。

Q2: finalconst 到底用哪个?

  • 值在编译期确定(如配置常量、颜色值、字号)→ 用 const
  • 值在运行时确定(如 API 返回、当前时间、用户输入)→ 用 final
  • const 对象可以共享实例,更省内存;final 更灵活。

Q3: Dart 是单线程的,如何执行密集计算?

Dart 是事件循环单线程模型(类似 JavaScript),但可以通过 Isolate 实现真正的并行计算。对于耗时任务,使用 await compute(function, data) 最为方便,它自动在后台 Isolate 运行并在完成后返回结果。

Q4: 命名参数和位置参数怎么选?

参数 ≤3 个且无歧义 → 位置参数;参数 >3 个或有多个同类型参数 → 命名参数。Flutter Widget 的构造函数普遍使用命名参数,因为它让代码像标记语言一样清晰。

Q5: ==identical() 有什么区别?

== 是值相等(可通过 @override 自定义),identical() 是引用相等(内存地址相同)。对于构造函数中使用 const 创建的对象,Dart 会自动缓存实例,此时 identical(a, b)a == b 都为 true。

Q6: Dart 的泛型和 Java 的有什么区别?

Dart 泛型在运行期会保留类型信息(reified generics),因此 List<int> is List<String> 在运行时会返回 false。而 Java 由于类型擦除,运行时无法区分 List<Integer>List<String>

Q7: 什么时候使用扩展方法,什么时候用 Mixin?

  • 扩展现有类(如 String、List)的功能 → 扩展方法
  • 复用一组方法的实现到多个类 → Mixin
  • 需要访问 this 并调用类自身方法 → Mixin

相关阅读

  • https://plumephp.com/flutter-widgets-layout/ — Widget 体系与 Flutter 布局系统
  • https://plumephp.com/flutter-state-management/ — Flutter 状态管理方案对比与实战
  • https://plumephp.com/flutter-async-networking/ — Dart 在网络请求中的实际应用

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「Flutter」更多文章

  1. Widget 体系与布局系统
  2. Flutter 状态管理全解析
  3. Flutter 测试策略与自动化