1.Dart中的 ?? 和 ?. 分别是什么作用?
-
??:空值合并运算符
如果左边表达式不为null,返回左边;否则返回右边。
String? name = null;
String display = name ?? "Guest"; // "Guest"
-
?.:安全调用运算符
当左边对象不为 null 时,调用其成员;否则返回 null(不会抛出异常)。
String? name = null;
int? length = name?.length; // null,不会报错
⚠️ 注意事项
?? 的右侧不一定是默认值,也可以是一个表达式或函数调用(会短路求值)。
?. 与 !(强制解包)谨慎搭配,避免在不明确是否为空时使用 !。
2. 简述Dart的异步模型,Future 和 Stream 的区别。
异步模型:Dart 是单线程异步模型,基于事件循环(Event Loop)。异步任务通过 Future 或 Stream 调度,不会阻塞主线程。
-
Future:表示一个一次性的异步计算结果(成功或失败)。可通过then、catchError或async/await处理。 -
Stream:表示一个零个或多个异步数据序列,随时间推送事件。可监听多个值、错误、完成事件。
⚠️ 注意事项:
Future的await只等待单个结果;Stream需要await for或监听器。Stream分为单订阅(single-subscription)和广播(broadcast)两种,注意区分。
3. mixin 和继承有什么区别?何时使用 mixin?
- 继承(extends):类与类之间是“is-a”关系,子类获得父类所有非私有成员,且只能单继承。
-
mixin:通过
with关键字实现代码复用,可“混入”多个 mixin 的方法和属性,没有层级限制。
使用场景:
- 需要跨类层次共享通用行为(如日志、序列化、工具方法)。
- 避免深层继承导致的复杂结构。
⚠️ 注意事项:
- mixin 不能有构造函数(除非用
mixin class)。on关键字可以限制 mixin 只能用在特定类型的类上。- 当你需要“复用行为”而不是构建类型层次时,优先考虑 mixin;
当类之间本质上是“父类-子类”关系时,使用 extends。
4. 说说Dart中的空安全(null safety)是如何保证安全的?
-
非空类型(Type):变量默认不能为
null,编译器会强制检查初始化与赋值。 -
可空类型(Type?):允许为
null,使用前必须进行空检查或使用?.、!。 - 延迟初始化(late):允许非空变量稍后初始化,但若使用前未赋值会抛出异常。
-
类型提升:对可空变量进行
if (variable != null)判断后,作用域内自动提升为非空类型。
⚠️ 注意事项:
late变量应谨慎使用,避免依赖外部保证。- 空安全在 Dart 2.12 及以上默认开启,迁移老代码需运行
dart migrate。
5. Dart 中 final 和 const 的区别是什么?
-
final:只能赋值一次,值在运行时确定。 -
const:编译时常量,值必须在编译时确定,且会隐式地final。const可以用于常量表达式,且相同值的const对象是单例的。
⚠️ 注意事项:
const构造函数可以优化性能,因为编译时常量可以复用。final实例变量必须在构造体运行前初始化。
6 什么是 Dart 的 isolate?与线程有何不同?
-
isolate 是 Dart 的并发单元,独立内存空间,不共享状态,通过
SendPort/ReceivePort传递消息。 - 不同于操作系统线程(共享内存),isolate 没有竞态条件,但通信成本更高。
⚠️ 注意事项:
compute函数是 Isolate 的简单封装,适合将耗时任务放到后台 isolate 执行。- 不要频繁创建 isolate。
7. async / await 与 Future.then 哪种更好?为什么?
async/await 更易读,错误处理可以用 try-catch,而 .then 需要单独捕获错误。两者本质相同,async/await 是语法糖。
⚠️ 注意事项:
- 在循环中使用
await会串行执行;如果需要并行,应使用Future.wait。
8. 解释 Dart 中的 factory 构造函数。
factory 构造函数不总是创建新实例,可以返回现有对象(如从缓存获取)或子类对象。它没有 this 访问权限。
⚠️ 注意事项:
factory命名构造函数常用于实现单例模式或对象池。
9. Dart 中 ??= 运算符的作用?
当变量为 null 时进行赋值。例如 a ??= 3; 如果 a 为 null 则赋值为 3,否则保持不变。
⚠️ 注意事项:
- 相当于
a = a ?? 3;,但更简洁。- 只在左侧为
null时计算右侧表达式。
10. 什么是 extension 方法?使用场景?
extension 允许为现有类添加新方法或属性,无需继承或修改原类。常用于扩展第三方库类或系统类(如 String)。
⚠️ 注意事项:
- 扩展方法不会覆盖原类方法。
- 要注意命名冲突,可通过
hide或显式指定扩展名解决。
11. Dart 中的 typedef 有什么用途?
为函数类型定义一个别名,提高代码可读性。也可以定义泛型函数类型。Dart 2.13+ 支持 typedef 用于非函数类型(如 typedef IntList = List<int>;)。
⚠️ 注意事项:
- 主要用于回调类型声明,使用时确保函数签名匹配。
12 解释 late 关键字及其风险。
late 表示一个非空变量可以延迟初始化,在第一次访问前必须赋值,否则抛出异常。常用于 late final 组合或依赖注入。
⚠️ 注意事项:
- 过度使用
late可能导致运行时错误,不如使用可空类型加安全调用。- 避免在
late变量初始化时访问this未完全构建的对象。
12 Dart 中如何实现枚举的扩展(如添加方法)?
Dart 2.17+ 支持增强型枚举,可以包含字段、构造函数和方法。示例:
enum Status {
loading, success, error;
bool get isError => this == Status.error;
}
早期版本可以通过扩展方法模拟。
⚠️ 注意事项:
枚举的构造函数必须是 const。
13. sync* 和 async* 生成器的区别?
-
sync*返回Iterable,使用yield同步产生值。 -
async*返回Stream,使用yield异步产生值,可等待Future。
⚠️ 注意事项:
async*必须用await for或监听来消费。- 生成器函数不能执行
return返回值(除非空返回)。