dart 基础知识查漏补缺
一、基础类型
1. late 关键字
有两种用法:
第一种是告知编译器该变量在使用前会执行初始化。因为 dart 编译器要求非空变量必须在初始化后才能使用:
late String description;
void main() {
description = 'Feijoada!';
print(description);
}
第二种是惰性赋值(类似 scala 中的 lazy),在使用前才进行初始化求值:
// 在使用变量 temperature 的时才真正执行 readThermometer() 方法。
late String temperature = readThermometer();
结合 final 使用 late final <variable>
,可以保证变量只初始化一次。如用于实现单例模式:
class Singleton {
Singleton._internal();
//命名构造函数,可调用它生成实例
factory Singleton() => _instance;
static late final Singleton _instance = Singleton._internal();
}
2. const 关键字
可以使用 as/collection if, spread 帮助声明一个常量
const Object i = 3; // Where i is a const Object with an int value...
const list = [i as int]; // Use a typecast.
const map = {if (i is int) i: 'int'}; // Use is and collection if.
const set = {if (list is List<int>) ...list}; // ...and a spread.
3. 字符串
多行字符串
var s1 = '''
You can create
multi-line strings like this one.
''';
var s2 = """This is also a
multi-line string.""";
raw 字符串
var s = r'In a raw string, not even \n gets special treatment.';
4. Lists
...? 操作符
List? list;
void main() {
list = [1, 2, 3];
var list2 = [0, ...?list];
print(list2);
}
collection if
var nav = ['Home', 'Furniture', 'Plants', if (promoActive) 'Outlet'];
collection for
var listOfInts = [1, 2, 3];
var listOfStrings = ['#0', for (var i in listOfInts) '#$i'];
assert(listOfStrings[1] == '#1');
5. Sets
声明一个空集合
var names = <String>{};
// Set<String> names = {}; // This works, too.
// var names = {}; // Creates a map, not a set.
6. Maps
注 Map 也支持 ... 和 ...? 扩展操作符 与 collection if 和 collection for 操作符
二、函数
1. 函数参数的默认值
命名参数的默认值
/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold = false, bool hidden = false}) {...}
位置参数的默认值
String say(String from, String msg, [String device = 'carrier pigeon']) {
var result = '$from says $msg with a $device';
return result;
}
2. 命令行参数
也可以使用 https://pub.dev/packages/args 对 arguments 进行解析
// Run the app like this: dart args.dart 1 test
void main(List<String> arguments) {
print(arguments);
assert(arguments.length == 2);
assert(int.parse(arguments[0]) == 1);
assert(arguments[1] == 'test');
}
3. 所有函数都有返回值
未指定则返回 null
foo() {}
assert(foo() == null);
三、操作符
1. 类型转换操作符
操作符 | 使用场景 |
---|---|
as | Typecast (also used to specify library prefixes) |
is | True if the object has the specified type |
is! | True if the object doesn’t have the specified type |
(employee as Person).firstName = 'Bob';
if (employee is Person) {
// Type check
employee.firstName = 'Bob';
}
判断是否同一类型还可以使用下面这种方式
object.runtimeType == Type.
2. 条件级联
querySelector('#confirm') // Get an object.
?..text = 'Confirm' // Use its members.
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'))
..scrollIntoView();
等价于
var button = querySelector('#confirm');
button?.text = 'Confirm';
button?.classes.add('important');
button?.onClick.listen((e) => window.alert('Confirmed!'));
button?.scrollIntoView();
3. 条件取值
// 如果 fooList 等于 null,则该表达式直接返回 null
fooList?[1]
四、类
1. 构造器初始化
对于 final 且非 late 的实例变量,只能在构造参数或初始化列表进行初始化:
class A {
//构造器参数
// A(this.number);
//或者初始化列表(冒号后面的部分)
A(int p): number = max(p, 0);
final int number;
}
有时初始化某个变量时,需要执行一些逻辑。通常可以在初始化列表中处理,如果无法处理的话,还可以使用 late final (注:变量最好是私有变量)或 factory 构造器。
factory 工厂方法
factory 跟静态方法作用类似,只不过它的返回值必须是当前对象实例。
举例:单例模式(借助非空判断)
class Singleton {
static Singleton _instance;
//命名构造函数,可调用它生成实例
Singleton._internal() {
_instance = this;
}
// factory 本身就是静态函数,所以可访问静态变量. _instance
// 由于无多线程问题,不需要双重校验锁,只校验一次即可
factory Singleton() => _instance ?? Singleton._internal();
}
2. 重写操作符方法
class Vector {
final int x, y;
Vector(this.x, this.y);
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
// Operator == and hashCode not shown.
// ···
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
3. interface 与 extends
① 单继承多实现。
② 隐式 interface,class 的所有成员变量(包括私有)构成了一个隐式 interface。
③ extends 抽象类,只需要实现抽象方法(没有body的方法)即可,而使用 implements 则抽象与非抽象方法都需要实现。
示例:
void main(List<String> arguments) {
final b = B();
print(b.sum(3));
}
abstract class A {
final int _x = 3;
//非抽象方法
void printX() {
print(_x);
}
//抽象方法
int sum(int y);
}
class B extends A {
@override
int sum(int y) {
return _x + y;
}
}
class C implements A {
@override
// TODO: implement _x
int get _x => throw UnimplementedError();
@override
void printX() {
// TODO: implement printX
}
@override
int sum(int y) {
// TODO: implement sum
throw UnimplementedError();
}
}
4. mixin
跟抽象类作用类似。区别是
① mixin 中可以写抽象方法也可以写非抽象方法。可以多继承(混入),使用关键词 with。
② 可以限定某个类在混入 mixin 时必须继承某个 class
③ 与 interface 的区别是,只需要实现抽象成员即可(如果有的话),而不是包含所有成员。
从语义上来说,mixin 是针对 abstract class 与 interface 的加强,抽象类的缺点是只能单继承,interface 的缺点是一般来说不能包含非抽象方法(因为即使有的话,也需要重写)。
示例:
void main(List<String> arguments) {
B b = B();
b.run();
print(b.num);
}
// 使用 on 限定在混入 Runner 时必须要继承 A
mixin Runner on A{
int num = 3;
void walk();
void run() {
print('I am running!');
}
}
class A {
}
//继承 A 并混入 Runner
class B extends A with Runner {
@override
void walk() {
// TODO: implement walk
}
}
5. extension 扩展方法
void main(List<String> arguments) {
assert('222'.parseInt() == 222);
}
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
double parseDouble() {
return double.parse(this);
}
}
五、泛型
泛型固化
dart 泛型是固化(reified)的,在运行时也不擦除。
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // java 中不能执行这个判断
六、library
部分导入、隐藏
// Import only foo.
import 'package:lib1/lib1.dart' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
重导出
/// file: src/dart_shelf_base.dart
class Awesome {
bool get isAwesome => true;
int _a = 3;
}
/// file: dart_shelf.dart
library dart_shelf;
export 'src/dart_shelf_base.dart';
重导出不会引入新的包的作用域,如需要引用,仍需要手动导入
library dart_shelf;
export 'src/dart_shelf_base.dart';
// 手动导入该包
import 'dart_shelf.dart';
// 或 import 'src/dart_shelf_base.dart';
void printNum() {
print(Awesome());
}
条件导入与导出
// 条件导入只需要将 export 改成 import
export 'src/hw_none.dart' // Stub implementation
if (dart.library.io) 'src/hw_io.dart' // dart:io implementation
if (dart.library.html) 'src/hw_html.dart'; // dart:html implementation
七、异步操作
//Future
Future<void> checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
//Stream
void main() async {
// ...
await for (final request in requestServer) {
handleRequest(request);
}
// ...
}
future 中一些方法的使用
void main(List<String> arguments) async {
var t1 = DateTime.now();
await shortest();
var t2 = DateTime.now();
print(t2.difference(t1));
}
final f1 = Future.delayed(Duration(seconds: 3), () => 3);
final f2 = Future.delayed(const Duration(seconds: 5), () => 0);
Future<void> timeout() async {
final int r1 = await f1.timeout(Duration(3), onTimeout: () => 1);
}
Future<void> shortest() async {
final int r2 = await Future.any([f1, f2]);
}
Future<void> longest() async {
//并行执行,等待最长的那个执行完毕即返回结果
final List<int> r3 = await Future.wait([f1, f2]);
//不同于下面这种写法,f1、f2是依次进行,即须等待f1执行完毕,才能开始执行f2。
// await f1;
// await f2;
}
stream 的创建与使用
1、通过静态方法生成流
//periodic 的作用是每隔 x 秒从 0 开始加 1
var counterStream =
Stream<int>.periodic(const Duration(seconds: 1), (x) => x)
.where((int x) => x.isEven) // Retain only even integer events.
.expand((var x) => [x, x+1]) // Duplicate each event.
.take(5);
//打印 0,1,2,3,4
counterStream.forEach(print);
2、将文件流转换成字符串
Stream<List<int>> content = File('someFile.txt').openRead();
List<String> lines = await content
.transform(utf8.decoder)
.transform(const LineSplitter())
.toList();
3、通过生成器产生流
// 函数在调用时创建流,在流被监听时开始运行,在函数返回时结束流
Stream<int> timedCounter(Duration interval, [int? maxCount]) async* {
int i = 0;
while (true) {
await Future.delayed(interval);
yield i++;
if (i == maxCount) break;
}
}
4、将 future 列表转换成流
// 遍历 future 列表,再通过 yield 将结果转换成 stream
Stream<T> streamFromFutures<T>(Iterable<Future<T>> futures) async* {
for (final future in futures) {
var result = await future;
yield result;
}
}
5、使用 StreamController
// 可以很方便地手动添加流数据
Stream<int> timedCounter(Duration interval, [int? maxCount]) {
late StreamController<int> controller;
Timer? timer;
int counter = 0;
void tick(_) {
counter++;
controller.add(counter); // Ask stream to send counter values as event.
if (counter == maxCount) {
timer?.cancel();
controller.close(); // Ask stream to shut down and tell listeners.
}
}
void startTimer() {
timer = Timer.periodic(interval, tick);
}
void stopTimer() {
timer?.cancel();
timer = null;
}
controller = StreamController<int>(
onListen: startTimer,
onPause: stopTimer,
onResume: startTimer,
onCancel: stopTimer);
return controller.stream;
}
八、生成器
同步生成器
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
异步生成器
Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}
递归优化
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
九、Callable 类
类初始化后,可以将类的实例当作函数方法来使用:
class WannabeFunction {
String call(String a, String b, String c) => '$a $b $c!';
}
var wf = WannabeFunction();
var out = wf('Hi', 'there,', 'gang');
void main() => print(out);
类型别名
typedef IntList = List<int>;
IntList il = [1, 2, 3];
十、注解
自定义注解
class Todo {
final String who;
final String what;
const Todo(this.who, this.what);
}
@Todo('seth', 'make this do something')
void doSomething() {
print('do something');
}
需要在运行时通过反射取得其值
https://dart.dev/tutorials/language/streams
https://dart.dev/articles/libraries/creating-streams
https://dart.dev/guides/language/coming-from/js-to-dart#streams