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

posted on 2022-09-15 11:38  Lemo_wd  阅读(118)  评论(0编辑  收藏  举报

导航