Dart 速查表 codelab

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

The Dart language is designed to be easy to learn for coders coming from other languages, but it has a few unique features. This codelab — which is based on a Dart language cheatsheet written by and for Google engineers — walks you through the most important of these language features.

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

The embedded editors in this codelab have partially completed code snippets. You can use these editors to test your knowledge by completing the code and clicking the Run button. If you need help, click the Hint button. To run the code formatter (dartfmt), click Format. The Reset button erases your work and restores the editor to its original state.

字符串插值

String interpolation

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

To put the value of an expression inside a string, use ${expression}. If the expression is an identifier, you can omit the {}.

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

Here are some examples of using string interpolation:

字符串

String

 

结果

Result

'${3 + 2}'   '5'
'${"word".toUpperCase()}'   'WORD'
'$myObject'  

myObject.toString() 的值

The value of myObject.toString()

代码样例

Code example

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

The following function takes two integers as parameters. Make it return a string containing both integers separated by a space. For example, stringify(2, 3) should return '2 3'.

避空运算符

Null-aware operators

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

Dart offers some handy operators for dealing with values that might be null. One is the ??= assignment operator, which assigns a value to a variable only if that variable is currently null:

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

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

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

Another null-aware operator is ??, which returns the expression on its left unless that expression’s value is null, in which case it evaluates and returns the expression on its right:

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

代码样例

Code example

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

Try putting the ??= and ?? operators to work below.

条件属性访问

Conditional property access

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

To guard access to a property or method of an object that might be null, put a question mark (?) before the dot (.):

myObject?.someProperty

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

The preceding code is equivalent to the following:

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

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

You can chain multiple uses of ?. together in a single expression:

myObject?.someProperty?.someMethod()

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

The preceding code returns null (and never calls someMethod) if either myObject or myObject.someProperty is null.

代码样例

Code example

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

Try using conditional property access to finish the code snippet below.

集合字面量(Collection literals)

Collection literals

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

Dart has built-in support for lists, maps, and sets. You can create them using literals:

final aListOfStrings = ['one', 'two', 'three'];
final aSetOfStrings = {'one', 'two', 'three'};
final aMapOfStringsToInts = {
  'one': 1,
  'two': 2,
  'three': 3,
};

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

Dart’s type inference can assign types to these variables for you. In this case, the inferred types are List<String>, Set<String>, and Map<String, int>.

你也可以手动指定类型:

Or you can specify the type yourself:

final aListOfInts = <int>[];
final aSetOfInts = <int>{};
final aMapOfIntToDouble = <int, double>{};

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

Specifying types is handy when you initialize a list with contents of a subtype, but still want the list to be List<BaseType>:

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

代码样例

Code example

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

Try setting the following variables to the indicated values.

箭头语法

Arrow syntax

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

You might have seen the => symbol in Dart code. This arrow syntax is a way to define a function that executes the expression to its right and returns its value.

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

For example, consider this call to the List class’s any method:

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

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

Here’s a simpler way to write that code:

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

代码样例

Code example

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

Try finishing the following statements, which use arrow syntax.

级连

Cascades

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

To perform a sequence of operations on the same object, use cascades (..). We’ve all seen an expression like this:

myObject.someMethod()

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

It invokes someMethod on myObject, and the result of the expression is the return value of someMethod.

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

Here’s the same expression with a cascade:

myObject..someMethod()

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

Although it still invokes someMethod on myObject, the result of the expression isn’t the return value — it’s a reference to myObject! Using cascades, you can chain together operations that would otherwise require separate statements. For example, consider this code:

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

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

With cascades, the code becomes much shorter, and you don’t need the button variable:

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

代码样例

Code example

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

Use cascades to create a single statement that sets the anInt, aString, and aList properties of a BigObject to 1, 'String!', and [3.0] (respectively) and then calls allDone().

Getters and setters

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

You can define getters and setters whenever you need more control over a property than a simple field allows.

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

For example, you can make sure a property’s value is valid:

class MyClass {
  int _aProperty = 0;

