3、Dart语言基础回顾:变量与数据类型、函数与闭包、面向对象编程、异步编程(Future/Stream)
好,咱们直接进入正题。这一章我打算带大家快速过一遍 Dart 的核心语法。你可能会想:「我都开始搞 Flutter 桌面端了,还要回头学基础?」嗯,我理解这个想法。但说实话,很多桌面端开发中的坑,恰恰是因为对 Dart 的某些特性理解不够深导致的。尤其是异步编程和闭包,在桌面端的高频交互场景下,用不好就会出大问题。
我个人习惯是,每接手一个新项目,都会先花半小时把语言基础在脑子里过一遍。不是为了背语法,而是为了建立「肌肉记忆」。下面我就按四个模块来梳理,每个模块我都会结合我在实际桌面端项目中的踩坑经历来讲。
3.1 变量与数据类型
Dart 是强类型语言,但同时也支持类型推断。说白了,你写 var name = 'Flutter' 和 String name = 'Flutter' 效果一样。我个人更推荐在变量名长、类型明显时用 var,在函数返回值或复杂泛型场景下显式声明类型。为什么?因为代码可读性会好很多。
数据类型分几类:
- 基本类型:int、double、String、bool。注意 Dart 里没有 byte、short 这些,int 在桌面端是 64 位的。
- 集合类型:List、Set、Map。这三个用得最多。我在项目中遇到过一个问题:用
List.from()做浅拷贝,结果修改子对象影响了原数据。你想想看,桌面端数据量大,这种引用传递的坑很容易被忽略。 - 特殊类型:dynamic、Object、Null。我建议尽量少用 dynamic,除非你明确知道自己在做什么。否则类型错误会在运行时才暴露,调试起来很痛苦。
重要提醒:Dart 中一切皆对象,包括 null。int、double 这些基本类型也是对象,所以它们可以调用方法,比如 42.toString()。
我的小技巧:在桌面端开发中,我习惯用 final 而不是 const 来声明那些只赋值一次的变量。因为 const 是编译时常量,要求值在编译期确定,而 final 更灵活。比如从配置文件读取的路径,用 final 就对了。
3.2 函数与闭包
Dart 的函数是一等公民。什么意思?就是函数可以赋值给变量、可以作为参数传递、也可以作为返回值。这在 Flutter 桌面端开发中太常见了——比如按钮的回调、Stream 的监听器,本质上都是函数。
来看一个简单的例子:
void main() {
// 函数赋值给变量
var sayHello = (String name) {
print('Hello, $name');
};
sayHello('Flutter');
// 函数作为参数
void execute(void Function(String) callback) {
callback('Desktop');
}
execute(sayHello);
}
闭包是函数加上了它创建时的上下文环境。说白了,就是函数「记住」了它外面的变量。我曾经在桌面端的一个列表筛选功能中,用闭包来保存每个筛选器的状态,结果因为闭包引用导致内存泄漏。嗯,这里要注意:闭包会持有外部变量的引用,如果那个变量是一个大对象,而闭包的生命周期又很长,就会造成内存问题。
避坑指南:我曾经在循环中使用闭包,结果所有闭包都共享了同一个循环变量。解决方案是用一个立即执行函数来捕获当前值,或者直接用 forEach 替代 for 循环。
3.3 面向对象编程
Dart 是纯面向对象的语言,但它的设计比 Java 更灵活。类、继承、接口、混入(Mixin)这些概念都要掌握。在桌面端开发中,我经常用 Mixin 来复用一些通用的行为,比如日志记录、权限检查等。
举个例子:
mixin Logger {
void log(String message) {
print('[LOG] $message');
}
}
class FileManager with Logger {
void readFile(String path) {
log('Reading file: $path');
// 实际读取逻辑
}
}
这里要注意,Dart 没有 interface 关键字,但每个类都可以当作接口来用。用 implements 关键字实现接口时,必须重写所有方法。我个人习惯用抽象类来定义接口,因为抽象类可以包含默认实现。
核心要点:Dart 的构造方法有几种特殊形式:命名构造方法、工厂构造方法、重定向构造方法。工厂构造方法在桌面端开发中特别有用,比如实现单例模式或者从缓存中返回对象。
3.4 异步编程(Future/Stream)
这是重头戏。桌面端应用和移动端不同,用户可能会同时打开多个窗口、执行多个耗时操作。如果异步编程没用好,界面卡顿是必然的。
Dart 的异步模型基于 Future 和 Stream。Future 代表一个将来会完成的值,Stream 代表一系列异步事件。我建议你记住一个原则:能用 Stream 就别用回调,能用 async/await 就别用 .then()。
来看一个 Future 的例子:
Future<String> fetchData() async {
// 模拟网络请求
await Future.delayed(Duration(seconds: 2));
return '数据加载完成';
}
void main() async {
print('开始加载...');
String result = await fetchData();
print(result);
}
Stream 在桌面端更常用,比如监听文件变化、用户输入事件等。我曾经在做一个文件同步工具时,用 Stream 来监听文件夹的变动,然后实时更新 UI。效果很好,但要注意 Stream 的订阅管理——忘记取消订阅会导致内存泄漏。
我的经验:在桌面端开发中,我习惯用 StreamController 来手动控制 Stream 的创建和销毁。记得在 dispose 方法中调用 streamController.close(),否则 Stream 会一直存在,造成资源浪费。
避坑指南:我曾经在异步函数中忘记加 await,结果代码顺序完全乱了。Dart 的异步函数如果不加 await,会立即返回一个 Future,后续代码会继续执行。这在桌面端可能导致 UI 更新和数据处理不同步。
好了,以上就是 Dart 语言基础的四个核心模块。说实话,这些内容看起来简单,但真正用好需要大量实践。我建议你在写桌面端代码时,多想想「这个变量用 final 还是 var?」、「这个回调会不会造成闭包泄漏?」、「这个异步操作有没有正确处理异常?」。养成这些习惯,你的代码质量会提升一个档次。
下一章我们会开始搭建 Flutter 桌面端的开发环境,到时候我会分享一些我踩过的坑,比如 Windows 上的路径问题、macOS 的权限配置等。咱们到时候见。