天天看點

1.學習 dart 語言的基本文法一段 Dart 的基礎代碼學習 Dart 的注意事項Dart 語言中的保留關鍵字變量預設值不可更變的量Dart 的内置變量類型函數運算符控制流語句異常類泛型庫和可見性異步支援生成器回調類隔離器類型定義中繼資料注釋總結

學習主幹:一名PHP程式員的Flutter學習之路

學習 dart 語言的基本文法,Dart 版本 2.5

  • 一段 Dart 的基礎代碼
  • 學習 Dart 的注意事項
  • Dart 語言中的保留關鍵字
  • 變量
  • 預設值
  • 不可更變的量
    • final
    • const
  • Dart 的内置變量類型
    • numbers
      • int
      • double
    • strings
    • booleans
    • sets
    • lists
    • maps
    • Runes
    • symbols
  • 函數
    • 可選參數
      • 命名參數
      • 位置參數
      • 預設參數值
    • 主函數
    • 第一類對象
    • 匿名函數
    • 詞法範圍
    • 文法閉包
    • 檢查是否相等的測試函數
    • 傳回值
  • 運算符
    • 算術運算符
    • 平等與關系運算符
    • 類型測試運算符
    • 指派運算符
    • 按位和移位運算符
    • 條件表達式
    • 級聯符号
    • 其它運算符
  • 控制流語句
    • if and else
    • for loops
    • while and do-while loops
    • break and continue
    • switch and case
    • assert
  • 異常
    • Throw
    • Catch
    • Finally
    • 使用類成員
    • 使用構造函數
    • 擷取一個對象類型
    • 執行個體變量
    • 構造函數
      • 預設構造函數
      • 非繼承構造函數
      • 命名構造函數
      • 調用父類非預設構造函數
      • 初始化清單
      • 重定向構造函數
      • 常量構造函數
      • 工廠構造函數
    • 方法
      • 執行個體方法
      • 擷取器和修改器
      • 抽象方法
    • 抽象類
    • 隐式接口
    • 擴充類
      • 重載成員
      • 重寫運算符
      • noSuchMethod()
    • 枚舉類型
      • 使用枚舉
    • 向類裡添加功能:mixins
    • 類變量及方法
      • 靜态變量
      • 靜态方法
  • 泛型
    • 為什麼使用泛型
    • 使用集合文字
    • 在構造函數中使用參數類型
    • 限制參數類型
    • 通用方法
  • 庫和可見性
    • 使用庫
      • 指定庫字首
      • 隻引入庫的部分
      • 懶加載一個庫
    • 實作庫
  • 異步支援
    • Handling Futures
    • 聲明異步函數
    • Handling Streams
  • 生成器
  • 回調類
  • 隔離器
  • 類型定義
  • 中繼資料
  • 注釋
    • 單行注釋
    • 多行注釋
    • 文檔注釋
  • 總結

我們先來了解一下 Dart,它是一種适用于網際網路的開放源代碼程式設計語言,由 Google 主導開發,于2011年10月公開,目标在于成為下一代結構化Web開發語言。不過很可惜這個語言一直不溫不火,相信很多人之前甚至都沒聽過這個語言,直到 Flutter 出現後才被衆人知曉。

文檔參考:https://dart.dev/guides/language/language-tour

一段 Dart 的基礎代碼

// 定義一個函數
printNumber(num aNumber) {
  print('The number is $aNumber.'); // 列印内容
}

// 主程式執行入口
main() {
  var number = 42; // 定義并初始化一個變量
  printNumber(number); // 調用自定義的函數
}

// 列印結果
// The number is 42.
           

*讓上述代碼在 DartPad 中試運作

學習 Dart 的注意事項

  1. 所有能夠使用變量引用的都是對象,每個對象都是一個類的執行個體。在 Dart 中 甚至連數字、函數和 null 都是對象,所有的對象都繼承于 Object 類。
  2. 使用靜态類型(例如前面示例中的 num ) 可以更清晰的表明你的意圖,并且可以讓靜态分析工具來分析你的代碼,但這并不是強制性的(在調試代碼的時候你可能注意到,沒有指定類型的變量的類型為 dynamic)。
  3. Dart 在運作之前會先解析你的代碼,你可以通過使用類型或者編譯時常量來幫助 Dart 去捕獲異常來讓代碼運作的更高效。
  4. Dart 支援頂級函數 (例如 main()),同時還支援在類中定義函數,如:靜态函數 和 執行個體函數。 你還可以在函數中定義再函數(嵌套函數 或 局部函數)。
  5. Dart 還支援頂級變量,以及在類中定義變量(靜态變量 和 執行個體變量)。執行個體變量有時候被稱之為字段(Fields)或者屬性(Properties)。
  6. 和 Java 不同的是,Dart 沒有 public、protected 和 private 關鍵字。如果一個辨別符以 (_) 開頭,則該辨別符在庫内是私有的。詳情請參考:庫和可見性。
  7. 辨別符必須為字母或者下劃線開頭,後面可以是其他字元和數字的組合。
  8. 有時候表達式 expression 和語句 statement 是有差別的,是以我們會針對不同的情況去說明,例如,條件表達式 condition ? expr1 : expr2 的值為 expr1 或 expr2。将其與 if-else 語句進行比較是沒有價值的。一條語句通常包含了一個或多個表達式,但是一個表達式不能直接包含一條語句。
  9. Dart 開發時僅提示兩個等級的問題,分别是 警告 和 錯誤。

Dart 語言中的保留關鍵字

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. 右上角角标為 1 的單詞是上下文關鍵字,隻在特定的地方才具有意義,這些關鍵字在代碼的任何地方都是有效的。
  2. 右上角角标為 2 的單詞是内置關鍵字,為了簡化将 JavaScript 代碼移植到 Dart,這些關鍵字在大多數地方都是有效的辨別符,但不能用在 類(class) 或 類型名稱(Type names) 中,也不能用作導入字首。
  3. 右上角角标為 3 的單詞是新釋出的關鍵字,是 Dart 語言的保留關鍵字。與 Dart 1.0 版本之後添加的異步支援有關。在任何函數中中不能使用 await 或 yield 來修飾 async,async*,或者 sync*。
  4. 表格中無角标的單詞都是系統保留關鍵字,不能作為辨別符。

變量

變量執行個體:

// 向變量 name 指派 'uSee',那麼 name 就是一個字元串變量
var name = 'uSee';

// 動态變量,通過指派來指定類型,不像單一變量隻能是一種變量類型
dynamic name = 'uSee';
dynamic name = 1;
dynamic name = true;

// 單一類型變量
String name = 'uSee';
           

預設值

Int lineCount;
assert(lineCount == null); // 真實寫代碼時不用寫 assert 這一行
           

未初始化的變量的預設值為 null,就算是數字類型的變量的預設值也是 null,因為在 數字(與Dart中的所有其他内容一樣)都是對象。

不可更變的量

如果你聲明了一個變量但是又不想它被再指派,那麼就可以使用 final 或者 const 來修飾。

注意:執行個體變量可以用 final 聲明而不能使用 const

final

被 final 聲明的變量隻能在聲明的時候指派一次,執行個體代碼如下:

final name = 'uSee';
name = 'iSee';

