Dart 是Google为客户端界面开发而创造的静态语言.它是Flutter框架指定的开发语言.

Flutter 框架支持多平台的应用开发.它目前支持 iOSAndroidWeb平台APP开发.未来它会实现全平台覆盖,支持:Mac OSWindowsFuchsia.

Dart虽然是静态类型语言,但它的语法干练简洁,和目前流行的开发语言C#JAVAC++JavaScript有相识的地方.这让它很容易学习.

Dart最大的特点是既支持JIT编译又支持AOT编译.

JIT带来的好处就是Dart支持开发时的代码热加载,这极大的提高了开发效率和体验.

Dart重要概念

  1. 保存在变量中的都是对象,数字、null、函数皆是对象.所有对象都继承之 Object类.所有的对象都是对应一个 的实例
  2. Dart 虽然是强类型语言,但它和Swift一样支持类型推断,不强制要求在设定变量时指定变量类型.
  3. 如果变量类型或返回值类型是动态的,可以同关键字dynamic修饰.
  4. main()是Dart 应用的入口函数.任何应用都必须有一个.
  5. Dart类中的变量访问权限控制并不是像Java一样通过关键字修饰,而是通过变量名首字符.如果变量名首字符是下划线开始_,表示该变量是私有变量.Dart,只有私有和公开两种变量访问权限类型.
  6. Dart的流程控制语句中条件判断的值只能是Boolean类型,不能像JavaScript一样可以接受 undefined、number、string.
  7. 在生产环境代码中 assert() 函数会被忽略,不会被调用。 在开发过程中, assert(*condition*) 会在非 true 的条件下抛出异常.
  8. Dart 表达式必须以分号”;”结尾.

变量和类型

Dart变量分两种类型:可以随时修改值的var,dynamic 和初始化后就不能修改值的const,final.用类型关键字(intString)定义的变量是可以修改的.

1
var name = 'Echo';

变量仅存储对象引用,name变量存储了一个 String 类型的对象引用。Echo是这个字符串引用对象的值.

可以将变量用为dynamic,则该变量可以动态修改它的值类型.

1
2
dynamic a = 'a chat';
a = 'a number';

未初始化的变量默认值是 null。即使变量是数字 类型默认值也是 null,因为在 Dart 中一切都是对象,数字类型 也不例外。

1
2
int lineCount;
assert(lineCount == null);

finalconst用来定义常量,用它们定义的变量初始化后就不能再被修改.cosnt只要求是编译时值未修改.PS.在给const初始化时可以使用插值表达式.

1
2
3
4
5
6
7
8
9

void main() {
final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';
var age = 30;
print(name); // Bob
print(nickname); // Bobby
print(age); // 30
}

Const 关键字不仅可以用于声明常量变量。 还可以用来创建常量值,以及声明创建常量值的构造函数。 任何变量都可以拥有常量值。

1
2
3
var foo = const [];
final bar = const [];
const baz = []; // Equivalent to `const []`

声明 const 的初始化表达式中 const 可以被省略。 比如上面的 baz

非 Final , 非 const 的变量是可以被修改的,即使这些变量 曾经引用过 const 值。

1
2
foo = [1, 2, 3]; // 曾经引用过 const [] 常量值。
baz = [42]; // Error: 常量变量不能赋值修改。

Number

Dart 的Number类型有两种:

int整数值不大于64位, 具体取决于平台。 在 Dart VM 上, 值的范围从 -263 到 263 - 1. Dart 被编译为 JavaScript 时,使用 JavaScript numbers, 值的范围从 -253 到 253 - 1.

double64位(双精度)浮点数,依据 IEEE 754 标准。

num 类型包括基本运算 +, -, /, 和 *, 以及 abs() ceil(), 和 floor(), 等函数方法。 定义在 int 类中。) 如果 num 及其亚类型找不到你想要的方法, 尝试查找使用 dart:math 库。

Dart 2.1 开始,必要的时候 int 字面量会自动转换成 double 类型.

1
double z = 1; // 相当于 double z = 1.0.

字符串和数字之间转换方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// String -> int
var one = int.parse('1');
assert(one == 1);

// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);

// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');

// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');

String

Dart 字符串是一组 UTF-16 单元序列。 字符串通过单引号或者双引号创建。

字符串可以通过 ${expression} 的方式内嵌表达式。 如果表达式是一个标识符,则 {} 可以省略。 在 Dart 中通过调用就对象的 toString() 方法来得到对象相应的字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to use the other delimiter.";

var s = 'string interpolation';

assert('Dart has $s, which is very handy.' ==
'Dart has string interpolation, ' +
'which is very handy.');
assert('That deserves all caps. ' +
'${s.toUpperCase()} is very handy!' ==
'That deserves all caps. ' +
'STRING INTERPOLATION is very handy!');

使用连续三个单引号或者三个双引号实现多行字符串对象的创建:

1
2
3
4
5
6
7
var s1 = '''
You can create
multi-line strings like this one.
''';

var s2 = """This is also a
multi-line string.""";

一个编译时常量的字面量字符串中,如果存在插值表达式,表达式内容也是编译时常量, 那么该字符串依旧是编译时常量。 插入的常量值类型可以是 null,数值,字符串或布尔值。

1
2
3
4
5
6
7
8
9
10
11
12
13
// const 类型数据
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';

// 非 const 类型数据
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = [1, 2, 3];

const validConstString = '$aConstNum $aConstBool $aConstString'; //const 类型数据
// const invalidConstString = '$aNum $aBool $aString $aConstList'; //非 const 类型数据

Boolean

Dart 使用 bool 类型表示布尔值。 Dart 只有字面量 true and false 是布尔类型, 这两个对象都是编译时常量。

Dart 的类型安全意味着不能使用 if (*nonbooleanValue*) 或者 assert (*nonbooleanValue*)。 而是应该像下面这样,明确的进行值检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 检查空字符串。
var fullName = '';
assert(fullName.isEmpty);

