文章目录
- Dart中dynamic,var,Object三者的区别?
- Dart中const和final的区别?
- Dart中..是什么?...是什么?
- Dart中如何给String扩展一个方法?
- Dart中async和await?
Dart中dynamic,var,Object三者的区别?
一句话总结
dynamic:动态类型变量。编译时不进行类型检查,可动态赋值任意类型。运行时才会校验,可能导致运行时错误。var:不是类型,而是关键字。一旦推断为某类型,就不能再赋其他类型的值。适合局部变量声明。Object:所有类型都继承自Object,编译时严格检查;要用更具体的方法需要is/as进行显式的向下转型才能用。
代码演示
dynamic:动态类型变量
dynamic value = "dart";
value = 3.14; // 合法:可改为double类型
value.toUpperCase(); // 编译不报错,运行时错误:double无该方法
value.toString(); // 合法:double有该方法
value.unknownMethod(); // 编译不报错,运行时抛出错误
var:类型推断变量
var x = 'hi'; // 推断为 String
// x = 42; // ❌ 编译期报错:不能把 int 赋给 String
x = 'hello';
var y; // 没有初始化器 => 推断为 dynamic
y = 'hi'; // ✅
y = 42; // ✅
Object:所有类型的根类
Object obj = "flutter";
// obj.length; // 编译错误:Object无length属性
(obj as String).length; // 合法:通过类型转换后可调用String方法
obj = 123; // 合法:int是Object的子类
obj.toString(); // 合法:Object有该方法
Dart中const和final的区别?
一句话总结
const:编译期常量,在编译时就必须能确定值,且不可修改。final:运行时常量,只能赋值一次。值在运行时确定,之后不可再修改。
代码演示
const:编译时常量
void main() {
// 合法:字面量是编译时常量
const maxCount = 100;
const pi = 3.14159;
const greeting = "Hello";
// 合法:const变量可由其他const变量计算而来
const double maxRadius = maxCount * pi;
// 非法:运行时才能确定的值不能用于const
// const currentTime = DateTime.now(); // 编译错误
}
// 类中使用:需声明为static const(类级别的编译时常量)
class Config {
static const apiUrl = "https://api.example.com";
}
final:运行时常量
void main() {
// 局部变量
final name = "Flutter"; // 类型推断为String
// name = "Dart"; // 编译错误:final变量不可重新赋值
// 运行时确定值(合法)
final currentTime = DateTime.now(); // 每次运行时值不同
}
// 类成员
class Person {
final String id;
// 可在构造函数中初始化
Person(this.id);
}
进阶用法:
const的不可变性是深度的,而final仅保证变量引用不可变,不限制对象内部状态:
// final变量:引用不可变,但对象内容可修改
final list = [1, 2, 3];
list.add(4); // 合法:list引用未变,仅修改内部元素
// list = [4, 5, 6]; // 非法:不能重新赋值
// const变量:引用和对象内容均不可修改
const constList = [1, 2, 3];
// constList.add(4); // 编译错误:const对象不可修改
// constList = [4, 5, 6]; // 编译错误:不能重新赋值
const变量在内存中只有一份实例,相同值的const变量会共享同一内存地址。而final变量每次都会创建新实例,即使值相同
const a = [1, 2];
const b = [1, 2];
print(identical(a, b)); // true,相同实例
final c = [1, 2];
final d = [1, 2];
print(identical(c, d)); // false,不同实例
const修饰的构造函数:
- 必须用于创建不可变对象,即类的所有实例变量必须是
final类型 - 构造函数体必须为空(使用
;结束,不能有{}) - 如果类有父类,父类也必须有常量构造函数
- 实例化时必须使用
const关键字(除非在常量上下文中) - 内部引用对象的类也要
const构造
正确示例:
// 正确的常量构造函数示例
class ImmutablePoint {
// 所有实例变量必须是final
final int x;
final int y;
// 常量构造函数,使用const修饰且没有函数体
const ImmutablePoint(this.x, this.y);
@override
String toString() => 'Point($x, $y)';
}
// 继承情况下的常量构造函数
class Immutable3DPoint extends ImmutablePoint {
final int z;
// 父类必须有常量构造函数,且这里必须调用父类的常量构造函数
const Immutable3DPoint(int x, int y, this.z) : super(x, y);
@override
String toString() => 'Point($x, $y, $z)';
}
void main() {
// 使用const创建实例
const point1 = ImmutablePoint(1, 2);
const point2 = ImmutablePoint(1, 2);
// 相同参数的const实例是同一个对象
print(point1 == point2); // 输出: true
// 在常量上下文中可以省略const
const points = [
ImmutablePoint(0, 0), // 等价于 const ImmutablePoint(0, 0)
ImmutablePoint(10, 20), // 等价于 const ImmutablePoint(10, 20)
];
// 3D点示例
const point3D = Immutable3DPoint(1, 2, 3);
print(point3D); // 输出: Point(1, 2, 3)
}
错误示例:
// 错误示例1:存在非final变量
class BadPoint1 {
int x; // 错误:不是final变量
final int y;
const BadPoint1(this.x, this.y); // 编译错误
}
// 错误示例2:有构造函数体
class BadPoint2 {
final int x;
final int y;
// 错误:常量构造函数不能有函数体
const BadPoint2(this.x, this.y) {
print('Creating point');
}
}
// 错误示例3:父类没有常量构造函数
class Parent {
final int value;
Parent(this.value); // 不是常量构造函数
}
class Child extends Parent {
// 错误:父类没有常量构造函数
const Child(int value) : super(value);
}
// 错误示例4
class Point {
final int x;
final int y;
// 常量构造函数
const Point(this.x, this.y);
}
// 虽然调用了常量构造函数,但未用 const 修饰,创建的是普通对象
final p1 = Point(1, 2); // 应该用 const Point(1, 2)
final p2 = Point(1, 2); // 相同参数,但未用 const
// 因为省略了 const,它们是不同的对象(== 为 false)
print(p1 == p2); // 输出 false(不符合常量语义的预期)
// 正确做法:显式使用 const
const p3 = Point(1, 2);
const p4 = Point(1, 2);
print(p3 == p4); // 输出 true(符合常量语义)
// 错误示例5:Address 类没有 const 构造函数(仅普通构造)
class Address {
final String city;
final String street;
// 普通构造函数(无 const)
Address(this.city, this.street);
}
class User {
final String name;
final Address address;
const User(this.name, this.address); // 常量构造函数
}
const user = User(
'Charlie',
Address('Guangzhou', 'River Road'), // 编译错误:Address 没有 const 构造
);
final 和 const 谁性能更好?
const更好,因为对象在编译期就分配好,运行时不再重复创建。- Flutter 中
const Widget会被缓存重用,减少构建时对象创建,优化性能。而final Widget每次 build 都要新建。
const 一定比 final 更好吗?
不完全。const 只能用于编译期可确定的值,场景有限。final 更灵活,适合依赖运行时的数据。
Dart中..是什么?...是什么?
一句话总结
..:级联操作符是允许对同一个对象连续连续调用多个方法或访问属性, 无需重复引用对象本身,简化链式操作。...:展开操作符将一个可迭代对象(如 List、Set、Map)的元素 “展开” 并插入到另一个集合中,简化集合的合并或初始化。
代码演示
..:级联操作符
class Person {
String name = '';
int age = 0;
void sayHello() => print("Hello, I'm $name");
void setAge(int a) => age = a;
}
void main() {
// 普通写法:多次引用对象
final person1 = Person();
person1.name = "Alice";
person1.setAge(25);
person1.sayHello();
// 级联操作写法:简化为链式调用
final person2 = Person()
..name = "Bob" // 设置属性
..setAge(30) // 调用方法
..sayHello(); // 继续调用方法
// 嵌套级联(对对象的属性进行级联)
final Map<String, dynamic> config = {};
config
..["host"] = "api.example.com"
..["port"] = 8080
..["headers"] = {}
..["headers"]["Content-Type"] = "application/json";
}
...:展开操作符
void main() {
final list1 = [1, 2, 3];
final list2 = [4, 5, 6];
// 合并两个列表(普通写法)
final combined1 = List.from(list1)..addAll(list2);
// 展开操作符写法(更简洁)
final combined2 = [...list1, ...list2]; // [1, 2, 3, 4, 5, 6]
// 混合元素和展开
final combined3 = [0, ...list1, 7, ...list2]; // [0, 1, 2, 3, 7, 4, 5, 6]
// 用于Set去重(Set本身去重特性+展开)
final set1 = {1, 2, 3};
final set2 = {3, 4, 5};
final combinedSet = {...set1, ...set2}; // {1, 2, 3, 4, 5}
// 空安全处理:可空迭代对象使用...?
List<int>? nullableList = null;
final safeList = [0, ...?nullableList, 6]; // [0, 6](若nullableList为null则忽略)
}
Dart中如何给String扩展一个方法?
一句话总结
- Dart 的 Extension Methods 特性允许你向现有的类(甚至是来自外部库的类)添加新功能,而无需修改原始类的代码或创建子类。Dart 2.7 及以上版本引入的特性,它是编译期静态分发,没有运行时开销。
代码演示
extension:扩展方法
// 定义String的扩展
extension StringExtensions on String {
// 扩展方法1:判断字符串是否为数字
bool isNumber() {
if (isEmpty) return false;
return double.tryParse(this) != null;
}
// 扩展方法2:判断字符串是否包含指定列表中的任意元素
bool containsAny(List<String> substrings) {
for (final sub in substrings) {
if (contains(sub)) return true;
}
return false;
}
// 将字符串转换为指定类型的列表(示例:按分隔符拆分后转换为int)
List<T>? toList<T>(T Function(String) converter, {String separator = ","}) {
try {
return split(separator).map((s) => converter(s.trim())).toList();
} catch (e) {
return null; // 转换失败返回null
}
}
}
// 扩展1
extension StringExt1 on String {
String format() => "Ext1: $this";
}
// 扩展2(与扩展1方法名冲突)
extension StringExt2 on String {
String format() => "Ext2: $this";
}
// 使用扩展方法
void main() {
final str1 = "123";
print(str1.isNumber()); // 输出: true
final str2 = "hello world";
print(str2.containsAny(["hello", "dart"])); // 输出: true
final str3 = "1,2,3,4";
final numbers = str3.toList<int>((s) => int.parse(s));
print(numbers); // 输出: [1, 2, 3, 4]
final str4 = "test";
// 直接调用会报错(方法名冲突)
// print(str4.format());
// 显式指定扩展名称调用
print(StringExt1(str4).format()); // 输出: "Ext1: test"
print(StringExt2(str4).format()); // 输出: "Ext2: test"
}
Dart中async和await?
一句话总结
async和await是处理异步操作的核心语法糖,它们让异步代码的写法更接近同步代码,大幅提升可读性,避免回调地狱(多层then嵌套)。
核心概念
async:标记异步函数
- 作用:声明一个函数为异步函数,使其返回值自动包装为
Future(即使函数实际返回非Future类型)。 - 特性:
- 异步函数内部可以使用
await关键字(非异步函数中不能使用await)。 - 若函数无返回值,实际返回
Future<void>;若返回值为T类型,实际返回Future<T>。
- 异步函数内部可以使用
await:暂停等待异步结果
- 作用:在异步函数中,暂停代码执行,等待
Future完成并获取其结果后再继续执行。 - 特性:
- 仅能在
async标记的函数内部使用。 await会将Future<T>转换为T类型(获取异步结果的 "解包" 操作)。- 等待期间不会阻塞当前线程(Dart 是单线程事件循环模型,会切换执行其他任务)。
- 仅能在
Dart 的 async/await 并不是多线程,而是基于 单线程事件循环(Event Loop)+ Future 微任务队列 实现的。
await并不会阻塞线程,只是让函数把后续代码注册为一个回调,等 Future 完成后再继续。- Dart VM 的事件循环模型类似 JavaScript:
- Microtask Queue(微任务队列,优先级高,包括Future.then的回调函数,async/await的回调函数.)
- Event Queue(事件队列,处理 IO/Timer/消息)
所以 await 本质是:语法糖包装了 then 回调。
代码演示
// 模拟异步请求
Future<String> fetchUser() async {
await Future.delayed(Duration(seconds: 1));
return "UserA";
}
Future<String> fetchOrders(String user) async {
await Future.delayed(Duration(seconds: 1));
return "Orders_of_$user";
}
Future<String> fetchOrderDetail(String orders) async {
await Future.delayed(Duration(seconds: 1));
return "Detail_of_$orders";
}
void main() async {
print("=== 使用 then 的写法 ===");
fetchUser().then((user) {
return fetchOrders(user).then((orders) {
return fetchOrderDetail(orders).then((detail) {
print("最终结果: $detail");
});
});
});
// 分隔开
await Future.delayed(Duration(seconds: 4));
print("\n=== 使用 async/await 的写法 ===");
final user = await fetchUser();
final orders = await fetchOrders(user);
final detail = await fetchOrderDetail(orders);
print("最终结果: $detail");
}
优先使用async/await而非then链:复杂异步逻辑中,async/await的线性代码结构更易维护。
void main() {
print('1: Main starts');
fetchUserOrder(); // 调用异步函数
print('4: Main ends');
}
Future<void> fetchUserOrder() async {
print('2: Fetching user order...');
// 模拟网络请求
final order = await Future.delayed(Duration(seconds: 2), () => 'Large Latte');
print('5: Your order is: $order');
}
// 输出顺序:
// 1: Main starts
// 2: Fetching user order...
// 4: Main ends
// (等待约2秒后)
// 5: Your order is: Large Latte
流程解析:
main()开始执行,打印1。- 调用
fetchUserOrder(),它是一个async函数。 - 进入
fetchUserOrder(),同步执行代码直到await,打印2。 - 遇到
await,fetchUserOrder()函数在此暂停并立即返回一个未完成的Future<void>给调用者。 main()函数继续同步执行,打印4,然后结束。此时main()函数执行完毕。- Event Loop 现在空闲了。大约 2 秒后,
Future.delayed完成,它将'Large Latte'作为结果,并将其回调任务(即print('5: Your order is: $order'))放入 Microtask Queue(对于Future,通常是微任务队列)。 - Event Loop 从微任务队列中取出这个回调任务并执行它,打印
5。
这个过程完美展示了 “暂停而不阻塞” 的含义。