Chinaunix首页 | 论坛 | 博客
  • 博客访问: 7110012
  • 博文数量: 3857
  • 博客积分: 6409
  • 博客等级: 准将
  • 技术积分: 15948
  • 用 户 组: 普通用户
  • 注册时间: 2008-09-02 16:48
个人简介

迷彩 潜伏 隐蔽 伪装

文章分类

全部博文(3857)

文章存档

2017年(5)

2016年(63)

2015年(927)

2014年(677)

2013年(807)

2012年(1241)

2011年(67)

2010年(7)

2009年(36)

2008年(28)

分类:

2012-04-23 09:52:33

原文地址:Dart 语言导览 作者:自语的骆驼

本文译自 Dart 语言官网 一文,原作者为 Kathy & Seth。原文授权许可为 。

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

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

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

目录 [] 

基本的 Dart 程序

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

main() { // 变量及取值 int number = 42; String text = "number 的数值为"; // 控制台输出 print("$text $number."); }

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

main()

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

//

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

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

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

"..." (或 '...')

字符串。

$变量名

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

print()

显示输出的简便途径。

样式

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

运行时模式

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;字符完全相同

字符串方法

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

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 类型的状态。 例如,replaceAll() 方法只返回新的 String 对象, 而没有改变原来的 String 对象。

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

StringBuffer 方法

要通过程序生成字符串,您可以使用 。 StringBuffer 在调用 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 # 。

数字

Dart 中的数字分为两种:

任意大小的整数

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

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

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

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

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 #。) 还需要注意的是, 与本文其余示例代码不同, 上述两例不能在强制模式下执行。

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

列表与集合方法

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

关于列表的更多信息请见 。

映射

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

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

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

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

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, 您可能需要使用 或 等方法获知 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()。 两种方法均可返回 对象。

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

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

内置类型小结

Dart 的 均有 特别的常量格式,并实现了内置接口。 例如,数字常量如 1 和 1.1, 它们实现了 num 接口。

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

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

函数

下面是一个简单的函数:

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) =>% 2 == 1);

与下面的代码等效:

bool isOdd(num 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 ((% i == 0) && (% i == 0)) // 括号可增强可读性 if (% i == 0 &&% 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

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

  1. 如果 x===y,返回 true。
  2. 否则,如果 x 或 y 为 null,则返回 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(!= b); // true;2 与 3 不等 print(a === c); // true;a 与 c 是完全相同的对象 print(!== 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
~表达式 一元位取补(0 变 1;1 变 0)
<< 左移
>> 右移

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

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 条件 如果 表达式1 为 true,则执行 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()); }

输出内容是 0、1,与预期的一样。 相反,在 JavaScript 中,该例会输出 2、2。

如果您要遍历的对象是 Collection 类型, 可以使用 方法。如果不需要知道当前遍历的位置, 使用 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.year***perience < 5) { continue; } candidate.interview(); }

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

candidates.filter((c) => c.year***perience >= 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 接口定义自己的异常类型。 常见异常的例子包括:

  • 索引越界
  • 无此方法
  • null 指针
  • 参数非法

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

抛出

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

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

您也可以抛出任意对象。

throw "骆马不够用了!"; 捕捉

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

try { breedMoreLlamas(); } catch (final OutOfLlama***ception e) { buyMoreLlamas(); }

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

try { breedMoreLlamas(); } catch (final OutOfLlama***ception 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 是一款面向对象的语言,支持类与单继承。 根类为 。

实例变量

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

class Point { num x, y; }

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

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

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...

getter 与 setter

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

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


对 getter 与 setter 的调用 与使用实例变量所生成的 getter 与 setter 无异。

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

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

运算符

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

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 与 bug 以跟踪开发进展。

扩展类

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

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

子类可重载实例的方法、getter 及 setter。

类级静态成员

使用 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); // '红色' }

接口

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

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

定义接口

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

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

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

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

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 是作为有默认类的接口实现的。 如所有, 包括 均为接口。

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

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

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

class MemoryCache implements ObjectCache { ... }

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

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

接口小结

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

泛型

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

为何要使用泛型?

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

例如, 如果您打算让列表只包含字符串, 可以将其定义为 List (读作“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 names = ['Seth', 'Kathy', 'Lars']; Map pages = { // 指定的类型:String 'index.html':'主页', // (也隐含了类型 String) 'robots.txt':'给网络蜘蛛看的', 'humans.txt':'咱是人类,不是机器' };

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

使用构造函数

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

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

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

Map views = new Map();

泛集合及其包含的类型

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

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

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

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

泛型小结

关于泛型的详细信息请参见《》一文。

库与可访问性

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

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

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

使用库

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

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

#import('dart:html');

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

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

指定前缀

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

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

库的实现

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

将文件与当前库关联

您可以将库的实现放在多个 .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,即它在内存中的全部值, 包括全局变量,只能在隔离内部使用。 在隔离之间通讯的唯一途径是传递消息。 消息通过端口发送。

消息的内容可以是:

  • 基本值(null、num、bool、double、String)
  • SendPort 的实例
  • 列表或映射,其元素为上述任意类型,或是其他列表和映射
  • 下可发送任意类型的对象

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

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

使用隔离

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

导入 isolate 库

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

#import('dart:isolate'); 创造新隔离

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

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

#import('dart:isolate'); runInIsolate() { print("来自隔离中的问候!"); } main() { spawnFunction(runInIsolate); }

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

发送消息

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

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

#import('dart:isolate'); echo() { // 在此接收消息,参见下一章节 } main() { SendPort sendPort = spawnFunction(echo); sendPort.send("来自 main 的问候"); }

发送任意类型的对象

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

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

接收消息

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

#import('dart:isolate'); echo() { port.receive((msg, SendPort reply) { print("我收到了:$msg"); }); } main() { SendPort sendPort = spawnFunction(echo); sendPort.send("来自 main 的问候"); } 接收回复

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

#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 输出内容的例子。

class="doc">

给骆马喂 class="crossref" href="../llama/Food.html">Food。通常每周一捆干草。

class="source"> feed(Food food) { // ... }

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

转换为指向 Food 类的 API 文档的 HTML 链接的。
阅读(1183) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~