// 检查 0 值。
var hitPoints = 0;
assert(hitPoints <= 0);

// 检查 null 值。
var unicorn;
assert(unicorn == null);

// 检查 NaN 。
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);

List (也称Array)

List类似于JavaScript中的Array.

1
2
List <int> l = [1,2,3]; // 指明 l 是 List的值类型是 int
var st = ['a', 'b', 'c']; // 类型推断系统会 指定 st 是值类型是 String的List

在 List 字面量之前添加 const 关键字,可以定义 List 类型的编译时常量:

1
2
var constantList = const [1, 2, 3];
// constantList[1] = 1; // 取消注释会引起错误。

Dart 在 2.3 引入了 Spread 操作符 (...) 和 null-aware Spread 操作符 (...?), 它提供了一种将多个元素插入集合的简洁方法。

例如,你可以使用 Spread 操作符 (...) 将一个 List 中的所有元素插入到 另一个 List 中:

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

如果 Spread 操作符右边可能为 null ,你可以使用 null-aware Spread 操作符 (...?) 来避免产生异常。

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

更多 Spread 操作符的内容和使用示例,参见 Spread 操作符提案。

Dart 在 2.3 还同时引入了 Collection IfCollection For, 在构建集合时,可以使用条件判断 (if) 和循环 (for) 。

1
2
3
4
5
6
var nav = [
'Home',
'Furniture',
'Plants',
if (promoActive) 'Outlet'
];

下面示例是使用 Collection For 将列表中的元素修改后添加到另一个列表中:

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

更多 Collection IfCollection For 的内容和使用示例,参阅 集合的流控制提案。

Map

Map 是用来关联 keys 和 values 的对象。 keys 和 values 可以是任何类型的对象。在一个 Map 对象中一个 key 只能出现一次。 但是 value 可以出现多次。 Dart 中 Map 通过 Map 字面量 和 Map 类型来实现。

1
2
3
4
5
6
7
8
9
10
11
12
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};

var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};

注意 Dart 会将 gifts 的类型推断为 Map<String, String>nobleGases 的类型推断为 Map<int, String> 。 如果尝试在上面的 map 中添加错误类型,那么分析器或者运行时会引发错误。

以上 Map 对象也可以使用 Map 构造函数创建:

1
2
3
4
5
6
7
8
9
var gifts = Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var nobleGases = Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';

类似 JavaScript ,添加 key-value 对到已有的 Map 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds'; // Add a key-value pair

assert(gifts['first'] == 'partridge'); // 从Map 获取一个key 值

assert(gifts['fifth'] == null); // 如果 Map 中不包含所要查找的 key,那么 Map 返回 null

assert(gifts.length == 2); // 使用 .length 函数获取当前 Map 中的 key-value 对数量

final constantMap = const {
2: 'helium',
10: 'neon',
18: 'argon',
}; // 创建 Map 类型运行时常量,要在 Map 字面量前加上关键字 const。

// constantMap[2] = 'Helium'; // 取消注释会引起错误。

在 Dart 2.3 中,Map 支持 Spread 操作符 (... and ...?) 和 Collection If 和 Collection For ,就像 List 一样。

Set

在 Dart 中 Set 是一个元素唯一且无需的集合。 Dart 为 Set 提供了 Set 字面量和 Set 类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'}; // Dart 推断 halogens 类型为 Set<String> 。如果尝试为它添加一个 错误类型的值,分析器或执行时会抛出错误。

var names = <String>{};
// Set<String> names = {}; // 这样也是可以的。
// var names = {}; // 这样会创建一个 Map ,而不是 Set 。


var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens); //使用 add() 或 addAll() 为已有的 Set 添加元素

assert(elements.length == 5); // 使用 .length 来获取 Set 中元素的个数

final constantSet = const {
'fluorine',
'chlorine',
'bromine',
'iodine',
'astatine',
};
// constantSet.add('helium'); // Uncommenting this causes an error.
// 在 Set 字面量前增加 const ,来创建一个编译时 Set 常量

在 Dart 2.3 中,Set 支持 Spread 操作符 (... and ...?) 和 Collection If 和 Collection For ,就像 List 一样。

Rune

在 Dart 中, Rune 用来表示字符串中的 UTF-32 编码字符。

Unicode 定义了一个全球的书写系统编码, 系统中使用的所有字母,数字和符号都对应唯一的数值编码。 由于 Dart 字符串是一系列 UTF-16 编码单元, 因此要在字符串中表示32位 Unicode 值需要特殊语法支持。

表示 Unicode 编码的常用方法是, \uXXXX, 这里 XXXX 是一个4位的16进制数。 例如,心形符号 (♥) 是 \u2665。 对于特殊的非 4 个数值的情况, 把编码值放到大括号中即可。 例如,emoji 的笑脸 (�) 是 \u{1f600}

String 类有一些属性可以获得 rune 数据。 属性 codeUnitAtcodeUnit 返回16位编码数据。 属性 runes 获取字符串中的 Rune 。

Symbol

一个 Symbol 对象表示 Dart 程序中声明的运算符或者标识符,类似于JavaScript中的symbol。 因为代码压缩后会改变标识符的名称,但不会改变标识符的符号。 通过字面量 Symbol ,也就是标识符前面添加一个 # 号,来获取标识符的 Symbol 。

