Dart 速查表 codelab

Dart 语言旨在让从其他编程语言转来的开发者们能够轻松学习,但也有它的独特之处。本篇将基于谷歌工程师编写的Dart 语言速查表为你介绍一些最重要的语言特性。

在这篇 codelab 中的嵌入式编辑器已经完成了部分代码片段。你可以在这些编辑器上将代码补充完整,然后点击 Run (运行) 按钮进行测试。如果你需要帮助,请点击 Hint (提示) 按钮。要运行代码格式化 (dartfmt),点击 Format (格式化) 按钮,Reset (重置) 按钮将会清除你的操作,并把编辑器恢复到初始状态。

备忘:

本页面内嵌了一些 DartPads 做例子展示,

如果你只看到了空白的框框(而没有任何内容),请查阅DartPad 常见问题页面。

字符串插值

为了将表达式的值放在字符串中,请使用 ${expression}。若表达式为单个标识符,则可以省略 {}。

下面是一些使用字符串插值的例子:

字符串 结果
'${3 + 2}' '5'
'${"word".toUpperCase()}' 'WORD'
'$myObject' myObject.toString() 的值

代码样例

下面的方法接收两个整形变量作为参数,然后让它返回一个包含以空格分隔的整数的字符串。例如,stringify(2, 3) 应该返回 '2 3'。

避空运算符

Dart 提供了一系列方便的运算符用于处理可能会为空值的变量。其中一个是 ??= 赋值运算符,仅当该变量为空值时才为其赋值:

1. int a; // The initial value of any object is null.
2. a ??= 3;
3. print(a); // <-- Prints 3.

5. a ??= 5;
6. print(a); // <-- Still prints 3.

另外一个避空运算符是 ??,如果该运算符左边的表达式返回的是空值,则会计算并返回右边的表达式。

1. print(1 ?? 3); // <-- Prints 1.
2. print(null ?? 12); // <-- Prints 12.

代码样例

尝试在下面使用 ??= 和 ?? 操作符。

条件属性访问

要保护可能会为空的属性的正常访问,请在点(.)之前加一个问号(?)。

1. myObject?.someProperty

上述代码等效于以下内容:

1. (myObject != null) ? myObject.someProperty : null

你可以在一个表达式中连续使用多个 ?.:

1. myObject?.someProperty?.someMethod()

如果 myObject 或 myObject.someProperty 为空,则前面的代码返回 null(并不再调用 someMethod)。

代码样例

尝试使用条件属性访问来完成下面的代码片段。

集合字面量(Collection literals)

Dart 内置了对 list、map 以及 set 的支持。你可以通过字面量直接创建它们:

1. final aListOfStrings = ['one', 'two', 'three'];
2. final aSetOfStrings = {'one', 'two', 'three'};
3. final aMapOfStringsToInts = {
4. 'one': 1,
5. 'two': 2,
6. 'three': 3,
7. };

Dart 的类型推断可以自动帮你分配这些变量的类型。在这个例子中,推断类型是 List<String>、Set<String>和 Map<String, int>。

你也可以手动指定类型:

1. final aListOfInts = <int>[];
2. final aSetOfInts = <int>{};
3. final aMapOfIntToDouble = <int, double>{};

在使用子类型的内容初始化列表,但仍希望列表为 List <BaseType> 时,指定其类型很方便:

1. final aListOfBaseType = <BaseType>[SubType(), SubType()];

代码样例

尝试将以下变量设定为指定的值。

箭头语法

你也许已经在 Dart 代码中见到过 => 符号。这种箭头语法是一种定义函数的方法,该函数将在其右侧执行表达式并返回其值。

例如,考虑调用这个 List 类中的 any 方法:

1. bool hasEmpty = aListOfStrings.any((s) {
2. return s.isEmpty;
3. });

这里是一个更简单的代码实现:

1. bool hasEmpty = aListOfStrings.any((s) => s.isEmpty);

代码样例

尝试使用箭头语法完成下面语句:

级连

要对同一对象执行一系列操作,请使用级联(..)。我们都看到过这样的表达式:

1. myObject.someMethod()

它在 myObject 上调用 someMethod 方法,而表达式的结果是 someMethod 的返回值。

下面是一个使用级连语法的相同表达式:

1. myObject..someMethod()

虽然它仍然在 myObject 上调用了 someMethod,但表达式的结果却不是该方法返回值,而是是 myObject 对象的引用!使用级联,你可以将需要单独操作的语句链接在一起。例如,请考虑以下代码:

1. var button = querySelector('#confirm');
2. button.text = 'Confirm';
3. button.classes.add('important');
4. button.onClick.listen((e) => window.alert('Confirmed!'));

使用级连能够让代码变得更加简洁,而且你也不再需要 button 变量了。

1. querySelector('#confirm')
2. ..text = 'Confirm'
3. ..classes.add('important')
4. ..onClick.listen((e) => window.alert('Confirmed!'));

代码样例

使用级联创建一个语句,分别将 BigObject 的 anInt 属性设为 1、aString 属性设为 String!、aList 属性设置为[3.0] 然后调用 allDone()。

Getters and setters

