Dart cheatsheet codelab
Dart语言的设计是为了让其它语言的编码人员可以轻松学习,但它也有一些独特的功能。这个codelab将伴你学习这些语言特性中最重要的内容 – 它基于由Google工程师为内部所编写的Dart language cheatsheet 。
在这个codelab的内嵌编辑器中有部分完成的代码片断。你可以使用这些编辑器来完成代码并点击Run按钮测试你掌握的知识。如果需要帮助,点击Hint按钮。要对代码格式化(dartfmt),点击Format。Reset按钮清除掉你的操作并将编辑器恢复到原始状态。
内嵌编辑器使用DartPad的一个实验版本。如果你发现了DartPad的漏洞或对DartPad有一些建议,请创建一个DartPad issue。如果你对本codelab中的文本或示例有任何建议,请在下方评论区进行回复。
译者注:测试时发现当前这个编辑器在部分浏览器下运行存在问题,如遇到问题请尝试使用其它浏览器
插值字符串
让将一个表达式的值放到字符串中,使用 ${expression}
。如果这个表达式是一个标识符,可以省略掉 {}
。
以下是一些使用插值字符串的示例:
字符串 | 结果 | |
---|---|---|
'${3 + 2}' |
'5' |
|
'${"word".toUpperCase()}' |
'WORD' |
|
'$myObject' |
myObject.toString() 的值 |
代码示例
以下函数接收两个整数作为参数。让其返回一个由空格分隔的包含这两个整数的字符串。例如,stringify(2, 3)
应返回 '2 3'
。
可判空运算符
Dart提供一些方便的运算符来处理可能为空(null)的值。其中一个是??=
赋值运算符,它仅对当前为空的变量进行赋值:
1 2 3 4 5 6 |
int a; // 任何对象的初始值都是null a ??= 3; print(a); // <-- 打印 3 a ??= 5; print(a); // <-- 仍然打印 3 |
另一个可判空运算符是??
,它返回左侧值,但在左侧表达式值为空时对右侧表达式进行运算并返回其结果:
1 2 |
print(1 ?? 3); // <-- 打印 1 print(null ?? 12); // <-- 打印 12 |
代码示例
试着在下面输入 ??=
和 ??
运算符。
条件属性访问
为防止所访问属性或方法的对象可能为空,在问号 (?
) 前添加添加点号 (.
):
1 |
myObject?.someProperty |
以上代码等同于下面的代码:
1 |
(myObject != null) ? myObject.someProperty : null |
可以在单个表达式中同时链式使用多个?.
:
1 |
myObject?.someProperty?.someMethod() |
如果myObject
或myObject.someProperty
为空的话以上代码返回空(并且不会调用someMethod()
)。
代码示例
试着使用条件属性访问来完成如下的代码片断。
集合字面量
Dart对列表(list)、映射(map)和集(set)有内置的支持。你可以使用字面量(literal)来创建它们:
1 2 3 4 5 6 7 |
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>
。
或者你也可以自己指定类型:
1 2 3 |
final aListOfInts = <int>[]; final aSetOfInts = <int>{}; final aMapOfIntToDouble = <int, double>{}; |
指定类型对于通过子类型的内容初始化列表但仍希望列表为 List<BaseType>
时会很方便:
1 |
final aListOfBaseType = <BaseType>[SubType(), SubType()]; |
代码示例
试着将如下变量设置为所提示的值。
箭头语法
你可能在Dart的代码中看到过=>
符号。这个箭头语法是一种定义函数的方式,它执行右侧的表达式并返回其值。
例如,思考下面对List
类的any()
方法的调用:
1 2 3 |
bool hasEmpty = aListOfStrings.any((s) { return s.isEmpty; }); |
书写这段代码更简便的方式如下:
1 |
bool hasEmpty = aListOfStrings.any((s) => s.isEmpty); |
代码示例
试着完成如下使用箭头语法的语句。
级联调用
要对相同对象执行一系列运算,使用级联运算符 (..
)。我们学习过下面这种表达式:
1 |
myObject.someMethod() |
它调用myObject
对象中的 someMethod()
,表达式的结果是 someMethod()
的返回值。
以下是使用级联运算符的相同表达式:
1 |
myObject..someMethod() |
虽然它还是调用了myObject
对象中的 someMethod()
,但表达式的结果不是其返回值,而是一个对myObject
的引用!使用级联调用,我们可以将原本需要用单独语句的运算串联起来。例如,思考下如下的代码:
1 2 3 4 |
var button = querySelector('#confirm'); button.text = 'Confirm'; button.classes.add('important'); button.onClick.listen((e) => window.alert('Confirmed!')); |
使用级联运算符,代码可以变得更为简短,并且无需使用button
变量:
1 2 3 4 |
querySelector('#confirm') ..text = 'Confirm' ..classes.add('important') ..onClick.listen((e) => window.alert('Confirmed!')); |
代码示例
使用级联运算符来创建一个单条语句设置BigObject
的anInt
, aString
和 aList
属性分别为1
, 'String!'
和 [3.0]
,然后调用allDone()
。
Getter和setter
在需要对属性进行普通属性所允许的更多控制时需要定义getter和setter。
译者注:get 和 set关键字必须要在类中使用
例如,你可以确保一个属性的值是有效的:
1 2 3 4 5 6 7 8 9 10 11 |
class MyClass { int _aProperty = 0; int get aProperty => _aProperty; set aProperty(int value) { if (value >= 0) { _aProperty = value; } } } |
你也可以使用 getter来定义一个计算属性:
1 2 3 4 5 6 7 8 9 10 11 12 |
class MyClass { List<int> _values = []; void addValue(int value) { _values.add(value); } // 一个计算属性 int get count { return _values.length; } } |
代码示例
设想你有一个保存私有List<double>
价格列表的购物车类。添加如下内容:
- 一个名为
total
的 getter 函数,返回价格的总和。 - 一个使用新列表进行替换的setter,新列表中不能包含负价格 (这时setter会抛出
InvalidPriceException
)。
可选位置参数
Dart有两类函数参数:位置参数和命名参数。位置参数你可能已经很熟悉了:
1 2 3 4 5 |
int sumUp(int a, int b, int c) { return a + b + c; } int total = sumUp(1, 2, 3); |
在Dart中,可以通过将位置参数放在方括号中来让它们成为可选项:
1 2 3 4 5 6 7 8 9 10 11 |
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:
1 2 3 4 5 6 |
int sumUpToFive(int a, [int b = 2, int c = 3, int d = 4, int e = 5]) { ... } int newTotal = sumUpToFive(1); print(newTotal); // <-- prints 15 |
代码示例
实现一个接收1到5个整数的函数joinWithCommas()
,然后返回一个由逗号分隔的这些数字的字符串。以下是一些函数调用和返回值的示例:
函数调用 | 返回值 | |
---|---|---|
joinWithCommas(1) |
'1' |
|
joinWithCommas(1, 2, 3) |
'1,2,3' |
|
joinWithCommas(1, 1, 1, 1, 1) |
'1,1,1,1,1' |
可选命名参数
使用花括号语法,可以定义带有名称的可选参数。
1 2 3 4 5 6 |
void printName(String firstName, String lastName, {String suffix}) { print('$firstName $lastName ${suffix ?? ''}'); } printName('Avinash', 'Gupta'); printName('Poshmeister', 'Moneybuckets', suffix: 'IV'); |
你可能已经预想到了,这些参数默认为 null,但你可以提供默认值:
1 2 3 |
void printName(String firstName, String lastName, {String suffix = ''}) { print('$firstName $lastName ${suffix}'); } |
函数中不能同时拥有可选位置参数和可选命名参数。
代码示例
在MyDataObject
类中添加一个 copyWith()
实例方法。它应当接收3个命名参数:
int newInt
String newString
double newDouble
在调用时, copyWith()
应返回一个基于当前实例的新的 MyDataObject
对象,如有数据则将前面参数的数据拷贝到对象的属性中。例如,如果 newInt
是非空的,那么将其值拷贝到anInt
中。
异常
Dart代码可以抛出和捕获异常。不同于Java,Dart中的所有异常都是非检查型(unchecked)异常。方法中不声明所要抛出的异常,也不要求你捕获任何异常。
Dart提供 Exception
和 Error
类型,但允许你抛出任何非空对象:
1 2 |
throw Exception('Something bad happened.'); throw 'Waaaaaaah!'; |
在处理导常时使用 try
, on
和 catch
关键字:
1 2 3 4 5 6 7 8 9 10 11 12 |
try { breedMoreLlamas(); } on OutOfLlamasException { // 一个具体的异常 buyMoreLlamas(); } on Exception catch (e) { // 其它任意异常 print('Unknown exception: $e'); } catch (e) { // 无指定类型,处理所有内容 print('Something really unknown: $e'); } |
try
关键字的作用与其它语言中的作用相同。使用on
关键字来通过类型过滤具体的异常,以及catch
关键字来获取对异常对象的引用。
如果你无法完整处理异常,使用rethrow
关键字来传播异常:
1 2 3 4 5 6 |
try { breedMoreLlamas(); } catch (e) { print('I was just trying to breed llamas!.'); rethrow; } |
不论是否抛出异常都要执行的代码使用 finally
:
1 2 3 4 5 6 7 8 |
try { breedMoreLlamas(); } catch (e) { … 处理异常 ... } finally { // 无论是否抛出异常都进行清理 cleanLlamaStalls(); } |
代码示例
实现下面的 tryFunction()
。它应当执行一个untrustworthy方法,然后执行如下内容:
- 如果
untrustworthy()
抛出ExceptionWithMessage
,调用带有异常类型和消息的logger.logException
(尝试使用on
和catch
)。 - 如果
untrustworthy()
抛出一个Exception
,调用带有异常类型的logger.logException
(尝试对其使用on
)。 - 如果
untrustworthy()
抛出任何其它对象,不要捕获该异常。 - 在捕获及处理了所有的异常后,调用
logger.doneLogging
(尝试使用finally
).
this
在构造函数中使用Dart提供了一个在构造函数中为属性赋值的快捷方式:即在声明构造函数时使用this.propertyName
:
1 2 3 4 5 6 7 8 9 |
class MyColor { int red; int green; int blue; MyColor(this.red, this.green, this.blue); } final color = MyColor(80, 80, 128); |
这一方法也适用于命名函数。属性名成为参数的名称:
1 2 3 4 5 6 7 |
class MyColor { ... MyColor({this.red, this.green, this.blue}); } final color = MyColor(red: 80, green: 80, blue: 80); |
对于可选参数,默认值与预期一致:
1 2 3 |
MyColor([this.red = 0, this.green = 0, this.blue = 0]); // 或 MyColor({this.red = 0, this.green = 0, this.blue = 0}); |
代码示例
为MyClass
添加一个使用this
方法为所类中所有的3个属性接收和赋值的单行构造函数。
初始化程序列表
有时在你实现一个构造函数时,需要在构造函数体执行前进行一些设置工作。例如,final字段必须在构造函数体执行之前拥有值。在一个初始化程序列表中执行这一任务,它在构造函数的签名和函数体之间:
1 2 3 4 5 |
Point.fromJson(Map<String, num> json) : x = json['x'], y = json['y'] { print('In Point.fromJson(): ($x, $y)'); } |
初始化程序列表也是一个放置断言(assert)的很好的地方,断言仅在开发时使用:
1 2 3 4 5 |
NonNegativePoint(this.x, this.y) : assert(x >= 0), assert(y >= 0) { print('I just made a NonNegativePoint: ($x, $y)'); } |
代码示例
完成如下的FirstTwoLetters
构造函数。使用一个初始化程序列表来将word
中的前两个字符赋值给letterOne
和LetterTwo
属性。更进一步,可添加assert
来捕获两个字符以内的单词。
命名构造函数
为允许类中有多个构造函数,Dart支持命名构造函数:
1 2 3 4 5 6 7 8 9 10 |
class Point { num x, y; Point(this.x, this.y); Point.origin() { x = 0; y = 0; } } |
要使用命名构造函数,通过全名来进行调用:
1 |
final myPoint = Point.origin(); |
代码示例
给Color类添加一个Color.black
命名函数,将所有的3个属性设置为0.
工厂构造函数
Dart支持工厂构造函数,这可以返回子类型或者甚至是null。使用factory
关键字来创建工厂构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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。
重定向构造函数
有时一个构造函数的唯一目的是重定向到相同类中的另一个构造函数。重定向构造函数的函数体为空,在冒号(:
)后带上一个构造函数调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Automobile { String make; String model; int mpg; // 本类的主构造函数 Automobile(this.make, this.model, this.mpg); // 重定向至主构造函数 Automobile.hybrid(String make, String model) : this(make, model, 60); // 重定向到一个命名函数 Automobile.fancyHybrid() : this.hybrid('Futurecar', 'Mark 2'); } |
代码示例
还记得以上的Color
类吗?创建一个名为black
的命名构造函数,但对属性进行手动赋值,将其重定向到默认构造函数,以0为参数。
常量构造函数
如果类生成永不修改的对象,可以让这些对象成为编译时常量。要进行实现,定义一个const
构造函数并确保让所有的实例对象为final。
1 2 3 4 5 6 7 8 9 |
class ImmutablePoint { const ImmutablePoint(this.x, this.y); final int x; final int y; static const ImmutablePoint origin = ImmutablePoint(0, 0); } |
代码示例
修改Recipe
类来让其实例可成为常量,并创建一个执行如下内容的常量构造函数:
- 拥有3个参数:
ingredients
,calories
和milligramsOfSodium
(以这个排序)。 - 使用
this.
语法来用参数值来自动为相同名称的对象属性赋值。 - 通过在构造函数中的
Recipe
前使用const
关键字来将其定义常量构造函数。
下一步进阶?
希望你使用这个codelab学习或测试了一些Dart语言最有趣的一些功能的知识并享受这一过程。以下是一些现在可做的事的一些建议:
- 尝试 其它 Dart codelabs。
- 阅读Dart语言导览。
- 各种使用DartPad。
- 获取 Dart SDK.