1
2
3
4
5
6
7
void main () {
var test = Map();
test[#cow] = 'sup';
print(test[#cow]); // sup
print("cow"); // cow
print(#cow); // Symbol("cow")
}

Symbol 字面量是编译时常量。

Symbol 是一种存储人类可读字符串和优化供计算机使用的字符串之间关系的方式。

函数

1
2
3
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}

Dart函数只需要函数名比不像有些语言需要 function关键字修饰.

函数名前可以写上该函数的返回值类型,并不是必须的.但是推荐的写法.

参数

可选参数可以是命名参数或者位置参数,但一个参数只能选择其中一种方式修饰。

命名可选参数

1
2
3
4
5
6
/// Sets the [bold] and [hidden] flags ...
/// 定义函数是,使用 {param1, param2, …} 来指定命名参数
void enableFlags({bool bold, bool hidden}) {...}

/// 调用函数时,可以使用指定命名参数 paramName: value
enableFlags(bold: true, hidden: false);

Flutter 创建实例的表达式可能很复杂, 因此窗口小部件构造函数仅使用命名参数。 这样创建实例的表达式更易于阅读。

可以使用@required 修饰命名参数,指定该参数在实例化必须调用,该方式可以在任何 Dart 代码中使用.

1
2
/// 此时 Scrollbar 是一个构造函数, 当 child 参数缺少时,分析器会提示错误。
const Scrollbar({Key key, @required Widget child})

位置可选参数

将参数放到 [] 中来标记参数是可选的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String say(String from, String msg, [String device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}

/// 下面是不使用可选参数调用下面方法
assert(say('Bob', 'Howdy') == 'Bob says Howdy');

/// 下面是使用可选参数调用上面方法
assert(say('Bob', 'Howdy', 'smoke signal') ==
'Bob says Howdy with a smoke signal');

默认参数

在定义方法的时候,可以使用 = 来定义可选参数的默认值。 默认值只能是编译时常量。 如果没有提供默认值,则默认值为 null。

1
2
3
4
5
/// 设置 [bold] 和 [hidden] 标志 ...
void enableFlags({bool bold = false, bool hidden = false}) {...}

// bold 值为 true; hidden 值为 false.
enableFlags(bold: true);

默认参数可以和命名可选参数位置可选参数配合使用.

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
26
27
28
/// 位置可选参数
String say(String from, String msg,
[String device = 'carrier pigeon', String mood]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
if (mood != null) {
result = '$result (in a $mood mood)';
}
return result;
}

assert(say('Bob', 'Howdy') ==
'Bob says Howdy with a carrier pigeon');


/// 命名可选参数
void doStuff(
{List<int> list = const [1, 2, 3],
Map<String, String> gifts = const {
'first': 'paper',
'second': 'cotton',
'third': 'leather'
}}) {
print('list: $list');
print('gifts: $gifts');
}

main() 函数

任何应用都必须有一个顶级 main() 函数,作为应用服务的入口。 main() 函数返回值为空,参数为一个可选的 List<String>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// web应用中
void main() {
querySelector('#sample_text_id')
..text = 'Click me!'
..onClick.listen(reverseText);
}


// 这样运行应用: 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');
}

函数是一等对象

一个函数可以作为另一个函数的参数

1
2
3
4
5
6
7
8
9
10
11
12
void printElement(int element) {
print(element);
}

var list = [1, 2, 3];

// 将 printElement 函数作为参数传递。
list.forEach(printElement);

/// 同样可以将一个函数赋值给一个变量
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

匿名函数

在Dart中可以定义一个没有名字的函数,这个函数被称为 匿名函数.也被称作 lambda 或者 closure .匿名函数可以赋值到一个变量中, 举个例子,在一个集合中可以添加或者删除一个匿名函数。

([[*Type*] *param1*[, …]]) { *codeBlock*; };

1
2
3
4
5
6
7
8
9
10
/// 下面例子中定义了一个包含一个无类型参数 item 的匿名函数。 list 中的每个元素都会调用这个函数,打印元素位置和值的字符串。
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});


/// 如果函数只有一条语句, 可以使用箭头简写。
list.forEach(
(item) => print('${list.indexOf(item)}: $item'));

词法作用域

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);
}
}
}

注意 nestedFunction() 可以访问所有的变量, 一直到顶级作用域变量。

词法闭包

闭包 即一个函数对象,即使函数对象的调用在它原始作用域之外, 依然能够访问在它词法作用域内的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// 返回一个函数,返回的函数参数与 [addBy] 相加。
Function makeAdder(num addBy) {
return (num i) => addBy + i;
}

void main() {
// 创建一个加 2 的函数。
var add2 = makeAdder(2);

// 创建一个加 4 的函数。
var add4 = makeAdder(4);

assert(add2(3) == 5);
assert(add4(3) == 7);
}

运算符

Dart中的运算符大部分是可以被重载.详情参考 重写运算符

算术运算符

Dart 支持常用的运算运算符,如下表所示:

Operator Meaning
+ Add
Subtract
-expr Unary minus, also known as negation (reverse the sign of the expression)
* Multiply
/ Divide
~/ Divide, returning an integer result
% Get the remainder of an integer division (modulo)
1
2
3
4
5
6
7
8
assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // 结果是双浮点型
assert(5 ~/ 2 == 2); // 结果是整型
assert(5 % 2 == 1); // 余数

assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');

Dart 还支持前缀和后缀,自增和自减运算符。

Operator Meaning
++var var = var + 1 (expression value is var + 1)
var++ var = var + 1 (expression value is var)
--var var = var – 1 (expression value is var – 1)
var-- var = var – 1 (expression value is var)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var a, b;

a = 0;
b = ++a; // a自加后赋值给b。
assert(a == b); // 1 == 1

a = 0;
b = a++; // a先赋值给b后,a自加。
assert(a != b); // 1 != 0

a = 0;
b = --a; // a自减后赋值给b。
assert(a == b); // -1 == -1

a = 0;
b = a--; // a先赋值给b后,a自减。
assert(a != b); // -1 != 0

关系运算符

下表列出了关系运算符及含义

Operator Meaning
== Equal; see discussion below
!= Not equal
> Greater than
< Less than
>= Greater than or equal to
<= Less than or equal to

要测试两个对象x和y是否表示相同的事物, 使用 == 运算符。 (在极少数情况下, 要确定两个对象是否完全相同,需要使用 identical() 函数。) 下面给出 == 运算符的工作原理:

  1. 如果 xy 可以 null,都为 null 时返回 true ,其中一个为 null 时返回 false。
  2. 结果为函数 x.==(y) 的返回值。 (如上所见, == 运算符执行的是第一个运算符的函数。 我们甚至可以重写很多运算符,包括 ==, 运算符的重写,参考 重写运算符。)

