Dart 学习笔记

dart-logo

阅读 language-tour 时做的一些笔记。

Important concepts

  • Dart 中一切皆对象,除了 null 之外,所有对象都继承自 Object
  • Dart 是强类型语言,但是类型标注是可选的,因为 Dart 中存在类型推断的机制,使用 var 关键字后编译器可以推断出具体的类型
  • 当开启了空安全(dart ≧ v2.12)之后,对于可为空的值必须明确指出(用 ?),可为空的值转换成不为空的值时使用 !
  • 当需要接收任意对象时使用 Object?
  • Dart 支持泛型,比如 List<int>
  • Dart 支持顶层函数和局部函数,同样也支持顶层变量
  • 标识符可以以字母或者 _ 开头,Dart 中 _ 开头的方法表示私有方法
  • Dart 中也分为表达式 expressions 和声明 statements,表达式有返回值而声明无返回值
  • Dart 工具提供了两种类型的问题:警告和错误。警告表示代码可能无法正确执行,错误分为编译期错误和运行时错误,编译期错误会导致程序无法执行,而运行时错误是代码执行过程中抛出的异常。

Variables

1
var name = 'Bob';

变量保存了引用,上面的例子中,name 变量包含了一个引用,该引用指向了一个值为 Bob 的 String 对象。

当使用 var 关键字时,变量的类型可以自动被推断出来,当然你也可以显式指出对象的类型。除此之外,如果你不想限定变量的类型,则可以使用 Obejct 或者 dynamic

默认值

如果没有开启空安全 (dart ≧ v2.12),则所有未被初始化的变量都会被初始化为 null,哪怕是数字型的变量,因为 Dart 中一切都是对象。但是,如果开启了空安全,则所有不可为空的变量会被要求先初始化才能使用。

Late 变量

Dart 2.12 之后添加了 late 修饰符,主要有两种用途:

  • 声明不可为空的顶层变量或者成员变量而不直接初始化值
  • 延迟初始化变量(使用到时才初始化)

第一种情况很好理解,因为如果开启了空安全,则顶层变量和成员变量必须要在声明的同时进行初始化,否则编译期无法保证空安全,所以引进 late 之后表明我们不想立马初始化该变量,但是它会在稍后被初始化。

第二种情况则适用于,一个变量对于程序来说是非必须的,或者性能消耗比较大,则可以用 late 修饰,这样,只有在使用到该变量时程序才会初始化它。比如:

1
2
// 如果该变量未被使用,则 _readThermometer() 不会被调用
late String temperature = _readThermometer(); // 延迟初始化

final 和 const

final 修饰的变量只能被赋值一次,并且 final 修饰的顶层变量会在第一次被使用时初始化。

1
2
3
4
final name = 'Bob'; // final 修饰的变量可以没有类型标注
final String nickname = 'Bobby';

name = 'Alice'; // Error: a final variable can only be set once.

const 变量是编译期常量(同时也是 final 的)。如果 const 修饰的是成员变量,则需要用 static const

1
2
const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere

finalconst 的不同之处在于,final 修饰的对象不能被修改,但是其属性可以被修改;而 const 修饰的对象和属性都不能被修改,它们是不可变的

Build-in types

Numbers

  • int, 不大于 64 位
  • double, 64 位浮点数

int 和 double 都是 num 的子类,num 类型的数据包含了基本的操作符,比如加减乘以及 abs(), ceil(), floor(), 位运算等。

Strings

  • Dart 中使用 String 可以用单引号或者双引号。不过,在单引号中的 String 对象中需要对 ' 进行转义。
  • 我们可以使用 ${expression} 在 String 中引用变量或者表达式。
  • 我们可以用 == 比较两个 String 的值是否一致。
  • 我们可以在 String 值之前加 r 来表示原始类型 (raw) 的 String,这样就不需要转义。
  • 我们可以使用三个单引号或者双引号来创建多行 String,同样不需要在其中使用转义符。
  • 我们可以使用字符相乘,但是字符必须在乘数系数的左边,如 🔥 * 3
  • 我们可以使用字符填充方法,比如 paddingLeftpaddingRight

Booleans

Dart 中使用 bool 表示布尔类型的值。

Lists

Dart 中数组是用 List 表示的。可以用如下的方式创建数组:

1
2
3
4
5
6
var list = [1, 2, 3];
var list = [
'Car',
'Boat',
'Plane', // Dart 中允许添加 trailing comma,这样可以防止复制粘贴出错
];

Dart 2.3 之后添加了扩展运算符 (...) 和空值敏感的扩展运算符 (...?):

1
2
3
var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);

Dart 中还有提供了集合 if集合 for 的操作:

1
2
3
4
5
6
7
8
9
10
11
12
var nav = [
'Home',
'Mall',
'Mine',
if (promoActive) 'Outlet'
];

var listOfInts = [1, 2, 3];
var listOfStrings = [
'#0',
for (var i in listOfInts) '#$i'
];

关于其它常见的 API 见 collections

Sets

Dart 中同样用 Set 类型表示无序且唯一的集合。

1
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};

Set 和 List 一样,支持扩展运算符、集合 if 和集合 for 的操作。

Maps

Map 是包含了 key 和 value 集合,其中 key 必须唯一。Dar 中的 Map 同样使用花括号 {} 表示,而且优先级比集合 Set 更高:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
}; // 自动推断出 map 的类型为 Map<String, String>

// 也可以通过这种方式创建
var gifts = Map<String, String>();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var names = {}; // 这种方式创建的是 map 而不是 set

// 访问 map 的语法 [],其实类似调用方法,具体获取到的值是不确定的
// 报错:Const variables must be initialized with a constant value
const lightRed = Colors.red[200];

Runes and grapheme clusters

Dart 中,使用 Runes 表示字符串的 Unicode 字符集。我们可以使用 characters 包下的类来操作字符。

1
2
3
4
5
6
import 'package:characters/characters.dart';
...
var hi = 'Hi ✡️';
print(hi);
print('The end of the string: ${hi.substring(hi.length - 1)}');
print('The last character: ${hi.characters.last}\n'); // 使用 characters 才能正常打印 emoji

Dart 中的字符串使用 UTF-16 编码,如果要用字符串表示 Unicode 字符则需要使用 \uXXXX 的语法。

1
print('\u{1f606}'); // 😆

Symbols

Dart 中使用 Symbol 对象表示操作符或者标识符,用 # + 修饰符表示。

Functions

Dart 中一切都是对象,函数的类型用 Function 表示,而且函数也可以作为变量或者作为方法的参数进行传递。

1
2
3
4
5
6
7
8
9
void say(String content) {
print(content);
}

// Dart 中只有单个表达式的函数可以使用缩写,用 => 代替 { }
bool isEvenNumber(int num) => num > 0 && num % 2 == 0;

// 这里的参数 body 表示一个返回类型为 Future<T> 的函数
void calculate<T>(Future<T> Function() body) {}

参数

Dart 中的函数除了可以有普通的参数,还可以是具名参数或者可选位置参数。如果使用了 Sound null safety (sdk ≧ 2.12),且没有指明默认值,则这两种类型参数都必须是可为空的,即参数后跟 ?

具名参数

具名参数 (Named parameters) 是必须写出名字的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 函数中使用 {} 圈起来的参数即具名参数
void enableFlags({bool? bold, int? index}) {}

// 可以给具名参数添加默认值
void enableFlags({bool? bold = true}) {}

// 用 required 标记具名参数中必须有的参数
void enableFlags({required bool? bold}) {}

// 调用函数,无参数
enableFlags()

// 调用时如果有参数名则必须写参数名称
enableFlags(
bold: true,
hidden: false
);

Dart 2.17 之后,可选具名参数的位置可以在普通参数之前:

1
2
3
4
5
// Dart 2.17 之前
List.generate(5, (index) => index, growable: true);

// Dart 2.17 之后
List.generate(5, growable: true, (index) => index);
可选位置参数

可选位置参数 (Optional positional parameters) 和普通的参数唯一的不同之处是它是可选的😅。用 [] 圈起来的参数即可选位置参数。比如:

1
2
3
4
5
6
7
8
// title 是必须的位置参数,而 subtitle 是可选的
void say(String title, [String? subtitle]) {
print(title);
if (subtitle != null) print(subtitle);
}

// 可选位置参数也可以有默认值
void sing(String song, [String? instrument = 'Piano']) { }

main() 函数

每个应用都有一个 main() 函数作为应用的入口。main() 函数通常返回空,并且可以有数组作为参数。

1
2
3
void main(List<String> arguments) {
print(arguments);
}

匿名函数

Dart 中的匿名函数和其它语言中类似,可以有多个参数或者没有参数,后跟方法体,形式如下:

1
2
3
([[Type] param1[, …]]) {
codeBlock;
};

如果只有单个的表达式或者只有返回值,可以使用箭头表达式:

1
([[Type] param1[, …]]) => expression; // 如果是赋值表达式,则该值就是返回值

Lexical scope

Dart 同样是具有词法作用域或静态作用域的语言,即只要还在代码作用域内的值,都能访问到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bool topLevel = true;

void main() {
var insideMain = true;

void myFunction() {
var insideFunction = true;

void nestedFunction() {
var insideNestedFunction = true;

assert(topLevel);
assert(insideMain);
assert(insideFunction);
assert(insideNestedFunction);
}
}
}

Lexical closures

词法闭包也叫函数闭包,和 JS 中的闭包概念类似,即定义在函数中的函数,同时使得该函数能够访问其它函数作用域中的变量。

1
2
3
4
5
6
7
8
9
Function makeAdder(int addBy) {
return (int i) => addBy + i;
}

void main() {
var add2 = makeAdder(2);
print(add2(3)); // 这里 add2 访问了 makeAdder 中定义的值,也即把 2 保存了下来
print(makeAdder(10)(3));
}

Operators

Dart 中支持的操作符表,见 Operators

Cascade notation

1
2
3
4
5
6
7
// cascade notation,可以在赋值的基础上对属性和方法进行调用
// 使得创建对象和属性赋值变得更简单,省去创建临时对象的步骤
var paint = Paint()
..color = 'Black'
..strokeCap = 'Round'
..strokeWidth = 5.0;
print(paint);

Control flow statements

switch and case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Dart 中的 case 语法有 fall through 机制
// 但是如果定义了 case 内容而没有 break 则会报错
var command = 'CLOSED';
switch (command) {
case 'EARLY_CLOSE': // fall through to 'CLOSED'
case 'CLOSED':
var youCantAccess = 1; // Dart 中的 case 自带作用域
print('bye bye');
continue afterClosed; // 可以使用 label 绕过无 break 的限制

afterClosed:
case 'CLOSED_AFTER':
// print(youCantAccess); // undefined

print('see you tomorrow');
break;
}

Exceptions

Dart 中所有异常都是 unchecked exceptions,意味着 Dart 不会强制你去捕捉异常。

Catch

Dart 中的 try catch 语法:

1
2
3
4
5
6
7
8
9
10
11
12
try {
breedMoreLlamas();
} on OutOfLlamasException {
// 特定类型的异常
buyMoreLlamas();
} on Exception catch (error, stacktrace) { // 第二个参数为错误栈
// 其它类型的异常
print('Unknown exception: $error');
} catch (error) {
// 所有其它未指定的错误
print('Something really unknown: $error');
}

Classes

Dart 中一切皆对象,除了 null 之外,所有对象都是某个类的实例。除了可以继承类之外,Dart 还提供了一种基于 mixin 的继承,也就是说可以通过 with 关键字继承某个没有构造器的类来扩展功能,还可以使用扩展函数

Using class members

所有对象都由成员变量和方法构成,使用 . 的语法访问对象的属性或者方法,使用 ?. 访问可为空对象的属性和方法。

Using constructors

Dart 中创建构造器除了可以使用类名,也可以使用 类名.构造器名() 的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class DummyClass {
int i = 0;

// 默认构造函数
DummyClass(this.i);

// 我们可以为构造函数定义名称,这种构造器被称为「具名构造器」
DummyClass.create(int i, String message) {
print(message);
this.i = i;
}

// 在「具名构造器」后为成员变量赋值
DummyClass.ten() : i = 10 { // :后的部分称为 initializer list
print('Ten ${this.i}'); // print: Ten 10
}
}

Getting an object’s type

我们可以使用 runtimeType 获得对象的类型 Type

1
2
var dummy = DummyClass.create(1);
print('The type of a is ${dummy.runtimeType}');

Instance variables

  • 所有未初始化可为空的实例变量都会被初始化为 null。
  • 所有实例变量都会默认自带 getter 函数,非 final 的实例变量和不带初始化器的 late final 实例变量会自动生成 setter 函数。
  • 非延迟初始化的实例变量的值会在对象创建之后,构造器和初始化器列表执行之前,就被赋值。可为空的赋值为 null,不可为空的赋值为初始值。
  • 实例变量可以是 final 的,但是必须在构造器或者初始化列表中进行赋值。

Constructors

Dart 中类的构造器和其它语言类似:

1
2
3
4
5
6
7
8
9
class Point {
double x = 0;
double y = 0;

Point(double x, double y) {
this.x = x;
this.y = y;
}
}

不过,Dart 提供了一种语法糖的写法:

1
2
3
4
5
6
class Point {
double x = 0;
double y = 0;

Point(this.x, this.y);
}
具名构造器

我们可以通过具名构造器 (Named constructors) 为一个类实现多个构造器。不过具名构造器不能被继承,如果想在子类中使用和父类相同的构造器,只能为子类单独实现。

1
2
3
4
5
6
7
8
9
class Weather {
var humidity = '';

Weather.display(this.humidity);
}

class Shower extends Weather{
Shower.display(String humidity) : super.display(humidity);
}
构造器初始化列表

除了调用父类构造器之外,我们还可以在构造器方法体执行之前初始化成员变量,称为 Initializer list

1
2
3
4
5
6
Point.fromJson(Map<String, double> json)
: x = json['x']!,
y = json['y']!,
super(x) {
print('In Point.fromJson(): ($x, $y)');
}

初始化起列表中,调用父类构造器必须放到最后。

构造器初始化顺序

默认情况下,子类会先调用父类中的默认构造函数(未定义的话会生成一个无参构造器),如果存在构造器初始化列表 (Initializer list) 则会先调用它们。所以,当使用默认构造函数创建对象时,构造器的初始化顺序为:

  1. 构造器初始化列表
  2. 父类无参构造器
  3. 子类无参构造器

另外,如果父类未定义默认构造器,则子类实现构造器时必须先调用父类的某个具名构造器。

重定向构造器
1
2
3
4
5
6
7
8
class Point {
double x, y;

Point(this.x, this.y);

// 使用 this 关键字重定向到默认构造器
Point.alongXAxis(double x) : this(x, 0);
}
常量构造器

当你想要创建的对象是编译期常量时(比如用于创建注解),可以使用 const 修饰构造器:

1
2
3
4
5
6
7
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);

final double x, y; // 记得所有的变量都要是 final 的

const ImmutablePoint(this.x, this.y);
}

使用常量构造器 (Constant Constructors) 初始化对象时,使用相同的值的对象只会在第一次创建时被初始化,也就是说相同值的对象只会初始化一次

工厂构造器

使用 factory 关键字的构造器,适用于不需要每次创建新对象的情况,比如使用了缓存。另一个使用场景是通过 JSON 数据初始化对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Logger {
final String name;
bool mute = false;

static final Map<String, Logger> _cache = <String, Logger>{};

factory Logger(String name) {
return _cache.putIfAbsent(name, () => Logger._internal(name));
}

factory Logger.fromJson(Map<String, Object> json) {
return Logger(json['name'].toString());
}

Logger._internal(this.name);

void log(String msg) {
if (!mute) print(msg);
}
}

Getters and setters

使用 getters 和 setters 方法定义一些需要通过计算得到的值。

1
2
3
4
5
6
7
8
9
10
11
12
class Rectangle {
double left, top, width, height;

Rectangle(this.left, this.top, this.width, this.height);

// Define two calculated properties: right and bottom.
double get right => left + width;
set right(double value) => left = value - width;

double get bottom => top + height;
set bottom(double value) => top = value - height;
}

Abstract classes

Dart 中的抽象类同样无法被直接实例化,如果需要实例化可以通过工厂构造器

1
2
3
4
5
abstract class AbstractContainer {
// Define constructors, fields, methods...

void updateChildren(); // Abstract method.
}

另外,在一个非 abstract 类中,我们可以使用 external 关键字修饰一个需要由子类去实现的抽象方法,例如:

1
2
3
class SomeClass {
external void doSomething();
}

Implicit interfaces

Dart 中所有的类都可以作为接口被实现。

1
2
// 使用 implement 关键字实现一个或多个接口
class Point implements Comparable, Location {...}

我们可以使用直接继承获得所有父类的实现,也可以使用 implements 将类作为接口实现,此时子类必须提供所有父类的属性和方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
abstract class Animal {
abstract String name;

int age;

Animal(this.age);
}

class Dog extends Animal {

@override
String name = "Dog";

Dog() : super(2);
}

// 如果使用 implements 则必须实现所有类属性
class Cat implements Animal {

@override
String name = "Cat";

@override
int age = 3;
}

Extending a class

Dart 中所有类都可以被继承,使用 extends 关键字继承父类,使用 super 引用父类。

Dart 2.17 之后允许在构造器中使用 super 语法:

1
2
3
4
5
6
7
8
9
10
class OutlinedButton extends Button {
const OutlinedButton({
super.key,
super.onPress = () {
print('Pressed OutlinedButton');
},
super.onLongPress,
required Widget super.child
});
}

这样就不需要调用父类构造器,写出 super(…) 这样的模板代码了。

Extension methods

和 Kotlin 类似,在 Dart 中我们同样可以通过扩展函数扩充函数库。

1
2
3
4
5
6
7
8
9
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}

double parseDouble() {
return double.parse(this);
}
}

Enumerated types

Dart 中的枚举类和 Java 中类似。

1
enum Color { red, green, blue }

Dart 2.17 之后允许在 enum 类中使用成员变量:

1
2
3
4
enum Water {
final int temperature;
const Water(this.temperature);
}

枚举类有以下限制:

  • 无法继承、实现或者混用 (mixin) 枚举类
  • 无法直接实例化

Adding features to a class: mixins

Dart 中还提供了另外一种复用代码的方式 mixin,我们可以把可复用的代码放到 mixin 类中。子类使用 with 关键字进行关联。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;

void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}

class Musician with Musical {
// ···
}

class Maestro extends Person with Musical {
Maestro(String maestroName) : super(maestroName) {
canPlayPiano = true;
}
}

除此之外,我们还可以限制 mixin 的使用范围 (on),以及像接口一样使用逗号 (,) 分割继承多个 mixin:

1
2
3
4
5
6
7
8
9
class Musician {
// ...
}
mixin MusicalPerformer on Musician {
// ...
}
class SingerDancer extends Musician with MusicalPerformer, DancerPerformer {
// ...
}

Class variables and methods

Dart 中的类同样可以使用静态变量和常量,以及静态方法。

1
2
3
4
5
class DummyClass {
static const a = 1;
static var b = 2;
static void f() { }
}

Generics

Dart 中的泛型(参数化类型)和 Java 以及 Kotlin 等语言非常相似,使用 <> 表示泛型。

为什么使用泛型

使用泛型一般有两个目的:

  • 指明泛型类型之后,减少代码出错。比如在集合中加入不该加入的值。
  • 使用泛型减少复制粘贴的代码。比如持有某个对象的类,当需要改变持有的对象而其它部分不发生变化时,如果使用了泛型就不用重新复制一个新的类。

Using collection literals

即在使用集合时定义集合类型,形式如 <type>[] or <type>{} or <keyType, valueType>{}

1
2
var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};

泛型集合及其类型

Java 中,由于泛型存在类型擦除,所以是无法确定集合的确切类型的。但是,Dart 中的泛型是 reified 的,也就是说在运行时也能得到集合的类型信息。

1
2
3
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

泛型参数类型的限制

Dart 中限制泛型参数边界同样使用 extends 关键字。

1
2
3
4
5
6
class Foo<T extends SomeBaseClass> {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}

class Extender extends SomeBaseClass {...}

Libraries and visibility

Dart 中,我们可以通过 import library 指示符创建模块化的、可共享的代码库。另外,即使不使用 library 指示符,每个 dart app 都是一个 library。

导入一个 library 的语法:

1
import 'dart:html'; // html 是 dart 内建的库,所以使用 dart 作为命名空间

其它库可以使用文件路径或者 package: 命名空间:

1
import 'package:test/test.dart'; // 导入一些三方库,比如通过 pub 包管理工具发布的三方库

指定库的前缀

如果两个库有相同的名称,可以通过 as 指定前缀解决冲突。

1
2
3
4
5
6
7
8
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// Uses Element from lib1.
Element element1 = Element();

// Uses Element from lib2.
lib2.Element element2 = lib2.Element();

部分导入

如果只使用到库的一部分,可以使用部分导入的语法,通过 showhide 做到。

1
2
3
4
5
// 只导入 foo
import 'package:lib1/lib1.dart' show foo;

// 导入除了 foo 意外的部分
import 'package:lib2/lib2.dart' hide foo;

实现 libraries

Create Library Packages

Asynchrony support