// 無法修改 name 的值
// Error: a final variable can only be set once.
           

const

const 是常量, 如果 const 聲明是在類中,則需要定義為 static const。可以直接定義一個常量的值,也 可以定義一個常量并使用它來初始化其值。

const bar = 1000000;
const double atm = 1.01325 * bar;
           

const 不僅僅隻用來定義常量,也可以用來定義一個變量的類型,這種類型是不可改變的。

var foo = const [];
final bar = const [];
const baz = [];

foo = [1, 2, 3]; // Was const []
baz = [42]; // Error: Constant variables can't be assigned a value.
           

Dart 的内置變量類型

numbers

數值類型,該類型支援兩種子類型 int 和 double。

int

整數型,其取值通常位于 -253 和 253 之間。

double

64-bit (雙精度) 浮點型,符合 IEEE 754 标準。

strings

字元串類型,strings是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}這種方式将表達式的值放在字元串中。

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

可以使用 ”+“ 操作符來把多個字元串連接配接為一個字元串。

booleans

布爾值,布爾值隻有兩種結果 true 或 false。

var name = 'uSee';
if (name) {
  // 當 if 判斷 name 時,傳回的就是布爾值,目前傳回 true
  print('You have a name!');
}
           

sets

無序集合,集合中的值唯一。

如果需要建立一個空集,則要在類型參數之前使用{},或将{}配置設定給Set類型的變量。

var names = <String>{};
// 或者
Set<String> names = {};

// var names = {}; // 這樣寫建立的則是 map 而不是 set
           

lists

清單,可以看作 PHP 中的數組 Array。

var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);

list[1] = 1;
assert(list[1] == 1);
           
var constantList = const [1, 2, 3];
// constantList[1] = 1; // 取消注釋會導緻錯誤
           

maps

Map 是一個鍵值對相關的對象。 鍵和值可以是任何類型的對象,鍵名唯一,鍵值可重複。看起來似乎和 Javascript 中的對象一樣。

var gifts = {
  // Key  : Value
  'first' : 'partridge',
  'second': 'turtledoves',
  'fifth' : 'golden rings'
};

var nobleGases = {
  2 : 'helium',
  10: 'neon',
  18: 'argon',
};

// Dart 中 gifts 跟 nobleGases 的差別是其鍵(key)
// gifts 的 key 為 String,nobleGases 的 key 為 int
           

Runes

在 Dart 中,runes 是 UTF-32 的字元串代碼點。

main() {
  var clapping = '\u{1f44f}';
  print(clapping);
  print(clapping.codeUnits);
  print(clapping.runes.toList());

  Runes input = new Runes(
      '\u2665  \u{1f605}  \u{1f60e}  \u{1f47b}  \u{1f596}  \u{1f44d}');
  print(new String.fromCharCodes(input));
}
           

*讓上述代碼在 DartPad 中試運作

symbols

暫時忽略,可參考 官方文檔

函數

Dart 是一種真正的面向對象的語言,連函數甚至都是對象,并且是具有類型函數。 這意味着可以将函數配置設定給變量或作為參數傳遞給其他函數。 您也可以将Dart類的執行個體稱為函數。

有關詳細資訊,請參見可調用類(Callable classes)。

下面是實作函數的示例:

bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

// 當然你也可以不寫函數的定義
isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}
           

對于僅有一個表達式的函數,可使用縮寫文法:

=> expr 是 { return expr; } 形式的縮寫,我們也稱之為箭頭文法。

可選參數

可選參數可以是 命名參數 或 位置參數,但不能同時選擇。

命名參數

調用函數時,你可以使用 paramName:value 來指定命名參數。 例如:

在定義函數的時候,使用 {param1, param2, …} 的形式來指定命名參數。例如:

// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold, bool hidden}) {...}
           

盡管命名參數是一種可選參數,但是您可以使用 @required 對其進行标注,以聲明該參數是強制性的,必須為該參數提供一個值。 例如:

當建立滾動條而不指定子參數時,則分析器将報告錯誤。

這是則需要使用 @required 進行标注,請取決于 meta package 且導入 package:meta/meta.dart

位置參數

在參數裝在 中括号[] 中就表示該參數是位置參數。

String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}
           

不帶位置參數的調用此函數的示例:

傳入了第三個參數(位置參數)調用此函數的示例:

預設參數值

您的函數可以使用 指派号 = 來定義命名參數和位置參數的預設值,如果未提供預設值,則預設值為 null。

這是為命名參數設定預設值的示例:

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold = false, bool hidden = false}) {...}

// bold will be true; hidden will be false.
enableFlags(bold: true);
           

如何為位置參數設定預設值的示例:

String say(String from, String msg, [String device = 'carrier pigeon', String mood]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  if (mood != null) {
    result = '$result (in a $mood mood)';
  }
  return result;
}

assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');
           

還可以将 list 或 map 作為預設值傳遞。

void doStuff(
    {List<int> list = const [1, 2, 3],
    Map<String, String> gifts = const {
      'first' : 'paper',
      'second': 'cotton',
      'third' : 'leather'
    }}) {
  print('list:  $list');
  print('gifts: $gifts');
}
           

主函數

每個應用程式都必須含有頂級 main() 函數,該函數充當該應用程式的入口。 main() 函數傳回 void,并具有可選的 List <String> 參數作為 main() 函數的參數。

下面是一個 web 應用程式中的 main() 函數示例:

void main() {
  querySelector('#sample_text_id')
    ..text = 'Click me!'
    ..onClick.listen(reverseText);
}
           

這是一個帶有參數的指令行應用程式的 main() 函數示例:

// Run the app like this: dart args.dart 1 test
void main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');
}
           

您可以使用 args library 來定義和解析指令行參數。

第一類對象

你可以将一個函數作為參數傳遞給另一個函數。 例如:

void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

// Pass printElement as a parameter.
list.forEach(printElement);
           

還可以将函數指派給變量,例如:

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

匿名函數

大多數的函數是已經命名好的,例如 main() 或 printElement(),不過還可以建立一個沒有名稱的函數,稱之為 匿名函數,有時也可以建立一個 lambda 或 閉包。 你可以将匿名函數配置設定給變量,這樣可以更友善你操作它。

匿名函數看起來類似于命名函數,示例:

([[Type] param1[, …]]) {
  codeBlock;
};
           

下面的示例為一個匿名函數,該匿名函數傳入了個參數 item,為清單中的每個子項調用的函數将列印一個字元串,其中包括指定索引處的值。

void main() {
  var list = ['apples', 'bananas', 'oranges'];
  list.forEach((item) {
    print('${list.indexOf(item)}: $item');
  });
}
           

如果該函數僅包含一個語句,則可以使用箭頭符号将其縮短。

void main() {
  var list = ['apples', 'bananas', 'oranges'];
  list.forEach(
    (item) => print('${list.indexOf(item)}: $item'));
}
           

*讓上述代碼在 DartPad 中試運作

詞法範圍

Dart 是一種詞法範圍的語言,這意味着變量的範圍是靜态确定的,隻需通過代碼的布局即可。 你可以“由内向外擴充花括号”以檢視變量是否在範圍内。

bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;

    void nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}
           

注意:nestedFunction() 可以使用各個層級的變量。