这里列出了每种关系运算符的示例:

1
2
3
4
5
6
assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(2 <= 3);

类型判定运算符

asis, 和 is! 运算符用于在运行时处理类型检查:

Operator Meaning
as Typecast (也被用于指定库前缀)
is True if the object has the specified type
is! False if the object has the specified type

例如, obj is Object 总是 true。 但是只有 obj 实现了 T 的接口时, obj is T 才是 true。

使用 as 运算符将对象强制转换为特定类型。 通常,可以认为是 is 类型判定后,被判定对象调用函数的一种缩写形式。 请考虑以下代码:

1
2
3
4
if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}

使用 as 运算符进行缩写:

1
(emp as Person).firstName = 'Bob';

提示: 以上代码并不是等价的。 如果 emp 为 null 或者不是 Person 对象, 那么第一个 is 的示例,后面将不回执行; 第二个 as 的示例会抛出异常。

赋值运算符

使用 = 为变量赋值。 使用 ??= 运算符时,只有当被赋值的变量为 null 时才会赋值给它。

1
2
3
4
// 将值赋值给变量a
a = value;
// 如果b为空时,将变量赋值给b,否则,b的值保持不变。
b ??= value;

复合赋值运算符(如 += )将算术运算符和赋值运算符组合在了一起。

= –= /= %= >>= ^=
+= *= ~/= <<= &= `

以下说明复合赋值运算符的作用:

Compound assignment Equivalent expression
For an operator op: a op= b a = a op b
Example: a += b a = a + b

以下示例使用赋值和复合赋值运算符:

1
2
3
var a = 2; // 使用 = 复制
a *= 3; // 复制并做乘法运算: a = a * 3
assert(a == 6);

逻辑运算符

逻辑操作符可以反转或组合布尔表达式。

Operator Meaning
!expr inverts the following expression (changes false to true, and vice versa)
`
&& logical AND

下面是关于逻辑表达式的示例:

1
2
3
if (!done && (col == 0 || col == 3)) {
// ...Do something...
}

按位和移位运算符

在 Dart 中,可以单独操作数字的某一位。 通常情况下整数类型使用按位和移位运算符来操作。

Operator Meaning
& AND
` `
^ XOR
~expr Unary bitwise complement (0s become 1s; 1s become 0s)
<< Shift left
>> Shift right

下面是关于按位和移位运算符的示例:

1
2
3
4
5
6
7
8
9
final value = 0x22;
final bitmask = 0x0f;

assert((value & bitmask) == 0x02); // AND
assert((value & ~bitmask) == 0x20); // AND NOT
assert((value | bitmask) == 0x2f); // OR
assert((value ^ bitmask) == 0x2d); // XOR
assert((value << 4) == 0x220); // Shift left
assert((value >> 4) == 0x02); // Shift right

条件表达式

Dart有两个运算符,有时可以替换 if-else 表达式, 让表达式更简洁:

1
condition ? expr1 : expr2

如果条件为 true, 执行 expr1 (并返回它的值): 否则, 执行并返回 expr2 的值。

1
expr1 ?? expr2

如果 expr1 是 non-null, 返回 expr1 的值; 否则, 执行并返回 expr2 的值。

1
2
3
4
5
/// 如果赋值是根据布尔值, 考虑使用 ?:
var visibility = isPublic ? 'public' : 'private';

/// 如果赋值是基于判定是否为 null, 考虑使用 ??。
String playerName(String name) => name ?? 'Guest';

级联运算符 (..)

级联运算符 (..) 可以实现对同一个对像进行一系列的操作。 除了调用函数, 还可以访问同一对象上的字段属性。 这通常可以节省创建临时变量的步骤, 同时编写出更流畅的代码。

1
2
3
4
querySelector('#confirm') // 获取对象。
..text = 'Confirm' // 调用成员变量。
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));

第一句调用函数 querySelector() , 返回获取到的对象。 获取的对象依次执行级联运算符后面的代码, 代码执行后的返回值会被忽略。

控制流程语句

你可以通过下面任意一种方式来控制 Dart 程序流程:

  • if and else
  • for loops
  • while and do-while loops
  • break and continue
  • switch and case
  • assert

和 JavaScript 不同, Dart 的判断条件必须是布尔值,不能是其他类型。

if 和 else

1
2
3
4
5
6
7
if (isRaining()) {
you.bringRainCoat();
} else if (isSnowing()) {
you.wearJacket();
} else {
car.putTopDown();
}

for 循环

1
2
3
4
var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
message.write('!');
}

闭包在 Dart 的 for 循环中会捕获循环的 index 索引值, 来避免 JavaScript 中常见的陷阱。

1
2
3
4
5
var callbacks = [];
for (var i = 0; i < 2; i++) {
callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());

和期望一样,输出的是 01。 但是示例中的代码在 JavaScript 中会连续输出两个 2

I如果要迭代一个实现了 Iterable 接口的对象, 可以使用 forEach() 方法, 如果不需要使用当前计数值, 使用 forEach() 是非常棒的选择;

1
candidates.forEach((candidate) => candidate.interview());

实现了 Iterable 的类(比如, List 和 Set)同样也支持使用 for-in 进行迭代操作 iteration

1
2
3
4
var collection = [0, 1, 2];
for (var x in collection) {
print(x); // 0 1 2
}

while 和 do-while

1
2
3
4
5
6
7
while (!isDone()) {
doSomething();
}

do {
printLine();
} while (!atEndOfPage());

break 和 continue

1
2
3
4
5
6
7
8
9
10
11
12
while (true) {
if (shutDownRequested()) break;
processIncomingRequests();
}

for (int i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
if (candidate.yearsExperience < 5) {
continue;
}
candidate.interview();
}

switch 和 case

在 Dart 中 switch 语句使用 == 比较整数,字符串,或者编译时常量。 比较的对象必须都是同一个类的实例(并且不可以是子类), 类必须没有对 == 重写。 枚举类型 可以用于 switch 语句。

case 语句中,每个非空的 case 语句结尾需要跟一个 break 语句。 除 break 以外,还有可以使用 continue, throw,者 return

当没有 case 语句匹配时,执行 default 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var command = 'OPEN';
switch (command) {
case 'CLOSED':
executeClosed();
break;
case 'PENDING':
executePending();
break;
case 'APPROVED':
executeApproved();
break;
case 'DENIED':
executeDenied();
break;
case 'OPEN':
executeOpen();
break;
default:
executeUnknown();
}

Dart 支持空 case 语句, 允许程序以 fall-through 的形式执行。

1
2
3
4
5
6
7
8
var command = 'CLOSED';
switch (command) {
case 'CLOSED': // Empty case falls through.
case 'NOW_CLOSED':
// Runs for both CLOSED and NOW_CLOSED.
executeNowClosed();
break;
}

assert

如果 assert 语句中的布尔条件为 false , 那么正常的程序执行流程会被中断。

1
2
3
4
5
6
7
8
// 确认变量值不为空。
assert(text != null);

// 确认变量值小于100。
assert(number < 100);

// 确认 URL 是否是 https 类型。
assert(urlString.startsWith('https'));

提示: assert 语句只在开发环境中有效, 在生产环境是无效的; Flutter 中的 assert 只在 debug 模式中有效。 开发用的工具,例如 dartdevc 默认是开启 assert 功能。 其他的一些工具, 例如 dartdart2js,支持通过命令行开启 assert : --enable-asserts

assert 的第二个参数可以为其添加一个字符串消息。

Dart 是一种基于类和 mixin 继承机制的面向对象的语言。 每个对象都是一个类的实例,所有的类都继承于 Object. 。 基于 * Mixin 继承* 意味着每个类(除 Object 外) 都只有一个超类, 一个类中的代码可以在其他多个继承类中重复使用。

使用类的成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var p = Point(2, 2);

// 为实例的变量 y 设置值。
p.y = 3;

// 获取变量 y 的值。
assert(p.y == 3);

// 调用 p 的 distanceTo() 方法。
num distance = p.distanceTo(Point(4, 4));


// 如果 p 为 non-null,设置它变量 y 的值为 4。
p?.y = 4;
/// 使用 ?. 来代替 . , 可以避免因为左边对象可能为 null , 导致的异常

使用构造函数

通过 构造函数 创建对象。 构造函数的名字可以是 ClassName 或者 ClassName.identifier。例如, 以下代码使用 PointPoint.fromJson() 构造函数创建 Point对象:

1
2
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

一些类提供了常量构造函数。 使用常量构造函数,在构造函数名之前加 const 关键字,来创建编译时常量时

1
var p = const ImmutablePoint(2, 2);

构造两个相同的编译时常量会产生一个唯一的, 标准的实例:

1
2
3
4
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // 它们是同一个实例。

获取对象的类型

使用对象的 runtimeType 属性, 可以在运行时获取对象的类型, runtimeType 属性回返回一个 Type 对象。

1
print('The type of a is ${a.runtimeType}');

实例变量

1
2
3
4
5
class Point {
num x; // 声明示例变量 x,初始值为 null 。
num y; // 声明示例变量 y,初始值为 null 。
num z = 0; // 声明示例变量 z,初始值为 0 。
}

所有实例变量都生成隐式 getter 方法。 非 final 的实例变量同样会生成隐式 setter 方法。

构造函数

通过创建一个与其类同名的函数来声明构造函数 (另外,还可以附加一个额外的可选标识符,如 命名构造函数 中所述)。 下面通过最常见的构造函数形式, 即生成构造函数, 创建一个类的实例

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
26
27
28
29
30
31
32
33
34
/// 例1
class Point {
num x, y;

Point(num x, num y) {
// 还有更好的方式来实现下面代码,敬请关注。
this.x = x;
this.y = y;
}
}

/// 例2
class Point {
num x, y;

// 在构造函数体执行前,
// 语法糖已经设置了变量 x 和 y。
Point(this.x, this.y);
}


/// 例3 命名构造函数
/// 使用命名构造函数可为一个类实现多个构造函数, 也可以使用命名构造函数来更清晰的表明函数意图
class Point {
num x, y;

Point(this.x, this.y);

// 命名构造函数
Point.origin() {
x = 0;
y = 0;
}
}

切记,构造函数不能够被继承, 这意味着父类的命名构造函数不会被子类继承。 如果希望使用父类中定义的命名构造函数创建子类, 就必须在子类中实现该构造函数。

默认情况下,子类的构造函数会自动调用父类的默认构造函数(匿名,无参数)。 父类的构造函数在子类构造函数体开始执行的位置被调用。 如果提供了一个 initializer list(初始化参数列表), 则初始化参数列表在父类构造函数执行之前执行。

  1. initializer list (初始化参数列表)
  2. superclass’s no-arg constructor (父类的无名构造函数)
  3. main class’s no-arg constructor (主类的无名构造函数)

如果父类中没有匿名无参的构造函数, 则需要手工调用父类的其他构造函数。 在当前构造函数冒号 (:) 之后,函数体之前,声明调用父类构造函数。

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
26
27
28
29
30
  String firstName;

Person.fromJson(Map data) {
print('in Person');
}
}

class Employee extends Person {
// Person does not have a default constructor;
// you must call super.fromJson(data).
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}

main() {
var emp = new Employee.fromJson({});

// Prints:
// in Person
// in Employee
if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}
(emp as Person).firstName = 'Bob';
}

/// in Person
/// in Employee

由于父类的构造函数参数在构造函数执行之前执行, 所以参数可以是一个表达式或者一个方法调用

1
2
3
4
class Employee extends Person {
Employee() : super.fromJson(getDefaultData());
// ···
}

初始化列表

除了调用超类构造函数之外, 还可以在构造函数体执行之前初始化实例变量。 各参数的初始化用逗号分隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 在构造函数体执行之前,
// 通过初始列表设置实例变量。
Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
/// 警告: 初始化程序的右侧无法访问 this 。

/// 在开发期间, 可以使用 assert 来验证输入的初始化列表。
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}

使用初始化列表可以很方便的设置 final 字段。 下面示例演示了,如何使用初始化列表初始化设置三个 final 字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import 'dart:math';

class Point {
final num x;
final num y;
final num distanceFromOrigin;

Point(x, y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
var p = new Point(2, 3);
print(p.distanceFromOrigin);
}

/// 3.605551275463989

重定向构造函数

有时构造函数的唯一目的是重定向到同一个类中的另一个构造函数。 重定向构造函数的函数体为空, 构造函数的调用在冒号 (:) 之后。

1
2
3
4
5
6
7
8
9
class Point {
num x, y;

// 类的主构造函数。
Point(this.x, this.y);

// 指向主构造函数
Point.alongXAxis(num x) : this(x, 0);
}

常量构造函数

如果该类生成的对象是固定不变的, 那么就可以把这些对象定义为编译时常量。 为此,需要定义一个 const 构造函数, 并且声明所有实例变量为 final

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

final num x, y;

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

工厂构造函数

当执行构造函数并不总是创建这个类的一个新实例时,则使用 factory 关键字。 例如,一个工厂构造函数可能会返回一个 cache 中的实例, 或者可能返回一个子类的实例。

以下示例演示了从缓存中返回对象的工厂构造函数:

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
26
27
28
29
30
31
class Logger {
final String name;
bool mute = false;

// 从命名的 _ 可以知,
// _cache 是私有属性。
static final Map<String, Logger> _cache =
<String, Logger>{};

factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = Logger._internal(name);
_cache[name] = logger;
return logger;
}
}

Logger._internal(this.name);

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

/// 提示: 工厂构造函数无法访问 this。

/// 工厂构造函的调用方式与其他构造函数一样:
var logger = Logger('UI');
logger.log('Button clicked');

方法

实例方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:math';

class Point {
num x, y;

Point(this.x, this.y);

num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
/// 对象的实例方法可以访问 this 和实例变量。 示例中的 distanceTo() 方法就是实例方法

Getter 和 Setter

Getter 和 Setter 是用于对象属性读和写的特殊方法。 回想之前的例子,每个实例变量都有一个隐式 Getter ,通常情况下还会有一个 Setter 。 使用 getset 关键字实现 Getter 和 Setter ,能够为实例创建额外的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Rectangle {
num left, top, width, height;

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

// 定义两个计算属性: right 和 bottom。
num get right => left + width;
set right(num value) => left = value - width;

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

void main() {
var rect = Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}

抽象方法

实例方法, getter, 和 setter 方法可以是抽象的, 只定义接口不进行实现,而是留给其他类去实现。

1
2
3
4
5
6
7
8
9
10
11
12
abstract class Doer {
// 定义实例变量和方法 ...

void doSomething(); // 定义一个抽象方法。
}

class EffectiveDoer extends Doer {
void doSomething() {
// 提供方法实现,所以这里的方法就不是抽象方法了...
}
}

抽象类

使用 abstract 修饰符来定义 抽象类 — 抽象类不能实例化。 抽象类通常用来定义接口,以及部分实现。 如果希望抽象类能够被实例化,那么可以通过定义一个 工厂构造函数 来实现。

抽象类通常具有 抽象方法。 下面是一个声明具有抽象方法的抽象类示例:

1
2
3
4
5
6
7
// 这个类被定义为抽象类,
// 所以不能被实例化。
abstract class AbstractContainer {
// 定义构造行数,字段,方法...

void updateChildren(); // 抽象方法。
}

隐式接口

每个类都隐式的定义了一个接口,接口包含了该类所有的实例成员及其实现的接口。 如果要创建一个 A 类,A 要支持 B 类的 API ,但是不需要继承 B 的实现, 那么可以通过 A 实现 B 的接口。

一个类可以通过 implements 关键字来实现一个或者多个接口, 并实现每个接口要求的 API。 例如:

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
26
27
28
// person 类。 隐式接口里面包含了 greet() 方法声明。
class Person {
// 包含在接口里,但只在当前库中可见。
final _name;

// 不包含在接口里,因为这是一个构造函数。
Person(this._name);

// 包含在接口里。
String greet(String who) => 'Hello, $who. I am $_name.';
}

// person 接口的实现。
class Impostor implements Person {
get _name => '';

String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
print(greetBob(Person('Kathy')));
print(greetBob(Impostor()));
}

/// 一个类可以实现多个接口
class Point implements Comparable, Location {...}

扩展类(继承)

使用 extends 关键字来创建子类, 使用 super 关键字来引用父类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}

class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}

重写类成员

子类可以重写实例方法,getter 和 setter。 可以使用 @override 注解指出想要重写的成员:

1
2
3
4
5
class SmartTelevision extends Television {
@override
void turnOn() {...}
// ···
}

covariant关键字

在类型继承中,子类型的重载的方法参数类型覆盖父类方法的参数类型,这会导致编译报错.你可以通过covariant关键字告诉类型分析系统你是故意这么做的.

1
2
3
4
5
6
7
8
9
class Animal {
void chase(Animal x) { ... }
}

class Mouse extends Animal { ... }

class Cat extends Animal {
void chase(covariant Mouse x) { ... }
}

重写运算符

下标的运算符可以被重写。 例如,想要实现两个向量对象相加,可以重写 + 方法。

| < | + | | | [] |
| —- | —- | —- | —– |
| > | / | ^ | []= |
| <= | ~/ | & | ~ |
| >= | * | << | == |
| | % | >> | |

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// 下面示例演示一个类重写 + 和 - 操作符
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);

// 运算符 == 和 hashCode 部分没有列出。 有关详情,请参考下面的注释。
// ···
}

void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);

assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}

noSuchMethod()

当代码尝试使用不存在的方法或实例变量时, 通过重写 noSuchMethod() 方法