任何需要对属性进行更多控制而不是允许简单字段访问的时候,你都可以自定义 getter 和 setter。

例如,你可以用来确保属性值合法:

1. class MyClass {
2. int _aProperty = 0;

4. int get aProperty => _aProperty;

6. set aProperty(int value) {
7. if (value >= 0) {
8. _aProperty = value;
9. }
10. }
11. }

你还可以使用 getter 来定义计算属性:

1. class MyClass {
2. List<int> _values = [];

4. void addValue(int value) {
5. _values.add(value);
6. }

8. // A computed property.
9. int get count {
10. return _values.length;
11. }
12. }

代码样例

想象你有一个购物车类,其中有一个私有的 List<double> 类型的 prices 属性。添加以下内容:

  • 一个名为 total 的 getter,用于返回总价格。
  • 只要新列表不包含任何负价格,setter 就会用新的列表替换列表(在这种情况下,setter 应该抛出 InvalidPriceException)。

可选位置参数

Dart 有两种传参方法:位置参数和命名参数。位置参数你可能会比较熟悉:

1. int sumUp(int a, int b, int c) {
2. return a + b + c;
3. }

5. int total = sumUp(1, 2, 3);

在 Dart 中,你可以通过将这些参数包裹在大括号中使其变成可选位置参数:

1. int sumUpToFive(int a, [int b, int c, int d, int e]) {
2. int sum = a;
3. if (b != null) sum += b;
4. if (c != null) sum += c;
5. if (d != null) sum += d;
6. if (e != null) sum += e;
7. return sum;
8. }

10. int total = sumUptoFive(1, 2);
11. int otherTotal = sumUpToFive(1, 2, 3, 4, 5);

可选位置参数永远放在方法参数列表的最后。除非你给它们提供一个默认值,否则默认为 null。

1. int sumUpToFive(int a, [int b = 2, int c = 3, int d = 4, int e = 5]) {
2. ...
3. }

5. int newTotal = sumUpToFive(1);
6. print(newTotal); // <-- prints 15

代码样例

实现一个名为 joinWithCommas 的方法,它接收一至五个整数,然后返回由逗号分隔的包含这些数字的字符串。以下是方法调用和返回值的一些示例:

方法调用 返回值
joinWithCommas(1) '1'
joinWithCommas(1, 2, 3) '1,2,3'
joinWithCommas(1, 1, 1, 1, 1) '1,1,1,1,1'

可选命名参数

你可以使用大括号语法定义可选命名参数。

1. void printName(String firstName, String lastName, {String suffix}) {
2. print('$firstName $lastName ${suffix ?? ''}');
3. }

5. printName('Avinash', 'Gupta');
6. printName('Poshmeister', 'Moneybuckets', suffix: 'IV');

正如你所料,这些参数默认为 null,但你也可以为其提供默认值。

1. void printName(String firstName, String lastName, {String suffix = ''}) {
2. print('$firstName $lastName ${suffix}');
3. }

一个方法不能同时使用可选位置参数和可选命名参数。

代码样例

向 MyDataObject 类添加一个 copyWith() 实例方法,它应该包含三个命名参数。

  • int newInt
  • String newString
  • double newDouble

当该方法被调用时,copyWith 应该根据当前实例返回一个新的MyDataObject 并将前面参数(如果有的话)的数据复制到对象的属性中。例如,如果 newInt 不为空,则将其值复制到 anInt 中。

异常

Dart 代码可以抛出和捕获异常。与 Java 相比,Dart 的所有异常都是 unchecked exception。方法不会声明它们可能抛出的异常,你也不需要捕获任何异常。

虽然 Dart 提供了 Exception 和 Error 类型,但是你可以抛出任何非空对象:

1. throw Exception('Something bad happened.');
2. throw 'Waaaaaaah!';

使用 try、on 以及 catch 关键字来处理异常:

1. try {
2. breedMoreLlamas();
3. } on OutOfLlamasException {
4. // A specific exception
5. buyMoreLlamas();
6. } on Exception catch (e) {
7. // Anything else that is an exception
8. print('Unknown exception: $e');
9. } catch (e) {
10. // No specified type, handles all
11. print('Something really unknown: $e');
12. }

try 关键字作用与其他大多数语言一样。使用 on 关键字按类型过滤特定异常,而 catch 关键字则能够获取捕捉到的异常对象的引用。

如果你无法完全处理该异常,请使用 rethrow 关键字再次抛出异常:

1. try {
2. breedMoreLlamas();
3. } catch (e) {
4. print('I was just trying to breed llamas!.');
5. rethrow;
6. }

要执行一段无论是否抛出异常都会执行的代码,请使用 finally:

1. try {
2. breedMoreLlamas();
3. } catch (e) {
4. … handle exception ...
5. } finally {
6. // Always clean up, even if an exception is thrown.
7. cleanLlamaStalls();
8. }

代码样例