文法閉包

閉包是可以在其文法範圍内通路變量的函數對象,即使該函數在其原始範圍之外使用也是如此。

閉包可以在其範圍中自定義變量,在以下示例中,makeAdder() 捕獲變量 addBy。無論傳回的函數到哪裡,它都會記住 addBy。

/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(num addBy) {
  // 這裡傳回的是一個閉包
  return (num i) => addBy + i;
}

void main() {
  // Create a function that adds 2.
  var add2 = makeAdder(2);

  // Create a function that adds 4.
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}
           

檢查是否相等的測試函數

這是測試頂級函數,靜态方法和執行個體方法是否相等的示例:

void foo() {} // A top-level function

class A {
  static void bar() {} // A static method
  void baz() {} // An instance method
}

void main() {
  var x;

  // Comparing top-level functions.
  x = foo;
  assert(foo == x);

  // Comparing static methods.
  x = A.bar;
  assert(A.bar == x);

  // Comparing instance methods.
  var v = A(); // Instance #1 of A
  var w = A(); // Instance #2 of A
  var y = w;
  x = w.baz;

  // These closures refer to the same instance (#2),
  // so they're equal.
  assert(y.baz == x);

  // These closures refer to different instances,
  // so they're unequal.
  assert(v.baz != w.baz);
}
           

傳回值

所有函數都傳回一個值,如果未定義傳回值則傳回 null,這是 Dart 語言隐式的添加到了函數體裡。

foo() {}
assert(foo() == null);
           

運算符

Dart 定義了下表中顯示的運算符。 你可以重載其中的許多運算符,請參考 可重載的運算符

Description (簡介) Operator (運算符)
unary postfix expr++    expr--    ()    []    .    ?.
unary prefix -expr    !expr    ~expr    ++expr    --expr
multiplicative *    /    %    ~/
additive +    -
shift <<    >>    >>>
bitwise AND &
bitwise XOR ^
bitwise OR |
relational and type test >=    >    <=    <    as    is    is!
equality == !=
logical AND &&
logical OR ||
if null ??
conditional expr1 ? expr2 : expr3
cascade . .
assignment =    *=    /=    +=    -=    &=    ^=    etc.

警告:運算符優先級請查閱 Dart語言規範。

使用運算符時即建立表達式,以下是一些運算符表達式的示例:

a++
a + b
a = b
a == b
c ? a : b
a is T
           

在運算符表中,每個運算符的優先級都比其後面各行中的運算符高。 例如,乘法運算符 % 的優先級高于相等運算符 ==,其優先級高于邏輯運算符 && 。 該優先級意味着以下兩行代碼以相同的方式執行:

// Parentheses improve readability.
if ((n % i == 0) && (d % i == 0)) ...

// Harder to read, but equivalent.
if (n % i == 0 && d % i == 0) ...
           

算術運算符

Operator (運算符) Meaning (含義)
+
-expr 一進制減号,也稱之為負号(反轉表達式符号)
*
/
~/ 取商(整數結果)
% 取模(餘數的整數結果)

示例:

assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // Result is a double
assert(5 ~/ 2 == 2); // Result is an int
assert(5 % 2 == 1); // Remainder

assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');
           

Dart 還支援字首和字尾的增量和減量運算符。

Operator (運算符) Meaning (含義)
++var var = var + 1 (表達式的值 = var + 1)
var++ var = var + 1 (表達式的值 = var)
−−var var = var – 1 (表達式的值 = var – 1)
var−− var = var – 1 (表達式的值 = var)

示例:

var a, b;

a = 0;
b = ++a; // Increment a before b gets its value.
assert(a == b); // 1 == 1

a = 0;
b = a++; // Increment a AFTER b gets its value.
assert(a != b); // 1 != 0

a = 0;
b = --a; // Decrement a before b gets its value.
assert(a == b); // -1 == -1

a = 0;
b = a--; // Decrement a AFTER b gets its value.
assert(a != b); // -1 != 0
           

平等與關系運算符

下表中列出了相等運算符和關系運算符的含義。

Operator (運算符) Meaning (含義)
== 等于
!= 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于

要測試兩個對象 X 和 Y是否相等,請使用 == 運算符。在極少數情況下,您需要知道兩個對象是否是完全相同的對象,請使用 identical() 函數。運算符的使用方式如下:

assert(2 == 2);
assert(2 != 3);
assert(3 >  2);
assert(2 <  3);
assert(3 >= 3);
assert(2 <= 3);
           

類型測試運算符

as,is 和 is! 運算符在運作時用來檢查類型非常友善。

Operator (運算符) Meaning (含義)
as Typecast(也用于指定庫字首)
is 如果檢驗對象是指定的類型,則傳回 True
is! 如果檢驗對象非指定的類型,則傳回 True

如果 obj 是由 T 的接口實作的,那麼表達式 obj is T 則傳回 True。例如:obj is Object 肯定總是傳回 True。

使用 as 運算符将對象轉換為特定類型。一般情況下,你應将其用在檢測對象語句的簡寫。 例如:

if (emp is Person) {
  // Type check
  emp.firstName = 'Bob';
}
           

使用 as 運算符可以縮短代碼:

如果 emp 為 null 或者不是 Person,則第一個示例不執行任何操作,而第二個示例則會引發異常。

指派運算符

你可以使用指派号 = 運算符來對變量進行指派。在變量為原本為 null 的情況下指派則需要使用 ??= 運算符。

// Assign value to a
a = value;
// Assign value to b if b is null; otherwise, b stays the same
b ??= value;
           

複合指派運算符 将運算與指派結合在一起。

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

邏輯運算符的示例:

if (!done && (col == 0 || col == 3)) {
  // ...Do something...
}
           

按位和移位運算符

Operator (運算符) Meaning (含義)
&
||
^ 異或 (同為 0,異為 1)
~expr 一進制按位補碼(0變為1,1變為0)
<< 往左位移
>> 往右位移

一個使用按位和移位運算符的示例:

final value = 0x22;
final bitmask = 0x0f;

assert((value & bitmask) == 0x02); // AND
assert((value & ~bitmask) == 0x20); // AND NOT
assert((value | bitmask) == 0x2f); // OR
assert((value ^ bitmask) == 0x2d); // XOR
assert((value << 4) == 0x220); // Shift left
assert((value >> 4) == 0x02); // Shift right
           

條件表達式

Dart 有兩種條件運算符,在你使用 if-else 表達式時更友善:

condition ? expr1 : expr2
// 如果條件為真,則結果為(傳回) expr1,否則結果為 expr2
           
expr1 ?? expr2
// 如果 expr1 非空則結果為(傳回) expr1,否則結果為 expr2
           

如果需要基于布爾表達式來判斷配置設定值時,請考慮使用 condition ? expr1 : expr2。

如果布爾表達式的結果為 null,請考慮使用 expr1 ?? expr2。

前面的示例至少可以用兩種其它其他方式編寫,但不那麼簡潔:

// Slightly longer version uses ?: operator.
String playerName(String name) => name != null ? name : 'Guest';

// Very long version uses if-else statement.
String playerName(String name) {
  if (name != null) {
    return name;
  } else {
    return 'Guest';
  }
}
           

級聯符号

級聯(..)允許對同一對象執行一系列操作。除了函數調用,還可以通路同一對象上的字段。這通常可以節省建立臨時變量的步驟,并允許編寫更多流暢的代碼。

思考以下代碼:

querySelector('#confirm') // Get an object.
  ..text = 'Confirm' // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));
           

第一個方法調用 querySelector() 傳回選擇器的對象。 遵循級聯表示法的代碼在此選擇器對象上運作,而忽略了可能傳回的其它後續值。

上一個示例等效于:

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
           

也可以嵌套級聯, 例如:

final addressBook = (AddressBookBuilder()
      ..name = 'jenny'
      ..email = '[email protected]'
      ..phone = (PhoneNumberBuilder()
            ..number = '415-555-0100'
            ..label = 'home')
          .build())
    .build();
           

注意在傳回實際對象的函數上構造級聯,例如以下的錯誤代碼:

var sb = StringBuffer();
sb.write('foo')
  ..write('bar'); // Error: method 'write' isn't defined for 'void'.
           

因為你不能在 void 上建構級聯,是以sb.write() 調用傳回 void,在此處建構級聯是以錯誤。

提示: 嚴格來說,級聯的 “雙點” 符号不是運算符。它隻是Dart文法的一部分。

其它運算符

在其他示例中,我們已經認識了大多數的運算符,接下來我們展示剩下的一些運算符:

Operator (運算符) Name(名稱) Meaning (含義)
() Function application 代表一個函數調用
[] List access 引用清單中指定索引處的值
. Member access 引用表達式的屬性,例如 foo.bar,意思是從表達式 foo 中選擇其屬性 bar
?. Conditional member access 類似于 ., 但是最左邊的操作數可以為 null; 例如 foo?.bar,意思是從表達式 foo 中選擇屬性bar,除非 foo 為 null(在這種情況下 foo?.bar 的值為 null)

有關 (.,|?.|..) 的更多資訊,請參見 Classes。

控制流語句

你可以使用以下任意方式控制 Dart 代碼的流程:

  • if and else
  • for loops
  • while and do-while loops
  • break and continue
  • switch and case
  • assert

你還可以使用 try-catch 和 throw 來影響控制流,參考 Exceptions

if and else

Dart 支援 if 語句和可選的 else 語句,如下例所示, 也可參閱 條件表達式。

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

不同于 JavaScript,條件表達式必須使用布爾值。 有關更多資訊請參見 布爾值。

for loops

你可以使用标準的 for 循環進行疊代,例如:

var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
  message.write('!');
}
           

Dart 語言種 for 循環内的閉包捕獲了索引的值,讓我們避免了 js 中常見的坑, 例如:

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

如預期的那樣,先輸出 0,然後輸出 1。相反,該示例将在 js 中先列印 2,然後還是列印 2。

如果要疊代的對象是一個 Iterable,則可以使用 forEach() 方法。 如果不需要了解目前的疊代計數器,則使用 forEach() 是一個不錯的選擇:

List 和 Set 之類的可疊代類也支援 for-in 形式的疊代:

var collection = [0, 1, 2];
for (var x in collection) {
  print(x); // 0 1 2
}
           

while and do-while loops

while 循環,在循環之前會先判斷開始條件:

while (!isDone()) {
  doSomething();
}
           

do-while 循環則是先執行一次循環代碼後再來判斷條件:

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

break and 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(例如清單或集合),則可以用不同的方式來編寫該示例:

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

switch and case

Dart 中的 switch 語句使用 == 比較整數、字元串或常量, 被比較的對象必須全部是同一類的執行個體(而不是其任何子類型的執行個體),并且該類不得覆寫 == 。 枚舉類型 在switch語句中表現不錯。

提示: Dart 中的 switch 語句僅在有限的情況下使用,例如在解釋器或掃描器中。

通常,每個非空的 case 子句都以 break 語句結尾。 結束非空的 case 子句還有其它的方式,如 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: Missing break

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

當然,Dart 也支援空子句,允許使用以下形式:

var command = 'CLOSED';
switch (command) {
  case 'CLOSED': // Empty case falls through.
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}
           

如果你想跳過失敗,可以使用 continue 語句和标簽:

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 子句可以具有局部變量,這些局部變量僅在該子句範圍内可使用。

assert

在開發過程中,使用斷言 - assert(condition, optionalMessage),如果條件為假,則中斷執行。你可以在本教程中找到斷言語句的示例。以下也是段驗示例:

// Make sure the variable has a non-null value.
assert(text != null);

// Make sure the value is less than 100.
assert(number < 100);

// Make sure this is an https URL.
assert(urlString.startsWith('https'));
           

要将消息附加到斷言,需要添加字元串作為斷言的第二個參數。

assert(urlString.startsWith('https'),
    'URL ($urlString) should start with "https".');
           

assert 的第一個參數可以是任何結果為布爾值的表達式。如果表達式的值為 true,則斷言成功并繼續執行。 如果為 false,則斷言失敗并引發異常(AssertionError)。

斷言再什麼時候起作用取決于你使用的工具和架構:

  • Flutter 在調試模式下啟用
  • 預設情況下,開發工具(例如 dartdevc)啟用斷言
  • 某些工具(例如 dart 和 dart2js)通過指令行标志 – enable-asserts 支援斷言。

    在生産模式時,斷言将被忽略且不會判斷斷言的參數。

異常

Dart 代碼可以引用并捕獲異常。異常即錯誤,訓示發生了意外情況。 如果未捕獲到異常,則引發異常的隔離将被挂起,并且通常隔離将終止程式。

與 Java 相比,Dart 的所有異常都是未經檢查的異常。 方法不聲明它們可能會引發哪些異常,并且不需捕獲任何異常。

Dart 提供 Exception 和 Error 類型,以及許多預定義的子類型。 當然,你可以定義自己的異常。 但是 Dart 程式可以将任何非 null 對象(不僅僅是 Exception 和 Error 對象)作為異常抛出。

Throw

以下時引發或引發異常的示例:

還可以抛出任意對象:

提示: 生産品質代碼通常會抛出已執行個體化的 Error 或 Exception 的類型。

因為抛出異常是一個表達式,是以可以在 => 語句以及允許表達式的其他任何地方抛出異常:

Catch

捕獲異常會阻止該異常的傳播(除非重新抛出該異常),捕獲異常後你就可以按自己的心意處理它:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}
           

要處理可能引發不止一種類型的異常的代碼,可以指定多個 catch 子句。 第一個與抛出的對象類型比對的 catch 子句處理異常。 如果 catch 子句未指定類型,則該子句可以處理任何類型的引發對象:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');
}
           

正如上述代碼,你可以同時使用 on 或 catch。在需要指定異常類型時使用,當異常處理程式需要異常對象時,則使用catch。

你可以往 catch() 中加入一個或兩個參數。第一個參數是引發的異常,而第二個參數是堆棧跟蹤(一個 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 關鍵字。

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

finally 子句在任何 catch 子句之後運作:

try {
  breedMoreLlamas();
} catch (e) {
  print('Error: $e'); // Handle the exception first.
} finally {
  cleanLlamaStalls(); // Then clean up.
}
           

你可以類庫導覽來學習了解更多關于 Exceptions 的資訊。

Dart 是一種具有類且基于 Mixin 的繼承的面向對象語言。 每個對象都是一個類的執行個體,并且所有類都來自Object。基于混合繼承,意味着盡管每個類(對象除外)都隻有一個父類,但是一個類的主體可以在多個類層次結構中複用。

使用類成員

對象具有由函數和資料(分别為方法和執行個體變量)組成的成員。調用方法時,您可以在對象上調用它:該方法可以通路該對象的功能和資料。

使用點 (.) 來調用執行個體的變量或方法:

var p = Point(2, 2);

// Set the value of the instance variable y.
p.y = 3;

// Get the value of y.
assert(p.y == 3);

// Invoke distanceTo() on p.
num distance = p.distanceTo(Point(4, 4));
           

在指派時,采用 (?.) 代替 (.),這是為了避免在最左邊的操作數為 null 時發生異常:

// If p is non-null, set its y value to 4.
p?.y = 4;
           

使用構造函數

你可以使用構造函數來建立對象,構造函數名稱可以是 ClassName 或 ClassName.identifier。例如以下代碼使用 Point() 和 Point.fromJson() 構造函數建立 Point 對象:

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
           

以下代碼具有相同的效果,但是在構造函數名稱之前使用可選的 new 關鍵字:

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
           

版本提示: New 關鍵字在 Dart 2 中成為了可選關鍵字。

一些類提供常量構造函數, 要使用常量構造函數建立編譯時常量,需要将 const 關鍵字放在構造函數名稱之前:

構造兩個相同的編譯時常量會産生一個規範的執行個體:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // They are the same instance!
           

在常量上下文中,可以在構造函數或文字之前省略 const。 例如面的代碼,它建立了一個 const 映射:

// Lots of const keywords here.
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
           

你可以省略除首次使用 const 關鍵字:

// Only one const, which establishes the constant context.
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
           

如果常量構造函數在常量上下文之外,且在不使用 const 的情況下被調用,則它将建立一個 非常量對象:

var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant

assert(!identical(a, b)); // NOT the same instance!
           

版本提示: 在 Dart 2 的恒定上下文中,const 關鍵字變為可選。

擷取一個對象類型

要在運作時擷取對象的類型,可以使用 Object 的 runtimeType 屬性,該屬性傳回 Type 對象。

到目前為止,你已經了解了如何使用課程, 本節的其餘部分将展示如何實作類。

執行個體變量

讓我們來看看如何聲明執行個體變量:

class Point {
  num x; // Declare instance variable x, initially null.
  num y; // Declare y, initially null.
  num z = 0; // Declare z, initially 0.
}
           

所有未初始化的執行個體變量的值都為 null。

所有執行個體變量都會生成一個的 getter 方法。非最終執行個體變量也會自動生成的 setter 方法。有關詳細資訊,請參見 Getters 和 Setters。

class Point {
  num x;
  num y;
}

void main() {
  var point = Point();
  point.x = 4; // Use the setter method for x.
  assert(point.x == 4); // Use the getter method for x.
  assert(point.y == null); // Values default to null.
}
           

如果你在聲明了執行個體變量的地方(不是在構造函數或方法中)初始化執行個體變量,則在建立執行個體時(即在構造函數及其初始化程式清單執行之前)設定值。

構造函數

通過建立一個與其類名相同的函數來聲明一個構造函數(另加一個可選的辨別符,如“命名構造函數”中所述)。構造函數的最常見形式是 生成構造函器(the generative constructor),它建立類的新執行個體:

class Point {
  num x, y;

  Point(num x, num y) {
    // There's a better way to do this, stay tuned.
    this.x = x;
    this.y = y;
  }
}
           

this 關鍵字引用目前執行個體。

提示: 僅當名稱沖突時才使用 this 選項。 否則,Dart 将忽略它。

将構造函數參數配置設定給執行個體變量的模式非常普遍,Dart 具有的文法糖将使這種方式的實作變得簡單:

class Point {
  num x, y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}
           

預設構造函數

如果不聲明構造函數,系統将會為你提供預設的構造函數。預設構造函數沒有參數,并在父類中調用無參數構造函數。

非繼承構造函數

子類不會從其父類中繼承構造函數。聲明沒有構造函數的子類僅具有預設(沒有參數,沒有名稱)構造函數。

命名構造函數

使用命名構造函數可以為一個類實作多個構造函數或提供額外的 思路(clarity 不知道怎麼翻譯 ?):

class Point {
  num x, y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin() {
    x = 0;
    y = 0;
  }
}
           

記住,構造函數是不能繼承的,這意味着父類的命名構造函數不會被子類繼承。 如果要使用超類中定義的命名構造函數建立子類,則必須在子類中先實作父類的構造函數。

調用父類非預設構造函數

預設情況下,子類中的構造函數會調用父類的未命名且無參數的構造函數。父類的構造函數在構造函數主體的開頭被調用。如果還使用了初始化的 list,它将在調用父類之前執行。詳細執行順序如下:

  • 初始化清單
  • 父類無參數構造函數
  • 主類無參數構造函數

如果父類沒有未命名、無參數的構造函數,則必須手動調用父類中的構造函數之一。在構造函數主體(如果有)之前,在冒号 (:) 之後指定父類構造函數。

在下面的示例中,Employee 類的構造函數為其父類 Person 調用命名的構造函數。

讓下述代碼在 DartPad 中試運作

class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // Prints:
  // in Person
  // in Employee
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
}
           

因為父類構造函數的參數是在調用構造函數之前求值的,是以參數可以是表達式,例如函數調用:

class Employee extends Person {
  Employee() : super.fromJson(getDefaultData());
  // ···
}
           

警告: 父類構造函數的參數無權限通路此函數。 例如,參數可以調用靜态方法,而不能調用執行個體方法。

初始化清單

除了調用父類構造函數外,還可以在構造函數主體運作之前初始化執行個體變量,用逗号分隔初始化程式。

// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}
           

警告: 初始化程式的右側無權通路此方法。

在開發過程中,你可以通過使用初始化清單中的 assert 來驗證輸入。

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}
           

在設定最終字段時使用初始化清單很友善。以下示例在初始化清單中初始化三個最終字段。

讓下述代碼在 DartPad 中試運作

import 'dart:math';

class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = new Point(2, 3);
  print(p.distanceFromOrigin);
}
           

重定向構造函數

有時,構造函數的唯一目的是重定向到同一類中的另一個構造函數。重定向構造函數的主體為空,構造函數調用出現在冒号 (:) 後面。

class Point {
  num x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(num x) : this(x, 0);
}
           

常量構造函數

如果你的類需要不會改變的對象,那麼你就可以使這些對象具有編譯時常量。是以需要定義 const 構造函數,并確定所有執行個體變量都是 最終變量。

class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final num x, y;

  const ImmutablePoint(this.x, this.y);
}
           

常量構造函數并不總是建立常量。有關詳細資訊,請參考有關 使用構造函數 的部分。

工廠構造函數

在實作并非總是建立其類的新執行個體的構造函數時,請使用 factory 關鍵字。 例如,工廠構造函數可能從緩存傳回一個執行個體,或者可能傳回一個子類型的執行個體。

以下示例示範了工廠構造函數從緩存中傳回對象的方法:

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

  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }

  Logger._internal(this.name);

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

提示: 工廠構造函數不允許使用 this 關鍵字

就像調用其他構造函數一樣,調用工廠構造函數:

var logger = Logger('UI');
logger.log('Button clicked');
           

方法

方法是提供對象行為的函數。

執行個體方法

對象上的執行個體方法可以通路執行個體變量。 以下示例中的 distanceTo() 方法是一個執行個體方法的示例:

import 'dart:math';

class Point {
  num x, y;

  Point(this.x, this.y);

  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}
           

擷取器和修改器

擷取器和修改器是特殊的方法,可提供對對象屬性的讀寫通路權限。回想一下,每個執行個體變量都有一個隐式的擷取器,如果可以的話還有一個修改器。你就可以通過使用 get 和 set 關鍵字實作擷取器和修改器來建立其他屬性:

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

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

  // Define two calculated properties: right and bottom.
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}
           

使用擷取器和修改器時你可以從執行個體變量開始,然後使用方法将它們包裝起來而無需更改用戶端代碼。

提示: 不管是否明确定義了擷取器,增量(++)之類的運算符都以預期的方式工作。為避免任何意外的副作用,操作員隻調用一次擷取器,并将其值儲存在一個臨時變量中。

抽象方法

執行個體,擷取器或修改器方法可以是抽象的,僅定義一個接口,将其留給其他類實作。抽象方法隻能存在于 抽象類 中。

要使方法抽象,請使用分号(;)來代替方法主體:

abstract class Doer {
  // Define instance variables and methods...
  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide an implementation, so the method is not abstract here...
  }
}
           

抽象類

使用 abstract 修飾符定義一個抽象類 - 一個無法執行個體化的類。 抽象類通常用于某些實作中,用于定義接口。 如果您希望抽象類看起來可執行個體化,請定義一個工廠構造函數。

抽象類通常具有抽象方法。以下時一個聲明具有抽象方法的抽象類的示例:

// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
  // Define constructors, fields, methods...

  void updateChildren(); // Abstract method.
}
           

隐式接口

每個類都隐式定義一個接口,該接口包含該類及其實作的所有接口的所有執行個體成員。如果你要建立一個支援B類 API 的A類而不繼承B的實作,則A類應實作B接口。

一個類通過在 Implements 子句中聲明一個或多個接口,然後提供接口所需的 API 來實作一個或多個接口。例如:

// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

  // In the interface.
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// An implementation of the Person interface.
class Impostor implements Person {
  get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

// greetBob 方法傳入名稱為 person(類型為 Person 類)的參數
String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}
           

以下是指定一個類實作多個接口的示例:

擴充類

使用 extends 來建立一個子類,在子類中使用 super 來父類:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

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

重載成員

子類可以覆寫執行個體方法,擷取器和修改器。你可以使用 @override 來聲明你需要覆寫的成員:

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}
           

縮小

代碼類型安全的方法參數或執行個體變量的類型,可以使用 covariant 關鍵字。

重寫運算符

你可以重載以下表中的運算符。例如,如果定義一個 Vector 類,則可以定義一個 + 方法來添加兩個向量。

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

提示: 你可能已經注意到,= 是不可重載的運算符。表達式

e1 != e2 隻是 !(e1 == e2)

的文法糖。

下面是一個覆寫 + 和 - 運算符的類的示例:

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // Operator == and hashCode not shown. For details, see note below.
  // ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}
           

如果你重載 ==,則還應該覆寫

Object's hashCode getter

。有關重寫 == 和 hashCode 的示例,請參考Implementing map keys。

更多有關 重載 的資訊,請參考 擴充類。

noSuchMethod()

想要在使用不存在的方法或執行個體變量時進行檢測或作出反應,則可以重寫

noSuchMethod()

class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}
           

除非滿足以下條件之一,否則不能調用未實作的方法:

  • 接收器具有靜态類型的

    dynamic

    (??? 我也沒懂)
  • 接收器具有定義未實作方法的靜态類型(抽象也是可以的),接收器的動态類型具有

    noSuchMethod()

    的實作,該實作與

    Object

    類中的實作不同。

更多資訊請參考 NoSuchMethod Forwarding Specification。

枚舉類型

枚舉類型是一種特殊的類,用于表示固定數量的常量值。

使用枚舉

使用

enum

關鍵字聲明枚舉類型:

枚舉中的每個值都有一個索引擷取器,它傳回該值在枚舉聲明中從零開始的位置。例如,第一個值的索引為0,第二個值的索引為1。

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

           

要擷取枚舉中所有值的清單,請使用枚舉的值常量。

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: // Without this, you see a WARNING.
    print(aColor); // 'Color.blue'
}
           

枚舉類型具有以下限制:

  • 你不能繼承,混入或實作一個枚舉。
  • 你不能顯式執行個體化枚舉。

有關更多資訊,請參見 Dart語言規範。

向類裡添加功能:mixins

Mixins 是一種在多個類層次結構中重用類代碼的方法。

要使用 mixin,請使用 with 關鍵字,後跟一個或多個 mixin 名稱。以下示例顯示了兩個使用 mixins 的類:

class Musician extends Performer with Musical {
  // ···
}

// 用起來有點像 trait
class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}
           

要實作 mixin,請建立一個擴充 Object 且不聲明構造函數的類。除非你想要 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 可以調用未定義的方法),請使用

on

來指定所需的父類:

mixin MusicalPerformer on Musician {
  // ···
}
           

版本提示: Dart 2.1 中引入了對 mixin 關鍵字的支援。早期版本中的代碼通常使用抽象類。 有關 2.1 版本中關于 mixin 的更多資訊,請參閱 Dart SDK 更改日志 和 2.1 mixin 規範。

類變量及方法

使用

static

關鍵字實作類範圍變量和方法。

靜态變量

靜态變量(類變量)對于類範圍的狀态和常量是很有用的:

class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}
           

靜态變量在使用之前不會初始化。

提示: 該頁面遵循 樣式指南建議,建議使用 lowerCamelCase 這種格式來命名常量。

靜态方法

靜态方法(類方法)不能在執行個體上操作,是以你無法通路它。例如:

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

提示: 對于常見或廣泛使用的實用程式和功能,請考慮使用頂級函數而非靜态方法。

你可以使用靜态方法作為編譯時常量。例如,可以将靜态方法作為參數傳遞給常量構造函數。

泛型

如果你了在 API 文檔中看了基本數組類型、List,你會發現這些類型實際上是

List <E>

。<…> 标記将 List 标記為通用(或參數化)類型 - 一種具有正式類型參數的類型。按照慣例,大多數類型變量都具有單字母名稱,例如 E,T,S,K 和 V。

  • E 代表 Element 元素
  • T 代表 Type 類型
  • S 沒有找到這個資料
  • K 代表 Key 鍵
  • V 代表 Value 值

為什麼使用泛型

泛型通常是實作類型安全所必需的,但是泛型不僅具有允許運作代碼的優點,而且還具有更多的優點:

  • 正确指定泛型類型可以生成更優質的代碼。
  • 你可以使用泛型來減少代碼重複。

如果你打算一個清單隻包含字元串,則可以将其聲明為

List <String>

(将其稱為 “字元串清單”)。這樣其他程式員和工具可以檢測到将非字元串類型的變量配置設定給該清單是錯誤的。

// static analysis: error/warning
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error
           

使用泛型的另一個原因是減少代碼重複。泛型讓你可以在多種類型之間共享單個接口和實作,同時仍可以利用靜态分析。假設你建立了一個用于緩存對象的接口:

abstract class ObjectCache {
  Object getByKey(String key);
  void setByKey(String key, Object value);
}
           

你會發現這個接口隻能使用在對象類型上,但這時你又想要字元串的接口,為此你又建立了另一個接口:

abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}
           

後來,你又決定要想要一個針對數字的接口…

泛型可以省去建立所有這些接口的麻煩。相反,你可以建立一個帶有類型參數的接口:

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}
           

在此代碼中,T 是替代類型。你可以将其視為占位符,以供後續開發人員定義。

使用集合文字

List,Set 和 Map文字 可以被參數化。 參數化文字與你已經看到的文字一樣,隻不過你在左括号之前添加了

<type>

(用于 Lists 和 Sets)或

<keyType,valueType>

(用于 Maps)。這是使用類型文字的示例:

var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{
  'index.html': 'Homepage',
  'robots.txt': 'Hints for web robots',
  'humans.txt': 'We are people, not machines'
};
           

在構造函數中使用參數類型

要在使用構造函數時指定一種或多種類型,請将類型放在類名之後的尖括号(<…>)中。例如:

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

限制參數類型

在實作泛類型時,你可能希望限制其參數的類型,那麼就可以使用繼承來做到這一點。

class Foo<T extends SomeBaseClass> {
  // Implementation goes here...
  String toString() => "Instance of 'Foo<$T>'";
}

class Extender extends SomeBaseClass {...}
           

可以将

SomeBaseClass

或其任何子類用作通用參數:

var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();

           

也可以不指定泛類型參數:

var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'
           

指定任何非 SomeBaseClass 類型的參數都會導緻錯誤:

通用方法

最初,Dart 的通用支援僅限于類。一種稱為通用方法的較新文法,允許在方法和函數上使用類型參數:

T first<T>(List<T> ts) {
  // Do some initial work or error checking, then...
  T tmp = ts[0];
  // Do some additional checking or processing...
  return tmp;
}
           

在這裡,第一個泛類型參數(

<T>

)允許你在多個地方使用類型參數 T:

  • 在函數的傳回類型中 (

    T

    )
  • 在參數類型中 (

    List<T>

    )
  • 在方法變量中 (

    T tmp

    )

更多有關泛類型的資訊,請參考 Using Generic Methods

庫和可見性

導入

import

和 庫指令

library

可以幫助你建立子產品化且可共享的代碼庫。庫不僅提供 API,而且是私有的機關:以下劃線 (_) 開頭的辨別符僅在庫的内部可見。每個 Dart 應用程式都是一個庫,即使它不使用庫指令。

可以使用 軟體包 分發庫。

使用庫

使用

import

指定如何在另一個庫的作用域中使用一個庫中的命名空間。

要導入的唯一必需參數是指定庫的 URI。對于内置庫,其 URI 是特别的 dart:scheme。 對于其他庫,可以使用檔案系統路徑或 package:scheme。

package

:方案指定由包管理器(例如 pub 工具)提供的庫。 例如:

提示: URI 代表統一資源辨別符。 URLs(統一資源定位符)是一種常見的URI。

指定庫字首

如果導入兩個辨別符沖突的庫,則可以為其中一個庫指定字首。例如,如果 library1 和 library2 都具有Element 類,示例如下:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// Uses Element from lib1.
Element element1 = Element();

// Uses Element from lib2.
lib2.Element element2 = lib2.Element();
           

隻引入庫的部分

如果隻想使用庫的一部分,則可以有選擇地導入該庫。例如:

// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
           

懶加載一個庫

延遲加載(也稱為懶加載)允許 Web 應用程式在需要庫時以及在需要時按需加載庫。在某些情況下,你可能會使用到延遲加載:

  • 為了減少網絡應用的初始啟動時間。
  • 執行 A/B 測試 - 例如,嘗試算法的替代實作。
  • 加載很少使用的功能,例如可選的螢幕和對話框。

隻有 dart2js 支援延遲加載。 Flutter、Dart VM 和 dartdevc 不支援延遲加載。更多相關資訊,參考 issue #33118 和 issue #27776

要延遲加載一個庫時,首先必須使用

deferred as

導入它。

當你需要該庫時,請使用庫的辨別符調用并

loadLibrary()

方法。

Future greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}
           

在前面的代碼中,

await

關鍵字将暫停執行,直到加載該庫為止。有關

async

await

的更多資訊,請參考 asynchrony support.。

你可以在一個庫上多次調用

loadLibrary()

而不會出現問題。因為該庫僅加載一次。

使用延遲加載時,請注意以下幾點:

  • 延遲庫的常量不是導入檔案中的常量。請記住,這些常量在加載延遲的庫之前不存在。
  • 你不能在導入檔案中使用延遲庫中的類型。而是考慮将接口類型移到由延遲庫和導入檔案導入的庫中。
  • Dart 将

    deferred

    作為命名空間隐式地将

    loadLibrary()

    插入到您定義的命名空間中。

    loadLibrary()

    函數傳回 Future。

實作庫

請參閱 建立庫包 以擷取有關如何實作庫包的建議,包括:

  • 如何建構庫的源代碼
  • 如何使用

    export

    指令
  • 何時使用

    part

    指令
  • 何時使用

    library

    指令

異步支援

Dart 庫充是傳回 Future 或 Stream 對象的函數。這些函數是異步的,它們被設定在可能耗時的操作(例如 I/O)之後傳回,而無需等待該操作完成。

async

await

關鍵字支援異步程式設計,使你可以編寫出看似同步實則異步的代碼。

Handling Futures

當你需要完成 Future 的結果時,有兩種選擇:

  • 使用

    async

    await

  • 如 in the library tour 中所述,使用 Future API

使用

async

await

的代碼是異步的,但看起來很像是同步代碼。例如下面是一些使用

await

等待異步函數結果的代碼:

要使用

await

,代碼必須位于

async

函數中,該函數被标記為

async

Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}
           

提示: 盡管異步功能可能會執行耗時的操作,但它不會等待這些操作。相反,異步函數僅在遇到第一個

await

表達式(details)之前執行。 然後它傳回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 對象訓示傳回一個對象的承諾。

await

表達式的值是傳回對象。

await

表達式使執行暫停,直到該對象可用為止。

如果在使用

await

時收到編譯錯誤,請確定

await

在異步函數中。

例如,要在應用程式的

main()

函數中使用

await

,必須将

main()

的正文标記為

async

Future main() async {
  checkVersion();
  print('In main: version is ${await lookUpVersion()}');
}
           

聲明異步函數

異步函數是一個函數,其主體帶有

async

修飾符。

向函數添加

async

關鍵字使其傳回 Future。例如,考慮以下同步函數,該函數傳回一個 String:

如果将其更改為異步函數,則傳回的值将為 Future:

請注意,該函數的主體不需要使用 Future API。 Dart 會在必要時建立 Future 對象。 如果你的函數沒有傳回有用的值,請使其傳回類型

