Dart语法
原文地址 zhuanlan.zhihu.com
Dart语法
目录收起Flutter(Dart)级联运算符(…)的使用延迟初始化变量异步支持https://dart.cn/guides/language/language-tour#handling-futures处理 Futurehttps://dart.cn/guides/language/language-tour#declaring-async-functions声明异步函数Extension 方法扩展方法空安全的原则类比Java理解Dart的Mixin继承机制1 使用Dart和Java实现该类2 场景拓展3 Mixin版本实现4 顺序的问题demo1demo2最后的总结
Flutter(Dart)级联运算符(…)的使用
级联运算符(…)可以让你在同一个对象上连续调用多个对象的变量或方法。
示例
示例代码:
querySelector('#confirm') // 获取对象 (Get an object).
..text = 'Confirm' // 使用对象的成员 (Use its members).
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
第一个方法 querySelector 返回了一个 Selector 对象,后面的级联操作符都是调用这个 Selector 对象的成员并忽略每个操作的返回值。
级联运算符的嵌套
级联运算符可以嵌套,例如:
final addressBook = (AddressBookBuilder()
..name = 'jenny'
..email = 'jenny@example.com'
..phone = (PhoneNumberBuilder()
..number = '415-555-0100'
..label = 'home')
.build())
.build();
错误的用法
在返回对象的函数中谨慎使用级联操作符。
例如,下面的代码是错误的:
var sb = StringBuffer();
sb.write('foo')
..write('bar'); // 出错:void 对象中没有方法 write (Error: method 'write' isn't defined for 'void').
上述代码中的 sb.write() 方法返回的是 void,返回值为 void 的方法则不能使用级联运算符。
延迟初始化变量
Dart 2.12 新增了 late 修饰符,这个修饰符可以在以下情况中使用:
- 声明一个非空变量,但不在声明时初始化。
- 延迟初始化一个变量。
通常 Dart 的语义分析会在一个已声明为非空的变量被使用前检查它是否已经被赋值,但有时这个分析会失败。例如:在检查顶级变量和实例变量时,分析通常无法判断它们是否已经被初始化,因此不会进行分析。
如果你确定这个变量在使用前就已经被声明,但 Dart 判断失误的话,你可以在声明变量的时候使用 late 修饰来解决这个问题。
late String description;
void main() {
description = 'Feijoada!';
print(description);
}
若 late 标记的变量在使用前没有初始化,在变量被使用时会抛出运行时异常。
如果一个 late 修饰的变量在声明时就指定了初始化方法,那么它实际的初始化过程会发生在第一次被使用的时候。这样的延迟初始化在以下场景中会带来便利:
- Dart 认为这个变量可能在后文中没被使用,而且初始化时将产生较大的代价。
- 你正在初始化一个实例变量,它的初始化方法需要调用 this。
在下面这个例子中,如果 temperature 变量从未被使用的话,那么 readThermometer() 将永远不会被调用:
// This is the program's only call to readThermometer().
late String temperature = readThermometer(); // Lazily initialized.
异步支持
Dart 代码库中有大量返回 Future 或 Stream 对象的函数,这些函数都是 异步 的,它们会在耗时操作(比如I/O)执行完毕前直接返回而不会等待耗时操作执行完毕。
async 和 await 关键字用于实现异步编程,并且让你的代码看起来就像是同步的一样。
https://dart.cn/guides/language/language-tour#handling-futures处理 Future
可以通过下面两种方式,获得 Future 执行完成的结果:
- 使用 async 和 await,在 异步编程 codelab 中有更多描述;
- 使用 Future API,具体描述参考 库概览。
使用 async 和 await 的代码是异步的,但是看起来有点像同步代码。例如,下面的代码使用 await 等待异步函数的执行结果。
await lookUpVersion();
必须在带有 async 关键字的 异步函数 中使用 await:
Future<void> checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
备注:
尽管 async 函数可能会执行一些耗时操作,但是它并不会等待这些耗时操作完成,相反,异步函数执行时会在其遇到第一个 await 表达式时返回一个 Future 对象,然后等待 await 表达式执行完毕后继续执行。
使用 try、catch 以及 finally 来处理使用 await 导致的异常:
try {
version = await lookUpVersion();
} catch (e) {
// React to inability to look up the version
}
你可以在异步函数中多次使用 await 关键字。例如,下面代码中等待了三次函数结果:
var entrypoint = await findEntryPoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
await 表达式的返回值通常是一个 Future 对象;如果不是的话也会自动将其包裹在一个 Future 对象里。 Future 对象代表一个“承诺”, await 表达式会阻塞直到需要的对象返回。
如果在使用 await 时导致编译错误,请确保 await 在一个异步函数中使用。例如,如果想在 main() 函数中使用 await,那么 main() 函数就必须使用 async 关键字标识。
void main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
} 备注:
如上的例子使用了声明为 async 的函数 checkVersion(),但没有等待其结果。在实际的开发中,如果代码假设函数已经执行完成,则可能导致一些异步的问题。想要避免这些问题,请使用 unawaited_futures 提示规则。
For an interactive introduction to using futures, async, and await, see the asynchronous programming codelab.
https://dart.cn/guides/language/language-tour#declaring-async-functions声明异步函数
异步函数 是函数体由 async 关键字标记的函数。
将关键字 async 添加到函数并让其返回一个 Future 对象。假设有如下返回 String 对象的方法:
String lookUpVersion() => '1.0.0';
将其改为异步函数,返回值是 Future:
Future
注意,函数体不需要使用 Future API。如有必要,Dart 会创建 Future 对象。
如果函数没有返回有效值,需要设置其返回类型为 Future
关于 Future、async 和 await 的使用介绍,可以参见这个 codelab: asynchronous programming codelab。
- Dart代码运行在单个执行“线程”中。
- 阻塞执行线程的代码会使你的程序“冻结”。
- 一个Future 对象用于表示 异步操作 的结果,这些正在处理的操作或 I/O 将会在稍后完成。
- 在异步函数中使用 await 关键字暂停代码的执行,直到对应的 future 完成。
- 可以使用 try-catch 表达式来捕获异步函数中代码的执行错误。
- 为了可以使不同的代码片段同时执行,可以为它们创建一个 isolate(如果你的程序是一个 Web 应用,则创建 worker 替代 isolate)
Extension 方法
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
// ···
}
扩展方法
扩展方法是向现有库添加功能的一种方式。你可能已经在不知道它是扩展方法的情况下使用了它。例如,当您在 IDE 中使用代码完成功能时,它建议将扩展方法与常规方法一起使用。
这里是一个在 String 中使用扩展方法的样例,我们取名为 parseInt(),它在 string_apis.dart 中定义:
import 'string_apis.dart';
...
print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.
空安全的原则
Dart 的空安全支持基于以下三条核心原则:
- 默认不可空。除非你将变量显式声明为可空,否则它一定是非空的类型。我们在研究后发现,非空是目前的 API 中最常见的选择,所以选择了非空作为默认值。
- 渐进迁移。你可以自由地选择何时进行迁移,多少代码会进行迁移。你可以使用混合模式的空安全,在一个项目中同时使用空安全和非空安全的代码。我们也提供了帮助你进行迁移的工具。
- 完全可靠。Dart 的空安全是非常可靠的,意味着编译期间包含了很多优化。如果类型系统推断出某个变量不为空,那么它 永远 不为空。当你将整个项目和其依赖完全迁移至空安全后,你会享有健全性带来的所有优势—— 更少的 BUG、更小的二进制文件以及更快的执行速度。
类比Java理解Dart的Mixin继承机制
本文使用的案例如下,一个类关系图
1 使用Dart和Java实现该类
类的分析
1 软件工程师和建筑工程师继承于工程师,工程师继承于工作者
2 美术教师和IT教师继承于教师,教师继承于工作者
Java版本
//工作者abstract class Worker { public abstract void doWork();//工作者需要工作 } //工程师class Engineer extends Worker { @Override public void doWork() { System.out.println("工程师在工作"); } } //教师class Teacher extends Worker { @Override public void doWork() { System.out.println("教师在教学"); } } //软件工程师class SoftwareEngineer extends Engineer { } //建筑工程师class BuildingEngineer extends Engineer { } //美术教师class ArtTeacher extends Teacher { } //IT教师class ITTeacher extends Teacher { }复制代码Dart版本
//工作者abstract class Worker { void doWork();//工作者需要工作 } //工程师class Engineer extends Worker { void doWork() { print('工程师在工作'); } } //教师class Teacher extends Worker { void doWork() { print('教师在教学'); } } //软件工程师class SoftwareEngineer extends Engineer { } //建筑工程师class BuildingEngineer extends Engineer { } //美术教师class ArtTeacher extends Teacher { } //IT教师class ITTeacher extends Teacher { }复制代码总结:
上面的场景感觉并没有什么区别,因为Dart是单继承,对于上面的简单场景无法体现出区别
2 场景拓展
赋予这些职业一些能力
类分析:
软件工程师和IT教师都具备修电脑的能力,建筑工程师和美术教师都具备手绘的能力,但是这些能力是他们特有的,不是工程师和教师具备的能力,所以不能在父辈中实现,这个时候就需要一个东西叫做接口。
Java版本
interface **CanFixComputer** { void fixComputer(); } interface **CanDesignSoftware** { void designSoftware(); } _//软件工程师_**class** **SoftwareEngineer** **extends** **Engineer** **implements** **CanFixComputer**, **CanDesignSoftware** { **@Override** public void fixComputer() { **System**.out.println("修电脑"); } **@Override** public void designSoftware() { **System**.out.println("设计软件"); } } _//IT教师_**class** **ITTeacher** **extends** **Teacher** **implements** **CanFixComputer** { **@Override** public void fixComputer() { **System**.out.println("修电脑"); } }复制代码**Dart版本**
**abstract** **class** **CanFixComputer** { void fixComputer(); } **abstract** **class** **CanDesignSoftware** { void designSoftware(); } _//软件工程师_**class** **SoftwareEngineer** **extends** **Engineer** **implements** **CanFixComputer**, **CanDesignSoftware** { void fixComputer() { print('修电脑'); } void designSoftware() { print('设计软件'); } } _//IT教师_**class** **ITTeacher** **extends** **Teacher** **implements** **CanFixComputer** { void fixComputer() { print('修电脑'); } }复制代码**总结:**
只有语法上的区别,Dart虽然没有接口,但是任何一个类就可以当作接口,所以本质上依然是一样的,
但是
如果使用Dart的Mixin思想去实现,是怎样的呢?
3 Mixin版本实现
1 封装修电脑能力和设计能力,使用factory关键字结合_权限符避免外部实例化和拓展
abstract class CanFixComputer { factory CanFixComputer._() { return null; } void fixComputer() { print('修电脑'); } } abstract class CanDesignSoftware { factory CanDesignSoftware._() { return null; } void designSoftware() { print('设计软件'); } }复制代码2 使用with
//软件工程师class SoftwareEngineer extends Engineer with CanFixComputer, CanDesignSoftware { } //IT教师class ITTeacher extends Teacher with CanFixComputer { }复制代码总结
每个具有某项特性的类不再需要具体去实现同样的功能,接口是没法实现功能的,而通过继承的方式虽然能实现功能,但已经有父类,同时不是一个父类,又不能多继承,所以这个时候,Dart的Mixin机制就比Java的接口会高效,开发上层的只需要关心当前需要什么特性,而开发功能模块的关心具体要实现什么功能。
4 顺序的问题
demo1
class First { void doPrint() { print('First'); } } class Second { void doPrint() { print('Second'); } } class Father { void doPrint() { print('Father'); } } class Son1 extends Father with First,Second { void doPrint() { print('Son1'); } } class Son2 extends Father with First implements Second { void doPrint() { print('Son2'); } } main() { Son1 son1 = new Son1(); son1.doPrint(); Son2 son2 = new Son2(); son2.doPrint(); }
优先级最高的总是类内实现的
demo2
class First { void doPrint() { print('First'); } } class Second { void doPrint() { print('Second'); } } class Father { void doPrint() { print('Father'); } } class Son1 extends Father with First,Second { } class Son2 extends Father with First implements Second { } main() { Son1 son1 = new Son1(); son1.doPrint(); Son2 son2 = new Son2(); son2.doPrint(); }
总结
可以看到,其实在Son2中implements只是说要实现他的doPrint()方法,这个时候其实具体实现是First中Mixin了具体实现。
而Mixin的具体顺序也是可以从代码倒过来看的,最后mixin的优先级是最高的。
最后的总结
1 Mixin将功能和类的上层设计分离,上层关心需要什么特性,开发功能模块关心具体实现什么功能
2 如果两个类继承于不同的父类但是有相同的功能,Mixin的优点就显示了出来
编辑于 2023-04-21 10:16・IP 属地山东开启赞赏赞赏开启后,读者将可以付费支持你的创作。Dart赞同添加评论分享喜欢收藏设置