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.
  4.  
  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()

如果 myObjectmyObject.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!'));

代码样例

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

Getters and setters

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

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

  1. class MyClass {
  2. int _aProperty = 0;
  3.  
  4. int get aProperty => _aProperty;
  5.  
  6. set aProperty(int value) {
  7. if (value >= 0) {
  8. _aProperty = value;
  9. }
  10. }
  11. }

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

  1. class MyClass {
  2. List<int> _values = [];
  3.  
  4. void addValue(int value) {
  5. _values.add(value);
  6. }
  7.  
  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. }
  4.  
  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. }
  9.  
  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. }
  4.  
  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. }
  4.  
  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!';

使用 tryon 以及 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 并传入使用异常类型和消息(尝试使用 oncatch)。

  • 如果 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;
  5.  
  6. MyColor(this.red, this.green, this.blue);
  7. }
  8.  
  9. final color = MyColor(80, 80, 128);

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

  1. class MyColor {
  2. ...
  3.  
  4. MyColor({this.red, this.green, this.blue});
  5. }
  6.  
  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 的前两个字符分配给 letterOneLetterTwo 属性。要获得额外的信用,请添加一个 断言 以捕获少于两个字符的单词。

命名构造方法

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

  1. class Point {
  2. num x, y;
  3.  
  4. Point(this.x, this.y);
  5.  
  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 {}
  2.  
  3. class Circle extends Shape {}
  4.  
  5. class Shape {
  6. Shape();
  7.  
  8. factory Shape.fromTypeName(String typeName) {
  9. if (typeName == 'square') return Square();
  10. if (typeName == 'circle') return Circle();
  11.  
  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;
  5.  
  6. // The main constructor for this class.
  7. Automobile(this.make, this.model, this.mpg);
  8.  
  9. // Delegates to the main constructor.
  10. Automobile.hybrid(String make, String model) : this(make, model, 60);
  11.  
  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);
  3.  
  4. final int x;
  5. final int y;
  6.  
  7. static const ImmutablePoint origin =
  8. ImmutablePoint(0, 0);
  9. }

代码样例

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

  • 该方法有三个参数:ingredientscaloriesmilligramsOfSodium。(按照此顺序)

  • 使用 this 语法自动将参数值分配给同名的对象属性。

  • Recipe 的构造方法声明之前,用 const 关键字使其成为常量。

下一步是什么?

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