1
2
3
4
5
6
7
8
9
class A {
// 如果不重写 noSuchMethod,访问
// 不存在的实例变量时会导致 NoSuchMethodError 错误。
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: ' +
'${invocation.memberName}');
}
}

除非符合下面的任意一项条件, 否则没有实现的方法不能够被调用:

  • receiver 具有 dynamic 的静态类型 。
  • receiver 具有静态类型,用于定义为实现的方法 (可以是抽象的), 并且 receiver 的动态类型具有 noSuchMethod() 的实现, 该实现与 Object 类中的实现不同。

有关更多信息,参考 noSuchMethod forwarding specification.

枚举类型

枚举类型也称为 enumerationsenums , 是一种特殊的类,用于表示数量固定的常量值。

使用枚举

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
26
/// 使用 enum 关键字定义一个枚举类型
enum Color { red, green, blue }

/// 枚举中的每个值都有一个 index getter 方法, 该方法返回值所在枚举类型定义中的位置(从 0 开始)。
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

/// 使用枚举的 values 常量, 获取所有枚举值列表( list )。
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

/// 可以在 switch 语句 中使用枚举, 如果不处理所有枚举值,会收到警告

var aColor = Color.blue;

switch (aColor) {
case Color.red:
print('Red as roses!');
break;
case Color.green:
print('Green as grass!');
break;
default: // 没有这个,会看到一个警告。
print(aColor); // 'Color.blue'
}

枚举类型具有以下限制:

  • 枚举不能被子类化,混合或实现。
  • 枚举不能被显式实例化。

为类添加功能: Mixin

Mixin 是复用类代码的一种途径, 复用的类可以在不同层级,之间可以不存在继承关系。

通过 with 后面跟一个或多个混入的名称,来 使用 Mixin , 下面的示例演示了两个使用 Mixin 的类

1
2
3
4
5
6
7
8
9
10
11
class Musician extends Performer with Musical {
// ···
}

class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}

通过创建一个继承自 Object 且没有构造函数的类,来 实现 一个 Mixin 。 如果 Mixin 不希望作为常规类被使用,使用关键字 mixin 替换 class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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');
}
}
}

指定只有某些类型可以使用的 Mixin - 比如, Mixin 可以调用 Mixin 自身没有定义的方法 - 使用 on 来指定可以使用 Mixin 的父类类型

1
2
3
mixin MusicalPerformer on Musician {
// ···
}

类变量和方法

使用 static 关键字实现类范围的变量和方法。

静态变量

静态变量(类变量)对于类级别的状态是非常有用的:

1
2
3
4
5
6
7
8
9
class Queue {
static const initialCapacity = 16;
// ···
}

void main() {
assert(Queue.initialCapacity == 16);
}
/// 静态变量只到它们被使用的时候才会初始化。

静态方法

静态方法(类方法)不能在实例上使用,因此它们不能访问 this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import 'dart:math';

class Point {
num x, y;
Point(this.x, this.y);

static num distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}

void main() {
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(2.8 < distance && distance < 2.9);
print(distance);
}

泛型

强类型语言,都会有泛型语法来支持语法的灵活性.

泛型使得可以在使用时声明类型,而不需要在编写的时候定义类型.

在类型安全上通常需要泛型支持, 它的好处不仅仅是保证代码的正常运行:

  • 正确指定泛型类型可以提高代码质量。
  • 使用泛型可以减少重复的代码。
1
2
3
4
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}

使用集合字面量

List , Set 和 Map 字面量也是可以参数化的。 参数化字面量和之前的字面量定义类似, 对于 List 或 Set 只需要在声明语句前加 <type> 前缀, 对于 Map 只需要在声明语句前加 <keyType, valueType> 前缀,

1
2
3
4
5
6
7
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'
};

使用泛型类型的构造函数

在调用构造函数的时,在类名字后面使用尖括号(<...>)来指定泛型类型。

1
2
3
var nameSet = Set<String>.from(names);

var views = Map<int, View>();

运行时中的泛型集合

Dart 中泛型类型是 固化的,也就是说它们在运行时是携带着类型信息的。

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

提示: 相反,Java中的泛型会被 擦除 ,也就是说在运行时泛型类型参数的信息是不存在的。 在Java中,可以测试对象是否为 List 类型, 但无法测试它是否为 List<String>

限制泛型类型

使用泛型类型的时候, 可以使用 extends 实现参数类型的限制。

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

class Extender extends SomeBaseClass {...}

/// 可以使用 SomeBaseClass 或其任意子类作为通用参数:
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();

使用泛型函数

最初,Dart 的泛型只能用于类。 新语法_泛型方法_,允许在方法和函数上使用类型参数

1
2
3
4
5
6
T first<T>(List<T> ts) {
// Do some initial work or error checking, then...
T tmp = ts[0];
// Do some additional checking or processing...
return tmp;
}

这里的 first (<T>) 泛型可以在如下地方使用参数 T

  • 函数的返回值类型 (T).
  • 参数的类型 (List<T>).
  • 局部变量的类型 (T tmp).

For more information about generics, see Using Generic Methods.

异步的支持

Dart 的异步处理和JavaScript处理异步一样,只是 Promise换成了Future.

处理 Future

可以通过下面两种方式,获得 Future 执行完成的结果:

  • 使用 asyncawait.
  • 使用 Future API,具体描述,参考 库概览.

使用 asyncawait 关键字的代码是异步的。 虽然看起来有点想同步代码。 例如,下面的代码使用 await 等待异步函数的执行结果。

1
2
3
4
5
6
7
8
9
10
11
12
Future checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}

/// 提示: 虽然异步函数可能会执行耗时的操作, 但它不会等待这些操作。 相反,异步函数只有在遇到第一个 await /// 表达式(详情见)时才会执行。 也就是说,它返回一个 Future 对象, 仅在await表达式完成后才恢复执行。


Future main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}

