1.泛型
Dart中泛型 (或 参数化) 类型使用<…>
符号表示。通常情况下,使用一个字母来代表类型参数, 例如 E, T, S, K, 和 V 等。1
2
3
4
5
6
7
8
9
10
11
12
13
14// 使用集合字面量
var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
// 运行时中的泛型集合
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
var views = Map<int, View>(); // _InternalLinkedHashMap<int, View>
泛型函数
1 | T first<T>(List<T> ts) { |
类的泛型
1 | class Location<T> { |
使用泛型类型的时候, 可以使用 extends
实现参数类型的限制。如果我们希望类型只能是num类型:1
2
3
4
5
6class Location<T extends num> {
T x;
T y;
Location(this.x, this.y);
}
2.库
import
和 library
指令可以用来创建一个模块化的,可共享的代码库。 库不仅提供了 API ,而且对代码起到了封装的作用: 以下划线 (_) 开头的标识符仅在库内可见。
库可以通过包来分发。有关 pub(集成在SDK中的包管理器)的信息,请参考 Pub Package 和 Asset Manager。
每个 Dart 应用程序都是一个库 ,虽然没有使用 library 指令。
库的导入
通过 import
指定一个库命名空间中的内如如何在另一个库中使用。对于内置库,URI 拥有自己特殊的dart:
前缀 。 对于其他的库,用系统文件路径或者package:
前缀指定由包管理器(如 pub 工具)提供的库。例如,Dart Web应用程序通常使用 dart:html 库,它们可以像这样导入:1
2
3import 'dart:html';
import 'package:flutter/material.dart';
指定库前缀
如果导入两个存在冲突标识符的库, 则可以为这两个库,或者其中一个指定前缀。 例如,如果 library1 和 library2 都有一个 Element 类, 那么可以通过下面的方式处理:1
2
3
4
5
6
7
8import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// 使用 lib1 中的 Element。
Element element1 = Element();
// 使用 lib2 中的 Element。
lib2.Element element2 = lib2.Element();
导入库的一部分
如果你只使用库的一部分功能,则可以选择需要导入的 内容。例如:1
2
3
4
5// Import only foo.
import 'package:lib1/lib1.dart' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
延迟加载库
Deferred loading (也称之为 lazy loading) 可以让应用在需要的时候再加载库。 下面是一些使用延迟加载库的场景:
- 减少 APP 的启动时间。
- 执行 A/B 测试,例如 尝试各种算法的 不同实现。
- 加载很少使用的功能,例如可选的屏幕和对话框。
要延迟加载一个库,需要先使用 deferred as
来导入:1
2
3
4
5
6
7import 'package:greetings/hello.dart' deferred as hello;
// 当需要使用的时候,使用库标识符调用 loadLibrary() 函数来加载库
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
在一个库上你可以多次调用
loadLibrary()
函数。但是该库只是载入一次。
Dart 隐含的把 loadLibrary() 函数导入到使用 deferred as 的命名空间 中。 loadLibrary() 方法返回一个 Future。
实现库
组织库的源文件y有export
命令和part
命令两种方式,官方不推荐使用part关键字
。export关键字
将每一个dart文件作为库文件,使用export关键字在某个库文件中单独导入。
mathUtils.dart
文件1
2
3int sum(int num1, int num2) {
return num1 + num2;
}dateUtils.dart
文件1
2
3
4
5
6
7
8
9String dateFormat(DateTime date) {
return "2019-12-17";
}
`utils.dart`文件
```dart
library utils;
export "mathUtils.dart";
export "dateUtils.dart";test_libary.dart
文件1
2
3
4
5
6import "lib/utils.dart";
main(List<String> args) {
print(sum(10, 20));
print(dateFormat(DateTime.now()));
}
Dart异步模型
Dart是单线程的,在开发中,我们经常会遇到一些耗时的操作需要完成,比如网络请求、文件读取等等。处理耗时操作一般有两种方式:
- 处理方式一: 多线程,比如Java、我们普遍的做法是开启一个新的线程(Thread),在新的线程中完成这些异步的操作,再通过线程间通信的方式,将拿到的数据传递给主线程。
- 处理方式二: 单线程+事件循环,比如JavaScript、Dart都是基于单线程加事件循环来完成耗时操作的处理。
不过单线程如何能进行耗时的操作呢?
单线程模型中主要就是在维护着一个事件循环(Event Loop)。
- 事实上事件循环并不复杂,它就是将需要处理的一系列事件(包括点击事件、IO事件、网络事件)放在一个事件队列(Event Queue)中。
- 不断的从事件队列(Event Queue)中取出事件,并执行其对应需要执行的代码块,直到事件队列清空位置。
Dart 库中包含许多返回 Future
或 Stream
对象的函数。这些函数在设置完耗时任务(例如 I/O 曹组)后, 就立即返回了,不会等待耗任务完成。 使用 async
和 await
关键字实现异步编程。 可以让你像编写同步代码一样实现异步操作。
Feature
可以将 Future 理解成 ES6 中的
Promise
。
在 Dart 库中随处可见 Future 对象,通常异步函数返回的对象就是一个 Future。 当一个 future 完成执行后,future 中的值就已经可以使用了。当 future 执行完成后,then() 中的代码会被执行。例如, HttpRequest.getString() 返回一个 future 对象,因为 HTTP 请求可能需要一段时间。 当 Future 完成并且保证字符串值有效后,使用 then() 来执行你需要的代码:1
2
3
4
5
6HttpRequest.getString(url).then((String result) {
print(result);
}).catchError((e) {
// Handle or ignore the error.
print(e);
});
使用 catchError()
来处理一些 Future 对象可能抛出的错误或者异常。then().catchError()
组合是 try-catch
的异步版本。
重要: 确保调用 catchError() 方式在 then() 的结果上,而不是在原来的 Future 对象上调用。 否则的话,catchError() 就只能处理原来 Future 对象抛出的异常, 而无法处理 then() 代码里面的异常。
等待多个 Future
有时代码逻辑需要调用多个异步函数, 并等待它们全部完成后再继续执行。 使用 Future.wait()
静态方法管理多个 Future 以及等待它们完成:1
2
3
4
5
6
7
8
9
10Future deleteLotsOfFiles() async => ...
Future copyLotsOfFiles() async => ...
Future checksumLotsOfOtherFiles() async => ...
await Future.wait([
deleteLotsOfFiles(),
copyLotsOfFiles(),
checksumLotsOfOtherFiles(),
]);
print('Done with all the long steps!');
async、await
可以将 async、await 理解成 ES6 中的 async、await
使用async、await
关键字的代码是异步的,虽然看起来有点像同步代码。
async
函数体被 async
标示符标记的函数,即是一个异步函数。 将 async 关键字添加到函数使其返回Future。1
2
3
4
5// 同步函数,它返回一个 String
String lookUpVersion() => '1.0.0';
// 例如,将来的实现将非常耗时,将其更改为异步函数,返回值是 Future 。
Future<String> lookUpVersion() async => '1.0.0';await
使用 await 表达式
会阻塞代码的执行,直到需要的对象返回为止。在 await 表达式
中, 表达式 的值通常是一个 Future 对象; 如果不是,这是表达式的值会被自动包装成一个 Future 对象。 Future 对象指明返回一个对象的承诺(promise)。 await 表达式
执行的结果为这个返回的对象。 要使用 await
, 代码必须在 异步函数(使用 async
标记的函数)中:1
2
3
4Future checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
注意,函数体不需要使用Future API。 如有必要, Dart 会创建 Future 对象。
如果函数没有返回有效值, 需要设置其返回类型为 Future<void>
。
在一个异步函数中可以多次使用 await 。 例如,下面代码中等待了三次函数结果:1
2
3var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
使用 try
, catch
, 和 finally
来处理代码中使用 await
导致的错误。1
2
3
4
5try {
version = await deleteLotsOfFiles();
} catch (e) {
// React to inability to look up the version
}
Feture和async、awiit区别
在直接使用 Future API
前,首先应该考虑 await
来替代。 代码中使用 await
表达式会比直接使用 Future API 更容易理解。1
2
3
4
5
6
7
8Future result = costlyQuery(url);
result
.then((value) => expensiveWork(value))
.then((_) => lengthyComputation())
.then((_) => print('Done!'))
.catchError((exception) {
/* Handle exception... */
});
在上面的示例中,方法按下面顺序执行:
- costlyQuery()
- expensiveWork()
- lengthyComputation()
下面是使用 await 编写的等效代码:1
2
3
4
5
6
7
8try {
final value = await costlyQuery(url);
await expensiveWork(value);
await lengthyComputation();
print('Done!');
} catch (e) {
/* Handle exception... */
}
使用async、awiit的方式,可以用类似同步代码的写法实现异步操作,推荐使用 async
和 await
关键字实现异步编程。