Future <void>

有關使用 futures,

async

await

的互動式介紹,請參見 異步程式設計代碼實驗室。

Handling Streams

當需要從

Stream

中擷取值時,有兩種選擇:

  • 使用

    async

    和 異步for循環(

    await for

    )。
  • 使用 Stream API,如 Stream 文檔 中所述。

提示: 在使用

await

之前,請確定它使代碼清晰,并且你确定确實要等待所有 stream 的結果。例如,你通常不應将

await

用于UI事件偵聽器,因為UI架構會發送無休止的事件流。

異步 for 循環具有以下形式:

await for (varOrType identifier in expression) {
  // Executes each time the stream emits a value.
}
           

表達式的值必須具有 Stream 類型,執行過程如下:

  1. 等到 stream 發出一個值
  2. 執行 for 循環的主體,并将變量設定為該發出值
  3. 重複 1 和 2,直到關閉 stream

要停止監聽 Stream,可以使用

break

return

語句,該語句會脫離 for 循環并取消 Stream。

如果在實作異步 for 循環時遇到編譯錯誤,請確定

await for

位于

async

函數中。

例如,要在應用程式的 main() 函數中使用異步 for 循環,必須将

main()

的正文标記為

async

Future main() async {
  // ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  // ...
}
           

更多有關異步程式設計的資訊,請參考文檔的 dart:async 部分。

生成器

當需要延遲生成值序列時,請考慮使用生成器函數。Dart 具有對兩種生成器功能的内置支援:

  • 同步生成器:傳回一個 Iterable 對象。
  • 異步生成器:傳回一個 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 類的執行個體,使用

call()

方法。

在下面的示例中,

WannabeFunction

類定義了一個 call() 函數,該函數接受三個字元串并将它們連接配接起來,每個字元串之間用空格分隔,并附加一個感歎号。

class WannabeFunction {
  call(String a, String b, String c) => '$a $b $c!';
}

main() {
  var wf = new WannabeFunction();
  var out = wf("Hi","there,","gang");
  print('$out');
}
           

*讓上述代碼在 DartPad 中試運作

隔離器

大多數計算機甚至在移動平台上,都具有多核CPU。為了利用這些核心,開發人員通常使用并發運作共享記憶體線程。但是,共享狀态并發容易出錯,并且可能導緻你需要一些複雜的代碼。

所有 Dart 代碼都在隔離器内運作,而不是線程。每個隔離區都有自己的記憶體堆,進而確定隔離區之間無法互相通路狀态。

有關更多資訊,請參考以下内容:

  • Dart asynchronous programming: Isolates and event loops
  • dart:isolate API reference, including Isolate.spawn() and TransferableTypedData
  • Background parsing cookbook on the Flutter site
  • Isolate sample app

類型定義

在 Dart 中,函數是對象,就像字元串和數字也是對象一樣。類型定義 或 函數類型别名 為函數類型提供一個名稱,你可以在聲明字段和傳回類型時使用該名稱。當将函數類型配置設定給變量時,類型定義會保留類型資訊。

以下代碼不使用

typedef

class SortedCollection {
  Function compare;

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

// Initial, broken implementation.
int sort(Object a, Object b) => 0;

void main() {
  SortedCollection coll = SortedCollection(sort);

  // All we know is that compare is a function,
  // but what type of function?
  assert(coll.compare is Function);
}
           

配置設定

f

進行比較時,類型資訊會丢失。

f

的類型是 (

Object

Object

) →

int

(where → means return),而比較的類型是 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);
}
           

提示: 目前,typedef 僅限于函數類型。我們希望這種情況會改變。

因為 typedef 隻是别名,是以它們提供了一種檢查任何函數類型的方法。例如:

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

中繼資料

使用中繼資料提供有關你的代碼的其他資訊。中繼資料聲明以字元

@

符号開頭,後跟對編譯常量的引用(例如

deprecated

)或對常量構造函數的調用。

所有 Dart 代碼都可以使用兩個注釋:

@deprecated

@override

。 有關使用

@override

的示例,請參見擴充類。以下是使用

@deprecated

注釋的示例:

class Television {
  /// _Deprecated: Use [turnOn] instead._
  @deprecated
  void activate() {
    turnOn();
  }

  /// Turns the TV's power on.
  void turnOn() {...}
}
           

你可以定義自己的中繼資料注釋。以下定義帶有兩個參數的 @todo 注釋的示例:

library todo;

class Todo {
  final String who;
  final String what;

  const Todo(this.who, this.what);
}
           

以下是使用 @todo 批注的示例:

import 'todo.dart';

@Todo('seth', 'make this do something')
void doSomething() {
  print('do something');
}
           

中繼資料可以出現在庫,類,typedef,類型參數,構造函數,工廠,函數,字段,參數或變量聲明之前,也可以出現在導入或導出指令之前。你可以在運作時使用反射來檢索中繼資料。

注釋

Dart 支援單行注釋、多行注釋及文檔注釋。

單行注釋

單行注釋以

//

開頭。Dart 編譯器會自動忽略 // 開始到和行尾之間的所有内容。

void main() {
  // TODO: refactor into an AbstractLlamaGreetingFactory?
  print('Welcome to my Llama farm!');
}

           

多行注釋

多行注釋以

結尾。 Dart 編譯器會自動忽略 之間的所有内容(*除非注釋為文檔注釋)。多行注釋可以嵌套。

void main() {
  /*
   * This is a lot of work. Consider raising chickens.

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

           

文檔注釋

文檔注釋是以

///

/**

開頭的多行或單行注釋。在連續的行上使用 /// 與多行 doc 注釋具有相同的效果。

在文檔注釋中,除非括在方括号中,否則 Dart 編譯器将忽略所有文本。使用方括号,你可以引用類、方法、字段、頂級變量、函數和參數。方括号中的名稱在已記錄程式元素的詞法範圍内解析。

以下是參考其他類和參數的文檔注釋示例:

/// A domesticated South American camelid (Lama glama).
///
/// Andean cultures have used llamas as meat and pack
/// animals since pre-Hispanic times.
class Llama {
  String name;

  /// Feeds your llama [Food].
  ///
  /// The typical llama eats one bale of hay per week.
  void feed(Food food) {
    // ...
  }

  /// Exercises your llama with an [activity] for
  /// [timeLimit] minutes.
  void exercise(Activity activity, int timeLimit) {
    // ...
  }
}
           

在生成的文檔中,[Food] 成為指向

Food

類的API文檔的連結。

要解析 Dart 代碼并生成 HTML 文檔,你可以使用 SDK 的文檔生成工具。有關生成的文檔的示例,請參閱 Dart API 文檔。 有關如何建構注釋的建議,請參閱 Dart Doc 注釋準則。

總結

該頁面總結了 Dart 語言的常用功能。我們也正在實作更多功能,但希望它們不會破壞現有代碼。有關更多資訊,請參見 Dart語言規範 和 Effective Dart。

要了解有關 Dart 核心庫的更多資訊,請參閱 A Tour of the Dart Libraries。

至此我們已經初步地了解了 Dart 語言,接下來我們要開始學習 2.學習大部分基礎 Widget 的使用方式