本文適合有代碼基礎的人,如果沒在代碼基礎請文章底部來源從頭開始學習
示例
// 定義一個函數
printInteger(int aNumber) {
print('The number is $aNumber.'); // 打印到控制臺。
}
// 應用從這里開始執行。
// 程序開始執行函數,該函數是特定的、必須的、頂級函數。
main() {
//定義變量,通過這種方式定義變量不需要指定變量類型。
var number = 42;
printInteger(number); // 調用函數。
}
重要的概念
在學習 Dart 語言時, 應該基于以下事實和概念:
任何保存在變量中的都是一個 對象 , 并且所有的對象都是對應一個 類 的實例。 無論是數字,函數和 null 都是對象。所有對象繼承自 Object 類。
盡管 Dart 是強類型的,但是 Dart 可以推斷類型,所以類型注釋是可選的。 在上面的代碼中, number 被推斷為 int 類型。 如果要明確說明不需要任何類型, 需要使用特殊類型 dynamic 。
Dart 支持泛型,如 List <int> (整數列表)或 List <dynamic> (任何類型的對象列表)。
Dart 支持頂級函數(例如 main() ), 同樣函數綁定在類或對象上(分別是 靜態函數 和 實例函數 )。 以及支持函數內創建函數 ( 嵌套 或 局部函數 ) 。
類似地, Dart 支持頂級 變量 , 同樣變量綁定在類或對象上(靜態變量和實例變量)。 實例變量有時稱為字段或屬性。
與 Java 不同,Dart 沒有關鍵字 “public” , “protected” 和 “private” 。 如果標識符以下劃線(_)開頭,則它相對于庫是私有的。 有關更多信息,參考 庫和可見性。
標識符 以字母或下劃線(_)開頭,后跟任意字母和數字組合。
Dart 語法中包含 表達式( expressions )(有運行時值)和 語句( statements )(沒有運行時值)。 例如,條件表達式 condition ? expr1 : expr2 的值可能是 expr1 或 expr2 。 將其與 if-else 語句 相比較,if-else 語句沒有值。 一條語句通常包含一個或多個表達式,相反表達式不能直接包含語句。
Dart 工具提示兩種類型問題:警告和錯誤。 警告只是表明代碼可能無法正常工作,但不會阻止程序的執行。 錯誤可能是編譯時錯誤或者運行時錯誤。 編譯時錯誤會阻止代碼的執行; 運行時錯誤會導致代碼在執行過程中引發 [異常](#exception)。
Dart關鍵字
key | 作用域 | 備注 | |
---|---|---|---|
abstract | 2 | 抽象方法/抽象類 | |
dynamic | 2 | ||
implements | 2 | ||
show | 1 | ||
as | 2 | ||
else | |||
import | 2 | ||
static | 2 | ||
assert | |||
enum | |||
in | |||
super | |||
async | 1 | ||
export | 2 | ||
interface | 2 | ||
switch | |||
await | 3 | ||
extends | |||
is | |||
sync | 1 | ||
break | |||
external | 2 | ||
library | 2 | ||
this | |||
case | |||
factory | 2 | ||
mixin | 2 | ||
throw | |||
catch | |||
false | |||
new | |||
true | |||
class | |||
final | |||
null | |||
try | |||
const | |||
finally | |||
on | 1 | ||
typedef | 2 | ||
continue | |||
for | |||
operator | 2 | ||
var | |||
covariant | 2 | ||
Function | 2 | ||
part | 2 | ||
void | |||
default | |||
get | 2 | ||
rethrow | |||
while | |||
deferred | 2 | ||
hide | 1 | ||
return | |||
with | |||
do | |||
if | |||
set | 2 | ||
yield | 3 |
避免使用這些單詞作為標識符。 但是,如有必要,標有上標的關鍵字可以用作標識符:
帶有 1 上標的單詞為 上下文關鍵字, 僅在特定位置具有含義。 他們在任何地方都是有效的標識符。
帶有 2 上標的單詞為 內置標識符, 為了簡化將 JavaScript 代碼移植到 Dart 的工作, 這些關鍵字在大多數地方都是有效的標識符, 但它們不能用作類或類型名稱,也不能用作 import 前綴。
帶有 3 上標的單詞是與 Dart 1.0 發布后添加的異步支持相關的更新,作為限制類保留字。
不能在標記為 async ,async* 或 sync* 的任何函數體中使用 await 或 yield 作為標識符。
關鍵字表中的剩余單詞都是保留字。 不能將保留字用作標識符。
abstract
- 抽象方法
實例方法, getter, 和 setter 方法可以是抽象的, 只定義接口不進行實現,而是留給其他類去實現。 抽象方法只存在于 抽象類 中。
定義一個抽象函數,使用分號 (;) 來代替函數體:
abstract class Doer {
// 定義實例變量和方法 ...
void doSomething(); // 定義一個抽象方法。
}
class EffectiveDoer extends Doer {
void doSomething() {
// 提供方法實現,所以這里的方法就不是抽象方法了...
}
}
調用抽象方法會導致運行時錯誤。
- 抽象類
使用 abstract 修飾符來定義 抽象類 — 抽象類不能實例化。 抽象類通常用來定義接口,以及部分實現。 如果希望抽象類能夠被實例化,那么可以通過定義一個 工廠構造函數 來實現。
抽象類通常具有 抽象方法。 下面是一個聲明具有抽象方法的抽象類示例:
// 這個類被定義為抽象類,
// 所以不能被實例化。
abstract class AbstractContainer {
// 定義構造行數,字段,方法...
void updateChildren(); // 抽象方法。
}
dynamic
使用 dynamic 注解替換推斷失敗的情況。
Dart 允許在許多地方省略類型注解,并嘗試推斷類型。在某些情況下,如果推斷失敗了,會默認指定為 dynamic 類型。如果 dynamic 類型與期望相同,那么從技術的角度來講,這是獲取類型最簡潔 的方式。
但是,這種方式是最不清晰的。任何一個閱讀代碼的人,當看到一個類型確實的成員時,是沒有辦法 知道,編寫的人是希望它是 dynamic 類型,還是期望它是其他的什么類型,或者閱讀的人就簡單的 認為是編寫的人忘記了指定類型。
當 dynamic 是你期望的類型,就應該指明它,這樣能讓你的意圖更清晰。
dynamic mergeJson(dynamic original, dynamic changes) => ...
在Dart 2之前,本規則恰恰是相反的:不要 為隱性類型的成員指定 dynamic 注解?;趶婎愋拖到y 和類型推斷,現在的開發者更希望 Dart 的行為類似于推斷的靜態類型語言?;谶@種心理模型,我們發現 代碼區域慢慢地失去了靜態類型所具有的安全及性能。
implements
每個類都隱式的定義了一個接口,接口包含了該類所有的實例成員及其實現的接口。 如果要創建一個 A 類,A 要支持 B 類的 API ,但是不需要繼承 B 的實現, 那么可以通過 A 實現 B 的接口。
一個類可以通過 implements 關鍵字來實現一個或者多個接口, 并實現每個接口要求的 API。 例如:
// person 類。 隱式接口里面包含了 greet() 方法聲明。
class Person {
// 包含在接口里,但只在當前庫中可見。
final _name;
// 不包含在接口里,因為這是一個構造函數。
Person(this._name);
// 包含在接口里。
String greet(String who) => 'Hello, $who. I am $_name.';
}
// person 接口的實現。
class Impostor implements Person {
get _name => '';
String greet(String who) => 'Hi $who. Do you know who I am?';
}
String greetBob(Person person) => person.greet('Bob');
void main() {
print(greetBob(Person('Kathy')));
print(greetBob(Impostor()));
}
下面示例演示一個類如何實現多個接口: Here’s an example of specifying that a class implements multiple interfaces:
class Point implements Comparable, Location {...}
show hide
導入庫的一部分
如果你只使用庫的一部分功能,則可以選擇需要導入的 內容。例如:
// Import only foo.
import 'package:lib1/lib1.dart' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
as
- 類型判定運算符
as, is, 和 is! 運算符用于在運行時處理類型檢查:
|Operator| Meaning|
| --- | ---|
|as| Typecast (也被用于指定庫前綴)|
|is |True if the object has the specified type|
|is!| False if the object has the specified type|
例如, obj is Object 總是 true。 但是只有 obj 實現了 T 的接口時, obj is T 才是 true。
使用 as 運算符將對象強制轉換為特定類型。 通常,可以認為是 is 類型判定后,被判定對象調用函數的一種縮寫形式。 請考慮以下代碼:
```
if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}
```
使用 as 運算符進行縮寫:
```
(emp as Person).firstName = 'Bob';
```
######提示:以上代碼并不是等價的。 如果 emp 為 null 或者不是 Person 對象, 那么第一個 is 的示例,后面將不回執行; 第二個 as 的示例會拋出異常。
-
指定庫前綴
如果導入兩個存在沖突標識符的庫, 則可以為這兩個庫,或者其中一個指定前綴。 例如,如果 library1 和 library2 都有一個 Element 類, 那么可以通過下面的方式處理:import 'package:lib1/lib1.dart'; import 'package:lib2/lib2.dart' as lib2; // 使用 lib1 中的 Element。 Element element1 = Element(); // 使用 lib2 中的 Element。 lib2.Element element2 = lib2.Element();
if else
Dart 支持 if - else 語句,其中 else 是可選的, 比如下面的例子, 另參考 conditional expressions.
if (isRaining()) {
you.bringRainCoat();
} else if (isSnowing()) {
you.wearJacket();
} else {
car.putTopDown();
}
和 JavaScript 不同, Dart 的判斷條件必須是布爾值,不能是其他類型。
import
使用庫
通過 import 指定一個庫命名空間中的內如如何在另一個庫中使用。 例如,Dart Web應用程序通常使用 dart:html 庫,它們可以像這樣導入:
import 'dart:html';
import 參數只需要一個指向庫的 URI。 對于內置庫,URI 擁有自己特殊的dart: 方案。 對于其他的庫,使用系統文件路徑或者 package: 方案 。 package: 方案指定由包管理器(如 pub 工具)提供的庫。例如:
import 'package:test/test.dart';
提示: URI 代表統一資源標識符。 URL(統一資源定位符)是一種常見的URI。
static
類變量和方法
使用 static 關鍵字實現類范圍的變量和方法。
-
靜態變量
靜態變量(類變量)對于類級別的狀態是非常有用的:class Queue { static const initialCapacity = 16; // ··· } void main() { assert(Queue.initialCapacity == 16); }
靜態變量只到它們被使用的時候才會初始化。
提示: 代碼準守風格推薦指南 中的命名規則, 使用 lowerCamelCase 來命名常量。
-
靜態方法
靜態方法(類方法)不能在實例上使用,因此它們不能訪問 this 。 例如:import 'dart:math'; class Point { num x, y; Point(this.x, this.y); static num distanceBetween(Point a, Point b) { var dx = a.x - b.x; var dy = a.y - b.y; return sqrt(dx * dx + dy * dy); } } void main() { var a = Point(2, 2); var b = Point(4, 4); var distance = Point.distanceBetween(a, b); assert(2.8 < distance && distance < 2.9); print(distance); }
提示: 對于常見或廣泛使用的工具和函數, 應該考慮使用頂級函數而不是靜態方法。
靜態函數可以當做編譯時常量使用。 例如,可以將靜態方法作為參數傳遞給常量構造函數。
asset
如果 assert 語句中的布爾條件為 false , 那么正常的程序執行流程會被中斷。 在本章中包含部分 assert 的使用, 下面是一些示例:
// 確認變量值不為空。
assert(text != null);
// 確認變量值小于100。
assert(number < 100);
// 確認 URL 是否是 https 類型。
assert(urlString.startsWith('https'));
提示: assert 語句只在開發環境中有效, 在生產環境是無效的; Flutter 中的 assert 只在 debug 模式 中有效。 開發用的工具,例如 dartdevc 默認是開啟 assert 功能。 其他的一些工具, 例如 dart 和 dart2js, 支持通過命令行開啟 assert : --enable-asserts。
assert 的第二個參數可以為其添加一個字符串消息。
assert(urlString.startsWith('https'),
'URL ($urlString) should start with "https".');
assert 的第一個參數可以是解析為布爾值的任何表達式。 如果表達式結果為 true , 則斷言成功,并繼續執行。 如果表達式結果為 false , 則斷言失敗,并拋出異常 (AssertionError) 。
enum
枚舉類型
枚舉類型也稱為 enumerations 或 enums , 是一種特殊的類,用于表示數量固定的常量值。
使用枚舉
使用 enum 關鍵字定義一個枚舉類型:
enum Color { red, green, blue }
枚舉中的每個值都有一個 index getter 方法, 該方法返回值所在枚舉類型定義中的位置(從 0 開始)。 例如,第一個枚舉值的索引是 0 , 第二個枚舉值的索引是 1。
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
使用枚舉的 values 常量, 獲取所有枚舉值列表( list )。
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);
可以在 switch 語句 中使用枚舉, 如果不處理所有枚舉值,會收到警告:
var aColor = Color.blue;
switch (aColor) {
case Color.red:
print('Red as roses!');
break;
case Color.green:
print('Green as grass!');
break;
default: // 沒有這個,會看到一個警告。
print(aColor); // 'Color.blue'
}
枚舉類型具有以下限制:
- 枚舉不能被子類化,混合或實現。
- 枚舉不能被顯式實例化。
有關更多信息,參考 Dart language specification 。
for in
for 循環
進行迭代操作,可以使用標準 for 語句。 例如:
var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
message.write('!');
}
閉包在 Dart 的 for 循環中會捕獲循環的 index 索引值, 來避免 JavaScript 中常見的陷阱。 請思考示例代碼:
var callbacks = [];
for (var i = 0; i < 2; i++) {
callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());
和期望一樣,輸出的是 0 和 1。 但是示例中的代碼在 JavaScript 中會連續輸出兩個 2 。
I如果要迭代一個實現了 Iterable 接口的對象, 可以使用 forEach() 方法, 如果不需要使用當前計數值, 使用 forEach() 是非常棒的選擇;
candidates.forEach((candidate) => candidate.interview());
實現了 Iterable 的類(比如, List 和 Set)同樣也支持使用 for-in 進行迭代操作 iteration :
var collection = [0, 1, 2];
for (var x in collection) {
print(x); // 0 1 2
}
extends super
擴展類(繼承)
使用 extends 關鍵字來創建子類, 使用 super 關鍵字來引用父類:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
async await
- 異步支持
Dart 庫中包含許多返回 Future 或 Stream 對象的函數. 這些函數在設置完耗時任務(例如 I/O 曹組)后, 就立即返回了,不會等待耗任務完成。 使用 async 和 await 關鍵字實現異步編程。 可以讓你像編寫同步代碼一樣實現異步操作。
- 處理 Future
可以通過下面兩種方式,獲得 Future 執行完成的結果:
使用 async 和 await.
使用 Future API,具體描述,參考 庫概覽.
使用 async 和 await 關鍵字的代碼是異步的。 雖然看起來有點想同步代碼。 例如,下面的代碼使用 await 等待異步函數的執行結果。
```
await lookUpVersion();
```
要使用 await , 代碼必須在 異步函數(使用 async 標記的函數)中:
```
Future checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
```
提示: 雖然異步函數可能會執行耗時的操作, 但它不會等待這些操作。 相反,異步函數只有在遇到第一個 await 表達式(詳情見)時才會執行。 也就是說,它返回一個 Future 對象, 僅在await表達式完成后才恢復執行。
使用 try, catch, 和 finally 來處理代碼中使用 await 導致的錯誤。
```
try {
version = await lookUpVersion();
} catch (e) {
// React to inability to look up the version
}
```
在一個異步函數中可以多次使用 await 。 例如,下面代碼中等待了三次函數結果:
```
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
```
在 await 表達式 中, 表達式 的值通常是一個 Future 對象; 如果不是,這是表達式的值會被自動包裝成一個 Future 對象。 Future 對象指明返回一個對象的承諾(promise)。 await 表達式 執行的結果為這個返回的對象。 await 表達式會阻塞代碼的執行,直到需要的對象返回為止。
如果在使用 await 導致編譯時錯誤, 確認 await 是否在一個異步函數中。 例如,在應用的 main() 函數中使用 await , main() 函數的函數體必須被標記為 async :
Future main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}
-
聲明異步函數
函數體被 async 標示符標記的函數,即是一個異步函數。 將 async 關鍵字添加到函數使其返回Future。 例如,考慮下面的同步函數,它返回一個 String :String lookUpVersion() => '1.0.0';
例如,將來的實現將非常耗時,將其更改為異步函數,返回值是 Future 。
Future<String> lookUpVersion() async => '1.0.0';
注意,函數體不需要使用Future API。 如有必要, Dart 會創建 Future 對象。
如果函數沒有返回有效值, 需要設置其返回類型為 Future<void> 。
-
處理 Stream
當需要從 Stream 中獲取數據值時, 可以通過一下兩種方式:使用 async 和 一個 異步循環 (await for)。
使用 Stream API, 更多詳情,參考 in the library tour。
提示: 在使用 await for 前,確保代碼清晰, 并且確實希望等待所有流的結果。 例如,通常不應該使用 await for 的UI事件偵聽器, 因為UI框架會發送無窮無盡的事件流。一下是異步for循環的使用形式:
await for (varOrType identifier in expression) { // Executes each time the stream emits a value. }
上面 表達式 返回的值必須是 Stream 類型。 執行流程如下:
- 等待,直到流發出一個值。
- 執行 for 循環體,將變量設置為該發出的值
- 重復1和2,直到關閉流。
使用 break 或者 return 語句可以停止接收 stream 的數據, 這樣就跳出了 for 循環, 并且從 stream 上取消注冊。 如果在實現異步 for 循環時遇到編譯時錯誤, 請檢查確保 await for 處于異步函數中。 例如,要在應用程序的 main() 函數中使用異步 fo r循環, main() 函數體必須標記為 async` :
Future main() async { // ... await for (var request in requestServer) { handleRequest(request); } // ... }
有關異步編程的更多信息,請參考 dart:async 部分。 同時也可參考文章 Dart Language Asynchrony Support: Phase 1 和 Dart Language Asynchrony Support: Phase 2, 以及 Dart language specification 。
switch case
在 Dart 中 switch 語句使用 == 比較整數,字符串,或者編譯時常量。 比較的對象必須都是同一個類的實例(并且不可以是子類), 類必須沒有對 == 重寫。 枚舉類型 可以用于 switch 語句。
提示: 在 Dart 中 Switch 語句僅適用于有限的情況下, 例如在 interpreter 或 scanner 中。
在 case 語句中,每個非空的 case 語句結尾需要跟一個 break 語句。 除 break 以外,還有可以使用 continue, throw,者 return。
當沒有 case 語句匹配時,執行 default 代碼:
var command = 'OPEN';
switch (command) {
case 'CLOSED':
executeClosed();
break;
case 'PENDING':
executePending();
break;
case 'APPROVED':
executeApproved();
break;
case 'DENIED':
executeDenied();
break;
case 'OPEN':
executeOpen();
break;
default:
executeUnknown();
}
下面的 case 程序示例中缺省了 break 語句,導致錯誤:
var command = 'OPEN';
switch (command) {
case 'OPEN':
executeOpen();
// ERROR: 丟失 break
case 'CLOSED':
executeClosed();
break;
}
但是, Dart 支持空 case 語句, 允許程序以 fall-through 的形式執行。
var command = 'CLOSED';
switch (command) {
case 'CLOSED': // Empty case falls through.
case 'NOW_CLOSED':
// Runs for both CLOSED and NOW_CLOSED.
executeNowClosed();
break;
}
在非空 case 中實現 fall-through 形式, 可以使用 continue 語句結合 lable 的方式實現:
var command = 'CLOSED';
switch (command) {
case 'CLOSED':
executeClosed();
continue nowClosed;
// Continues executing at the nowClosed label.
nowClosed:
case 'NOW_CLOSED':
// Runs for both CLOSED and NOW_CLOSED.
executeNowClosed();
break;
}
case 語句可以擁有局部變量, 這些局部變量只能在這個語句的作用域中可見。
sync
生成器
當您需要延遲生成( lazily produce )一系列值時, 可以考慮使用生成器函數。 Dart 內置支持兩種生成器函數:
- Synchronous 生成器: 返回一個 Iterable 對象。
- Asynchronous 生成器: 返回一個 Stream 對象。
通過在函數體標記 sync*, 可以實現一個同步生成器函數。 使用 yield 語句來傳遞值:
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
通過在函數體標記 async*, 可以實現一個異步生成器函數。 使用 yield 語句來傳遞值:
Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}
如果生成器是遞歸的,可以使用 yield* 來提高其性能:
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
有關生成器的更多信息,請參考文章 Dart Language Asynchrony Support: Phase 2 。
break continue
使用 break 停止程序循環:
while (true) {
if (shutDownRequested()) break;
processIncomingRequests();
}
使用 continue 跳轉到下一次迭代:
for (int i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
if (candidate.yearsExperience < 5) {
continue;
}
candidate.interview();
}
如果對象實現了 Iterable 接口 (例如,list 或者 set)。 那么上面示例完全可以用另一種方式來實現:
candidates
.where((c) => c.yearsExperience >= 5)
.forEach((c) => c.interview());
Mixin
為類添加功能: Mixin
Mixin 是復用類代碼的一種途徑, 復用的類可以在不同層級,之間可以不存在繼承關系。
通過 with 后面跟一個或多個混入的名稱,來 使用 Mixin , 下面的示例演示了兩個使用 Mixin 的類:
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
通過創建一個繼承自 Object 且沒有構造函數的類,來 實現 一個 Mixin 。 如果 Mixin 不希望作為常規類被使用,使用關鍵字 mixin 替換 class 。 例如:
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}
指定只有某些類型可以使用的 Mixin - 比如, Mixin 可以調用 Mixin 自身沒有定義的方法 - 使用 on 來指定可以使用 Mixin 的父類類型:
mixin MusicalPerformer on Musician {
// ···
}
版本提示: mixin 關鍵字在 Dart 2.1 中被引用支持。 早期版本中的代碼通常使用 abstract class 代替。 更多有關 Mixin 在 2.1 中的變更信息,請參見 Dart SDK changelog 和 2.1 mixin specification 。
提示: 對 Mixin 的一些限制正在被移除。 關于更多詳情,參考 proposed mixin specification.
有關 Dart 中 Mixin 的理論演變,參考 A Brief History of Mixins in Dart.
Final Const
Final 和 Const
使用過程中從來不會被修改的變量, 可以使用 final 或 const, 而不是 var 或者其他類型, Final 變量的值只能被設置一次; Const 變量在編譯時就已經固定 (Const 變量 是隱式 Final 的類型.) 最高級 final 變量或類變量在第一次使用時被初始化。
提示: 實例變量可以是 final 類型但不能是 const 類型。 必須在構造函數體執行之前初始化 final 實例變量 —— 在變量聲明中,參數構造函數中或構造函數的初始化列表中進行初始化。
創建和設置一個 Final 變量:
final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';
final 不能被修改:
name = 'Alice'; // Error: 一個 final 變量只能被設置一次。
如果需要在編譯時就固定變量的值,可以使用 const 類型變量。 如果 Const 變量是類級別的,需要標記為 static const。 在這些地方可以使用在編譯時就已經固定不變的值,字面量的數字和字符串, 固定的變量,或者是用于計算的固定數字:
const bar = 1000000; // 壓力單位 (dynes/cm2)
const double atm = 1.01325 * bar; // 標準氣壓
Const 關鍵字不僅可以用于聲明常量變量。 還可以用來創建常量值,以及聲明創建常量值的構造函數。 任何變量都可以擁有常量值。
var foo = const [];
final bar = const [];
const baz = []; // Equivalent to `const []`
聲明 const 的初始化表達式中 const 可以被省略。 比如上面的 baz。 有關更多信息,參考 DON’T use const redundantly。
非 Final , 非 const 的變量是可以被修改的,即使這些變量 曾經引用過 const 值。
foo = [1, 2, 3]; // 曾經引用過 const [] 常量值。
Const 變量的值不可以修改:
baz = [42]; // Error: 常量變量不能賦值修改。
更多關于使用 const 創建常量值,參考 Lists, Maps, 和 Classes。
throw catch finnaly on rethrow
異常
Dart 代碼可以拋出和捕獲異常。 異常表示一些未知的錯誤情況。 如果異常沒有被捕獲, 則異常會拋出, 導致拋出異常的代碼終止執行。
和 Java 有所不同, Dart 中的所有異常是非檢查異常。 方法不會聲明它們拋出的異常, 也不要求捕獲任何異常。
Dart 提供了 Exception 和 Error 類型, 以及一些子類型。 當然也可以定義自己的異常類型。 但是,此外 Dart 程序可以拋出任何非 null 對象, 不僅限 Exception 和 Error 對象。
-
throw
下面是關于拋出或者 引發 異常的示例:throw FormatException('Expected at least 1 section');
也可以拋出任意的對象:throw 'Out of llamas!';
提示: 高質量的生產環境代碼通常會實現 Error 或 Exception 類型的異常拋出。因為拋出異常是一個表達式, 所以可以在 => 語句中使用,也可以在其他使用表達式的地方拋出異常:
void distanceTo(Point other) => throw UnimplementedError();
catch
捕獲異??梢员苊猱惓@^續傳遞(除非重新拋出( rethrow )異常)。 可以通過捕獲異常的機會來處理該異常:try { breedMoreLlamas(); } on OutOfLlamasException { buyMoreLlamas(); }
通過指定多個 catch 語句,可以處理可能拋出多種類型異常的代碼。 與拋出異常類型匹配的第一個 catch 語句處理異常。 如果 catch 語句未指定類型, 則該語句可以處理任何類型的拋出對象:
try { breedMoreLlamas(); } on OutOfLlamasException { // 一個特殊的異常 buyMoreLlamas(); } on Exception catch (e) { // 其他任何異常 print('Unknown exception: $e'); } catch (e) { // 沒有指定的類型,處理所有異常 print('Something really unknown: $e'); }
-
on catch
如上述代碼所示,捕獲語句中可以同時使用 on 和 catch ,也可以單獨分開使用。 使用 on 來指定異常類型, 使用 catch 來 捕獲異常對象。
catch() 函數可以指定1到2個參數, 第一個參數為拋出的異常對象, 第二個為堆棧信息 ( 一個 StackTrace 對象 )。
try { // ··· } on Exception catch (e) { print('Exception details:\n $e'); } catch (e, s) { print('Exception details:\n $e'); print('Stack trace:\n $s'); }
-
rethrow
如果僅需要部分處理異常, 那么可以使用關鍵字 rethrow 將異常重新拋出。void misbehave() { try { dynamic foo = true; print(foo++); // Runtime error } catch (e) { print('misbehave() partially handled ${e.runtimeType}.'); rethrow; // Allow callers to see the exception. } } void main() { try { misbehave(); } catch (e) { print('main() finished handling ${e.runtimeType}.'); } }
-
finally
不管是否拋出異常, finally 中的代碼都會被執行。 如果 catch 沒有匹配到異常, 異常會在 finally 執行完成后,再次被拋出:try { breedMoreLlamas(); } finally { // Always clean up, even if an exception is thrown. cleanLlamaStalls(); }
任何匹配的 catch 執行完成后,再執行 finally :
try { breedMoreLlamas(); } catch (e) { print('Error: $e'); // Handle the exception first. } finally { cleanLlamaStalls(); // Then clean up. }
更多詳情,請參考 Exceptions 章節。
Typedefs
在 Dart 中,函數也是對象,就想字符和數字對象一樣。 使用 typedef ,或者 function-type alias 為函數起一個別名, 別名可以用來聲明字段及返回值類型。 當函數類型分配給變量時,typedef會保留類型信息。
請考慮以下代碼,代碼中未使用 typedef :
class SortedCollection {
Function compare;
SortedCollection(int f(Object a, Object b)) {
compare = f;
}
}
// Initial, broken implementation. // broken ?
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
// 雖然知道 compare 是函數,
// 但是函數是什么類型 ?
assert(coll.compare is Function);
}
當把 f 賦值給 compare 的時候,類型信息丟失了。 f 的類型是 (Object, Object) → int (這里 → 代表返回值類型), 但是 compare 得到的類型是 Function 。如果我們使用顯式的名字并保留類型信息, 這樣開發者和工具都可以使用這些信息:
typedef Compare = int Function(Object a, Object b);
class SortedCollection {
Compare compare;
SortedCollection(this.compare);
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
assert(coll.compare is Function);
assert(coll.compare is Compare);
}
提示: 目前,typedefs 只能使用在函數類型上, 我們希望將來這種情況有所改變。
由于 typedefs 只是別名, 他們還提供了一種方式來判斷任意函數的類型。例如:
typedef Compare<T> = int Function(T a, T b);
int sort(int a, int b) => a - b;
void main() {
assert(sort is Compare<int>); // True!
}
Function
Dart 是一門真正面向對象的語言, 甚至其中的函數也是對象,并且有它的類型 Function 。 這也意味著函數可以被賦值給變量或者作為參數傳遞給其他函數。 也可以把 Dart 類的實例當做方法來調用。 有關更多信息,參考 Callable classes.
Deferred
延遲加載庫
Deferred loading (也稱之為 lazy loading) 可以讓應用在需要的時候再加載庫。 下面是一些使用延遲加載庫的場景:
減少 APP 的啟動時間。
執行 A/B 測試,例如 嘗試各種算法的 不同實現。
加載很少使用的功能,例如可選的屏幕和對話框。
要延遲加載一個庫,需要先使用 deferred as 來導入:
import 'package:greetings/hello.dart' deferred as hello;
當需要使用的時候,使用庫標識符調用 loadLibrary() 函數來加載庫:
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
在前面的代碼,使用 await 關鍵字暫停代碼執行一直到庫加載完成。 關于 async 和 await 的更多信息請參考 異步支持。
在一個庫上你可以多次調用 loadLibrary() 函數。但是該庫只是載入一次。
使用延遲加載庫的時候,請注意一下問題:
延遲加載庫的常量在導入的時候是不可用的。 只有當庫加載完畢的時候,庫中常量才可以使用。
在導入文件的時候無法使用延遲庫中的類型。 如果你需要使用類型,則考慮把接口類型移動到另外一個庫中, 讓兩個庫都分別導入這個接口庫。
Dart 隱含的把 loadLibrary() 函數導入到使用 deferred as 的命名空間 中。 loadLibrary() 方法返回一個 Future。
變量類型
Dart 語言支持以下內建類型:
- Number
- String
- Boolean
- List (也被稱為 Array)
- Map
- Set
- Rune (用于在字符串中表示 Unicode 字符)
- Symbol
這些類型都可以被初始化為字面量。 例如, 'this is a string' 是一個字符串的字面量, true 是一個布爾的字面量。
因為在 Dart 所有的變量終究是一個對象(一個類的實例), 所以變量可以使用 構造函數 進行初始化。 一些內建類型擁有自己的構造函數。 例如, 通過 Map() 來構造一個 map 變量。
-
Number
Dart 語言的 Number 有兩種類型:
-
int
整數值不大于64位, 具體取決于平臺。 在 Dart VM 上, 值的范圍從 -263 到 263 - 1. Dart 被編譯為 JavaScript 時,使用 JavaScript numbers, 值的范圍從 -253 到 253 - 1.
-
double
64位(雙精度)浮點數,依據 IEEE 754 標準。
int 和 double 都是 num. 的亞類型。 num 類型包括基本運算 +, -, /, 和 *, 以及 abs(), ceil(), 和 floor(), 等函數方法。 (按位運算符,例如?,定義在 int 類中。) 如果 num 及其亞類型找不到你想要的方法, 嘗試查找使用 dart:math 庫。
整數類型不包含小數點。 下面是定義整數類型字面量的例子:
var x = 1; var hex = 0xDEADBEEF;
如果一個數字包含小數點,那么就是小數類型。 下面是定義小數類型字面量的例子:
var y = 1.1; var exponents = 1.42e5;
從 Dart 2.1 開始,必要的時候 int 字面量會自動轉換成 double 類型。
double z = 1; // 相當于 double z = 1.0.
版本提示: 在 2.1 之前,在 double 上下文中使用 int 字面量是錯誤的。
以下是將字符串轉換為數字的方法,反之亦然:
// String -> int var one = int.parse('1'); assert(one == 1); // String -> double var onePointOne = double.parse('1.1'); assert(onePointOne == 1.1); // int -> String String oneAsString = 1.toString(); assert(oneAsString == '1'); // double -> String String piAsString = 3.14159.toStringAsFixed(2); assert(piAsString == '3.14');
int 特有的傳統按位運算操作,移位(<<, >>),按位與(&)以及 按位或(|)。 例如:
assert((3 << 1) == 6); // 0011 << 1 == 0110 assert((3 >> 1) == 1); // 0011 >> 1 == 0001 assert((3 | 4) == 7); // 0011 | 0100 == 0111
數字類型字面量是編譯時常量。 在算術表達式中,只要參與計算的因子是編譯時常量, 那么算術表達式的結果也是編譯時常量。
const msPerSecond = 1000; const secondsUntilRetry = 5; const msUntilRetry = secondsUntilRetry * msPerSecond;
-
-
String
Dart 字符串是一組 UTF-16 單元序列。 字符串通過單引號或者雙引號創建。
var s1 = 'Single quotes work well for string literals.'; var s2 = "Double quotes work just as well."; var s3 = 'It\'s easy to escape the string delimiter.'; var s4 = "It's even easier to use the other delimiter.";
字符串可以通過 ${expression} 的方式內嵌表達式。 如果表達式是一個標識符,則 {} 可以省略。 在 Dart 中通過調用就對象的 toString() 方法來得到對象相應的字符串。
var s = 'string interpolation'; assert('Dart has $s, which is very handy.' == 'Dart has string interpolation, ' + 'which is very handy.'); assert('That deserves all caps. ' + '${s.toUpperCase()} is very handy!' == 'That deserves all caps. ' + 'STRING INTERPOLATION is very handy!');
提示: == 運算符用來測試兩個對象是否相等。 在字符串中,如果兩個字符串包含了相同的編碼序列,那么這兩個字符串相等。 units.
可以使用 + 運算符來把多個字符串連接為一個,也可以把多個字面量字符串寫在一起來實現字符串連接:
var s1 = 'String ' 'concatenation' " works even over line breaks."; assert(s1 == 'String concatenation works even over ' 'line breaks.'); var s2 = 'The + operator ' + 'works, as well.'; assert(s2 == 'The + operator works, as well.');
使用連續三個單引號或者三個雙引號實現多行字符串對象的創建:
var s1 = ''' You can create multi-line strings like this one. '''; var s2 = """This is also a multi-line string.""";
使用 r 前綴,可以創建 “原始 raw” 字符串:
var s = r"In a raw string, even \n isn't special.";
參考 Runes 來了解如何在字符串中表達 Unicode 字符。
一個編譯時常量的字面量字符串中,如果存在插值表達式,表達式內容也是編譯時常量, 那么該字符串依舊是編譯時常量。 插入的常量值類型可以是 null,數值,字符串或布爾值。
// const 類型數據 const aConstNum = 0; const aConstBool = true; const aConstString = 'a constant string'; // 非 const 類型數據 var aNum = 0; var aBool = true; var aString = 'a string'; const aConstList = [1, 2, 3]; const validConstString = '$aConstNum $aConstBool $aConstString'; //const 類型數據 // const invalidConstString = '$aNum $aBool $aString $aConstList'; //非 const 類型數據
更多關于 string 的使用, 參考 字符串和正則表達式.
-
Boolean
Dart 使用 bool 類型表示布爾值。 Dart 只有字面量 true and false 是布爾類型, 這兩個對象都是編譯時常量。Dart 的類型安全意味著不能使用 if (nonbooleanValue) 或者 assert (nonbooleanValue)。 而是應該像下面這樣,明確的進行值檢查:
// 檢查空字符串。 var fullName = ''; assert(fullName.isEmpty); // 檢查 0 值。 var hitPoints = 0; assert(hitPoints <= 0); // 檢查 null 值。 var unicorn; assert(unicorn == null); // 檢查 NaN 。 var iMeantToDoThis = 0 / 0; assert(iMeantToDoThis.isNaN);
-
-
List
幾乎每種編程語言中最常見的集合可能是 array 或有序的對象集合。 在 Dart 中的 Array 就是 List 對象, 通常稱之為 List 。Dart 中的 List 字面量非常像 JavaScript 中的 array 字面量。 下面是一個 Dart List 的示例:
var list = [1, 2, 3];
提示: Dart 推斷 list 的類型為 List<int> 。 如果嘗試將非整數對象添加到此 List 中, 則分析器或運行時會引發錯誤。 有關更多信息,請閱讀 類型推斷。
Lists 的下標索引從 0 開始,第一個元素的索引是 0。 list.length - 1 是最后一個元素的索引。 訪問 List 的長度和元素與 JavaScript 中的用法一樣:
var list = [1, 2, 3]; assert(list.length == 3); assert(list[1] == 2); list[1] = 1; assert(list[1] == 1);
在 List 字面量之前添加 const 關鍵字,可以定義 List 類型的編譯時常量:
var constantList = const [1, 2, 3]; // constantList[1] = 1; // 取消注釋會引起錯誤。
List 類型包含了很多 List 的操作函數。 更多信息參考 泛型 和 集合.
-
Set
在 Dart 中 Set 是一個元素唯一且無需的集合。 Dart 為 Set 提供了 Set 字面量和 Set 類型。版本提示: 雖然 Set 類型 一直是 Dart 的核心部分, 但在 Dart2.2 中才引入了 Set 字面量 。
下面是通過字面量創建 Set 的一個簡單示例:
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
Note: Dart 推斷 halogens 類型為 Set<String> 。如果嘗試為它添加一個 錯誤類型的值,分析器或執行時會拋出錯誤。更多內容,參閱 類型推斷。
要創建一個空集,使用前面帶有類型參數的 {} ,或者將 {} 賦值給 Set 類型的變量:
var names = <String>{}; // Set<String> names = {}; // 這樣也是可以的。 // var names = {}; // 這樣會創建一個 Map ,而不是 Set 。
是 Set 還是 Map ? Map 字面量語法同 Set 字面量語法非常相似。 因為先有的 Map 字母量語法,所以 {} 默認是 Map 類型。 如果忘記在 {} 上注釋類型或賦值到一個未聲明類型的變量上, 那么 Dart 會創建一個類型為 Map<dynamic, dynamic> 的對象。
使用 add() 或 addAll() 為已有的 Set 添加元素:
var elements = <String>{}; elements.add('fluorine'); elements.addAll(halogens);
使用 .length 來獲取 Set 中元素的個數:
var elements = <String>{}; elements.add('fluorine'); elements.addAll(halogens); assert(elements.length == 5);
在 Set 字面量前增加 const ,來創建一個編譯時 Set 常量:
final constantSet = const { 'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine', }; // constantSet.add('helium'); // Uncommenting this causes an error.
更多關于 Set 的內容,參閱 Generic 及 Set。
-
Map
通常來說, Map 是用來關聯 keys 和 values 的對象。 keys 和 values 可以是任何類型的對象。在一個 Map 對象中一個 key 只能出現一次。 但是 value 可以出現多次。 Dart 中 Map 通過 Map 字面量 和 Map 類型來實現。下面是使用 Map 字面量的兩個簡單例子:
var gifts = { // Key: Value 'first': 'partridge', 'second': 'turtledoves', 'fifth': 'golden rings' }; var nobleGases = { 2: 'helium', 10: 'neon', 18: 'argon', };
提示: Dart 會將 gifts 的類型推斷為 Map<String, String>, nobleGases 的類型推斷為 Map<int, String> 。 如果嘗試在上面的 map 中添加錯誤類型,那么分析器或者運行時會引發錯誤。 有關更多信息,請閱讀類型推斷。。
以上 Map 對象也可以使用 Map 構造函數創建:
var gifts = Map(); gifts['first'] = 'partridge'; gifts['second'] = 'turtledoves'; gifts['fifth'] = 'golden rings'; var nobleGases = Map(); nobleGases[2] = 'helium'; nobleGases[10] = 'neon'; nobleGases[18] = 'argon';
提示: 這里為什么只有 Map() ,而不是使用 new Map()。 因為在 Dart 2 中,new 關鍵字是可選的。 有關更多信息,參考 構造函數的使用。
類似 JavaScript ,添加 key-value 對到已有的 Map 中:
var gifts = {'first': 'partridge'}; gifts['fourth'] = 'calling birds'; // Add a key-value pair
類似 JavaScript ,從一個 Map 中獲取一個 value:
var gifts = {'first': 'partridge'}; assert(gifts['first'] == 'partridge');
如果 Map 中不包含所要查找的 key,那么 Map 返回 null:
var gifts = {'first': 'partridge'}; assert(gifts['fifth'] == null);
使用 .length 函數獲取當前 Map 中的 key-value 對數量:
var gifts = {'first': 'partridge'}; gifts['fourth'] = 'calling birds'; assert(gifts.length == 2);
創建 Map 類型運行時常量,要在 Map 字面量前加上關鍵字 const。
final constantMap = const { 2: 'helium', 10: 'neon', 18: 'argon', }; // constantMap[2] = 'Helium'; // 取消注釋會引起錯誤。
更名多關于 Map 的內容,參考 Generics and Maps.
-
Rune
在 Dart 中, Rune 用來表示字符串中的 UTF-32 編碼字符。Unicode 定義了一個全球的書寫系統編碼, 系統中使用的所有字母,數字和符號都對應唯一的數值編碼。 由于 Dart 字符串是一系列 UTF-16 編碼單元, 因此要在字符串中表示32位 Unicode 值需要特殊語法支持。
表示 Unicode 編碼的常用方法是, \uXXXX, 這里 XXXX 是一個4位的16進制數。 例如,心形符號 (?) 是 \u2665。 對于特殊的非 4 個數值的情況, 把編碼值放到大括號中即可。 例如,emoji 的笑臉 (?) 是 \u{1f600}。
String 類有一些屬性可以獲得 rune 數據。 屬性 codeUnitAt 和 codeUnit 返回16位編碼數據。 屬性 runes 獲取字符串中的 Rune 。
######提示: 謹慎使用 list 方式操作 Rune 。 這種方法很容易引發崩潰, 具體原因取決于特定的語言,字符集和操作。 有關更多信息,參考 How do I reverse a String in Dart? on Stack Overflow.
-
Symbol
一個 Symbol 對象表示 Dart 程序中聲明的運算符或者標識符。 你也許永遠都不需要使用 Symbol ,但要按名稱引用標識符的 API 時, Symbol 就非常有用了。 因為代碼壓縮后會改變標識符的名稱,但不會改變標識符的符號。 通過字面量 Symbol ,也就是標識符前面添加一個 # 號,來獲取標識符的 Symbol 。
#radix #bar
Symbol 字面量是編譯時常量。
控制流程語句
你可以通過下面任意一種方式來控制 Dart 程序流程:
if and else
for loops
while and do-while loops
break and continue
switch and case
assert
使用 try-catch 和 throw 也可以改變程序流程, 詳見 Exceptions。