Dart 语言导览

来自Dart 语言
跳转至: 导航搜索


本文译自 Dart 语言官网 A Tour of the Dart Language 一文,原作者为 Kathy & Seth。原文授权许可为 CC-BY 3.0

欢迎参观 Dart 语言! 在这里您将看到 Dart 各项主要功能的用法, 包括变量、运算符、类、库等。 本文假定您已经了解如何用其他语言编程。

提示: 如需尝试各项功能,请使用 Dart Editor 创建服务器应用项目。

如需详细了解某项语言功能,请参考 Dart 语言规范

目录

基本的 Dart 程序[编辑]

以下代码 用到了 Dart 最基本的一些功能。

main() {
  // 变量及取值
  int number   = 42;
  String text  = "number 的数值为";

  // 控制台输出
  print("$text $number.");
}

下面讲解该程序中大部分 Dart 应用都能用到的部分:

main()[编辑]

最特别的,必需, 应用开始执行时的顶级函数。

//[编辑]

表示该行剩下的部分为#注释。 也可以这样写:

/* 可以超过一行的注释内容 */

int, String[编辑]

通过静态类型说明声明变量。

"..." (或 '...')[编辑]

字符串。

$变量名[编辑]

字符串内插入变量的字符串值或 toString() 的返回值。

print()[编辑]

显示输出的简便途径。

样式[编辑]

我们的代码遵循 Dart 样式指南中的约定。 譬如说,约定缩进长度为两个空格。

运行时模式[编辑]

Dart 程序可以以生产模式或强制模式运行。

生产模式(Production mode)是 Dart 程序默认的运行时模式, 为速度而优化。 生产模式下,可选的静态类型声明会被忽略。

强制模式(Checked mode)是适合开发者使用的模式, 可帮助您揪出运行过程中的一些类型错误。 例如,如果将非字符串数据赋值给声明为 String 类型的变量, 则会报告异常。

我们建议您在强制模式下开发与调试, 而在生产模式下部署。

变量[编辑]

下面是创建变量并给其赋值的例子:

var name = 'Bob';

变量及引用。名为 name 的变量包含对值为“Bob”的 String 对象的引用。

默认值[编辑]

未初始化的变量均有一初始值 null。包括数字类型,属性均为对象。

num lineCount;
lineCount == null; // 表达式结果为 true


可选类型[编辑]

您可以选择在变量声明语句中加上静态类型:

String name = 'Bob';

添加类型名称有助于清晰地表达您的意图。 编译器和编辑器等工具可以利用这些类型声明 提供 bug 预警与代码补全功能。

final[编辑]

如果您不打算改变某个变量的值,可以使用 final 代替var,或者 在类型名称前加上 final 关键字。final 变量一旦赋值,便无法再更改。

final String name = 'Bob';
name = 'Alice'; // 编译器报错 ERROR (VM 或 JavaScript)

小结[编辑]

Dart 中变量类型可选,尽管我们通常推荐使用类型说明。 变量可以标记为 final 以锁定其值。 未初始化的变量均有初始值 null

内置类型[编辑]

Dart 语言对以下类型提供特别支持:

您可以使用常量初始化任一特殊类型对象。 例如,'这是一个字符串' 是字符串常量, 而 true 是布尔型常量。

由于 Dart 中的所有变量均为对象—— 某的一个实例—— 您通常可以用构造函数创建变量。 对内置类型的变量也适用。 例如,您可以使用 Map() 构造函数创建映射, 代码为 new Map()

字符串[编辑]

Dart 中的字符串是一组 Unicode 字符代码的序列。 您可以使用单引号或双引号创建字符串:

var s1 = '单引号可以很好地作为字符串常量。';
var s2 = "双引号的效果也是一样的。";
var s3 = '字符串分界符 \' 的转义很方便。';
var s4 = "使用双引号作为分界符更方便,不用对 ' 进行转义。";

您可以使用 ${表达式} 在字符串中嵌入表达式数值。 如果表达式是变量名称,可以省去 {}

var s = '内插字符串(string interpolation)';

print('Dart 的 $s 很方便使用。');
print('如果需要全部大写,${s.toUpperCase()} 非常方便!');

您可以将相邻的字符串常量连接为字符串:

var s = '字符串''连接'
        "甚至可以跨行进行";
print(s); // 字符串连接甚至可以跨行进行

另一种创建多行字符串的方法是 使用三个引号,单双皆可。

var s1 = '''
您可以像这样
创建多行字符串。
''';

var s2 = """我也是
多行字符串啊喂。""";

您可以在字符串开头加上 @ 创建“纯”字符串(译注:即赋值时不转义)。

var s = @'在纯字符串中,连 \n 都会被忽略。';

与其他所有对象一样,您可以使用 == 运算符检查两个字符串是否等价(字符全部相同):

var name = 'NAME';
var greeting = "Hello, $name!";
var greetingTemplate = 'Hello, NAME!';

print(greeting == greetingTemplate); // 输出 true;字符完全相同

字符串方法[编辑]

所有字符串常量的类型均为 StringString 有一些实用方法,包括比较等功能。

var fullName = 'Cuthbert Musgrave Girdlestone, III';

fullName.startsWith('Cuthbert');            // true;以“Cuthbert”开头
fullName.endsWith('III');                   // true;以“III”结尾
fullName.contains(new RegExp('Musgrave'));  // true;匹配正则表达式“/Musgrave/”

字符串对象无法改变(immutable), 即只能创建,但不能修改。 如果您仔细阅读 String API 文档 会发现,所有方法都不会 真正地改变 String 类型的状态。 例如,replaceAll() 方法只返回新的 String 对象, 而没有改变原来的 String 对象。

var greetingTemplate = 'Hello, NAME!';
var greeting = greetingTemplate.replaceAll(new RegExp("NAME"), 'Bob');
print(greeting == greetingTemplate); // 输出 false;greetingTemplate 没有变化

StringBuffer 方法[编辑]

要通过程序生成字符串,您可以使用 StringBufferStringBuffer 在调用 toString() 之前不会生成新的 String 对象。

var sb = new StringBuffer();

sb.add("使用 StringBuffer");
sb.addAll([" 可", "高效", "创建", "字符串,"]);
sb.add("数量越多 ").add("效果越明显。");

var fullString = sb.toString();

print(fullString); // 使用 StringBuffer 可高效创建字符串,
                   // 数量越多 效果越明显。

sb.clear();        // 清空对象!


注: 目前,编译为 JavaScript 代码后 StringBuffer 执行很慢。 详情请见 bug #1216

数字[编辑]

Dart 中的数字分为两种:

int[编辑]

任意大小的整数

double[编辑]

十进制 64 位双精度浮点数,遵循 IEEE 754 规范所约定的格式

