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 連結的。