  int get aProperty => _aProperty;

  set aProperty(int value) {
    if (value >= 0) {
      _aProperty = value;
    }
  }
}

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

You can also use a getter to define a computed property:

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

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

  // A computed property.
  int get count {
    return _values.length;
  }
}

代码样例

Code example

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

Imagine you have a shopping cart class that keeps a private List<double> of prices. Add the following:

  • 一个名为 total 的 getter,用于返回总价格。

    A getter called total that returns the sum of the prices

  • 只要新列表不包含任何负价格,setter 就会用新的列表替换列表(在这种情况下,setter 应该抛出 InvalidPriceException。)

    A setter that replaces the list with a new one, as long as the new list doesn’t contain any negative prices (in which case the setter should throw an InvalidPriceException).

可选位置参数

Optional positional parameters

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

Dart has two kinds of function parameters: positional and named. Positional parameters are the kind you’re likely familiar with:

int sumUp(int a, int b, int c) {
  return a + b + c;
}

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

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

With Dart, you can make these positional parameters optional by wrapping them in brackets:

int sumUpToFive(int a, [int b, int c, int d, int e]) {
  int sum = a;
  if (b != null) sum += b;
  if (c != null) sum += c;
  if (d != null) sum += d;
  if (e != null) sum += e;
  return sum;
}

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

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

Optional positional parameters are always last in a function’s parameter list. Their default value is null unless you provide another default value:

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

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

代码样例

Code example

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

Implement a function called joinWithCommas that accepts one to five integers, then returns a string of those numbers separated by commas. Here are some examples of function calls and returned values:

方法调用Function call   返回值Returned value
joinWithCommas(1)   '1'
joinWithCommas(1, 2, 3)   '1,2,3'
joinWithCommas(1, 1, 1, 1, 1)   '1,1,1,1,1'


可选命名参数

Optional named parameters

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

Using a curly brace syntax, you can define optional parameters that have names.

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

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

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

As you might expect, the value of these parameters is null by default, but you can provide default values:

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

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

A function can’t have both optional positional and optional named parameters.

代码样例

Code example

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

Add a copyWith instance method to the MyDataObject class. It should take three named parameters:

  • int newInt
  • String newString
  • double newDouble

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

When called, copyWith should return a new MyDataObject based on the current instance, with data from the preceding parameters (if any) copied into the object’s properties. For example, if newInt is non-null, then copy its value into anInt.

异常

Exceptions

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

Dart code can throw and catch exceptions. In contrast to Java, all of Dart’s exceptions are unchecked exceptions. Methods don’t declare which exceptions they might throw, and you aren’t required to catch any exceptions.

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

Dart provides Exception and Error types, but you’re allowed to throw any non-null object:

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

使用 tryon 以及 catch 关键字来处理异常:

Use the try, on, and catch keywords when handling exceptions:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');
}

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

The try keyword works as it does in most other languages. Use the on keyword to filter for specific exceptions by type, and the catch keyword to get a reference to the exception object.

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

If you can’t completely handle the exception, use the rethrow keyword to propagate the exception:

try {
  breedMoreLlamas();
} catch (e) {
  print('I was just trying to breed llamas!.');
  rethrow;
}

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

To execute code whether or not an exception is thrown, use finally:

try {
  breedMoreLlamas();
} catch (e) {
  … handle exception ...
} finally {
  // Always clean up, even if an exception is thrown.
  cleanLlamaStalls();
}

代码样例

Code example

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

Implement tryFunction below. It should execute an untrustworthy method and then do the following:

  • 如果 untrustworthy 抛出了 ExceptionWithMessage,则调用 logger.logException 并传入使用异常类型和消息(尝试使用 oncatch)。

    If untrustworthy throws an ExceptionWithMessage, call logger.logException with the exception type and message (try using on and catch).

  • 如果 untrustworthy 抛出了一个 Exception,则调用 logger.logException 并传入使用异常类型(这次请尝试使用 on)。

    If untrustworthy throws an Exception, call logger.logException with the exception type (try using on for this one).

  • 如果 untrustworthy 抛出了其他对象,请不要捕获该异常。

    If untrustworthy throws any other object, don’t catch the exception.

  • 捕获并处理完所有内容后,调用 logger.doneLogging(尝试使用 finally)。

    After everything’s caught and handled, call logger.doneLogging (try using finally).