在下面实现 tryFunction() 方法。它应该会执行一个不可靠的方法,然后做以下操作:

  • 如果 untrustworthy() 抛出了 ExceptionWithMessage,则调用 logger.logException 并传入使用异常类型和消息(尝试使用 on 和 catch)。
  • 如果 untrustworthy() 抛出了一个 Exception,则调用 logger.logException 并传入使用异常类型(这次请尝试使用 on)。
  • 如果 untrustworthy() 抛出了其他对象,请不要捕获该异常。
  • 捕获并处理完所有内容后,调用 logger.doneLogging(尝试使用 finally)。

在构造方法中使用 this

Dart 提供了一个方便的快捷方式,用于为构造方法中的属性赋值:在声明构造方法时使用 this.propertyName。

1. class MyColor {
2. int red;
3. int green;
4. int blue;

6. MyColor(this.red, this.green, this.blue);
7. }

9. final color = MyColor(80, 80, 128);

此技巧同样也适用于命名参数。属性名为参数的名称:

1. class MyColor {
2. ...

4. MyColor({this.red, this.green, this.blue});
5. }

7. final color = MyColor(red: 80, green: 80, blue: 80);

对于可选参数,默认值为期望值:

1. MyColor([this.red = 0, this.green = 0, this.blue = 0]);
2. // or
3. MyColor({this.red = 0, this.green = 0, this.blue = 0});

代码样例

使用 this 语法向 MyClass 添加一行构造方法,并接收和分配全部(三个)属性。

Initializer lists

有时,当你在实现构造函数时,您需要在构造函数体执行之前进行一些初始化。例如,final 修饰的字段必须在构造函数体执行之前赋值。在初始化列表中执行此操作,该列表位于构造函数的签名与其函数体之间:

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

初始化列表也是放置断言的便利位置,它仅会在开发期间运行:

1. NonNegativePoint(this.x, this.y)
2. : assert(x >= 0),
3. assert(y >= 0) {
4. print('I just made a NonNegativePoint: ($x, $y)');
5. }

代码样例

完成下面的 FirstTwoLetters 的构造函数。使用的初始化列表将 word 的前两个字符分配给 letterOne 和 LetterTwo 属性。要获得额外的信用,请添加一个 断言 以捕获少于两个字符的单词。

命名构造方法

为了允许一个类具有多个构造方法,Dart 支持命名构造方法:

1. class Point {
2. num x, y;

4. Point(this.x, this.y);

6. Point.origin() {
7. x = 0;
8. y = 0;
9. }
10. }

为了使用命名构造方法,请使用全名调用它:

1. final myPoint = Point.origin();

代码样例

给 Color 类添加一个叫做 Color.black 的方法,它将会把三个属性的值都设为 0。

工厂构造方法

Dart 支持工厂构造方法。它能够返回其子类甚至 null 对象。要创建一个工厂构造方法,请使用 factory 关键字。

1. class Square extends Shape {}

3. class Circle extends Shape {}

5. class Shape {
6. Shape();

8. factory Shape.fromTypeName(String typeName) {
9. if (typeName == 'square') return Square();
10. if (typeName == 'circle') return Circle();

12. print('I don\'t recognize $typeName');
13. return null;
14. }
15. }

代码样例

填写名为 IntegerHolder.fromList 的工厂构造方法,使其执行以下操作:

  • 若列表只有一个值,那么就用它来创建一个 IntegerSingle。
  • 如果这个列表有两个值,那么按其顺序创建一个 IntegerDouble。
  • 如果这个列表有三个值,那么按其顺序创建一个 IntegerTriple。
  • 否则返回 null。

重定向构造方法

有时一个构造方法仅仅用来重定向到该类的另一个构造方法。重定向方法没有主体,它在冒号(:)之后调用另一个构造方法。

1. class Automobile {
2. String make;
3. String model;
4. int mpg;

6. // The main constructor for this class.
7. Automobile(this.make, this.model, this.mpg);

9. // Delegates to the main constructor.
10. Automobile.hybrid(String make, String model) : this(make, model, 60);

12. // Delegates to a named constructor
13. Automobile.fancyHybrid() : this.hybrid('Futurecar', 'Mark 2');
14. }

代码样例

还记得我们之前提到的 Color 类吗?创建一个叫做 black 的命名构造方法,但这次我们不要手动分配属性,而是将 0 作为参数,重定向到默认的构造方法。

Const 构造方法

如果你的类生成的对象永远都不会更改,则可以让这些对象成为编译时常量。为此,请定义 const 构造方法并确保所有实例变量都是 final 的。

1. class ImmutablePoint {
2. const ImmutablePoint(this.x, this.y);

4. final int x;
5. final int y;

7. static const ImmutablePoint origin =
8. ImmutablePoint(0, 0);
9. }

代码样例

修改 Recipe 类,使其实例成为常量,并创建一个执行以下操作的常量构造方法:

  • 该方法有三个参数:ingredients、calories 和 milligramsOfSodium。(按照此顺序)
  • 使用 this 语法自动将参数值分配给同名的对象属性。
  • 在 Recipe 的构造方法声明之前,用 const 关键字使其成为常量。

下一步是什么?

我们希望你能够喜欢这个 codelab 来学习或测试你对 Dart 语言中一些最有趣的功能的知识。下面是一些有关现在该做什么的建议: