本文适合有代码基础的人,如果没在代码基础请文章底部来源从头开始学习
示例
// 定义一个函数
printInteger(int aNumber) {
print('The number is $aNumber.'); // 打印到控制台。
}
// 应用从这里开始执行。
// 程序开始执行函数,该函数是特定的、必须的、顶级函数。
main() {
//定义变量,通过这种方式定义变量不需要指定变量类型。
var number = 42;
printInteger(number); // 调用函数。
}
重要的概念
在学习 Dart 语言时, 应该基于以下事实和概念:
任何保存在变量中的都是一个 对象 , 并且所有的对象都是对应一个 类 的实例。 无论是数字,函数和 null 都是对象。所有对象继承自 Object 类。
尽管 Dart 是强类型的,但是 Dart 可以推断类型,所以类型注释是可选的。 在上面的代码中, number 被推断为 int 类型。 如果要明确说明不需要任何类型, 需要使用特殊类型 dynamic 。
Dart 支持泛型,如 List
(整数列表)或 List (任何类型的对象列表)。 Dart 支持顶级函数(例如 main() ), 同样函数绑定在类或对象上(分别是 静态函数 和 实例函数 )。 以及支持函数内创建函数 ( 嵌套 或 局部函数 ) 。
类似地, Dart 支持顶级 变量 , 同样变量绑定在类或对象上(静态变量和实例变量)。 实例变量有时称为字段或属性。
与 Java 不同,Dart 没有关键字 “public” , “protected” 和 “private” 。 如果标识符以下划线(_)开头,则它相对于库是私有的。 有关更多信息,参考 库和可见性。
标识符 以字母或下划线(_)开头,后跟任意字母和数字组合。
Dart 语法中包含 表达式( expressions )(有运行时值)和 语句( statements )(没有运行时值)。 例如,条件表达式 condition ? expr1 : expr2 的值可能是 expr1 或 expr2 。 将其与 if-else 语句 相比较,if-else 语句没有值。 一条语句通常包含一个或多个表达式,相反表达式不能直接包含语句。
Dart 工具提示两种类型问题:警告和错误。 警告只是表明代码可能无法正常工作,但不会阻止程序的执行。 错误可能是编译时错误或者运行时错误。 编译时错误会阻止代码的执行; 运行时错误会导致代码在执行过程中引发 [异常](#exception)。
Dart关键字
key | 作用域 | 备注 | |
---|---|---|---|
abstract | 2 | 抽象方法/抽象类 | |
dynamic | 2 | ||
implements | 2 | ||
show | 1 | ||
as | 2 | ||
else | |||
import | 2 | ||
static | 2 | ||
assert | |||
enum | |||
in | |||
super | |||
async | 1 | ||
export | 2 | ||
interface | 2 | ||
switch | |||
await | 3 | ||
extends | |||
is | |||
sync | 1 | ||
break | |||
external | 2 | ||
library | 2 | ||
this | |||
case | |||
factory | 2 | ||
mixin | 2 | ||
throw | |||
catch | |||
false | |||
new | |||
true | |||
class | |||
final | |||
null | |||
try | |||
const | |||
finally | |||
on | 1 | ||
typedef | 2 | ||
continue | |||
for | |||
operator | 2 | ||
var | |||
covariant | 2 | ||
Function | 2 | ||
part | 2 | ||
void | |||
default | |||
get | 2 | ||
rethrow | |||
while | |||
deferred | 2 | ||
hide | 1 | ||
return | |||
with | |||
do | |||
if | |||
set | 2 | ||
yield | 3 |
避免使用这些单词作为标识符。 但是,如有必要,标有上标的关键字可以用作标识符:
带有 1 上标的单词为 上下文关键字, 仅在特定位置具有含义。 他们在任何地方都是有效的标识符。
带有 2 上标的单词为 内置标识符, 为了简化将 JavaScript 代码移植到 Dart 的工作, 这些关键字在大多数地方都是有效的标识符, 但它们不能用作类或类型名称,也不能用作 import 前缀。
带有 3 上标的单词是与 Dart 1.0 发布后添加的异步支持相关的更新,作为限制类保留字。
不能在标记为 async ,async* 或 sync* 的任何函数体中使用 await 或 yield 作为标识符。
关键字表中的剩余单词都是保留字。 不能将保留字用作标识符。
abstract
- 抽象方法
实例方法, getter, 和 setter 方法可以是抽象的, 只定义接口不进行实现,而是留给其他类去实现。 抽象方法只存在于 抽象类 中。
定义一个抽象函数,使用分号 (;) 来代替函数体:
abstract class Doer {
// 定义实例变量和方法 ...
void doSomething(); // 定义一个抽象方法。
}
class EffectiveDoer extends Doer {
void doSomething() {
// 提供方法实现,所以这里的方法就不是抽象方法了...
}
}
调用抽象方法会导致运行时错误。
- 抽象类
使用 abstract 修饰符来定义 抽象类 — 抽象类不能实例化。 抽象类通常用来定义接口,以及部分实现。 如果希望抽象类能够被实例化,那么可以通过定义一个 工厂构造函数 来实现。
抽象类通常具有 抽象方法。 下面是一个声明具有抽象方法的抽象类示例:
// 这个类被定义为抽象类,
// 所以不能被实例化。
abstract class AbstractContainer {
// 定义构造行数,字段,方法...
void updateChildren(); // 抽象方法。
}
dynamic
使用 dynamic 注解替换推断失败的情况。
Dart 允许在许多地方省略类型注解,并尝试推断类型。在某些情况下,如果推断失败了,会默认指定为 dynamic 类型。如果 dynamic 类型与期望相同,那么从技术的角度来讲,这是获取类型最简洁 的方式。
但是,这种方式是最不清晰的。任何一个阅读代码的人,当看到一个类型确实的成员时,是没有办法 知道,编写的人是希望它是 dynamic 类型,还是期望它是其他的什么类型,或者阅读的人就简单的 认为是编写的人忘记了指定类型。
当 dynamic 是你期望的类型,就应该指明它,这样能让你的意图更清晰。
dynamic mergeJson(dynamic original, dynamic changes) => ...
在Dart 2之前,本规则恰恰是相反的:不要 为隐性类型的成员指定 dynamic 注解。基于强类型系统 和类型推断,现在的开发者更希望 Dart 的行为类似于推断的静态类型语言。基于这种心理模型,我们发现 代码区域慢慢地失去了静态类型所具有的安全及性能。
implements
每个类都隐式的定义了一个接口,接口包含了该类所有的实例成员及其实现的接口。 如果要创建一个 A 类,A 要支持 B 类的 API ,但是不需要继承 B 的实现, 那么可以通过 A 实现 B 的接口。
一个类可以通过 implements 关键字来实现一个或者多个接口, 并实现每个接口要求的 API。 例如:
// person 类。 隐式接口里面包含了 greet() 方法声明。
class Person {
// 包含在接口里,但只在当前库中可见。
final _name;
// 不包含在接口里,因为这是一个构造函数。
Person(this._name);
// 包含在接口里。
String greet(String who) => 'Hello, $who. I am $_name.';
}
// person 接口的实现。
class Impostor implements Person {
get _name => '';
String greet(String who) => 'Hi $who. Do you know who I am?';
}
String greetBob(Person person) => person.greet('Bob');
void main() {
print(greetBob(Person('Kathy')));
print(greetBob(Impostor()));
}
下面示例演示一个类如何实现多个接口: Here’s an example of specifying that a class implements multiple interfaces:
class Point implements Comparable, Location {...}
show hide
导入库的一部分
如果你只使用库的一部分功能,则可以选择需要导入的 内容。例如:
// Import only foo.
import 'package:lib1/lib1.dart' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
as
- 类型判定运算符
as, is, 和 is! 运算符用于在运行时处理类型检查:
|Operator| Meaning|
| --- | ---|
|as| Typecast (也被用于指定库前缀)|
|is |True if the object has the specified type|
|is!| False if the object has the specified type|
例如, obj is Object 总是 true。 但是只有 obj 实现了 T 的接口时, obj is T 才是 true。
使用 as 运算符将对象强制转换为特定类型。 通常,可以认为是 is 类型判定后,被判定对象调用函数的一种缩写形式。 请考虑以下代码:
```
if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}
```
使用 as 运算符进行缩写:
```
(emp as Person).firstName = 'Bob';
```
######提示:以上代码并不是等价的。 如果 emp 为 null 或者不是 Person 对象, 那么第一个 is 的示例,后面将不回执行; 第二个 as 的示例会抛出异常。
-
指定库前缀
如果导入两个存在冲突标识符的库, 则可以为这两个库,或者其中一个指定前缀。 例如,如果 library1 和 library2 都有一个 Element 类, 那么可以通过下面的方式处理:import 'package:lib1/lib1.dart'; import 'package:lib2/lib2.dart' as lib2; // 使用 lib1 中的 Element。 Element element1 = Element(); // 使用 lib2 中的 Element。 lib2.Element element2 = lib2.Element();
if else
Dart 支持 if - else 语句,其中 else 是可选的, 比如下面的例子, 另参考 conditional expressions.
if (isRaining()) {
you.bringRainCoat();
} else if (isSnowing()) {
you.wearJacket();
} else {
car.putTopDown();
}
和 JavaScript 不同, Dart 的判断条件必须是布尔值,不能是其他类型。
import
使用库
通过 import 指定一个库命名空间中的内如如何在另一个库中使用。 例如,Dart Web应用程序通常使用 dart:html 库,它们可以像这样导入:
import 'dart:html';
import 参数只需要一个指向库的 URI。 对于内置库,URI 拥有自己特殊的dart: 方案。 对于其他的库,使用系统文件路径或者 package: 方案 。 package: 方案指定由包管理器(如 pub 工具)提供的库。例如:
import 'package:test/test.dart';
提示: URI 代表统一资源标识符。 URL(统一资源定位符)是一种常见的URI。
static
类变量和方法
使用 static 关键字实现类范围的变量和方法。
-
静态变量
静态变量(类变量)对于类级别的状态是非常有用的:class Queue { static const initialCapacity = 16; // ··· } void main() { assert(Queue.initialCapacity == 16); }
静态变量只到它们被使用的时候才会初始化。
提示: 代码准守风格推荐指南 中的命名规则, 使用 lowerCamelCase 来命名常量。
-
静态方法
静态方法(类方法)不能在实例上使用,因此它们不能访问 this 。 例如:import 'dart:math'; class Point { num x, y; Point(this.x, this.y); static num distanceBetween(Point a, Point b) { var dx = a.x - b.x; var dy = a.y - b.y; return sqrt(dx * dx + dy * dy); } } void main() { var a = Point(2, 2); var b = Point(4, 4); var distance = Point.distanceBetween(a, b); assert(2.8 < distance && distance < 2.9); print(distance); }
提示: 对于常见或广泛使用的工具和函数, 应该考虑使用顶级函数而不是静态方法。
静态函数可以当做编译时常量使用。 例如,可以将静态方法作为参数传递给常量构造函数。
asset
如果 assert 语句中的布尔条件为 false , 那么正常的程序执行流程会被中断。 在本章中包含部分 assert 的使用, 下面是一些示例:
// 确认变量值不为空。
assert(text != null);
// 确认变量值小于100。
assert(number < 100);
// 确认 URL 是否是 https 类型。
assert(urlString.startsWith('https'));
提示: assert 语句只在开发环境中有效, 在生产环境是无效的; Flutter 中的 assert 只在 debug 模式 中有效。 开发用的工具,例如 dartdevc 默认是开启 assert 功能。 其他的一些工具, 例如 dart 和 dart2js, 支持通过命令行开启 assert : --enable-asserts。
assert 的第二个参数可以为其添加一个字符串消息。
assert(urlString.startsWith('https'),
'URL ($urlString) should start with "https".');
assert 的第一个参数可以是解析为布尔值的任何表达式。 如果表达式结果为 true , 则断言成功,并继续执行。 如果表达式结果为 false , 则断言失败,并抛出异常 (AssertionError) 。
enum
枚举类型
枚举类型也称为 enumerations 或 enums , 是一种特殊的类,用于表示数量固定的常量值。
使用枚举
使用 enum 关键字定义一个枚举类型:
enum Color { red, green, blue }
枚举中的每个值都有一个 index getter 方法, 该方法返回值所在枚举类型定义中的位置(从 0 开始)。 例如,第一个枚举值的索引是 0 , 第二个枚举值的索引是 1。
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
使用枚举的 values 常量, 获取所有枚举值列表( list )。
List colors = Color.values;
assert(colors[2] == Color.blue);
可以在 switch 语句 中使用枚举, 如果不处理所有枚举值,会收到警告:
var aColor = Color.blue;
switch (aColor) {
case Color.red:
print('Red as roses!');
break;
case Color.green:
print('Green as grass!');
break;
default: // 没有这个,会看到一个警告。
print(aColor); // 'Color.blue'
}
枚举类型具有以下限制:
- 枚举不能被子类化,混合或实现。
- 枚举不能被显式实例化。
有关更多信息,参考 Dart language specification 。
for in
for 循环
进行迭代操作,可以使用标准 for 语句。 例如:
var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
message.write('!');
}
闭包在 Dart 的 for 循环中会捕获循环的 index 索引值, 来避免 JavaScript 中常见的陷阱。 请思考示例代码:
var callbacks = [];
for (var i = 0; i < 2; i++) {
callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());
和期望一样,输出的是 0 和 1。 但是示例中的代码在 JavaScript 中会连续输出两个 2 。
I如果要迭代一个实现了 Iterable 接口的对象, 可以使用 forEach() 方法, 如果不需要使用当前计数值, 使用 forEach() 是非常棒的选择;
candidates.forEach((candidate) => candidate.interview());
实现了 Iterable 的类(比如, List 和 Set)同样也支持使用 for-in 进行迭代操作 iteration :
var collection = [0, 1, 2];
for (var x in collection) {
print(x); // 0 1 2
}
extends super
扩展类(继承)
使用 extends 关键字来创建子类, 使用 super 关键字来引用父类:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
async await
- 异步支持
Dart 库中包含许多返回 Future 或 Stream 对象的函数. 这些函数在设置完耗时任务(例如 I/O 曹组)后, 就立即返回了,不会等待耗任务完成。 使用 async 和 await 关键字实现异步编程。 可以让你像编写同步代码一样实现异步操作。
- 处理 Future
可以通过下面两种方式,获得 Future 执行完成的结果:
使用 async 和 await.
使用 Future API,具体描述,参考 库概览.
使用 async 和 await 关键字的代码是异步的。 虽然看起来有点想同步代码。 例如,下面的代码使用 await 等待异步函数的执行结果。
```
await lookUpVersion();
```
要使用 await , 代码必须在 异步函数(使用 async 标记的函数)中:
```
Future checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
```
提示: 虽然异步函数可能会执行耗时的操作, 但它不会等待这些操作。 相反,异步函数只有在遇到第一个 await 表达式(详情见)时才会执行。 也就是说,它返回一个 Future 对象, 仅在await表达式完成后才恢复执行。
使用 try, catch, 和 finally 来处理代码中使用 await 导致的错误。
```
try {
version = await lookUpVersion();
} catch (e) {
// React to inability to look up the version
}
```
在一个异步函数中可以多次使用 await 。 例如,下面代码中等待了三次函数结果:
```
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
```
在 await 表达式 中, 表达式 的值通常是一个 Future 对象; 如果不是,这是表达式的值会被自动包装成一个 Future 对象。 Future 对象指明返回一个对象的承诺(promise)。 await 表达式 执行的结果为这个返回的对象。 await 表达式会阻塞代码的执行,直到需要的对象返回为止。
如果在使用 await 导致编译时错误, 确认 await 是否在一个异步函数中。 例如,在应用的 main() 函数中使用 await , main() 函数的函数体必须被标记为 async :
Future main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}
-
声明异步函数
函数体被 async 标示符标记的函数,即是一个异步函数。 将 async 关键字添加到函数使其返回Future。 例如,考虑下面的同步函数,它返回一个 String :String lookUpVersion() => '1.0.0';
例如,将来的实现将非常耗时,将其更改为异步函数,返回值是 Future 。
Future
lookUpVersion() async => '1.0.0'; 注意,函数体不需要使用Future API。 如有必要, Dart 会创建 Future 对象。
如果函数没有返回有效值, 需要设置其返回类型为 Future
。
-
处理 Stream
当需要从 Stream 中获取数据值时, 可以通过一下两种方式:使用 async 和 一个 异步循环 (await for)。
使用 Stream API, 更多详情,参考 in the library tour。
提示: 在使用 await for 前,确保代码清晰, 并且确实希望等待所有流的结果。 例如,通常不应该使用 await for 的UI事件侦听器, 因为UI框架会发送无穷无尽的事件流。一下是异步for循环的使用形式:
await for (varOrType identifier in expression) { // Executes each time the stream emits a value. }
上面 表达式 返回的值必须是 Stream 类型。 执行流程如下:
- 等待,直到流发出一个值。
- 执行 for 循环体,将变量设置为该发出的值
- 重复1和2,直到关闭流。
使用 break 或者 return 语句可以停止接收 stream 的数据, 这样就跳出了 for 循环, 并且从 stream 上取消注册。 如果在实现异步 for 循环时遇到编译时错误, 请检查确保 await for 处于异步函数中。 例如,要在应用程序的 main() 函数中使用异步 fo r循环, main() 函数体必须标记为 async` :
Future main() async { // ... await for (var request in requestServer) { handleRequest(request); } // ... }
有关异步编程的更多信息,请参考 dart:async 部分。 同时也可参考文章 Dart Language Asynchrony Support: Phase 1 和 Dart Language Asynchrony Support: Phase 2, 以及 Dart language specification 。
switch case
在 Dart 中 switch 语句使用 == 比较整数,字符串,或者编译时常量。 比较的对象必须都是同一个类的实例(并且不可以是子类), 类必须没有对 == 重写。 枚举类型 可以用于 switch 语句。
提示: 在 Dart 中 Switch 语句仅适用于有限的情况下, 例如在 interpreter 或 scanner 中。
在 case 语句中,每个非空的 case 语句结尾需要跟一个 break 语句。 除 break 以外,还有可以使用 continue, throw,者 return。
当没有 case 语句匹配时,执行 default 代码:
var command = 'OPEN';
switch (command) {
case 'CLOSED':
executeClosed();
break;
case 'PENDING':
executePending();
break;
case 'APPROVED':
executeApproved();
break;
case 'DENIED':
executeDenied();
break;
case 'OPEN':
executeOpen();
break;
default:
executeUnknown();
}
下面的 case 程序示例中缺省了 break 语句,导致错误:
var command = 'OPEN';
switch (command) {
case 'OPEN':
executeOpen();
// ERROR: 丢失 break
case 'CLOSED':
executeClosed();
break;
}
但是, Dart 支持空 case 语句, 允许程序以 fall-through 的形式执行。
var command = 'CLOSED';
switch (command) {
case 'CLOSED': // Empty case falls through.
case 'NOW_CLOSED':
// Runs for both CLOSED and NOW_CLOSED.
executeNowClosed();
break;
}
在非空 case 中实现 fall-through 形式, 可以使用 continue 语句结合 lable 的方式实现:
var command = 'CLOSED';
switch (command) {
case 'CLOSED':
executeClosed();
continue nowClosed;
// Continues executing at the nowClosed label.
nowClosed:
case 'NOW_CLOSED':
// Runs for both CLOSED and NOW_CLOSED.
executeNowClosed();
break;
}
case 语句可以拥有局部变量, 这些局部变量只能在这个语句的作用域中可见。
sync
生成器
当您需要延迟生成( lazily produce )一系列值时, 可以考虑使用生成器函数。 Dart 内置支持两种生成器函数:
- Synchronous 生成器: 返回一个 Iterable 对象。
- Asynchronous 生成器: 返回一个 Stream 对象。
通过在函数体标记 sync*, 可以实现一个同步生成器函数。 使用 yield 语句来传递值:
Iterable
int k = 0;
while (k < n) yield k++;
}
通过在函数体标记 async*, 可以实现一个异步生成器函数。 使用 yield 语句来传递值:
Stream asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}
如果生成器是递归的,可以使用 yield* 来提高其性能:
Iterable naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
有关生成器的更多信息,请参考文章 Dart Language Asynchrony Support: Phase 2 。
break continue
使用 break 停止程序循环:
while (true) {
if (shutDownRequested()) break;
processIncomingRequests();
}
使用 continue 跳转到下一次迭代:
for (int i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
if (candidate.yearsExperience < 5) {
continue;
}
candidate.interview();
}
如果对象实现了 Iterable 接口 (例如,list 或者 set)。 那么上面示例完全可以用另一种方式来实现:
candidates
.where((c) => c.yearsExperience >= 5)
.forEach((c) => c.interview());
Mixin
为类添加功能: Mixin
Mixin 是复用类代码的一种途径, 复用的类可以在不同层级,之间可以不存在继承关系。
通过 with 后面跟一个或多个混入的名称,来 使用 Mixin , 下面的示例演示了两个使用 Mixin 的类:
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
通过创建一个继承自 Object 且没有构造函数的类,来 实现 一个 Mixin 。 如果 Mixin 不希望作为常规类被使用,使用关键字 mixin 替换 class 。 例如:
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}
指定只有某些类型可以使用的 Mixin - 比如, Mixin 可以调用 Mixin 自身没有定义的方法 - 使用 on 来指定可以使用 Mixin 的父类类型:
mixin MusicalPerformer on Musician {
// ···
}
版本提示: mixin 关键字在 Dart 2.1 中被引用支持。 早期版本中的代码通常使用 abstract class 代替。 更多有关 Mixin 在 2.1 中的变更信息,请参见 Dart SDK changelog 和 2.1 mixin specification 。
提示: 对 Mixin 的一些限制正在被移除。 关于更多详情,参考 proposed mixin specification.
有关 Dart 中 Mixin 的理论演变,参考 A Brief History of Mixins in Dart.
Final Const
Final 和 Const
使用过程中从来不会被修改的变量, 可以使用 final 或 const, 而不是 var 或者其他类型, Final 变量的值只能被设置一次; Const 变量在编译时就已经固定 (Const 变量 是隐式 Final 的类型.) 最高级 final 变量或类变量在第一次使用时被初始化。
提示: 实例变量可以是 final 类型但不能是 const 类型。 必须在构造函数体执行之前初始化 final 实例变量 —— 在变量声明中,参数构造函数中或构造函数的初始化列表中进行初始化。
创建和设置一个 Final 变量:
final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';
final 不能被修改:
name = 'Alice'; // Error: 一个 final 变量只能被设置一次。
如果需要在编译时就固定变量的值,可以使用 const 类型变量。 如果 Const 变量是类级别的,需要标记为 static const。 在这些地方可以使用在编译时就已经固定不变的值,字面量的数字和字符串, 固定的变量,或者是用于计算的固定数字:
const bar = 1000000; // 压力单位 (dynes/cm2)
const double atm = 1.01325 * bar; // 标准气压
Const 关键字不仅可以用于声明常量变量。 还可以用来创建常量值,以及声明创建常量值的构造函数。 任何变量都可以拥有常量值。
var foo = const [];
final bar = const [];
const baz = []; // Equivalent to `const []`
声明 const 的初始化表达式中 const 可以被省略。 比如上面的 baz。 有关更多信息,参考 DON’T use const redundantly。
非 Final , 非 const 的变量是可以被修改的,即使这些变量 曾经引用过 const 值。
foo = [1, 2, 3]; // 曾经引用过 const [] 常量值。
Const 变量的值不可以修改:
baz = [42]; // Error: 常量变量不能赋值修改。
更多关于使用 const 创建常量值,参考 Lists, Maps, 和 Classes。
throw catch finnaly on rethrow
异常
Dart 代码可以抛出和捕获异常。 异常表示一些未知的错误情况。 如果异常没有被捕获, 则异常会抛出, 导致抛出异常的代码终止执行。
和 Java 有所不同, Dart 中的所有异常是非检查异常。 方法不会声明它们抛出的异常, 也不要求捕获任何异常。
Dart 提供了 Exception 和 Error 类型, 以及一些子类型。 当然也可以定义自己的异常类型。 但是,此外 Dart 程序可以抛出任何非 null 对象, 不仅限 Exception 和 Error 对象。
-
throw
下面是关于抛出或者 引发 异常的示例:throw FormatException('Expected at least 1 section');
也可以抛出任意的对象:throw 'Out of llamas!';
提示: 高质量的生产环境代码通常会实现 Error 或 Exception 类型的异常抛出。因为抛出异常是一个表达式, 所以可以在 => 语句中使用,也可以在其他使用表达式的地方抛出异常:
void distanceTo(Point other) => throw UnimplementedError();
catch
捕获异常可以避免异常继续传递(除非重新抛出( rethrow )异常)。 可以通过捕获异常的机会来处理该异常:try { breedMoreLlamas(); } on OutOfLlamasException { buyMoreLlamas(); }
通过指定多个 catch 语句,可以处理可能抛出多种类型异常的代码。 与抛出异常类型匹配的第一个 catch 语句处理异常。 如果 catch 语句未指定类型, 则该语句可以处理任何类型的抛出对象:
try { breedMoreLlamas(); } on OutOfLlamasException { // 一个特殊的异常 buyMoreLlamas(); } on Exception catch (e) { // 其他任何异常 print('Unknown exception: $e'); } catch (e) { // 没有指定的类型,处理所有异常 print('Something really unknown: $e'); }
-
on catch
如上述代码所示,捕获语句中可以同时使用 on 和 catch ,也可以单独分开使用。 使用 on 来指定异常类型, 使用 catch 来 捕获异常对象。
catch() 函数可以指定1到2个参数, 第一个参数为抛出的异常对象, 第二个为堆栈信息 ( 一个 StackTrace 对象 )。
try { // ··· } on Exception catch (e) { print('Exception details:\n $e'); } catch (e, s) { print('Exception details:\n $e'); print('Stack trace:\n $s'); }
-
rethrow
如果仅需要部分处理异常, 那么可以使用关键字 rethrow 将异常重新抛出。void misbehave() { try { dynamic foo = true; print(foo++); // Runtime error } catch (e) { print('misbehave() partially handled ${e.runtimeType}.'); rethrow; // Allow callers to see the exception. } } void main() { try { misbehave(); } catch (e) { print('main() finished handling ${e.runtimeType}.'); } }
-
finally
不管是否抛出异常, finally 中的代码都会被执行。 如果 catch 没有匹配到异常, 异常会在 finally 执行完成后,再次被抛出:try { breedMoreLlamas(); } finally { // Always clean up, even if an exception is thrown. cleanLlamaStalls(); }
任何匹配的 catch 执行完成后,再执行 finally :
try { breedMoreLlamas(); } catch (e) { print('Error: $e'); // Handle the exception first. } finally { cleanLlamaStalls(); // Then clean up. }
更多详情,请参考 Exceptions 章节。
Typedefs
在 Dart 中,函数也是对象,就想字符和数字对象一样。 使用 typedef ,或者 function-type alias 为函数起一个别名, 别名可以用来声明字段及返回值类型。 当函数类型分配给变量时,typedef会保留类型信息。
请考虑以下代码,代码中未使用 typedef :
class SortedCollection {
Function compare;
SortedCollection(int f(Object a, Object b)) {
compare = f;
}
}
// Initial, broken implementation. // broken ?
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
// 虽然知道 compare 是函数,
// 但是函数是什么类型 ?
assert(coll.compare is Function);
}
当把 f 赋值给 compare 的时候,类型信息丢失了。 f 的类型是 (Object, Object) → int (这里 → 代表返回值类型), 但是 compare 得到的类型是 Function 。如果我们使用显式的名字并保留类型信息, 这样开发者和工具都可以使用这些信息:
typedef Compare = int Function(Object a, Object b);
class SortedCollection {
Compare compare;
SortedCollection(this.compare);
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
assert(coll.compare is Function);
assert(coll.compare is Compare);
}
提示: 目前,typedefs 只能使用在函数类型上, 我们希望将来这种情况有所改变。
由于 typedefs 只是别名, 他们还提供了一种方式来判断任意函数的类型。例如:
typedef Compare = int Function(T a, T b);
int sort(int a, int b) => a - b;
void main() {
assert(sort is Compare); // True!
}
Function
Dart 是一门真正面向对象的语言, 甚至其中的函数也是对象,并且有它的类型 Function 。 这也意味着函数可以被赋值给变量或者作为参数传递给其他函数。 也可以把 Dart 类的实例当做方法来调用。 有关更多信息,参考 Callable classes.
Deferred
延迟加载库
Deferred loading (也称之为 lazy loading) 可以让应用在需要的时候再加载库。 下面是一些使用延迟加载库的场景:
减少 APP 的启动时间。
执行 A/B 测试,例如 尝试各种算法的 不同实现。
加载很少使用的功能,例如可选的屏幕和对话框。
要延迟加载一个库,需要先使用 deferred as 来导入:
import 'package:greetings/hello.dart' deferred as hello;
当需要使用的时候,使用库标识符调用 loadLibrary() 函数来加载库:
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
在前面的代码,使用 await 关键字暂停代码执行一直到库加载完成。 关于 async 和 await 的更多信息请参考 异步支持。
在一个库上你可以多次调用 loadLibrary() 函数。但是该库只是载入一次。
使用延迟加载库的时候,请注意一下问题:
延迟加载库的常量在导入的时候是不可用的。 只有当库加载完毕的时候,库中常量才可以使用。
在导入文件的时候无法使用延迟库中的类型。 如果你需要使用类型,则考虑把接口类型移动到另外一个库中, 让两个库都分别导入这个接口库。
Dart 隐含的把 loadLibrary() 函数导入到使用 deferred as 的命名空间 中。 loadLibrary() 方法返回一个 Future。
变量类型
Dart 语言支持以下内建类型:
- Number
- String
- Boolean
- List (也被称为 Array)
- Map
- Set
- Rune (用于在字符串中表示 Unicode 字符)
- Symbol
这些类型都可以被初始化为字面量。 例如, 'this is a string' 是一个字符串的字面量, true 是一个布尔的字面量。
因为在 Dart 所有的变量终究是一个对象(一个类的实例), 所以变量可以使用 构造函数 进行初始化。 一些内建类型拥有自己的构造函数。 例如, 通过 Map() 来构造一个 map 变量。
-
Number
Dart 语言的 Number 有两种类型:
-
int
整数值不大于64位, 具体取决于平台。 在 Dart VM 上, 值的范围从 -263 到 263 - 1. Dart 被编译为 JavaScript 时,使用 JavaScript numbers, 值的范围从 -253 到 253 - 1.
-
double
64位(双精度)浮点数,依据 IEEE 754 标准。
int 和 double 都是 num. 的亚类型。 num 类型包括基本运算 +, -, /, 和 *, 以及 abs(), ceil(), 和 floor(), 等函数方法。 (按位运算符,例如»,定义在 int 类中。) 如果 num 及其亚类型找不到你想要的方法, 尝试查找使用 dart:math 库。
整数类型不包含小数点。 下面是定义整数类型字面量的例子:
var x = 1; var hex = 0xDEADBEEF;
如果一个数字包含小数点,那么就是小数类型。 下面是定义小数类型字面量的例子:
var y = 1.1; var exponents = 1.42e5;
从 Dart 2.1 开始,必要的时候 int 字面量会自动转换成 double 类型。
double z = 1; // 相当于 double z = 1.0.
版本提示: 在 2.1 之前,在 double 上下文中使用 int 字面量是错误的。
以下是将字符串转换为数字的方法,反之亦然:
// String -> int var one = int.parse('1'); assert(one == 1); // String -> double var onePointOne = double.parse('1.1'); assert(onePointOne == 1.1); // int -> String String oneAsString = 1.toString(); assert(oneAsString == '1'); // double -> String String piAsString = 3.14159.toStringAsFixed(2); assert(piAsString == '3.14');
int 特有的传统按位运算操作,移位(<<, >>),按位与(&)以及 按位或(|)。 例如:
assert((3 << 1) == 6); // 0011 << 1 == 0110 assert((3 >> 1) == 1); // 0011 >> 1 == 0001 assert((3 | 4) == 7); // 0011 | 0100 == 0111
数字类型字面量是编译时常量。 在算术表达式中,只要参与计算的因子是编译时常量, 那么算术表达式的结果也是编译时常量。
const msPerSecond = 1000; const secondsUntilRetry = 5; const msUntilRetry = secondsUntilRetry * msPerSecond;
-
-
String
Dart 字符串是一组 UTF-16 单元序列。 字符串通过单引号或者双引号创建。
var s1 = 'Single quotes work well for string literals.'; var s2 = "Double quotes work just as well."; var s3 = 'It\'s easy to escape the string delimiter.'; var s4 = "It's even easier to use the other delimiter.";
字符串可以通过 ${expression} 的方式内嵌表达式。 如果表达式是一个标识符,则 {} 可以省略。 在 Dart 中通过调用就对象的 toString() 方法来得到对象相应的字符串。
var s = 'string interpolation'; assert('Dart has $s, which is very handy.' == 'Dart has string interpolation, ' + 'which is very handy.'); assert('That deserves all caps. ' + '${s.toUpperCase()} is very handy!' == 'That deserves all caps. ' + 'STRING INTERPOLATION is very handy!');
提示: == 运算符用来测试两个对象是否相等。 在字符串中,如果两个字符串包含了相同的编码序列,那么这两个字符串相等。 units.
可以使用 + 运算符来把多个字符串连接为一个,也可以把多个字面量字符串写在一起来实现字符串连接:
var s1 = 'String ' 'concatenation' " works even over line breaks."; assert(s1 == 'String concatenation works even over ' 'line breaks.'); var s2 = 'The + operator ' + 'works, as well.'; assert(s2 == 'The + operator works, as well.');
使用连续三个单引号或者三个双引号实现多行字符串对象的创建:
var s1 = ''' You can create multi-line strings like this one. '''; var s2 = """This is also a multi-line string.""";
使用 r 前缀,可以创建 “原始 raw” 字符串:
var s = r"In a raw string, even \n isn't special.";
参考 Runes 来了解如何在字符串中表达 Unicode 字符。
一个编译时常量的字面量字符串中,如果存在插值表达式,表达式内容也是编译时常量, 那么该字符串依旧是编译时常量。 插入的常量值类型可以是 null,数值,字符串或布尔值。
// const 类型数据 const aConstNum = 0; const aConstBool = true; const aConstString = 'a constant string'; // 非 const 类型数据 var aNum = 0; var aBool = true; var aString = 'a string'; const aConstList = [1, 2, 3]; const validConstString = '$aConstNum $aConstBool $aConstString'; //const 类型数据 // const invalidConstString = '$aNum $aBool $aString $aConstList'; //非 const 类型数据
更多关于 string 的使用, 参考 字符串和正则表达式.
-
Boolean
Dart 使用 bool 类型表示布尔值。 Dart 只有字面量 true and false 是布尔类型, 这两个对象都是编译时常量。Dart 的类型安全意味着不能使用 if (nonbooleanValue) 或者 assert (nonbooleanValue)。 而是应该像下面这样,明确的进行值检查:
// 检查空字符串。 var fullName = ''; assert(fullName.isEmpty); // 检查 0 值。 var hitPoints = 0; assert(hitPoints <= 0); // 检查 null 值。 var unicorn; assert(unicorn == null); // 检查 NaN 。 var iMeantToDoThis = 0 / 0; assert(iMeantToDoThis.isNaN);
-
-
List
几乎每种编程语言中最常见的集合可能是 array 或有序的对象集合。 在 Dart 中的 Array 就是 List 对象, 通常称之为 List 。Dart 中的 List 字面量非常像 JavaScript 中的 array 字面量。 下面是一个 Dart List 的示例:
var list = [1, 2, 3];
提示: Dart 推断 list 的类型为 List
。 如果尝试将非整数对象添加到此 List 中, 则分析器或运行时会引发错误。 有关更多信息,请阅读 类型推断。 Lists 的下标索引从 0 开始,第一个元素的索引是 0。 list.length - 1 是最后一个元素的索引。 访问 List 的长度和元素与 JavaScript 中的用法一样:
var list = [1, 2, 3]; assert(list.length == 3); assert(list[1] == 2); list[1] = 1; assert(list[1] == 1);
在 List 字面量之前添加 const 关键字,可以定义 List 类型的编译时常量:
var constantList = const [1, 2, 3]; // constantList[1] = 1; // 取消注释会引起错误。
List 类型包含了很多 List 的操作函数。 更多信息参考 泛型 和 集合.
-
Set
在 Dart 中 Set 是一个元素唯一且无需的集合。 Dart 为 Set 提供了 Set 字面量和 Set 类型。版本提示: 虽然 Set 类型 一直是 Dart 的核心部分, 但在 Dart2.2 中才引入了 Set 字面量 。
下面是通过字面量创建 Set 的一个简单示例:
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
Note: Dart 推断 halogens 类型为 Set
。如果尝试为它添加一个 错误类型的值,分析器或执行时会抛出错误。更多内容,参阅 类型推断。 要创建一个空集,使用前面带有类型参数的 {} ,或者将 {} 赋值给 Set 类型的变量:
var names =
{}; // Set names = {}; // 这样也是可以的。 // var names = {}; // 这样会创建一个 Map ,而不是 Set 。 是 Set 还是 Map ? Map 字面量语法同 Set 字面量语法非常相似。 因为先有的 Map 字母量语法,所以 {} 默认是 Map 类型。 如果忘记在 {} 上注释类型或赋值到一个未声明类型的变量上, 那么 Dart 会创建一个类型为 Map
的对象。 使用 add() 或 addAll() 为已有的 Set 添加元素:
var elements =
{}; elements.add('fluorine'); elements.addAll(halogens); 使用 .length 来获取 Set 中元素的个数:
var elements =
{}; elements.add('fluorine'); elements.addAll(halogens); assert(elements.length == 5); 在 Set 字面量前增加 const ,来创建一个编译时 Set 常量:
final constantSet = const { 'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine', }; // constantSet.add('helium'); // Uncommenting this causes an error.
更多关于 Set 的内容,参阅 Generic 及 Set。
-
Map
通常来说, Map 是用来关联 keys 和 values 的对象。 keys 和 values 可以是任何类型的对象。在一个 Map 对象中一个 key 只能出现一次。 但是 value 可以出现多次。 Dart 中 Map 通过 Map 字面量 和 Map 类型来实现。下面是使用 Map 字面量的两个简单例子:
var gifts = { // Key: Value 'first': 'partridge', 'second': 'turtledoves', 'fifth': 'golden rings' }; var nobleGases = { 2: 'helium', 10: 'neon', 18: 'argon', };
提示: Dart 会将 gifts 的类型推断为 Map
, nobleGases 的类型推断为 Map 。 如果尝试在上面的 map 中添加错误类型,那么分析器或者运行时会引发错误。 有关更多信息,请阅读类型推断。。 以上 Map 对象也可以使用 Map 构造函数创建:
var gifts = Map(); gifts['first'] = 'partridge'; gifts['second'] = 'turtledoves'; gifts['fifth'] = 'golden rings'; var nobleGases = Map(); nobleGases[2] = 'helium'; nobleGases[10] = 'neon'; nobleGases[18] = 'argon';
提示: 这里为什么只有 Map() ,而不是使用 new Map()。 因为在 Dart 2 中,new 关键字是可选的。 有关更多信息,参考 构造函数的使用。
类似 JavaScript ,添加 key-value 对到已有的 Map 中:
var gifts = {'first': 'partridge'}; gifts['fourth'] = 'calling birds'; // Add a key-value pair
类似 JavaScript ,从一个 Map 中获取一个 value:
var gifts = {'first': 'partridge'}; assert(gifts['first'] == 'partridge');
如果 Map 中不包含所要查找的 key,那么 Map 返回 null:
var gifts = {'first': 'partridge'}; assert(gifts['fifth'] == null);
使用 .length 函数获取当前 Map 中的 key-value 对数量:
var gifts = {'first': 'partridge'}; gifts['fourth'] = 'calling birds'; assert(gifts.length == 2);
创建 Map 类型运行时常量,要在 Map 字面量前加上关键字 const。
final constantMap = const { 2: 'helium', 10: 'neon', 18: 'argon', }; // constantMap[2] = 'Helium'; // 取消注释会引起错误。
更名多关于 Map 的内容,参考 Generics and Maps.
-
Rune
在 Dart 中, Rune 用来表示字符串中的 UTF-32 编码字符。Unicode 定义了一个全球的书写系统编码, 系统中使用的所有字母,数字和符号都对应唯一的数值编码。 由于 Dart 字符串是一系列 UTF-16 编码单元, 因此要在字符串中表示32位 Unicode 值需要特殊语法支持。
表示 Unicode 编码的常用方法是, \uXXXX, 这里 XXXX 是一个4位的16进制数。 例如,心形符号 (♥) 是 \u2665。 对于特殊的非 4 个数值的情况, 把编码值放到大括号中即可。 例如,emoji 的笑脸 (�) 是 \u{1f600}。
String 类有一些属性可以获得 rune 数据。 属性 codeUnitAt 和 codeUnit 返回16位编码数据。 属性 runes 获取字符串中的 Rune 。
######提示: 谨慎使用 list 方式操作 Rune 。 这种方法很容易引发崩溃, 具体原因取决于特定的语言,字符集和操作。 有关更多信息,参考 How do I reverse a String in Dart? on Stack Overflow.
-
Symbol
一个 Symbol 对象表示 Dart 程序中声明的运算符或者标识符。 你也许永远都不需要使用 Symbol ,但要按名称引用标识符的 API 时, Symbol 就非常有用了。 因为代码压缩后会改变标识符的名称,但不会改变标识符的符号。 通过字面量 Symbol ,也就是标识符前面添加一个 # 号,来获取标识符的 Symbol 。
#radix #bar
Symbol 字面量是编译时常量。
控制流程语句
你可以通过下面任意一种方式来控制 Dart 程序流程:
if and else
for loops
while and do-while loops
break and continue
switch and case
assert
使用 try-catch 和 throw 也可以改变程序流程, 详见 Exceptions。
文章节选总结自来源