在构造方法中使用 this

Using this in a constructor

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

Dart provides a handy shortcut for assigning values to properties in a constructor: use this.propertyName when declaring the constructor:

class MyColor {
  int red;
  int green;
  int blue;

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

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

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

This technique works for named parameters, too. Property names become the names of the parameters:

class MyColor {
  ...

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

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

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

For optional parameters, default values work as expected:

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

代码样例

Code example

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

Add a one-line constructor to MyClass that uses this. syntax to receive and assign values for all three properties of the class.

Initializer lists

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

Sometimes when you implement a constructor, you need to do some setup before the constructor body executes. For example, final fields must have values before the constructor body executes. Do this work in an initializer list, which goes between the constructor’s signature and its body:

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

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

The initializer list is also a handy place to put asserts, which run only during development:

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

代码样例

Code example

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

同样的,直到我修复了我代码或者测试之前,我都看不见 print() 的输出。

Complete the FirstTwoLetters constructor below. Use an initializer list to assign the first two characters in word to the letterOne and LetterTwo properties. For extra credit, add an assert to catch words of less than two characters.

命名构造方法

Named constructors

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

To allow classes to have multiple constructors, Dart supports named constructors:

class Point {
  num x, y;

  Point(this.x, this.y);

  Point.origin() {
    x = 0;
    y = 0;
  }
}

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

To use a named constructor, invoke it using its full name:

final myPoint = Point.origin();

代码样例

Code example

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

Give the Color class a constructor named Color.black that sets all three properties to zero.

工厂构造方法

Factory constructors

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

Dart supports factory constructors, which can return subtypes or even null. To create a factory constructor, use the factory keyword:

class Square extends Shape {}

class Circle extends Shape {}

class Shape {
  Shape();

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

    print('I don\'t recognize $typeName');
    return null;
  }
}

代码样例

Code example

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

Fill in the factory constructor named IntegerHolder.fromList, making it do the following:

  • 若列表只有一个值,那么就用它来创建一个 IntegerSingle

    If the list has one value, create an IntegerSingle with that value.

  • 如果这个列表有两个值,那么按其顺序创建一个 IntegerDouble

    If the list has two values, create an IntegerDouble with the values in order.

  • 如果这个列表有三个值,那么按其顺序创建一个 IntegerTriple

    If the list has three values, create an IntegerTriple with the values in order.

  • 否则返回 null。

    Otherwise, return null.

重定向构造方法

Redirecting constructors

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

Sometimes a constructor’s only purpose is to redirect to another constructor in the same class. A redirecting constructor’s body is empty, with the constructor call appearing after a colon (:).

class Automobile {
  String make;
  String model;
  int mpg;

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

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

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

代码样例

Code example

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

Remember the Color class from above? Create a named constructor called black, but rather than manually assigning the properties, redirect it to the default constructor with zeros as the arguments.

Const 构造方法

Const constructors

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

If your class produces objects that never change, you can make these objects compile-time constants. To do this, define a const constructor and make sure that all instance variables are final.

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

  final int x;
  final int y;

  static const ImmutablePoint origin =
      ImmutablePoint(0, 0);
}

代码样例

Code example

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

Modify the Recipe class so its instances can be constants, and create a constant constructor that does the following:

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

    Has three parameters: ingredients, calories, and milligramsOfSodium (in that order).

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

    Uses this. syntax to automatically assign the parameter values to the object properties of the same name.

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

    Is constant, with the const keyword just before Recipe in the constructor declaration.

下一步是什么?

What next?

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

We hope you enjoyed using this codelab to learn or test your knowledge of some of the most interesting features of the Dart language. Here are some suggestions for what to do now: