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 提供了一系列方便的运算符用于处理可能会为空值的变量。其中一个是 ??=
赋值运算符,仅当该变量为空值时才为其赋值:
- int a; // The initial value of any object is null.
- a ??= 3;
- print(a); // <-- Prints 3.
- a ??= 5;
- print(a); // <-- Still prints 3.
另外一个避空运算符是 ??
,如果该运算符左边的表达式返回的是空值,则会计算并返回右边的表达式。
- print(1 ?? 3); // <-- Prints 1.
- print(null ?? 12); // <-- Prints 12.
代码样例
尝试在下面使用 ??=
和 ??
操作符。
条件属性访问
要保护可能会为空的属性的正常访问,请在点(.
)之前加一个问号(?
)。
- myObject?.someProperty
上述代码等效于以下内容:
- (myObject != null) ? myObject.someProperty : null
你可以在一个表达式中连续使用多个 ?.
:
- myObject?.someProperty?.someMethod()
如果 myObject
或 myObject.someProperty
为空,则前面的代码返回 null(并不再调用 someMethod
)。
代码样例
尝试使用条件属性访问来完成下面的代码片段。
集合字面量(Collection literals)
Dart 内置了对 list、map 以及 set 的支持。你可以通过字面量直接创建它们:
- 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>
。
你也可以手动指定类型:
- final aListOfInts = <int>[];
- final aSetOfInts = <int>{};
- final aMapOfIntToDouble = <int, double>{};
在使用子类型的内容初始化列表,但仍希望列表为 List <BaseType>
时,指定其类型很方便:
- final aListOfBaseType = <BaseType>[SubType(), SubType()];
代码样例
尝试将以下变量设定为指定的值。
箭头语法
你也许已经在 Dart 代码中见到过 =>
符号。这种箭头语法是一种定义函数的方法,该函数将在其右侧执行表达式并返回其值。
例如,考虑调用这个 List
类中的 any
方法:
- bool hasEmpty = aListOfStrings.any((s) {
- return s.isEmpty;
- });
这里是一个更简单的代码实现:
- bool hasEmpty = aListOfStrings.any((s) => s.isEmpty);
代码样例
尝试使用箭头语法完成下面语句:
级连
要对同一对象执行一系列操作,请使用级联(..
)。我们都看到过这样的表达式:
- myObject.someMethod()
它在 myObject
上调用 someMethod
方法,而表达式的结果是 someMethod
的返回值。
下面是一个使用级连语法的相同表达式:
- myObject..someMethod()
虽然它仍然在 myObject
上调用了 someMethod
,但表达式的结果却不是该方法返回值,而是是 myObject
对象的引用!使用级联,你可以将需要单独操作的语句链接在一起。例如,请考虑以下代码:
- var button = querySelector('#confirm');
- button.text = 'Confirm';
- button.classes.add('important');
- button.onClick.listen((e) => window.alert('Confirmed!'));
使用级连能够让代码变得更加简洁,而且你也不再需要 button
变量了。
- querySelector('#confirm')
- ..text = 'Confirm'
- ..classes.add('important')
- ..onClick.listen((e) => window.alert('Confirmed!'));
代码样例
使用级联创建一个语句,分别将 BigObject
的 anInt
属性设为 1
、aString
属性设为 String!
、aList
属性设置为[3.0]
然后调用 allDone()
。
Getters and setters
任何需要对属性进行更多控制而不是允许简单字段访问的时候,你都可以自定义 getter 和 setter。
例如,你可以用来确保属性值合法:
- class MyClass {
- int _aProperty = 0;
- int get aProperty => _aProperty;
- set aProperty(int value) {
- if (value >= 0) {
- _aProperty = value;
- }
- }
- }
你还可以使用 getter 来定义计算属性:
- class MyClass {
- List<int> _values = [];
- void addValue(int value) {
- _values.add(value);
- }
- // A computed property.
- int get count {
- return _values.length;
- }
- }
代码样例
想象你有一个购物车类,其中有一个私有的 List<double>
类型的 prices 属性。添加以下内容:
一个名为
total
的 getter,用于返回总价格。只要新列表不包含任何负价格,setter 就会用新的列表替换列表(在这种情况下,setter 应该抛出
InvalidPriceException
)。
可选位置参数
Dart 有两种传参方法:位置参数和命名参数。位置参数你可能会比较熟悉:
- int sumUp(int a, int b, int c) {
- return a + b + c;
- }
- int total = sumUp(1, 2, 3);
在 Dart 中,你可以通过将这些参数包裹在大括号中使其变成可选位置参数:
- 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。
- int sumUpToFive(int a, [int b = 2, int c = 3, int d = 4, int e = 5]) {
- ...
- }
- int newTotal = sumUpToFive(1);
- 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' |
可选命名参数
你可以使用大括号语法定义可选命名参数。
- void printName(String firstName, String lastName, {String suffix}) {
- print('$firstName $lastName ${suffix ?? ''}');
- }
- printName('Avinash', 'Gupta');
- printName('Poshmeister', 'Moneybuckets', suffix: 'IV');
正如你所料,这些参数默认为 null,但你也可以为其提供默认值。
- void printName(String firstName, String lastName, {String suffix = ''}) {
- print('$firstName $lastName ${suffix}');
- }
一个方法不能同时使用可选位置参数和可选命名参数。
代码样例
向 MyDataObject
类添加一个 copyWith()
实例方法,它应该包含三个命名参数。
int newInt
String newString
double newDouble
当该方法被调用时,copyWith
应该根据当前实例返回一个新的MyDataObject
并将前面参数(如果有的话)的数据复制到对象的属性中。例如,如果 newInt
不为空,则将其值复制到 anInt
中。
异常
Dart 代码可以抛出和捕获异常。与 Java 相比,Dart 的所有异常都是 unchecked exception。方法不会声明它们可能抛出的异常,你也不需要捕获任何异常。
虽然 Dart 提供了 Exception 和 Error 类型,但是你可以抛出任何非空对象:
- throw Exception('Something bad happened.');
- throw 'Waaaaaaah!';
使用 try
、on
以及 catch
关键字来处理异常:
- 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
关键字则能够获取捕捉到的异常对象的引用。
如果你无法完全处理该异常,请使用 rethrow
关键字再次抛出异常:
- try {
- breedMoreLlamas();
- } catch (e) {
- print('I was just trying to breed llamas!.');
- rethrow;
- }
要执行一段无论是否抛出异常都会执行的代码,请使用 finally
:
- try {
- breedMoreLlamas();
- } catch (e) {
- … handle exception ...
- } finally {
- // Always clean up, even if an exception is thrown.
- cleanLlamaStalls();
- }
代码样例
在下面实现 tryFunction()
方法。它应该会执行一个不可靠的方法,然后做以下操作:
如果
untrustworthy()
抛出了ExceptionWithMessage
,则调用logger.logException
并传入使用异常类型和消息(尝试使用on
和catch
)。如果
untrustworthy()
抛出了一个Exception
,则调用logger.logException
并传入使用异常类型(这次请尝试使用on
)。如果
untrustworthy()
抛出了其他对象,请不要捕获该异常。捕获并处理完所有内容后,调用
logger.doneLogging
(尝试使用finally
)。
在构造方法中使用 this
Dart 提供了一个方便的快捷方式,用于为构造方法中的属性赋值:在声明构造方法时使用 this.propertyName
。
- class MyColor {
- int red;
- int green;
- int blue;
- MyColor(this.red, this.green, this.blue);
- }
- final color = MyColor(80, 80, 128);
此技巧同样也适用于命名参数。属性名为参数的名称:
- class MyColor {
- ...
- MyColor({this.red, this.green, this.blue});
- }
- final color = MyColor(red: 80, green: 80, blue: 80);
对于可选参数,默认值为期望值:
- MyColor([this.red = 0, this.green = 0, this.blue = 0]);
- // or
- MyColor({this.red = 0, this.green = 0, this.blue = 0});
代码样例
使用 this
语法向 MyClass
添加一行构造方法,并接收和分配全部(三个)属性。
Initializer lists
有时,当你在实现构造函数时,您需要在构造函数体执行之前进行一些初始化。例如,final 修饰的字段必须在构造函数体执行之前赋值。在初始化列表中执行此操作,该列表位于构造函数的签名与其函数体之间:
- Point.fromJson(Map<String, num> json)
- : x = json['x'],
- y = json['y'] {
- print('In Point.fromJson(): ($x, $y)');
- }
初始化列表也是放置断言的便利位置,它仅会在开发期间运行:
- NonNegativePoint(this.x, this.y)
- : assert(x >= 0),
- assert(y >= 0) {
- print('I just made a NonNegativePoint: ($x, $y)');
- }
代码样例
完成下面的 FirstTwoLetters
的构造函数。使用的初始化列表将 word
的前两个字符分配给 letterOne
和 LetterTwo
属性。要获得额外的信用,请添加一个 断言
以捕获少于两个字符的单词。
命名构造方法
为了允许一个类具有多个构造方法,Dart 支持命名构造方法:
- class Point {
- num x, y;
- Point(this.x, this.y);
- Point.origin() {
- x = 0;
- y = 0;
- }
- }
为了使用命名构造方法,请使用全名调用它:
- final myPoint = Point.origin();
代码样例
给 Color 类添加一个叫做 Color.black
的方法,它将会把三个属性的值都设为 0。
工厂构造方法
Dart 支持工厂构造方法。它能够返回其子类甚至 null 对象。要创建一个工厂构造方法,请使用 factory
关键字。
- 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;
- }
- }
代码样例
填写名为 IntegerHolder.fromList
的工厂构造方法,使其执行以下操作:
若列表只有一个值,那么就用它来创建一个
IntegerSingle
。如果这个列表有两个值,那么按其顺序创建一个
IntegerDouble
。如果这个列表有三个值,那么按其顺序创建一个
IntegerTriple
。否则返回 null。
重定向构造方法
有时一个构造方法仅仅用来重定向到该类的另一个构造方法。重定向方法没有主体,它在冒号(:
)之后调用另一个构造方法。
- 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');
- }
代码样例
还记得我们之前提到的 Color
类吗?创建一个叫做 black
的命名构造方法,但这次我们不要手动分配属性,而是将 0 作为参数,重定向到默认的构造方法。
Const 构造方法
如果你的类生成的对象永远都不会更改,则可以让这些对象成为编译时常量。为此,请定义 const
构造方法并确保所有实例变量都是 final 的。
- class ImmutablePoint {
- const ImmutablePoint(this.x, this.y);
- final int x;
- final int y;
- static const ImmutablePoint origin =
- ImmutablePoint(0, 0);
- }
代码样例
修改 Recipe
类,使其实例成为常量,并创建一个执行以下操作的常量构造方法:
该方法有三个参数:
ingredients
、calories
和milligramsOfSodium
。(按照此顺序)使用
this
语法自动将参数值分配给同名的对象属性。在
Recipe
的构造方法声明之前,用const
关键字使其成为常量。
下一步是什么?
我们希望你能够喜欢这个 codelab 来学习或测试你对 Dart 语言中一些最有趣的功能的知识。下面是一些有关现在该做什么的建议:
尝试阅读 其他 Dart codelab。
阅读 Dart 语言之旅。
在 DartPad 上进行练习。