intdouble 都是 num 的子接口。 num 接口定义了基本的运算符,如 +-/*, 以及位运算符,如 >>

num 接口中还有 abs()ceil()floor() 等方法。 如果 num 及其子接口中没有您需要的功能, Math 类中可能会有所提供。

整数是没有小数点的数字。 下面是一些定义整数常量的例子:

var x = 1;
var hex = 0xDEADBEEF;
var bigInt = 3465346583465243765923847659234765928347659567398475647495873984572947593470294387093493456870849216348723763945678236420938467345762304958724596873045876234572037862934765294365243652548673456705673465273465246734506873456729457623845623456234650457693475603768922346728346256;

如果数字含有小数点, 则为双精度浮点数。 以下是一些定义双精度浮点数常量的例子:

var y = 1.1;
var exponents = 1.42e5;

下例展示如何在字符串和数字类型之间进行相互转换:

// string -> int
var one = Math.parseInt("1");                   // 1

// string -> double
var onePointOne = Math.parseDouble("1.1");      // 1.1

// int -> string
String oneAsString = 1.toString();              // "1"

// double -> string
String piAsString = 3.14159.toStringAsFixed(2); // "3.14"

布尔[编辑]

Dart 有正式的布尔类型,名为 bool。 只有两种对象的类型为 bool: 布尔常量,truefalse

当 Dart 需要一个布尔值时, 如果该值不是 true, 那就肯定是 false。 不像在 JavaScript 中 1 或非 null 对象不作为 true 对待。

例如,对于下述的代码:

var name = 'Bob';
if (name) {
  print("你有名字耶!"); // 在 JavaScript 中可以输出,但 Dart 中不能
}

在 JavaScript 环境,该段代码会输出“你有名字耶!”,因为 name 是非 null 对象。 但在 Dart 中,这段代码不会输出任何内容, 因为 name 被作为 false 对待 (表达式 name != true 成立)。

下面是另一段 JavaScript 与 Dart 表现截然不同的示例代码:

if (1) {
  print("JavaScript 会输出本行,因为它认为 1 等于 true。");
} else {
  print("Dart 会输出本行,因为它认为 1 *不* 等于 true。");
}


注: Dart 实验室目前对上述两例的表现有误, 它会输出 JavaScript 的执行结果。 (详情请见 bug #1190。) 还需要注意的是, 与本文其余示例代码不同, 上述两例不能在强制模式下执行。

Dart 对布尔值的处理方式 是为了避免很多值被作为 true 对待 所引发的诸多奇怪现象。 对您而言, 应避免使用 if (非布尔值) 这样的代码, 而应该对值进行显式检查。 例如:

// 检查空字符串
var fullName = '';
if (fullName.isEmpty()) {
  print("请输入姓名");
}

// 检查零分
var hitPoints = 0;
if (hitPoints == 0) {
  print("啊呃!你好像挂掉了哎。");
}

// 检查 null
var unicorn = null;
if (unicorn == null) {
  print("许愿不够给力。再加点油!");
}

// 检查 NaN
var iMeantToDoThis = 0/0;
if (iMeantToDoThis.isNaN()) {
  print("0/0 不是数字。");
}

列表(即数组)[编辑]

几乎所有编程语言中都有的最常见的集合或许就是 数组 了——或者称之为有顺序的对象集合。 在 Dart 中,数组属于 List 类型的对象, 因此我们通常称之为 列表。 当您将 Dart 编译为 JavaScript 脚本时, Dart 列表会被编译为 JavaScript 数组。

Dart 列表常量与 JavaScript 数组常量一样。 这是一个简单的 Dart 列表:

var list = [1,2,3];

您可以获取列表的长度 以及引用列表元素, 语法与 JavaScript 相同:

var list = [1,2,3];
print(list.length); // 元素数目:3
print(list[1]);     // 第二项:2

您可以使用 add() 方法将元素添加到列表:

var list = [1,2,3];
list.add(4);

要将元素从列表移除 (减短列表长度), 请使用 removeRange() 方法:

var list = [1,2,3,4];
list.removeRange(2, 1); // 移除第三个元素

遍历[编辑]

如果需要处理列表的每一个元素, 您可以使用 forfor...inforEach()。 如果需要当前遍历到的索引编号,请使用 for 语句:

var list = [1,2,3];
for (var x = 0; x < list.length; x++) {
  print('$x: ${list[x]}');
}

如果不需要索引编号, 可以使用 for...in 语句:

var list = [1,2,3];
for (final x in list) {
  print(x);
}

如果只是想对列表各元素应用某个函数, 请使用 forEach() 方法:

var list = [1,2,3];
void printElement(element) => print(element);
list.forEach(printElement);

或者更简洁地:

var list = [1,2,3];
list.forEach((element) => print(element));

列表与集合方法[编辑]

forEach() 方法 是 List 接口及其超接口 Collection 所定义的诸多实用方法之一。 其他还有如: filter() 方法 可返回新的集合,只包含满足特定条件的元素。 every()some() 方法 分别可用来检查集合是否匹配 所有条件或至少一个条件。 sort() 方法 允许按任意需要的条件对列表进行排序。

关于列表的更多信息请见 #泛型

映射[编辑]

总的来说,映射是包含键值对应关系的对象。 Dart 对映射的支持是通过映射常量以及 Map 接口实现的。

下面是简单 Dart 映射的例子:

var gifts = {     // 映射常量
// 键       值
  "第一天": "山鹑",
  "第二天": "斑鸠",
  "第五天": "环颈雉"};

在映射常量中,每个 必须为字符串。 如果您使用 Map 的构造函数, 还有其他选择: 键可以是字符串、数字或其他任意实现了 Hashable 接口的对象。

var map = new Map();   // 使用 Map 构造函数
map[1] = "山鹑";       // 键为 1;值为“山鹑”
map[2] = "斑鸠";       // 键为 2;值为“斑鸠”
map[5] = "环颈雉";     // 键为 5;值为“环颈雉”

映射中的 可以是任意对象或 null

将新的键值对添加到现有映射中的方法 与 JavaScript 中无异:

var gifts = { "第一天": "山鹑" };
gifts["第四天"] = "乌鸫";   // 添加一组键值对

从映射中提取值的方法也和 JavaScript 中一样:

var gifts = { "第一天": "山鹑" };
print(gifts['第一天']);        // 山鹑

如果您查找的键在映射中不存在, 将在返回中得到 null。 不过,由于值可以为 null, 您可能需要使用 containsKey()putIfAbsent() 等方法获知 null 的确切情况。

var gifts = { "第一天": "山鹑" };
print(gifts['第五天']);        // null

使用 .length 可以获取映射中键值对的数目:

var gifts = { "第一天": "山鹑" };
gifts["第四天"] = "乌鸫";
print(gifts.length);         // 2

要从映射中移除键值对, 可以使用 remove() 方法:

var gifts = { "第一天": "山鹑" };
gifts["第四天"] = "乌鸫";
gifts.remove('第一天');
print(gifts.length);           // 1
print(gifts['第一天']);        // null

您可以使用 Map.from() 构造函数复制映射:

var gifts = { "第一天": "山鹑" };
var regifts = new Map.from(gifts);
print(regifts['第一天']);      // 山鹑

遍历[编辑]

遍历映射的内容有几种方法。 使用 forEach() 方法 可同时访问键值。

var gifts = {
  "第一天": "山鹑",
  "第二天": "斑鸠",
  "第五天": "环颈雉"};
gifts.forEach((k,v) => print('$k:$v'));

注: 不要指望 forEach() 按特定顺序返回键值对。

如果您只对键或值感兴趣, 可以分别使用 getKeys()getValues()。 两种方法均可返回 Collection 对象。

var gifts = {"第一天": "山鹑", "第二天": "斑鸠"};
var values = gifts.getValues();
values.forEach((v) => print(v));      // 山鹑, 斑鸠

注: 映射对象本身不会扩展到 Collection 接口。

内置类型小结[编辑]

Dart 的 #内置类型 均有 特别的常量格式,并实现了内置接口。 例如,数字常量如 11.1, 它们实现了 num 接口。

您通常会用常量创建大多数内置类型对象, 但也可以用构造函数。 布尔类型有所不同,因为您无法创建类型为 bool 的新对象; 只能用 truefalse 实现。

关于映射与列表的详细信息, 请参见 #泛型

函数[编辑]

下面是一个简单的函数:

String say(String from, String msg) => "$from 说“$msg”";

下面是调用该函数的示例:

print(say("Bob", "Hello")); // "Bob 说“Hello”"

若忽略类型,上面的代码也可以这样写:

say(from, msg) => "$from 说“$msg”";

不过,我们推荐在函数签名处说明参数类型。

=> e; 语法是 { return e; } 的简写式。 如 say(from, msg) => "$from 说“$msg”"; 与下面的写法等效:

say(from, msg) {
  return "$from 说“$msg”";
}

可选参数[编辑]

将函数参数放在 [] 括号内可将其标记为可选参数。

String say(String from, String msg, [String device]) {
  var result = "$from 说“$msg”";
  if (device != null) {
    result = "$result(通过 $device 发送)";
  }
  return result;
}

下面是调用时不提供可选参数的例子:

print(say("Bob", "你好呀")); // Bob 说“你好呀”

下面是提供第三个参数调用该函数时的例子:

print(say("Bob", "你好呀", "狼烟"));
// Bob 说“你好呀”(通过 狼烟 发送)

可选参数的默认值[编辑]

您可以给可选参数指定默认值。默认值必须为编译时常量。 如果没有提供默认值,其值则为 null(上例即是)。

String say(String from, String msg, [String device='信鸽']) {
  var result = "$from 说“$msg”";
  if (device != null) {
    result = "$result(通过 $device 发送)";
  }
  return result;
}

如果省去可选参数,则会使用默认值:

print(say("Bob", "你好呀")); // Bob 说“你好呀”(通过 信鸽 发送)

命名参数[编辑]

可选参数同时也是命名参数。

print(say("Bob", "你好呀", device: "易拉罐话筒"));
// Bob 说“你好呀”(通过 易拉罐话筒 发送)

第一类函数[编辑]

您可以将函数作为参数传递给其他函数。例如:

List ages = [1,4,5,7,10,14,21];
List oddAges = ages.filter((i) => i % 2 == 1);

与下面的代码等效:

bool isOdd(num i) => i % 2 == 1;
List ages = [1,4,5,7,10,14,21];
List oddAges = ages.filter(isOdd);

您也可以将函数赋值给变量,如:

var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
print(loudify('hello'));

词法封装[编辑]

函数可将周边区域定义的变量进行封装处理。 下例展示 makeAdder 函数如何捕获变量 n 并将其传递给 makeAdder 所返回的函数。 不论所返回的函数在哪里调用,它都能捕获变量 n

Function makeAdder(num n) {
  return (num i) => n + i;
}

main() {
  var add2 = makeAdder(2);
  print(add2(3)); // 5
}

(特别感谢 Bob Nystrom 提供本例。)

返回值[编辑]

所有函数都会返回一个值。如果未指定返回值,则将在函数主体的末端隐式调用 return null; 语句。

函数小结[编辑]

Dart 支持第一类函数,以及可选参数、命名参数和参数默认值。 函数可被赋值给变量,或作为参数传递给其他函数。 函数还支持词法封装,可访问其直接词域以外的变量。

运算符[编辑]

Dart 支持运算符。 它不仅定义运算符, 还允许您重新定义其中很多运算符。

下表按优先级顺序 列出了 Dart 的全部运算符。

描述 运算符 结合性
一元后缀 表达式++ 表达式-- () [] .
一元前缀 -表达式 !表达式 ~表达式 ++表达式 --表达式
乘除 * / % ~/
加减 + -
移位 << >>
关系 is is! >= > <= <
相等性 == != === !==
位 AND &
位 XOR ^
位 OR |
逻辑 AND &&
逻辑 OR ||
条件 表达式 ? 表达式 : 表达式
赋值 = *= /= ~/= %= += -= <<= >>= &= ^= |=


例如, % 运算符的优先级 高于 == 运算符,因此在其之前执行; 而 == 的优先级又高于 && 运算符。 这种优先顺序意味着 下面两行代码是等价执行的:

if ((n % i == 0) && (d % i == 0)) // 括号可增强可读性
if (n % i == 0 && d % i == 0)     // 难以阅读,但与上一行等价

本段落将涉及下述主题:

算术运算符[编辑]

Dart 支持常用的算术运算符。

运算符 定义
+
-表达式 一元否定(令表达式符号取反)
*
/
~/ 除,返回结果的整数部分
% 求余

例:

int a = 2;
int b = 3;

print('${a + b}');  // 5
print('${a - b}');  // -1
print('${a * b}');  // 6
print('${a / b}');  // 0.6666666666666666
print('${a ~/ b}'); // 0 (商)
print('${a % b}');  // 2 (余数)

Dart 还支持前后缀自增减运算符。

运算符 定义
++变量 变量 = 变量 + 1 (表达式值为 变量 + 1
变量++ 变量 = 变量 + 1 (表达式值为 变量
--变量 变量 = 变量 – 1 (表达式值为 变量 – 1
变量-- 变量 = 变量 – 1 (表达式值为 变量

例:

int a = 2;

print('${ ++a }'); // 3 (先自增后返回)
print('${ a++ }'); // 3 (先返回后自增)
print('${ a-- }'); // 4 (先返回后自减)
print('${ --a }'); // 2 (先自减后返回)

相等性与关系运算符[编辑]

运算符 定义
== 相等 (参见下文讨论)
!= 不等
=== 实例相同
!== 实例不同
> 大于
< 小于
>= 大于或等于
<= 小于或等于
is 如果对象为指定类型则为 true (参见下文讨论)
is! 如果对象为指定类型则为 false

要测试 xy 两个对象是否代表相同的事物,请使用 == 运算符。 您通常不必使用 === 运算符,该运算符用于测试两个对象是否是完全一样的对象。 == 运算符的工作原理如下:

  1. 如果 x===y,返回 true
  2. 否则,如果 xynull,则返回 false
  3. 否则,返回表达式 x.equals(y) 的结果。

isis! 运算符可方便检查类型。 如果 对象 实现了 T 所指定的接口, 则 对象 is T 的结果为 true。 例如,对象 is Object 总为 true

下面的例子用到了各个相等性与关系运算符:

int a = 2;
int b = 3;
int c = a;

print(a == 2);       // true;2 与 2 相等
print(a != b);       // true;2 与 3 不等
print(a === c);      // true;a 与 c 是完全相同的对象
print(a !== b);      // true;2 与 3 不是相同的对象
print(b > a);        // true;3 大于 2
print(a < b);        // true;2 小于 3
print(b >= b);       // true;3 大于或等于 3
print(a <= b);       // true;2 小于或等于 3
print(a is num);     // true;2 是数字类型
print(a is! String); // true;2 是 int 而非字符串类型

赋值运算符[编辑]

您可以使用 = 运算符进行赋值。 也可以使用组合赋值运算符, 它们是运算符与赋值操作的结合。

组合赋值符 等效表达式
对于运算符 op a op= b a = a op b
例: a += b a = a + b

下面是赋值运算符总表:

=
+=
–=
*=
/=
~/=
%=
<<=
>>=
&=
^=
|=

下面的例子同时用到了赋值符和组合赋值运算符:

int a = 2;           // 使用 = 赋值

a *= 3;              // 相乘并赋值:a = a * 3
print('a *= 3: $a'); // 输出 a *= 3: 6

逻辑运算符[编辑]

您可以使用逻辑运算符对布尔表达式进行取反或组合。

运算符 定义
!表达式 反转以下表达式 (false 变为 true,反之亦然)
|| 逻辑 OR
&& 逻辑 AND
if (!done && (col == 0 || col == 3)) {
  // ...执行一些操作
}

位操作与移位运算符[编辑]

在 Dart 中,您可以操作对象各位。 这些运算符通常应与整数搭配使用。

运算符 定义
& AND
| OR
^ XOR
~表达式 一元位取补(0110
<< 左移
>> 右移

下例使用了位操作与移位运算符。

int value = 0x22;
int bitmask = 0x0F;

print(value);                                // 34 (0x22)
print(value.toRadixString(16));              // 22
print(value.toRadixString(2));               // 100010
print((value & bitmask).toRadixString(16));  // 2  (AND)
print((value & ~bitmask).toRadixString(16)); // 20 (AND NOT)
print((value | bitmask).toRadixString(16));  // 2f (OR)
print((value ^ bitmask).toRadixString(16));  // 2d (XOR)
print((value << 4).toRadixString(16));       // 220
print((value >> 4).toRadixString(16));       // 2

其他运算符[编辑]

运算符 名称 定义
() 函数应用 代表函数调用
[] 列表访问 代表列表特定索引位置的值
表达式1 ? 表达式2 : 表达式3 条件 如果 表达式1true,则执行 expr2

否则,执行 表达式3

(技术上讲属于特别语法,不是运算符)
. 成员访问 代表表达式的一个属性; 例:foo.bar 从表达式 foo 中选取属性 bar

运算符的方法本质[编辑]

运算符只是名字比较特别的实例方法。 例如,表达式 1 + 2 调用了 1+ 方法,参数为 2—即 1.+(2)。 这种模型有重要意义:

  • Dart 允许您重载很多运算符。 例如,如果您定义了 Vector 类, 可以再定义一个 + 方法,用来求两个向量之和。
  • 对于二元运算符, 左侧的运算元决定了 应选用运算符的版本。 例如,如果您定义了 Vector 类与 Point 类, aVector + aPoint 中会使用 Vector 版本的 +

以下运算符可以重载:

<
>
<=
>=

+
/
~/
*
%
|
^
&
<<
>>
[]

[]= (列表赋值)
~
equals() (==) *

* 目前 == 运算符可以重载,但不会一直这样。 不久以后,自定义 == 动作的途径 将是重载 equals() 方法。

关于重载运算符的例子,请参见《类》一节的运算符段落。

运算符小结[编辑]

Dart 运算符的外观与行为应该不令人感到陌生。 从技术层面来看,运算符是特别命名的方法, 在首个操作数处调用。 因此,操作数的顺序可能会造成差异: a+b 的结果可能与 b+a 的不尽相同。 您可以重载很多运算符。

流程控制[编辑]

您可以使用下述语句控制 Dart 代码的执行流程:

if 与 else[编辑]

if (isRaining()) {
  you.bringRainCoat();
} else if (isSnowing()) {
  you.wearJacket();
} else {
  car.putTopDown();
}

请记住,与 JavaScript 不同,Dart 将所有 非 true 的值当作 false 对待。 详情请参见布尔值

for 循环[编辑]

您可以使用标准 for 循环进行遍历。

for (int i = 0; i < candidates.length; i++) {
  candidates[i].interview();
}

Dart 中 for 循环的退出可以正确捕获索引值, 没有 JavaScript 中讨厌的设计缺陷。 例如可以这样:

main() {
  var callbacks = [];
  for (var i = 0; i < 2; i++) {
    callbacks.add(() => print(i));
  }
  callbacks.forEach((c) => c());
}

输出内容是 01,与预期的一样。 相反,在 JavaScript 中,该例会输出 22

如果您要遍历的对象是 Collection 类型, 可以使用 forEach() 方法。如果不需要知道当前遍历的位置, 使用 forEach() 是个不错的选择。

candidates.forEach((candidate) => candidate.interview());

集合还支持 for-in 形式的遍历:

var collection = [0, 1, 2];
for (var x in collection) {
  print(x);
}
// 输出内容:
// 0
// 1
// 2

while 与 do while[编辑]

while 循环语句可在循环前判断条件是否成立。

while (!auctionItem.currentWinner(bidder) &&
       auctionItem.currentBid < bidder.maximumBid) {
  auctionItem.placeBid(bidder, auction.currentBid + 1);
}

do while 循环可在循环之后判断条件是否成立。

do {
  printLine();
} while (!atEndOfPage());

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();
}

如果您在使用 Collection,该例还可以这样写:

candidates.filter((c) => c.yearsExperience >= 5)
          .forEach((c) => c.interview());

switch 与 case[编辑]

Dart 中的 switch 语句使用 == 比较对象。记得在每条非空 case 分句后加上 break 语句,以避免跳到下一句(属于错误,参见下文)。 default 分句可用于捕获无匹配的条件。

var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClose();
    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();
    // 错误:缺少 break 会触发异常!!

  case 'CLOSED':
    executeClose();
    break;
}

不过 Dart 的确支持空的 case 分句, 允许跳到下一分句。

var command = 'CLOSED';
switch (command) {
  case 'CLOSED':     // 空分句会跳到下一分句
  case 'NOW_CLOSED':
    // CLOSED 与 NOW_CLOSED 均执行这段代码
    executeClose();
    break;
}

异常处理[编辑]

Dart 代码可以抛出与捕捉异常。 异常是预期以外情况发生的错误信号。 如果没有捕捉, 异常将不断返回至程序顶层。

与 Java 不同的是,Dart 的所有异常都是非强制异常。 方法不会声明可能抛出的异常, 您也不必捕捉所有异常。

Dart 提供了 Exception 接口以及很多预定义的异常类型。 当然,您可以通过扩展 Exception 接口定义自己的异常类型。 常见异常的例子包括:

不过,Dart 程序可以将任意对象作为异常抛出。

抛出[编辑]

您可以这样抛出——或者说 引发——异常。

throw new IllegalArgumentException('数值必须大于零');

您也可以抛出任意对象。

throw "骆马不够用了!";

捕捉[编辑]

捕捉——或者说捕获——异常,可避免该异常向上级扩散。 捕捉为处理异常提供了可能。

try {
  breedMoreLlamas();
} catch (final OutOfLlamasException e) {
  buyMoreLlamas();
}

若要处理抛出多种类型异常的代码,您可以指定多条捕捉分句。 第一条捕捉分句匹配抛出对象的类型, 负责处理异常。 如果捕捉分句未指定类型,该分句将能处理任意类型的抛出对象。

try {
  breedMoreLlamas();
} catch (final OutOfLlamasException e) {  // 特定异常
  buyMoreLlamas();
} catch (final Exception e) {             // 任意异常
  print("Unknown exception: $e");
} catch (final e) {                       // 类型不确定,全部处理
  print("Something really unknown: $e");
}

finally[编辑]

为确保不论是否有异常抛出,仍有一部分代码能够执行, 请使用 finally 分句。

如果没有捕捉分句与异常相匹配, 将执行 finally 分句,而异常也将不断扩散。

try {
  breedMoreLlamas();
} finally {
  cleanLlamaStalls();  // 总会执行,即使有异常抛出
}

finally 分句也会在匹配到任意一条捕捉分句后执行。

try {
  breedMoreLlamas();
} catch (final e) {
  print("Error: $e");  // 先处理异常
} finally {
  cleanLlamaStalls();  // 然后执行 finally 分句
}


[编辑]

Dart 是一款面向对象的语言,支持类与单继承。 根类为 Object

实例变量[编辑]

您可以这样通过实例变量 (或称成员变量) 声明一个类:

class Point {
  num x, y;
}

所有未初始化的实例变量都有默认值 null

不论实例变量是否为 final 变量, 均隐式包含了 getter 方法。 非 final 实例变量 隐式包含了 setter 方法。 (gettersetter 将在下文深入讨论。)

main() {
  var point = new Point();

  // 使用 x 的 setter 方法
  point.x = 4;

  // 使用 x 的 getter 方法
  print(point.x);  // 4

  // 默认值为 null
  print(point.y);  // null
}

实例变量的初始化[编辑]

如果您在声明 (而非通过构造函数或方法) 实例变量时对其进行初始化, 初始值必须为编译时常量。

注:此限制目前尚在讨论当中。

编译时常量的例子如数字常量:

class Point {
  num x = 0,
      y = 0;
}

要给实例变量赋非常量值, 请使用构造函数体(将在下一段落讲解)。

构造函数[编辑]

您可以通过创建与类同名的方法来声明构造函数。 最常见的构造函数形式是 生成构造函数, 可创建该类的新实例。

class Point {
  num x, y;

  Point(num x, num y) {
    // 还有更好的方法实现,敬请期待
    this.x = x;
    this.y = y;
  }
}

关键词 this 可引用当前实例。

注:仅当存在名字冲突时需要使用 this。其余情况下,Dart 的编写风格要求省略 this

将构造函数参数赋值给成员变量的语句特别常用, 因此 Dart 提供了简化代码的糖衣语法。

class Point {
  num x, y;

  // this.x = x 与 this.y = y 的糖衣语法
  Point(this.x, this.y);
}

默认构造函数[编辑]

如果您不声明构造函数, 将会调用默认构造函数。 默认构造函数没有参数, 调用的是超类的无参构造函数。

初始化列表[编辑]

final 变量必须在对象赋值给 this 之前初始化。 您可以使用先于构造函数体执行的初始化列表 初始化任意 final 变量。

#import('dart:html');

class Button {
  final Collection<ButtonHandler> handlers;
  final String domId;
  final Element elem;
  
                // : 以后便是初始化列表
  Button(domId) : domId = domId,
                  handlers = [],
                  elem = document.query(domId) {
    bindHandlers();
  }

  bindHandlers() {
   // ...
  }
}

初始化语句的右侧无权访问 this

命名构造函数[编辑]

您可以使用命名构造函数实现同一个类的多个构造函数 或让代码更为清晰。

class Point {
  num x, y;

  // 命名构造函数
  Point.fromJson(Map json) : x = json['x'], y = json['y'];

  Point(this.x, this.y);
}

通过 new 创建命名构造函数的新实例:

var jsonData = JSON.parse('{"x":1, "y":2}');
var point = new Point.fromJson(jsonData);

常量构造函数[编辑]

Dart 的对象创建非常确定, 因此避免了其他语言中的棘手问题。 为实现这种确定性,Dart 中只允许将固定的编译时表达式 作为实例变量声明的初始值。

固定的编译时常量对象也称为 const 对象。 要创建 const 对象,需要定义 const 构造函数,并确保所有实例字段均有 final 关键字。

class Point {
  final num x, y;
  const Point(this.x, this.y);
  static final Point origin = const Point(0, 0);
}

由于编译时常量是常量且固定, 构造两个一样的 const 对象 会生成完全一样的实例。

void main() {
  var a = const Point(1, 1);
  var b = const Point(1, 1);

  print(a === b); // 输出 true,两个实例完全一样!
}

注:其他运行时常量的例子如 数字常量与字符串常量。

factory 构造函数[编辑]

要实现不总是创建类的新实例的构造函数, 可使用 factory 关键字。 例如,factory 构造函数 可能会返回缓存中的实例, 或是可能返回子类的实例。

下例演示了返回缓存中对象的 factory 构造函数。

class Logger {
  final String name;
  bool mute = false;

  static Map<String, Logger> _cache;

  factory Logger(String name) {
    if (_cache == null) {
      _cache = {};
    }

    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = new Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  log(String msg) {
    if (!mute) {
      print(msg);
    }
  }
}

对其他构造函数, 如需调用 factory 构造函数,您可以使用 new 关键字:

var logger = new Logger('UI');
logger.log('按钮被点击了');

注:factory 构造函数无权访问 this

方法[编辑]

方法是提供对象行为的函数。

实例方法[编辑]

对象的实例方法可以访问实例变量与 this, 下例中的 distanceTo() 方法便是实例方法的一个例子。

class Point {
  num x, y;
  Point(this.x, this.y);

  num distanceTo(Point other) {
    return Math.sqrt(((x-other.x)*(x-other.x)) + ((y-other.y)*(y-other.y)));
  }
}

您可以这样调用 Point 实例的 distanceTo() 方法:

var point = new Point(2, 2);
num distance = point.distanceTo(new Point(4,4));
print(distance);  // 2.82842...

gettersetter[编辑]

gettersetter 方法提供了对象内部状态的读写权限。 调用 gettersetter 时, 请省略后面的圆括号。 您可以使用 getset 关键字定义 gettersetter

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  num get right()           => left + width;
      set right(num value)  => left = value - width;
  num get bottom()          => top + height;
      set bottom(num value) => top = value - height;
}


gettersetter 的调用 与使用实例变量所生成的 gettersetter 无异。

var rect = new Rectangle(3, 4, 20, 15);
print(rect.left); // 3
rect.right = 12;
print(rect.left); // -8

有了 gettersetter, 您可以将实例变量封装成方法, 而不必修改客户端代码。

运算符[编辑]

由于运算符只是名称特别的实例方法, 您可以重载很多运算符。 下例中的类重载了 +- 运算符。

class Vector {
  final int x,y;
  const Vector(this.x, this.y);

  Vector operator +(Vector v) { // 重载 + (a + b)
    return new Vector(x + v.x, y + v.y);
  }
  
  Vector operator -(Vector v) { // 重载 - (a - b)
    return new Vector(x - v.x, y - v.y);
  }
  
  Vector operator negate() {    // 重载一元取反运算符 (-a)
    return new Vector(-x,-y);
  }
  
  String toString() => '($x,$y)';
}

main() {
  Vector v = new Vector(2,3);
  Vector w = new Vector(2,2);
  print(v);   // (2,3)
  print(-v);  // (-2,-3)
  print(v+w); // (4,5)
  print(v-w); // (0,1)
}


实作时请注意: 重载 - 运算符只会影响二元的减法运算。 如需重载一元形式的 -, 您必须使用特别的标识符 negate

抽象类[编辑]

Dart 即将支持抽象类与抽象方法。

至 2012 年 4 月 4 日,抽象类尚未实现。 请关注 bug 1603 与 bug 1605 以跟踪开发进展。

扩展类[编辑]

使用 extends 关键字可创建子类,super 关键词可引用其超类。

class Television {
  turnOn() {
    _illuminateDisplay();
    _activeIrSensor();
  }
}

class SmartTelevision extends Television {
  turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
}

子类可重载实例的方法、gettersetter

类级静态成员[编辑]

使用 static 关键字可实现类级别的变量与方法。

静态方法[编辑]

静态方法(类级方法)不操作实例, 因此无权访问 this

class Point {
  num x, y;
  Point(this.x, this.y);

  static num distanceBetween(Point a, Point b) {
    return Math.sqrt(((a.x-b.x)*(a.x-b.x)) + ((a.y-b.y)*(a.y-b.y)));
  }
}

main() {
  var a = new Point(2, 2);
  var b = new Point(4, 4);
  print(Point.distanceBetween(a, b));  // 2.82842...
}

最佳实践:对于常用或广泛使用的功能和函数, 请考虑使用顶级函数

替代静态方法。

静态变量[编辑]

静态变量(类级变量)对处理类级状态与常量非常有用。

class Color {
  static final RED = const Color('红色');
  final String name;
  const Color(this.name);
  String toString() => name;
}

main() {
  print(Color.RED); // '红色'
}

接口[编辑]

接口是定义与对象交互方式的类型。 接口可以指定类型、 构造函数、 实例变量 (或者更精确地,gettersetter) 以及超接口。 不过接口不能指定方法与构造函数当中的代码。

接口在指定 API 但不详细指定 API 如何实现时很方便。

定义接口[编辑]

您可以使用 interface 关键词定义接口。 例如,下面的代码定义了 Hashable 接口:

interface Hashable {
  int hashCode();
}

注意定义的格式,接口没有方法体({...}), 只有一个分号(;)。

接口的实现[编辑]

类可以通过在 implements 分句中声明来实现一或多个接口, 并提供接口所需的 API。 例如:

class Point implements Hashable {
  num x, y;
  ...
  // Hashable 所需的 API
  int hashCode() {
    int result = 17;
    result = 37 * result + x.hashCode();
    result = 37 * result + y.hashCode();
    return result;
  }
  ...
}

下例指定了 一个实现多个接口的类:

class Point implements Comparable, Hashable {
  ...
}

注: 不久后您将能够将 Dart 类作为接口对待。 该功能在创建虚对象测试时会很有用。

扩展接口[编辑]

您可以创建构建在一或多个接口之上的接口。 新接口称为子接口, 所继承的全部接口均为其超接口

使用 extends 关键词 可指定作为基础的一或多个接口。 下面是创建 Hashable 子接口的例子:

interface HashablePoint extends Hashable {
  num x, y;
}

注: 技术上而言,接口没有 xy 等实例变量。 看起来是实例变量的部分,实际上是 声明 gettersetter 方法的快捷方式。

下面是实现子接口 并检查类型的例子:

class Point implements HashablePoint {
  num x, y; // HashablePoint 所需

  // Hashable 所需
  int hashCode() {
    ...
  }
}

void main() {
  Point p = new Point();
  print(p is Point);          // true
  print(p is Hashable);       // true
  print(p is HashablePoint);  // true
}

定义构造函数与默认类[编辑]

接口只要指定了默认类, 就可以定义构造函数。

注: 很多 Dart API 是作为有默认类的接口实现的。 如所有内置类型, 包括 numint 均为接口。

使用 default 关键字可指定 默认类。 例如:

interface ObjectCache default MemoryCache {
  ObjectCache();
  ...
}

默认类的代码可以是这样:

class MemoryCache implements ObjectCache {
  ...
}

通过接口调用构造函数 会导致接口默认类的等效构造函数 被调用。 例如:

var cache = new ObjectCache(); // 与 new MemoryCache() 相同

接口小结[编辑]

Dart 中到处都是接口, 包括内置类型与 Dart 标准库中所定义的很多类型。 由于 Dart 接口可以有默认类, 您可以使用接口构造函数。

泛型[编辑]

如果您仔细阅读了 API 文档中基本数组类型 List 的相关内容, 会发现其类型实际上是 List<E><...> 符号将 List 标记为 泛型 (或称 参数化类型)—一种可以声明 常规类型参数的类型。

为何要使用泛型?[编辑]

由于 Dart 中类型可选, 您不会遇到必须使用泛型的情况。 不过有时您可能会想用泛型 或其他类型: 类型(不论是否为泛型)有助于代码文档及注释的编写, 能简便地表达您的意图。

例如, 如果您打算让列表只包含字符串, 可以将其定义为 List<String> (读作“List of String”——字符串的列表)。 这样您和其他程序员,以及您的工具 (如 Dart Editor 与强制模式下的 Dart VM) 将能侦测到,将非字符串量赋值给列表 可能有误。

List<String> names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
...
names.add(42); // 强制模式下会失败(生产模式下可以通过)

另一个使用泛型的理由是 减少代码的重复量。 泛型允许您将单个接口及实现 在多种类型间共享, 同时仍保有强制模式的优势 并进行静态分析预警。 例如, 假设您创建了用于缓存对象的接口:

interface ObjectCache {
  Object getByKey(String key);
  setByKey(String key, Object value);
}

您发现需要该接口的字符串专用版本, 因此您又创建了另一个接口:

interface StringCache {
  String getByKey(String key);
  setByKey(String key, String value);
}

在此之后,您又决定想要该接口的数字专用版本…… 后续情况你懂的。

泛型可以免除挨个创建这些接口的麻烦。 您只需创建单个接口,并读入类型参数:

interface Cache<T> {
  T getByKey(String key);
  setByKey(String key, T value);
}

这段代码中的 T 是替代类型。 可以看作开发者可能会特别定义的占位符。

使用集合常量[编辑]

内置的两种集合类型—— 列表映射 ——均已参数化。 参数化的常量与之前见过的常量几乎没有区别, 只需要在左括号前加上 <type>。 例如:

List<String> names = <String>['Seth', 'Kathy', 'Lars'];
Map<String, String> pages = <String>{ // 指定的类型:String
    'index.html':'主页',  // (也隐含了类型 String)
    'robots.txt':'给网络蜘蛛看的',
    'humans.txt':'咱是人类,不是机器' };

注: 映射常量的总是字符串类型。 花括号前的类型指定的是映射的类型。

使用构造函数[编辑]

使用构造函数时,若要指定一或多个类型, 请将类型放在类名或接口名后面的尖括号 (<...>)中。 例如:

List<String> names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
Set<String> nameSet = new Set<String>.from(names);

下面的代码可创建 键为整数、值为 View 类型的映射:

Map<int, View> views = new Map<int, View>();

泛集合及其包含的类型[编辑]

Dart 中的泛型已经过具体化—— 它们会在运行时携带类型信息。 例如,即使是在生产模式下, 您也可以测试集合类型:

List<String> names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>);           // true

不过 is 表达式 只能检查集合的类型—而不是 其内部的对象。 在生产模式中, List<String> 可以包含一些非字符串项。 这时的处理方案是 检查各项的类型 或将项目操作代码包含在异常处理语句中。

注: 与此相反, Java 中的泛型使用了 erasure 类型, 也就是说泛型参数在运行时会被移除。 Java 中可以测试对象是不是 List, 但不能测试它是不是 List<String>

泛型小结[编辑]

关于泛型的详细信息请参见《Dart 中的可选类型》一文。

库与可访问性[编辑]

本章讲解如何使用 导入#import)、 包含#source)以及 新库#library) 指令, 可帮助您 创建模块化且可共享的代码库。 库的作用不仅是提供 API, 还是隐私单位: 它们可以隐藏实现的细节,如私有变量。

每个 Dart 应用都是一个库, 即使它没有使用 #library 指令。

注: Dart 中的库体系 将有所变动。 本章描述的是目前的工作方式。

使用库[编辑]

使用 #import 可指定将一个库的命名空间 用在另一个库的作用域内。

Dart web 应用通常会用到 dart:html 库,可以这样导入:

#import('dart:html');

#import 所需的唯一参数是指向库的 URI。 对于内置库, URI 以特别的 dart: 前缀区别。 对于其他库, URI 相对于当前目录。 (未来还将允许指定 web 服务器上的文件。) 例如:

#import('dart:io');
#import('mylib/mylib.dart');
#import('../../util/utilslib.dart');

指定前缀[编辑]

如果您导入的两个库的标识符有冲突, 可以为其中一个或两者都指定前缀。 例如,如果 library1library2 均定义了 Element, 可以这样编写代码:

#import('lib1/library1.dart');
#import('lib2/library2.dart', prefix:'lib2');
...
var element1 = new Element();      // 使用 library1 中的 Element
var element2 = new lib2.Element(); // 使用 library2 中的 Element

库的实现[编辑]

使用 #source (亦称 包含指令) 可指定属于当前库的文件, 而 #library 可指定文件实现了一个库。

将文件与当前库关联[编辑]

您可以将库的实现放在多个 .dart 文件中, 并在该库的主文件中使用 #source 关联。

被包含的文件不能含有指令。 库中必须有一个文件 包含库所需的所有指令 (如 #import#library)。

下面是一个例子:

// 文件 Ballgame.dart 中的内容
#import('dart:html');

#source('Ball.dart');
#source('Util.dart');
...
main() {
  ...
}

该例中,Ball.dart 与 Util.dart 会被编译进 Ballgame.dart 这个库当中。 Ballgame.dart 没有 #library 语句, 但请记住,每个 Dart 应用都是隐式的库。

声明库[编辑]

要明确声明一个库,可以使用 #library 语句。 例如:

#library('slider_sample');           // 声明这是一个库。

#import('dart:html');                // 该库用到了 html 库...
#import('../ui_lib/view/view.dart'); // ...和 view 库。

#source('SliderSample.dart');        // 从 SliderSample.dart 中获取代码。

注: #library 语句中的字符串目前用不到。

声明私有成员[编辑]

如果标识符以下划线开头, 便是所属库的私有成员。 例如, 在下面的代码中,Incrementer 类 有一个名为 _hiddenNum 的实例变量。

#library('HidingLibrary');

class Incrementer {
  num _hiddenNum = 0;
  
  void incrementNum() {
    _hiddenNum++;
  }
  
  void printNum() {
    print("隐含的数字为 $_hiddenNum");
  }
}

void main() {
  var o = new Incrementer();
  print("在这里可以读取到 _hiddenNum (${o._hiddenNum})。");
}

main() 方法可以引用 _hiddenNum 因为它与 Incrementer 处于同一个库当中。 但在该库以外,_hiddenNum 将不可访问, 即便是在 Incrementer 的子类中。

例如,下面的代码无法使用 _hiddenNum 因为与 Incrementer 分别处于不同的库当中 (在库 HidingLib/hider.dart 中实现):

#library('SubclassingLibrary');
#import('../HidingLib/hider.dart');

// 此类在 Incrementer 所处库之外,因此无法使用 _hiddenNum
class DoubleIncrementer extends Incrementer {
  void incrementNum() {
    //_hiddenNum = _hiddenNum + 2; // 无法解析 _hiddenNum
    super.incrementNum();          // 只能这样令 _hiddenNum 自增
    super.incrementNum();
  }
}

库与可见性小结[编辑]

如果需要使用库,请用 #import;如果需要将文件拉取到库中,请用 #source;如果需要声明您正在实现一个库, 请用 #library。 以下划线(_)命名的成员是库的私有成员。

隔离[编辑]

Dart 中的所有代码都在隔离的上下文中执行。 您可以使用额外的隔离进行并发编程, 以及更安全地执行第三方代码。

隔离概念[编辑]

每个隔离都有自己的 heap,即它在内存中的全部值, 包括全局变量,只能在隔离内部使用。 在隔离之间通讯的唯一途径是传递消息。 消息通过端口发送。

消息的内容可以是:

  • 基本值(nullnumbooldoubleString
  • SendPort 的实例
  • 列表或映射,其元素为上述任意类型,或是其他列表和映射
  • 特殊情况下可发送任意类型的对象

列表与映射可以循环引用其自身。 每条消息在发送到其他隔离时都会产生副本,以确保 一个隔离无法更改其他隔离中消息的状态。

根据实现的不同,隔离可能分开在不同进程或线程中执行。 对于 web 应用,如果可用,隔离可以编译为 Web worker。

使用隔离[编辑]

要使用隔离,您需要导入 isolate 库, 创造新隔离,并发送与接收消息。

导入 isolate[编辑]

隔离相关的 API 可以在 dart:isolate 库中找到。

#import('dart:isolate');

创造新隔离[编辑]

任意顶级函数或静态方法 都是有效的隔离切入点。 切入点不应要求参数输入,且应返回 void。 使用函数末尾作为隔离切入点是不合法的。 将切入点传递给 spawnFunction()

注:dart2js 尚不支持将静态方法作为隔离切入点。

#import('dart:isolate');

runInIsolate() {
  print("来自隔离中的问候!");
}

main() {
  spawnFunction(runInIsolate);
}

我们计划支持从 URI 所指向的代码创造隔离。

发送消息[编辑]

可通过 SendPort 将消息发送到隔离。 spawnFunction() 方法会返回指向其 SendPort 的句柄。

如果只是要发送一条消息,可以使用 send()

#import('dart:isolate');

echo() {
  // 在此接收消息,参见下一章节
}

main() {
  SendPort sendPort = spawnFunction(echo);
  sendPort.send("来自 main 的问候");
}

发送任意类型的对象[编辑]

特殊情况下(如在 Dart VM 内部使用 spawnFunction()) 可以将任意类型的对象实例发送给隔离。 对象消息在发送时会产生副本。

编译到 JavaScript 时, 尚未实现将任意对象实例发送给隔离, 即使使用了 spawnFunction()

接收消息[编辑]

使用 ReceivePort 可接收发往隔离的消息。可以从 port 属性获取指向 ReceivePort 的句柄。 然后使用传递给 receive() 方法的回调函数处理新消息。

#import('dart:isolate');

echo() {
  port.receive((msg, SendPort reply) {
    print("我收到了:$msg");
  });
}

main() {
  SendPort sendPort = spawnFunction(echo);
  sendPort.send("来自 main 的问候");
}

接收回复[编辑]

使用 SendPort 的 call() 方法可方便地发送消息与接收回复。 call() 方法会返回 Future 作为回复。

#import('dart:isolate');

echo() {
  port.receive((msg, SendPort reply) {
    reply.send("我收到了:$msg");
  });
}

main() {
  SendPort sendPort = spawnFunction(echo);
  sendPort.call("来自 main 的问候").then((reply) {
    print(reply);    // 我收到了:来自 main 的问候
  });
}

类型定义[编辑]

在 Dart 中,函数与字符串和数字一样,也是对象。 typedef,或称 函数类型别名,可以对函数命名, 以便在声明语句与返回类型时使用。 当函数类型被赋值给变量时,typedef 会保留类型信息。

请看下述代码,其中未使用 typedef

class SortedCollection {
  Function compare;

  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}

int sort(Object a, Object b) => ... ;

main() {
  SortedCollection collection = new SortedCollection(sort);

  // 我们只知道 compare 是一个函数,但它是哪种类型的函数?
  print(collection.compare is Function);  // true
}

在将 f 赋值给 compare 时,其类型信息丢失。 f 的类型为 (Object, Object) → int,而 compare 的类型为 Function。 如果修改代码,使用确切的名称并保留类型信息, 开发人员与工具便都能利用该信息。

加上 typedef 可让 Dart 保留类型信息。

typedef int Compare(Object a, Object b);

class SortedCollection {
  Compare compare;

  SortedCollection(this.compare);
}

int sort(Object a, Object b) => ... ;

main() {
  SortedCollection collection = new SortedCollection(sort);
  print(collection.compare is Function);  // true
  print(collection.compare is Compare);   // true
}

注: 目前,typedef 仅限对函数类型使用。 未来版本的语言规范中可能会有变化。

由于 typedef 只是别名,它们也提供了检查任意函数类型的途径。 例如:

typedef int Compare(int a, int b);

int sort(int a, int b) => a - b;

main() {
  print(sort is Compare);  // true!
}

typedef 还可以参数化。

typedef int Compare<T>(T a, T b);

class SortedCollection<T> {
  Compare<T> compare;
  SortedCollection(this.compare);
}

main() {
  SortedCollection<int> s = new SortedCollection<int>((a,b) => a - b);
  print(s.compare is Compare<int>);  // true
  print(s.compare('a','b'));  // 强制模式下会抛出异常
}

注释[编辑]

Dart 支持单行注释、多行注释以及文档注释。

单行注释[编辑]

单行注释以 // 开头。 // 与行尾之间的所有内容都会被 Dart 编译器忽略。

main() {
  // TODO: 以抽象的骆马欢迎方式重构?
  print("欢迎造访我的骆马农场!");
}

多行注释[编辑]

多行注释以 /* 开头,以 */ 结尾。 /**/ 直接的所有内容都会被 Dart 编译器忽略(除非该注释为文档注释,参见下文)。 多行注释可以嵌套。

main() {
  /*
   * 任务很多。还在考虑养鸡。

  Llama larry = new Llama();
  larry.feed();
  larry.exercise();
  larry.clean();
   */
}

文档注释[编辑]

文档注释是以 /** 开头的多行注释。 Dart 编译器将忽略文档注释中除了括号内部分以外的所有文字。 您可以使用方括号在注释中嵌入类、方法以及字段的链接。 方括号中的名称将被解析为所引用程序元素的词法作用范围。

下面是引用其他类与参数的文档注释示例:

/**
 * 骆马 (Lama glama) 是经驯化的南美骆驼,
 * 自前西班牙时期起被安第斯山脉土著广泛用于
 * 肉类生产与驮运。
 */
class Llama {
  String name;

  /**
   * 给骆马喂 [Food]。通常每周一捆干草。
   */
  feed(Food food) {
    ...
  }

  /**
   * 让骆马做 [timeLimit] 分钟的 [activity] 锻炼。
   */
  exercise(Activity activity, int timeLimit) {
    ...
  }
}

您可以使用 SDK 中附带的 dartdoc 工具解析 Dart 代码并生成 HTML。下面是 dartdoc 输出内容的例子。

<div class="doc">
<p>给骆马喂 <a class="crossref" href="../llama/Food.html">Food</a>。通常每周一捆干草。</p>
<pre class="source">
feed(Food food) {
  // ...
}
</pre>
</div>

请注意 [Food] 是如何从文档注释

转换为指向 Food 类的 API 文档的 HTML 链接的。