await 表达式 中, 表达式 的值通常是一个 Future 对象; 如果不是,这是表达式的值会被自动包装成一个 Future 对象。 Future 对象指明返回一个对象的承诺(promise)。 await 表达式 执行的结果为这个返回的对象。 await 表达式会阻塞代码的执行,直到需要的对象返回为止。

声明异步函数

函数体被 async 标示符标记的函数,即是一个_异步函数_。 将 async 关键字添加到函数使其返回Future。 例如,考虑下面的同步函数,它返回一个 String

1
2
3
String lookUpVersion() => '1.0.0'; 
/// 将来的实现将非常耗时,将其更改为异步函数,返回值是 Future
Future<String> lookUpVersion() async => '1.0.0';

注意,函数体不需要使用Future API。 如有必要, Dart 会创建 Future 对象

处理 Stream

当需要从 Stream 中获取数据值时, 可以通过一下两种方式:

  • 使用 async 和 一个 异步循环await for)。
  • 使用 Stream API, 更多详情,参考in the library tour

提示: 在使用 await for 前,确保代码清晰, 并且确实希望等待所有流的结果。 例如,通常不应该使用 await for 的UI事件侦听器, 因为UI框架会发送无穷无尽的事件流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
格式
await for (varOrType identifier in expression) {
// Executes each time the stream emits a value.
}
*/

Future main() async {
// ...
await for (var request in requestServer) {
handleRequest(request);
}
// ...
}

上面 *表达式* 返回的值必须是 Stream 类型。 执行流程如下:

  1. 等待,直到流发出一个值。
  2. 执行 for 循环体,将变量设置为该发出的值
  3. 重复1和2,直到关闭流。

使用 break 或者 return 语句可以停止接收 stream 的数据, 这样就跳出了 for 循环, 并且从 stream 上取消注册。 如果在实现异步 for 循环时遇到编译时错误, 请检查确保 await for 处于异步函数中。例如,要在应用程序的 main() 函数中使用异步 fo r循环, main() 函数体必须标记为 async

异常

Dart 代码可以抛出和捕获异常。 异常表示一些未知的错误情况。 如果异常没有被捕获, 则异常会抛出, 导致抛出异常的代码终止执行。

和 Java 有所不同, Dart 中的所有异常是非检查异常。 方法不会声明它们抛出的异常, 也不要求捕获任何异常。

throw

1
2
3
4
5
6
7
8
/// 下面是关于抛出或者 引发 异常的示例
throw FormatException('Expected at least 1 section');

/// 也可以抛出任意的对象
throw 'Out of llamas!';

/// 因为抛出异常是一个表达式, 所以可以在 => 语句中使用,也可以在其他使用表达式的地方抛出异常
void distanceTo(Point other) => throw UnimplementedError();

提示: 高质量的生产环境代码通常会实现 ErrorException 类型的异常抛出。

catch

捕获异常可以避免异常继续传递(除非重新抛出( rethrow )异常)。 可以通过捕获异常的机会来处理该异常

1
2
3
4
5
try {
breedMoreLlamas();
} on OutOfLlamasException {
buyMoreLlamas();
}

通过指定多个 catch 语句,可以处理可能抛出多种类型异常的代码。 与抛出异常类型匹配的第一个 catch 语句处理异常。 如果 catch 语句未指定类型, 则该语句可以处理任何类型的抛出对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
try {
breedMoreLlamas();
} on OutOfLlamasException {
// 一个特殊的异常
buyMoreLlamas();
} on Exception catch (e) {
// 其他任何异常
print('Unknown exception: $e');
} catch (e) {
// 没有指定的类型,处理所有异常
print('Something really unknown: $e');
}

/// catch() 函数可以指定1到2个参数, 第一个参数为抛出的异常对象, 第二个为堆栈信息

try {
// ···
} on Exception catch (e) {
print('Exception details:\n $e');
} catch (e, s) {
print('Exception details:\n $e');
print('Stack trace:\n $s');
}

如上述代码所示,捕获语句中可以同时使用 oncatch ,也可以单独分开使用。 使用 on 来指定异常类型, 使用 catch 来 捕获异常对象。

如果仅需要部分处理异常, 那么可以使用关键字 rethrow 将异常重新抛出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void misbehave() {
try {
dynamic foo = true;
print(foo++); // Runtime error
} catch (e) {
print('misbehave() partially handled ${e.runtimeType}.');
rethrow; // Allow callers to see the exception.
}
}

void main() {
try {
misbehave();
} catch (e) {
print('main() finished handling ${e.runtimeType}.');
}
}

finally

不管是否抛出异常, finally 中的代码都会被执行。 如果 catch 没有匹配到异常, 异常会在 finally 执行完成后,再次被抛出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
try {
breedMoreLlamas();
} finally {
// Always clean up, even if an exception is thrown.
cleanLlamaStalls();
}

/// 任何匹配的 catch 执行完成后,再执行 finally
try {
breedMoreLlamas();
} catch (e) {
print('Error: $e'); // Handle the exception first.
} finally {
cleanLlamaStalls(); // Then clean up.
}

模块

在Dart中一个应用就是一个包,而包中一个dart文件就是一个模块.

Dart的核心库例如:dart:mathdart:html无需下载,直接可以引用.而第三方提供的库例如:httptest需要通过Dart的包管理器pub下载到本地.引用时需要在包名前加上package:.

1
2
import 'dart:convert' as convert; // 核心包
import 'package:http/http.dart' as http; // 需要下载,

如果是自己写的模块可以通过相对或绝对路径引用:

1
import '../../lib/parser.dart'

如果导入两个存在冲突标识符的库, 则可以为这两个库,或者其中一个指定前缀。

1
2
3
4
5
6
7
8
import '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
7
import 'package:greetings/hello.dart' deferred as hello;

/// 当需要使用的时候,使用库标识符调用 loadLibrary() 函数来加载库
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}

参考

https://dart.dev/guides/language/language-tour

https://hackernoon.com/why-flutter-uses-dart-dd635a054ebf

https://medium.com/dartlang/announcing-dart-2-80ba01f43b6