Dart 中有很多返回 FutureStream 对象的函数,我们可以使用它们编写出异步的代码。除此之外,Dart 还提供了 asyncawait 关键字,让你可以像 JS 一样,更方便地写出一些更具易读性的异步代码。

常见的用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
Future<String> lookUpVersion() async => '1.0.0';

// 在方法末尾添加 async 关键字,说明方法执行耗时操作,返回值通常是 Future
// 如果 async 方法无有意义的返回值,可以返回 Future<void>
Future<String> checkVersion() async {
var version = '';
try {
version = await lookUpVersion();
} catch (e) {
// React to inability to look up the version
}
return verion;
}

Stream 和 Future 的不同之处在于,它可以连续接收多个异步事件。我们可以通过 StreamBuilder 对持续发生变化的数据流进行监听,比如通过 WebSocket 获取数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
StreamBuilder(
stream: webSocketChannel.stream,
builder: (context, snapshot) {
String message = '';
if (snapshot.connectionState != ConnectionState.active ||
snapshot.hasError) {
message = 'has error';
}
if (snapshot.hasData) {
message = snapshot.data.toString();
} else {
message = 'no data';
}
return Container(
padding: EdgeInsets.symmetric(vertical: 10),
child: Text('$message',
style: Theme.of(context).textTheme.headline6),
);
},
),
)

另外,我们还可以通过 Stream 实现事件总线的功能,比如 event_bus 就是基于 Stream 实现的。

Generators

Dart 中的 Generator 函数和 ES6 中相似,如果你想要延迟生成一系列值,可以考虑使用生成器。Dart 中的 Generator 函数分为两种:

  • 同步生成器:返回 Iterable 对象
  • 异步生成器:返回 Stream 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 同步生成器
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++;
}

// 递归的生成器,使用 yield* 提高性能
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}

Callable classes

我们可以通过给某个类实现 call() 方法,使得该类可以像方法一样被调用。

1
2
3
4
5
6
7
8
9
class WannabeFunction {
String call(String a, String b, String c) {
print('WannabeFunction.call()');
return '$a $b $c!';
}
}

var wf = WannabeFunction(); // 初始化该类对象,然后可以像方法一样调用它们
wf('Hi', 'there,', 'gang') // 实际调用的是 call() 方法

Isolates

与其它语言的并发机制不同,Dart 中并没有采用共享状态的并发机制 (shared-state concurrency),而是使用了 isolates。所有的代码都运行在自己的 isolate 中,每个 isolate 都有自己的内存堆,保证了相互独立性。

更多资料见:Isolates

Typedefs

Dart 中,一切皆是对象,函数也是对象,但是函数对象的信息在运行时往往会被丢失,比如下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
class SortedCollection {
Function compare;

SortedCollection(int f(Object a, Object b)) : compare = f;
}

int sort(Object a, Object b) => 0;

SortedCollection sc = SortedCollection(sort);

// 只知道 compare 是 Function,但是其具体类型被丢失了
assert(sc.compare is Function);

typedef 就是为了解决上面这个问题而出现的,我们可以给方法类型一个别名,这样,当方法被赋值到一个变量上时就能保留其类型信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 函数的信息被保存在 Compare 中
typedef Compare = int Function(Object a, Object b);

class SortedCollection {
Compare compare; // 用于接收我们定义的函数

SortedCollection(this.compare);
}

int sort(Object a, Object b) => 0;

SortedCollection sc = SortedCollection(sort);
assert(sc.compare is Compare); // 现在可以确定函数的具体类型了

typedef 也可以使用泛型:

1
2
3
4
5
typedef Compare<T> = int Function(T a, T b);

int sort(int a, int b) => a - b;

assert(sort is Compare<int>); // True!

Metadata

通过元信息注解,我们可以为类和方法提供额外的信息。Dart 中自带的注解有 @deprecated@override,除此之外,你也可以自定义注解,只要在类的构造器上使用 const 关键字就可以了:

1
2
3
4
5
6
class Todo {
final String who;
final String what;

const Todo(this.who, this.what);
}

Comments

Dart 支持三种类型的注释:单行注释、多行注释和文档注释。

1
2
3
4
5
6
7
8
9
10
/// 文档注释,使用 [] 给成员属性或者方法添加链接,比如查看 [main] 方法
/// 虽然 Dart 中也能用 /** */ 作为文档注释,但是编辑器会提醒你用 /// 代替
void main() {
// 单行注释
print('Howdy!');
/*
* 多行注释
*/
print('Hola!');
}