內容

學習 Dart 作為 JavaScript 開發人員

本指南旨在利用您的 JavaScript 程式設計知識學習 Dart。它展示了兩種語言中的主要相似性和差異,並介紹了 JavaScript 中不支援的 Dart 概念。作為 JavaScript 開發人員,Dart 應該感覺很熟悉,因為這兩種語言共用許多概念。

與 JavaScript 一樣,Dart 在事件迴圈上執行,因此兩種語言以類似的方式執行程式碼。例如,非同步概念,例如未來 (JavaScript 中的 Promise) 和 async/await 語法非常相似。

與 JavaScript 不同,Dart 是強類型。如果您使用過 TypeScript 或 Flow,這應該可以簡化學習 Dart。如果您主要使用純 JavaScript,則可能需要更多調整。透過強類型,Dart 會在編譯之前捕捉到 JavaScript 程式碼中可能存在的許多錯誤。

Dart 預設啟用 Null 安全性。JavaScript 不支援 Null 安全性。作為 JavaScript 開發人員,可能需要一段時間才能學習如何撰寫 Null 安全程式碼,但權衡利弊後,可以更好地防範 Null 參考例外,這些例外甚至可以在編譯 Dart 程式碼之前就偵測到。(從而避免在對結果為 Null 的 JavaScript 變數進行運算時發生的那些令人害怕的 TypeErrors。)

慣例和 linting

#

JavaScript 和 Dart 都具有強制執行標準慣例的程式碼檢查工具。雖然 JavaScript 提供許多工具、標準和組態,但 Dart 有一組官方佈局和樣式慣例,加上一個程式碼檢查器來簡化相容性。Dart 分析器會檢查程式碼並提供更多分析功能。若要自訂專案的程式碼檢查規則,請遵循 自訂靜態分析 說明。

Dart 提供 dart fix 來尋找並修正錯誤。

Dart 也提供類似於 JavaScript 工具(如 Prettier)的程式碼格式化工具。若要格式化任何 Dart 專案中的程式碼,請在命令列上執行 dart format。Dart 和 Flutter 的 IDE 外掛也提供此功能。

Dart 支援在集合、參數或引數的逗號分隔清單中使用尾隨逗號。當您新增尾隨逗號時,格式化工具會將每個清單項目置於其自己的行上。當您認為清單日後可能會有更多項目時,請新增尾隨逗號。避免只為了格式化而新增尾隨逗號。

JavaScript 僅支援在清單和映射文字中使用尾隨逗號。

內建類型

#

JavaScript 和 Dart 都將其資料分類為類型。每個變數都有一個關聯的類型。類型決定變數可以儲存的值的種類,以及可以在這些值上執行的運算。Dart 與 JavaScript 的不同之處在於,它會為每個運算式和變數指定一個靜態類型。靜態類型預測變數的值或運算式值的執行時期類型。這表示 Dart 應用程式具有健全的靜態類型。

JavaScript 提供基本類型 numstringboolean,以及 null 值,以及陣列Map 類型。

Dart 支援下列內建類型

  • 數字 (numintdouble)
  • 字串 (String)
  • 布林值 (bool)
  • 清單 (List,也稱為陣列)
  • 集合 (Set)
  • 映射 (Map)
  • 符號 (Symbol)
  • null (Null)

若要進一步了解,請查看 Dart 語言導覽 中的 內建類型

Dart 中所有非 Null 類型都是 Object 的子類型。所有值也都是物件。Dart 不像 JavaScript 一樣使用「基本類型」。相反地,Dart 會對數字、布林值和 null 值進行正規化或標準化。這表示只有一個數值為 1int 值存在。

例如:相等運算子 ==identical() 方法會針對數字類型的相同值傳回 true。檢閱以下程式碼中顯示的範例

dart
var a = 2;
var b = 1 + 1;

print(a == b); // Prints true
print(identical(a, b)); // Prints true; only one "2" object exists

原始型別

#

本節說明 Dart 如何表示 JavaScript 中的基本類型。

數字

#

Dart 有三種資料類型用於儲存數字

num
等同於 JavaScript 中的泛數字類型。
int
沒有小數部分的數字值。
double
任何 64 位元 (雙精度) 浮點數。

Dart API 將所有這些類型包含為類別。intdouble 類型都將 num 共用為其父類別

num subclasses Object and int and double each subclass num

由於 Dart 將數字視為物件,因此數字可以將其自己的公用程式函數公開為物件方法。您不需要使用其他物件將函數套用至數字。

例如,將 double 四捨五入為整數

js
let rounded = Math.round(2.5);
dart
var rounded = 2.5.round();

字串

#

Dart 中的字串與 JavaScript 中的字串運作方式相同。要撰寫字串文字,請將其括在單引號 (') 或雙引號 (") 中。大多數 Dart 開發人員使用單引號,但語言並未強制執行任何標準。如果您不想在字串中跳脫單引號,請使用雙引號。

dart
var a = 'This is a string.';
跳脫特殊字元
#

若要將具有其他意義的字元包含在字串中,例如用於字串內插的 $,您必須跳脫該字元。在 Dart 中跳脫特殊字元的方式與 JavaScript 和大多數其他語言相同。若要跳脫特殊字元,請在該字元之前加上反斜線字元 (\)。

以下程式碼顯示一些範例。

dart
final singleQuotes = 'I\'m learning Dart'; // I'm learning Dart
final doubleQuotes = "Escaping the \" character"; // Escaping the " character
final dollarEscape = 'The price is \$3.14.'; // The price is $3.14.
final backslashEscape = 'The Dart string escape character is \\.';
final unicode = '\u{1F60E}'; // 😎,  Unicode scalar U+1F60E
字串內插
#

JavaScript 支援範本文字。這些文字使用反引號 (`) 字元分隔符,原因如下

  • 允許多行字串
  • 內插嵌入表達式的字串
  • 建立稱為標記樣板的特殊建構

在 Dart 中,您不需要將字串括在反引號中即可串接字串或在字串文字中使用內插。

若要深入了解,請查看 Dart 語言之旅中的字串

與 JavaScript 樣板文字相同,您可以使用 ${<expression>} 語法將表達式插入字串文字中。Dart 使用此語法,並允許您在表達式使用單一識別碼時省略大括號。

dart
var food = 'bread';
var str = 'I eat $food'; // I eat bread
var str = 'I eat ${food}'; // I eat bread

字串串接和多行宣告

#

在 JavaScript 中,您可以使用樣板文字定義多行字串。Dart 有兩種方法可以定義多行字串。

  1. 使用隱含字串串接:即使跨越多行,Dart 也會串接任何相鄰的字串文字
    dart
    final s1 = 'String '
        'concatenation'
        " even works over line breaks.";
  2. 使用多行字串文字:當在字串兩側使用三個引號(單引號或雙引號)時,文字可以跨越多行。
    dart
    final s2 = '''
    You can create
    multiline strings like this one.
    ''';
    
    final s3 = """
    This is also a
    multiline string.""";

相等性

#

當兩個字串包含相同的程式碼單元順序時,Dart 會將它們視為相等。若要判斷兩個字串是否具有相同的順序,請使用等於運算子 (==)。

dart
final s1 = 'String '
    'concatenation'
    " works even over line breaks.";
assert(s1 ==
    'String concatenation works even over '
        'line breaks.');

布林值

#

Dart 和 Javascript 中的布林值表示二進位條件。這兩個值表示值或表達式是否為 truefalse。您可以使用文字 truefalse 傳回這些值,或使用 x < 5y == null 等表達式產生這些值。

js
let isBananaPeeled = false;
dart
var isBananaPeeled = false;

變數

#

Dart 中的變數就像 JavaScript 中的變數一樣,有兩個例外

  1. 每個變數都有型別。
  2. Dart 會在區塊層級範圍設定所有變數,就像 JavaScript 中的 letconst 變數一樣。

Dart 變數有兩種取得型別的方式

  1. 宣告:在宣告中寫入型別。
  2. 推論:用於初始化變數的表達式。根據慣例,當分析器可以推論型別時,請使用 varfinal
js
// Declare and initialize a variable at once
let name = "bob";
dart
// Declare a variable with a specific type
// when you don't provide an initial value
String name;
// Declare and initialize a variable
// at the same time and Dart infers
// the type
var name = 'bob';

變數只能接受其型別的值。

dart
var name = 'bob';
name = 5; // Forbidden, as `name` has type `String`.

如果您未提供初始值或明確型別,Dart 會推論變數型別為萬用型別 dynamic

與 JavaScript 變數一樣,您可以將任何值指定給使用 dynamic 型別的 Dart 變數。

js
// Declare a variable
let name;
// Initialize the variable
name = "bob";
dart
// Declare a variable without a type or assigned value
// and Dart infers the 'dynamic' type
var name;
// Initialize the variable and the type remains `dynamic`
name = 'bob';
name = 5; // Allowed, as `name` has type `dynamic`.

final 和 const

#

JavaScript 和 Dart 都使用變數修飾詞。兩者都使用 const,但 const 的運作方式不同。在 JavaScript 中使用 const 的地方,Dart 使用 final

當你將 final 新增至 Dart 變數,或將 const 新增至 JavaScript 變數時,你必須在其他程式碼讀取其值之前初始化變數。初始化後,你無法變更這些變數的參考。

當 Dart 使用 const 時,它指的是編譯時建立的特殊值。Dart 使用有限的運算式建立這些不可變的值。這些運算式不得有副作用。在這些條件下,編譯器可以預測常數變數或運算式的精確值,而不仅仅是其靜態類型。

dart
final String name;
// Cannot read name here, not initialized.
if (useNickname) {
  name = "Bob";
} else {
  name = "Robert";
}
print(name); // Properly initialized here.

在 Dart 中,常數變數必須包含常數值。非常數變數可以包含你也可以標記為 const 的常數值。

dart
var foo = const [];
  // foo is not constant, but the value it points to is.
  // You can reassign foo to a different list value,
  // but its current list value cannot be altered.

const baz = []; // Equivalent to `const []`

同樣地,類別可以有自己的 const 建構函數,產生不可變的執行個體。

你無法修改 JavaScript 或 Dart 中的 const 變數。JavaScript 允許你修改 const 物件的欄位,但 Dart 不允許。

如需進一步了解,請參閱 類別 區段。

Null 安全

#

與 JavaScript 不同,Dart 支援空值安全性。在 Dart 中,所有類型預設為不可為空。這對 Dart 開發人員有益,因為 Dart 在編寫程式碼時會捕捉空值參考例外,而不是在執行時。

可為 Null 的型別與不可為 Null 的型別

#

以下程式碼範例中沒有任何變數可以為 null

dart
// In null-safe Dart, none of these can ever be null.
var i = 42; // Inferred to be an int.
String name = getFileName();
final b = Foo(); // Foo() invokes a constructor

若要指出變數可能具有 null 值,請將 ? 新增至其類型宣告

dart
int? aNullableInt = null;

其他任何類型宣告也一樣,例如函式宣告

dart
String? returnsNullable() {
  return random.nextDouble() < 0.5
    ? 'Sometimes null!'
    : null;
}

String returnsNonNullable() {
  return 'Never null!';
}

可為 Null 的運算子

#

Dart 支援多個處理可為空性的運算子。與 JavaScript 一樣,Dart 支援空值指定運算子 (??=)、空值合併運算子 (??) 和選擇性鏈結運算子 (?.)。這些運算子的作用與 JavaScript 相同。

!運算子

#

在可為空變數或運算式可能為非空的情況下,你可以使用 (!) 運算子告知編譯器抑制任何編譯時錯誤。將此運算子置於運算式之後。

不要將此與 Dart 的 not (!) 運算子混淆,後者使用相同的符號,但置於運算式之前。

dart
int? a = 5;

int b = a; // Not allowed.
int b = a!; // Allowed.

在執行時,如果 a 變成 null,則會發生執行時錯誤。

?. 運算子一樣,在存取物件上的屬性或方法時使用 ! 運算子

dart
myObject!.someProperty;
myObject!.someMethod();

如果 myObject 在執行時為 null,則會發生執行時錯誤。

函式

#

雖然 Dart 的函數與 JavaScript 中的函數運作方式大致相同,但它們有一些額外的功能,以及在宣告時有一些細微的語法差異。與 JavaScript 類似,你可以在任何地方宣告函數,無論是在頂層、類別欄位或區域範圍。

js
// On the top level
function multiply(a, b) {
  return a * b;
}

// As a class field
class Multiplier {
  multiply(a, b) {
    return a * b;
  }
}

// In a local scope
function main() {
  function multiply(a, b) {
    return a * b;
  }

  console.log(multiply(3, 4));
}
dart
// On the top level
int multiply(a, b) {
  return a * b;
}

// As a class field
class Multiplier {
  multiply(a, b) {
    return a * b;
  }
}

// In a local scope
main() {
  multiply(a, b) {
    return a * b;
  }

  print(multiply(3, 4));
}

箭號語法

#

Dart 和 JavaScript 都支援箭頭語法 (=>),但支援方式不同。在 Dart 中,你只能在函數包含單一表達式或回傳陳述式時使用箭頭語法。

例如,以下的 isNoble 函數是等效的

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

參數

#

在 JavaScript 中,所有參數都可以是位置參數。預設情況下,Dart 需要你將所有參數作為引數傳遞給函數。

dart
int multiply(int a, int b) {
  return a * b;
}

main() {
  multiply(3, 5); // Valid. All parameters are provided.
  multiply(3); // Invalid. All parameters must be provided.
}

這可以在兩種情況下改變

  1. 位置參數標記為選用。
  2. 參數已命名且未標記為必要。

若要定義選用位置參數,請將它們括在方括號中,並置於任何必要位置參數之後。你不能在選用參數後加上必要參數。

由於空安全,選用位置參數必須具有預設值或標記為可為空。若要深入了解,請參閱關於 空安全的 前一章節。

以下程式碼有一個有效的範例和兩個無效的範例,這些範例定義了選用位置參數。

dart
// Valid: `b` has a default value of 5. `c` is marked as nullable.
multiply(int a, [int b = 5, int? c]) {
  ...
}
// Invalid: a required positional parameter follows an optional one.
multiply(int a, [int b = 5], int c) {
  ...
}
// Invalid: Neither optional positional parameter has a default
//          value or has been flagged as nullable.
multiply(int a, [int b, int c]) {
  ...
}

以下範例顯示如何呼叫帶有選用參數的函數

dart
multiply(int a, [int b = 5, int? c]) {
  ...
}

main() {
  // All are valid function calls.
  multiply(3);
  multiply(3, 5);
  multiply(3, 5, 7);
}

Dart 支援命名參數。這些參數不必按照在位置參數中定義的順序提供。你改用名稱來參考它們。預設情況下,這些參數是選用的,除非它們標記為必要。命名參數的定義方式是將它們括在花括號中。你可以將命名參數與必要位置參數結合使用,在此情況下,命名參數總是置於位置參數之後。在使用命名參數呼叫函數時,請透過在傳遞的值前面加上參數名稱並以冒號分隔,來傳遞值。例如,f(namedParameter: 5)

同樣地,對於空安全,未標記為必要的命名參數也需要具有預設值或標記為可為空。

以下程式碼定義了一個帶有命名參數的函數

dart
// Valid:
// - `a` has been flagged as required
// - `b` has a default value of 5
// - `c` is marked as nullable
// - Named parameters follow the positional one
multiply(bool x, {required int a, int b = 5, int? c}) {
  ...
}

以下範例呼叫帶有命名參數的函數

dart
// All are valid function calls.
// Beyond providing the required positional parameter:
multiply(false, a: 3); // Only provide required named parameters
multiply(false, a: 3, b: 9); // Override default value of `b`
multiply(false, c: 9, a: 3, b: 2); // Provide all named parameters out of order

一級函式

#

JavaScript 和 Dart 將函數視為一等公民。這表示 Dart 將函數視為任何其他物件。例如,以下程式碼顯示如何將函數作為參數傳遞給另一個函數

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

var list = [1, 2, 3];

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

匿名函式

#

JavaScript 和 Dart 都支援 匿名函數,或沒有名稱的函數。與命名函數一樣,你可以像傳遞任何其他值一樣傳遞匿名函數。例如,將匿名函數儲存在變數中、將它們作為引數傳遞給另一個函數,或從另一個函數回傳它們。

JavaScript 有兩種宣告匿名函數的方法

  1. 使用標準函數表達式
  2. 使用箭頭語法

同樣地,Dart 也有兩種方式宣告匿名函式。兩者運作方式類似於 JavaScript 箭頭表達式。Dart 的匿名函式不支援一般函式表達式附帶的額外功能。例如,JavaScript 支援函式表達式作為建構函式,或建立自訂綁定至 this。

如需進一步了解,請參閱 類別 區段。

js
// A regular function expression
// assigned to a variable
let funcExpr = function(a, b) {
  return a * b;
}
// The same anonymous function
// expressed as an arrow
// function with curly braces.
let arrowFuncExpr = (a, b) => {
  return a * b;
}
// An arrow function with only
// one return statement as
// its contents does not
// require a block.
let arrowFuncExpr2 = (a, b) => a * b;
dart
// Assign an anonymous function
// to a variable.
var blockFunc =
  optionalCallback ?? (int a, int b) {
    return a * b;
};

// For an expression with only a return statement,
// you can use the arrow syntax:
var singleFunc = (int a, int b) => a * b;

與 JavaScript 一樣,你可以將匿名函式傳遞給其他函式。開發人員在使用陣列和清單的 map 函式時,通常會傳遞匿名函式

js
// returns [4, 5, 6]
[1, 2, 3].map(e => e + 3);

// returns [5, 7, 9]
[1, 2, 3].map(e => {
  e *= 2;
  return e + 3;
});
dart
// returns [4, 5, 6]
[1, 2, 3].map((e) => e + 3).toList();

// returns [5, 7, 9]
var list2 = [1, 2, 3].map((e) {
  e *= 2;
  return e + 3;
}).toList();

產生器函式

#

兩種語言都支援 產生器函式。這些函式會傳回一個可迭代的項目集合,計算以避免不必要的運算。

若要撰寫 Dart 中的產生器函式,請在函式參數後加上 sync* 關鍵字,並傳回 Iterable。使用 yield 關鍵字將項目新增至最後的 iterable,或使用 yield* 新增整組項目。

下列範例顯示如何撰寫基本產生器函式

js
function* naturalsTo(n) {
  let k = 0;
  while (k < n) {
    yield k++;
  }
}

// Returns [0, 1, 2, 3, 4]
for (let value of naturalsTo(5)) {
  console.log(value);
}
dart
Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) {
    yield k++;
  }
}

// Returns an iterable with [0, 1, 2, 3, 4]
print(naturalsTo(5).toList());
js
function* doubleNaturalsTo(n) {
  let k = 0;
  while (k < n) {
    yield* [k, k];
    k++;
  }
}

// Returns [0, 0, 1, 1, 2, 2]
for (let value of doubleNaturalsTo(3)) {
  console.log(value);
}
dart
Iterable<int> doubleNaturalsTo(int n) sync* {
  int k = 0;
  while (k < n) {
    yield* [k, k];
    k++;
  }
}

// Returns an iterable with [0, 0, 1, 1, 2, 2]
print(doubleNaturalsTo(3));

你也可以定義非同步產生器函式,傳回串流而非可迭代項目。在後續的 非同步 區段中深入了解。

陳述式

#

此區段說明 JavaScript 和 Dart 之間的陳述差異。

控制流程(if/else、for、while、switch)

#

大部分控制陳述的運作方式與其 JavaScript 對應項相同。有些對 集合 有額外的用途。

反覆運算

#

雖然 JavaScript 和 Dart 都具有 for-in 迴圈,但其行為不同。

JavaScript 的 for-in 迴圈會反覆運算物件的屬性。若要反覆運算 JavaScript 可迭代物件的元素,你必須使用 for-ofArray.forEach()。Dart 的 for-in 迴圈運作方式類似於 JavaScript 的 for-of

下列範例顯示如何反覆運算集合並列印出每個元素

js
for (const element of list) {
  console.log(element);
}
dart
for (final element in list) {
  print(element);
}

Switch

#

switch 語句中使用 continue 時,可以將其與標籤結合使用,並將標籤放在案例上

dart
switch (testEnum) {
  case TestEnum.A:
    print('A');
    continue b;
  b:
  case TestEnum.B:
    print('B');
    break;
}

運算子

#

Dart 和 JavaScript 都包含預定義的運算子。這兩種語言都不支援新增運算子。Dart 支援使用 operator 關鍵字來覆寫一些現有的運算子。例如

dart
class Vector {
  final double x;
  final double y;
  final double z;
  Vector(this.x, this.y, this.z);
  Vector operator +(Vector other) => Vector(
    x + other.x, 
    y + other.y,
    z + other.z,
  );
  Vector operator *(double scalar) => Vector(
    x * scalar,
    y * scalar,
    z * scalar,
  );
}

算術運算子

#

這兩種語言的相等性和關係運算子幾乎相同,如下表所示

含義JavaScript 運算子Dart 運算子
加法++
減法--
一元減法,也稱為取負-expr-expr
乘法**
除法//
除法,傳回整數結果~/
取得整數除法的餘數 (模數)%%
x = x + 1 (表達式值為 x + 1)++x++x
x = x + 1 (表達式值為 x)x++x++
x = x - 1 (表達式值為 x - 1)--x--x
x = x - 1 (表達式值為 x)x--x--

例如

dart
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

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

您可能已經注意到 Dart 也包含一個 ~/ 運算子 (稱為截斷除法運算子),它會除以一個 double 並輸出一個取整的整數

dart
assert(25 == 50.4 ~/ 2);
assert(25 == 50.6 ~/ 2);
assert(25 == 51.6 ~/ 2);

相等性和關係運算子

#

這兩種語言的相等性和關係運算子工作方式相同

含義JavaScript 運算子Dart 運算子
嚴格相等=====
抽象相等==
嚴格不相等!==!=
抽象不相等!=
大於>>
小於<<
大於或等於>=>=
小於或等於<=<=

JavaScript 運算子 ==!= 沒有等效項。

例如

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

類型測試運算子

#

這兩種語言的測試運算子實作略有不同

含義JavaScript 運算子Dart 運算子
類型轉換x as T
如果物件具有指定的類型,則為 truex instanceof Tx is T
如果物件缺少指定的類型,則為 true!(x instanceof T)x is! T

如果 obj 實作 T 指定的介面,則 obj is T 的結果為 true。例如,obj is Object? 永遠為 true。

使用類型轉換運算子 (as) 以確保值具有特定類型。如果您知道物件將具有該類型,編譯器可以使用該類型。

例如

dart
(person as Employee).employeeNumber = 4204583;

如果您不知道物件是否為類型 T,請使用 is T 在使用物件之前檢查類型。

在 Dart 中,局部變數的類型會在 if 語句的範圍內更新。實例變數則不然。

dart
if (person is Employee) {
   person.employeeNumber = 4204583;
}

邏輯運算子

#

你可以使用邏輯運算子來反轉或組合布林表達式。兩種語言的邏輯運算子是相同的。

含義JavaScript 運算子Dart 運算子
反轉下一個表達式(將 false 變為 true,反之亦然)!x!x
邏輯 OR||||
邏輯 AND&&&&

JavaScript 允許在需要布林值的地方使用任何值。然後它會將這些值轉換為 truefalse。JavaScript 認為空字串和數字 0 是「假」值。Dart 允許在條件和邏輯運算子的運算元中使用 bool 值。

例如

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

按位元和位移運算子

#

你可以使用按位元和位移運算子與整數來操作數字的個別位元。兩種語言的運算子幾乎相同,如下表所示

含義JavaScript 運算子Dart 運算子
按位元 AND&&
按位元 OR||
按位元 XOR^^
單元按位元補數(0 變成 1;1 變成 0)~expr~expr
向左位移<<<<
向右位移>>>>
無符號向右位移>>>>>>

例如

dart
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
assert((-value >> 4) == -0x03); // Shift right
assert((value >>> 4) == 0x02); // Unsigned shift right
assert((-value >>> 4) > 0); // Unsigned shift right

條件運算子

#

Dart 和 JavaScript 都包含一個條件運算子 (?:) 來評估表達式。一些開發人員將其稱為三元運算子,因為它需要三個運算元。由於 Dart 有另一個需要三個運算元的運算子 ([]=),因此將此運算子 (?:) 稱為條件運算子。此運算子對表達式的工作方式類似於 if-else 對語句的工作方式。

js
let visibility = isPublic ? "public" : "private";
dart
final visibility = isPublic ? 'public' : 'private';

賦值運算子

#

使用 (=) 運算子來指定值。

dart
// Assign value to a
a = value;

此運算子還有一個 null 感知變體 (??=)。

如需瞭解更多資訊,請參閱 null 指定 運算子區段。

JavaScript 和 Dart 包含運算子,用於計算並將新值指定給表達式中的變數。這些指定運算子使用右側值和變數初始值作為運算元。

下表列出這些指定運算子

運算子說明
=指定
+=加法指定
-=減法指定
*=乘法指定
/=除法指定
~/=截斷除法指定
%=餘數(模數)指定
>>>=無符號右位移指定
^=按位元 XOR 指定
<<=左位移指定
>>=右位移指定
&=按位元 AND 指定
|=按位元 OR 指定

JavaScript 不支援 ~/= 指定運算子。

dart
var a = 5;
a *= 2; // Multiply `a` by 2 and assign the result back to a.
print(a); // `a` is now 10.

串接(.. 運算子)

#

Dart 允許您串接單一物件上的多個方法呼叫、屬性指定,或同時串接這兩者。Dart 將此稱為「串接」,並使用串接語法(..)來執行此動作。

JavaScript 沒有這個語法。

以下範例顯示如何使用串接語法在一個新建立的物件上串接多個方法

dart
var animal = Animal() // Sets multiple properties and methods
  ..name = "Bob"
  ..age = 5
  ..feed()
  ..walk();

print(animal.name); // "Bob"
print(animal.age); // 5

若要讓第一個串接語法具有 Null 感知,請將其寫成 ?..

dart
var result = maybePerson
    ?..employment = employer
    ..salary = salary;

如果 maybePerson 值為 null,Dart 會忽略整個串接。

集合

#

本節介紹 Dart 中的一些集合類型,並將它們與 JavaScript 中的類似類型進行比較。

清單

#

Dart 以與 JavaScript 陣列相同的方式撰寫清單文字。Dart 使用方括號將清單括起來,並使用逗號分隔值。

dart
// Initialize list and specify full type
final List<String> list1 = <String>['one', 'two', 'three'];

// Initialize list using shorthand type
final list2 = <String>['one', 'two', 'three'];

// Dart can also infer the type
final list3 = ['one', 'two', 'three'];

以下程式碼範例概述您可以對 Dart List 執行的基本動作。以下範例顯示如何使用索引運算子從 List 中擷取值。

dart
final fruits = <String>['apple', 'orange', 'pear'];
final fruit = fruits[1];

使用 add 方法將值新增到 List 的結尾。使用 addAll 方法新增另一個 List

dart
final fruits = <String>['apple', 'orange', 'pear'];
fruits.add('peach');
fruits.addAll(['kiwi', 'mango']);

使用 insert 方法在特定位置插入值。使用 insertAll 方法在特定位置插入另一個 List

dart
final fruits = <String>['apple', 'orange', 'pear'];
fruits.insert(0, 'peach');
fruits.insertAll(0, ['kiwi', 'mango']);

使用索引和指定運算子更新 List 中的值

dart
final fruits = <String>['apple', 'orange', 'pear'];
fruits[2] = 'peach';

使用下列其中一種方法從 List 中移除項目

dart
final fruits = <String>['apple', 'orange', 'pear'];
// Remove the value 'pear' from the list.
fruits.remove('pear');
// Removes the last element from the list.
fruits.removeLast();
// Removes the element at position 1 from the list.
fruits.removeAt(1);
// Removes the elements with positions greater than
// or equal to start (1) and less than end (3) from the list.
fruits.removeRange(1, 3);
// Removes all elements from the list that match the given predicate.
fruits.removeWhere((fruit) => fruit.contains('p'));

使用 length 取得 List 中的值的數量

dart
final fruits = <String>['apple', 'orange', 'pear'];
assert(fruits.length == 3);

使用 isEmpty 檢查 List 是否為空

dart
var fruits = [];
assert(fruits.isEmpty);

使用 isNotEmpty 檢查 List 是否不為空

dart
final fruits = <String>['apple', 'orange', 'pear'];
assert(fruits.isNotEmpty);

已填滿

#

Dart 的 List 類別包括一種方法,可以用來建立每個項目都具有相同值的清單。這個 filled 建構函式會建立一個大小為 n 的固定長度清單,並只有一個預設值。以下範例建立一個包含 3 個項目的清單

dart
final list1 = List.filled(3, 'a'); // Creates: [ 'a', 'a', 'a' ]
  • 預設情況下,您無法新增或移除這個清單中的元素。若要允許這個清單新增或移除元素,請在參數清單的結尾新增 , growable: true
  • 您可以使用索引值存取和更新這個清單的元素。

產生

#

Dart List 類別包含一種建立遞增值清單的方法。這個 generate 建構函式會建立一個長度為 n 的固定長度清單,並使用範本建立元素值。此範本會將索引作為參數。

dart
// Creates: [ 'a0', 'a1', 'a2' ]
final list1 = List.generate(3, (index) => 'a$index');

集合

#

與 JavaScript 不同,Dart 支援使用文字定義 Set。Dart 定義集合的方式與清單相同,但使用大括號而不是方括號。集合是無序的集合,只包含唯一的項目。Dart 使用雜湊碼來強制執行這些項目的唯一性,這表示物件需要雜湊值才能儲存在 Set 中。

下列程式碼片段顯示如何初始化 Set

dart
final abc = {'a', 'b', 'c'};

建立空集合的語法一開始可能令人困惑,因為指定空大括號 ({}) 會建立一個空的 Map。若要建立一個空的 Set,請在 {} 宣告之前加上一個型別引數,或將 {} 指定給型別為 Set 的變數

dart
final names = <String>{};
// Set<String> names = {}; // This works, too.
// final names = {}; // Creates an empty map, not a set.

下列範例概述你可以對 Dart Set 執行的基本動作。

使用 add 方法將值新增到 Set。使用 addAll 方法新增多個值

dart
final fruits = {'apple', 'orange', 'pear'};
fruits.add('peach');
fruits.addAll(['kiwi', 'mango']);

Set 中使用下列其中一種方法從集合中移除內容

dart
final fruits = {'apple', 'orange', 'pear'};
// Remove the value 'pear' from the set.
fruits.remove('pear');
// Remove all elements in the supplied list from the set.
fruits.removeAll(['orange', 'apple']);
// Removes all elements from the list that match the given predicate.
fruits.removeWhere((fruit) => fruit.contains('p'));

使用 length 取得 Set 中的值的數量

dart
final fruits = {'apple', 'orange', 'pear'};
assert(fruits.length == 3);

使用 isEmpty 檢查 Set 是否為空

dart
var fruits = <String>{};
assert(fruits.isEmpty);

使用 isNotEmpty 檢查 Set 是否不為空

dart
final fruits = {'apple', 'orange', 'pear'};
assert(fruits.isNotEmpty);

對應

#

Dart 中的 Map 型別類似於 JavaScript 中的 Map 型別。這兩種型別都會將金鑰與值關聯在一起。如果所有金鑰都有相同的型別,金鑰可以是任何物件型別。這項規則也適用於值。每個金鑰最多出現一次,但你可以多次使用相同的值。

Dart 將字典建立在雜湊表上。這表示金鑰需要可雜湊。每個 Dart 物件都包含一個雜湊。

考慮這些使用文字建立的簡單 Map 範例

dart
final gifts = {
  'first': 'partridge',
  'second': 'turtle doves',
  'fifth': 'golden rings'
};

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

下列程式碼範例提供您可以對 Dart Map 執行的基本動作概觀。下列範例顯示如何使用索引運算子從 Map 中擷取值。

dart
final gifts = {'first': 'partridge'};
final gift = gifts['first'];

使用 containsKey 方法檢查 Map 是否包含鍵。

dart
final gifts = {'first': 'partridge'};
assert(gifts.containsKey('fifth'));

使用索引指定運算子 ([]=) 新增或更新 Map 中的項目。如果 Map 尚未包含鍵,Dart 會新增項目。如果鍵存在,Dart 會更新其值。

dart
final gifts = {'first': 'partridge'};
gifts['second'] = 'turtle'; // Gets added
gifts['second'] = 'turtle doves'; // Gets updated

使用 addAll 方法新增另一個 Map。使用 addEntries 方法新增其他項目到 Map

dart
final gifts = {'first': 'partridge'};
gifts['second'] = 'turtle doves';
gifts.addAll({
  'second': 'turtle doves',
  'fifth': 'golden rings',
});
gifts.addEntries([
  MapEntry('second', 'turtle doves'),
  MapEntry('fifth', 'golden rings'),
]);

使用 remove 方法從 Map 中移除項目。使用 removeWhere 方法移除符合特定測試的所有項目。

dart
final gifts = {'first': 'partridge'};
gifts.remove('first');
gifts.removeWhere((key, value) => value == 'partridge');

使用 length 取得 Map 中的鍵值對數量。

dart
final gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);

使用 isEmpty 檢查 Map 是否為空。

dart
final gifts = {};
assert(gifts.isEmpty);

使用 isNotEmpty 檢查 Map 是否不為空。

dart
final gifts = {'first': 'partridge'};
assert(gifts.isNotEmpty);

不可修改

#

純 JavaScript 不支援不可變性。Dart 提供多種方法讓陣列、集合或字典等集合變成不可變的。

  • 如果集合是編譯時期常數且不應該修改,請使用 const 關鍵字
    const fruits = <String>{'apple', 'orange', 'pear'};
  • Set 指定給 final 欄位,表示 Set 本身不必是編譯時期常數。這可確保無法使用另一個 Set 覆寫欄位,但仍允許修改 Set 的大小或內容
    final fruits = <String>{'apple', 'orange', 'pear'};
  • 使用 unmodifiable 建構函式建立集合類型的最終版本 (如下列範例所示)。這會建立一個無法變更其大小或內容的集合
dart
final _set = Set<String>.unmodifiable(['a', 'b', 'c']);
final _list = List<String>.unmodifiable(['a', 'b', 'c']);
final _map = Map<String, String>.unmodifiable({'foo': 'bar'});

展開運算子

#

與 JavaScript 一樣,Dart 支援使用擴散運算子 (...) 和 null 感知擴散運算子 (...?) 將清單嵌入到另一個清單中。

dart
var list1 = [1, 2, 3];
var list2 = [0, ...list1]; // [0, 1, 2, 3]
// When the list being inserted could be null:
list1 = null;
var list2 = [0, ...?list1]; // [0]

這也適用於集合和映射

dart
// Spread operator with maps
var map1 = {'foo': 'bar', 'key': 'value'};
var map2 = {'foo': 'baz', ...map1}; // {foo: bar, key: value}
// Spread operator with sets
var set1 = {'foo', 'bar'};
var set2 = {'foo', 'baz', ...set1}; // {foo, baz, bar}

集合 if/for

#

在 Dart 中,forif 關鍵字在處理集合時具有額外的功能。

集合 if 陳述式僅在符合指定條件時才包含清單文字中的項目

dart
var nav = [
  'Home',
  'Furniture',
  'Plants',
  if (promoActive) 'Outlet',
];

它在映射和集合中工作方式類似。

一個集合 for 陳述式允許多個項目映射到另一個清單

dart
var listOfInts = [1, 2, 3];
var listOfStrings = [
  '#0',
  for (var i in listOfInts) '#$i',
]; // [#0, #1, #2, #3]

這在映射和集合中也以相同的方式工作。

非同步

#

與 JavaScript 類似,Dart 虛擬機器 (VM) 執行單一事件迴圈,處理您的所有 Dart 程式碼。這表示非同步的類似規則適用於此。您的所有程式碼都同步執行,但您可以使用不同的順序來處理它,具體取決於您如何使用可用的非同步工具。以下是其中一些建構,以及它們如何與其 JavaScript 對應項相關。

未來

#

Future 是 Dart 版本的 JavaScript Promise。兩者都是非同步操作的結果,稍後會解析。

Dart 或 Dart 套件中的函式可能會傳回 Future,而不是它們所代表的值,因為該值可能直到稍後才會提供。

以下範例顯示處理 future 在 Dart 中的工作方式與 promise 在 JavaScript 中的工作方式相同。

js
const httpResponseBody = func();

httpResponseBody.then(value => {
  console.log(
    `Promise resolved to a value: ${value}`
  );
});
dart
Future<String> httpResponseBody = func();

httpResponseBody.then((String value) {
  print('Future resolved to a value: $value');
});

類似地,future 可能會像 promise 一樣失敗。捕捉錯誤的工作方式也相同

js
httpResponseBody
  .then(...)
  .catch(err => {
    console.log(
      "Promise encountered an error before resolving."
    );
  });
dart
httpResponseBody
  .then(...)
  .catchError((err) {
    print(
      'Future encountered an error before resolving.'
    );
  });

您也可以建立 future。若要建立 Future,請定義並呼叫 async 函式。當您有需要成為 Future 的值時,請將函式轉換為以下範例所示。

dart
String str = 'String Value';
Future<String> strFuture = Future<String>.value(str);

非同步/等待

#

如果您熟悉 JavaScript 中的 promise,您可能也熟悉 async/await 語法。此語法在 Dart 中相同:函式標記為 async,而 async 函式總是傳回 Future。如果函式傳回 String 並標記為 async,它會傳回 Future<String>。如果它沒有傳回任何內容,但它是 async,它會傳回 Future<void>

以下範例顯示如何撰寫 async 函式

js
// Returns a Promise of a string,
// as the method is async
async fetchString() {
  // Typically some other async
  // operations would be done here.
  return "String Value";
}
dart
// Returns a future of a string,
// as the method is async
Future<String> fetchString() async {
  // Typically some other async
  // operations would be done here.
  return 'String Value';
}

呼叫此 async 函式如下所示

dart
Future<String> stringFuture = fetchString();
stringFuture.then((String str) {
  print(str); // 'String Value'
});

使用 await 關鍵字取得 future 的值。與 JavaScript 中一樣,這消除了呼叫 Future 上的 then 以取得其值的需要,並允許您以更同步的方式撰寫非同步程式碼。與 JavaScript 中一樣,等待 future 僅在 async 環境(例如另一個 async 函式)中才有可能。

以下範例顯示如何等待未來取得其值

dart
// We can only await futures within an async context.
Future<void> asyncFunction() async {
  var str = await fetchString();
  print(str); // 'String Value'
}

若要深入了解 Futureasync/await 語法,請參閱 非同步程式設計 實作範例。

串流

#

Dart 的非同步工具箱中的另一個工具是 Stream。雖然 JavaScript 有自己的串流概念,但 Dart 的串流更類似於 Observable,就像在常用的 rxjs 函式庫中找到的一樣。如果您碰巧熟悉這個函式庫,那麼 Dart 的串流應該會讓您感到熟悉。

對於不熟悉這些概念的人來說:Stream 基本運作方式就像 Future,但隨著時間推移,會散布多個值,就像事件匯流排一樣。您的程式碼可以聆聽串流,而且它可以完成或達到失敗狀態。

聆聽

#

若要聆聽串流,請呼叫其 listen 方法並提供回呼方法。每當串流發出值時,Dart 便會呼叫這個方法

dart
Stream<int> stream = ...
stream.listen((int value) {
  print('A value has been emitted: $value');
});

listen 方法包含用於處理錯誤或串流完成時的選用回呼

dart
stream.listen(
  (int value) { ... },
  onError: (err) {
    print('Stream encountered an error! $err');
  },
  onDone: () {
    print('Stream completed!');
  },
);

listen 方法會傳回 StreamSubscription 的執行個體,您可以使用它來停止聆聽串流

dart
StreamSubscription subscription = stream.listen(...);
subscription.cancel();

這不是聆聽串流的唯一方法。類似於 Futureasync/await 語法,您可以在 async 環境中將串流與 for-in 迴圈結合使用。for 迴圈會為發出的每個項目呼叫回呼方法,而且當串流完成或發生錯誤時便會結束

dart
Future<int> sumStream(Stream<int> stream) async {
  var sum = 0;
  await for (final value in stream) {
    sum += value;
  }
  return sum;
}

當以這種方式聆聽串流時發生錯誤,錯誤會在包含 await 關鍵字的那一行重新擲回。您可以使用 try-catch 陳述式來處理這個錯誤

dart
try {
  await for (final value in stream) { ... }
} catch (err) {
  print('Stream encountered an error! $err');
}

建立串流

#

Future 一樣,您有許多不同的方法可以建立串流。Stream 類別有實用程式建構函式,用於從 FutureIterable 建立串流,或用於建立在定時區間發出值的串流。若要深入了解,請參閱 Stream API 頁面。

StreamController
#

實用程式類別 StreamController 可以建立和控制串流。其 stream 屬性公開它控制的串流。其方法提供將事件新增到該串流的方法。

例如,add 方法可以發射新項目,而 close 方法完成串流。

以下範例顯示串流控制器的基本用法

dart
var listeners = 0;
StreamController<int>? controller;
controller = StreamController<int>(
  onListen: () {
    // Emit a new value every time the stream gets a new listener.
    controller!.add(listeners++);
    // Close the stream after the fifth listener.
    if (listeners > 5) controller.close();
  }
);
// Get the stream for the stream controller
var stream = controller.stream;
// Listen to the stream
stream.listen((int value) {
  print('$value');
});
非同步產生器
#

非同步產生器函式可以建立串流。這些函式類似於同步產生器函式,但使用 async* 關鍵字並傳回 Stream

在非同步產生器函式中,yield 關鍵字會將給定的值發射到串流中。不過,yield* 關鍵字會與串流搭配使用,而不是與其他可迭代物件搭配使用。這允許將其他串流的事件發射到此串流中。在以下範例中,函式會在最新產生的串流完成後繼續執行。

dart
Stream<int> asynchronousNaturalsTo(int n) async* {
  var k = 0;
  while (k < n) yield k++;
}

Stream<int> stream = asynchronousNaturalsTo(5);

// Prints each of 0 1 2 3 4 in succession.
stream.forEach(print(value));

非同步程式設計 文件中深入了解未來、串流和其他非同步功能。

類別

#

表面上,Dart 中的類別類似於 JavaScript 中的類別,儘管 JavaScript 類別在技術上更像是原型包裝器。在 Dart 中,類別是語言的標準功能。本節涵蓋在 Dart 中定義和使用類別,以及它們與 JavaScript 的不同之處。

「this」內容

#

Dart 中的 this 關鍵字比 JavaScript 中的更直接。在 Dart 中,您無法將函式繫結到 this,而 this 永遠不會依賴執行內容(就像在 JavaScript 中一樣)。在 Dart 中,this 僅在類別中使用,並且總是參考目前的執行個體。

建構函式

#

本節討論建構函式在 Dart 中與 JavaScript 中的差異。

標準建構函式

#

標準類別建構函式看起來非常類似於 JavaScript 建構函式。在 Dart 中,constructor 關鍵字會被完整的類別名稱取代,並且所有參數都必須明確指定型別。在 Dart 中,new 關鍵字曾經是建立類別執行個體的必要條件,但現在已變成選用,而且不再建議使用。

dart
class Point {
  final double x;
  final double y;

  Point(double x, double y) : this.x = x, this.y = y { }
}

// Create a new instance of the Point class
Point p = Point(3, 5);

初始化清單

#

使用初始化清單撰寫建構函式。將初始化清單插入建構函式的參數和主體之間。

dart
class Point {
  ...
  Point.fromJson(Map<String, double> json)
      : x = json['x']!,
        y = json['y']! {
    print('In Point.fromJson(): ($x, $y)');
  }
  ...
}

建構函式參數

#

在建構函式中撰寫用於指定類別欄位的程式碼會感覺像是在建立樣板程式碼,因此 Dart 提供了一些語法糖,稱為 初始化參數,以簡化此程序

dart
class Point {
  double x;
  double y;

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

// Create a new instance of the Point class
Point p = Point(3, 5);

與函式類似,建構函式有選項可以採用定位參數或命名參數

dart
class Point {
  ...
  // With an optional positioned parameter
  Point(this.x, [this.y = 5]);
  // With named parameters
  Point({ required this.y, this.x = 5 });
  // With both positional and named parameters
  Point(int x, int y, { boolean multiply }) {
    ...
  }
  ...
}

命名建構函式

#

與 JavaScript 不同,Dart 允許類別有多個建構函式,方法是允許您為它們命名。您可以選擇只有一個未命名的建構函式,任何其他建構函式都必須命名

dart
const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  double x = 0;
  double y = 0;

  Point(this.x, this.y);

  // Named constructor
  Point.origin()
      : x = xOrigin,
        y = yOrigin;
}

常數建構函式

#

若要啟用不可變類別執行個體,請使用 const 建構函式。具有 const 建構函式的類別只能具有 final 執行個體變數。

dart
class ImmutablePoint {
  final double x, y;

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

建構函式重新導向

#

您可以從其他建構函式呼叫建構函式,以防止程式碼重複或為參數新增其他預設值

dart
class Point {
  double x, y;

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

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

工廠建構函式

#

當您不需要建立新的類別執行個體時,可以使用工廠建構函式。一個範例是傳回快取執行個體時

dart
class Logger {
  static final Map<String, Logger> _cache =
      <String, Logger>{};
 
  final String name;
 
  // Factory constructor that returns a cached copy,
  // or creates a new one if it is not yet available.
  factory Logger(String name) {
    return _cache.putIfAbsent(
        name, () => _cache[name] ??= Logger._internal(name);
  }

  // Private constructor for internal use only
  Logger._internal(this.name);
}

方法

#

在 Dart 和 JavaScript 中,方法都作為提供物件行為的函式。

js
function doSomething() { // This is a function
  // Implementation..
}

class Example {
  doSomething() { // This is a method
    // Implementation..
  }
}
dart
void doSomething() { // This is a function
 // Implementation..
}

class Example {
 void doSomething() { // This is a method
   // Implementation..
 }
}

擴充類別

#

Dart 允許類別擴充其他類別,就像 JavaScript 一樣。

dart
class Animal {
  int eyes;
 
  Animal(this.eyes);
 
  makeNoise() {
    print('???');
  }
}

class Cat extends Animal {
  Cat(): super(2);

  @override
  makeNoise() {
    print('Meow');
  }
}
Animal animal = Cat();
print(animal.eyes); // 2
animal.makeNoise(); // Meow

當覆寫父類別的方法時,請使用 @override 註解。雖然此註解是選用的,但它顯示覆寫是有意的。如果方法實際上並未覆寫超類別方法,Dart 分析器會顯示警告。

使用 super 關鍵字仍然可以呼叫正在覆寫的父方法

dart
class Cat extends Animal {
  ...
  @override
  makeNoise() {
    print('Meow');
    super.makeNoise();
  }
}
Animal animal = Cat();
animal.makeNoise(); // Meow
                    // ???

類別作為介面

#

與 JavaScript 類似,Dart 沒有介面的獨立定義。但是,與 JavaScript 不同的是,所有類別定義都兼作介面;您可以使用 implements 關鍵字將類別實作為介面。

當類別實作為介面時,其公開 API 必須由新類別實作。與 extends 不同的是,其方法和欄位實作不會與新類別共用。雖然類別只能擴充一個類別,但您可以一次實作多個介面,即使實作類別已經擴充另一個類別也是如此。

dart
class Consumer {
  consume() {
    print('Eating food...');
  }
}
class Cat implements Consumer {
  consume() {
    print('Eating mice...');
  }
}
Consumer consumer = Cat();
consumer.consume(); // Eating mice

實作介面時,無法呼叫 super 方法,因為方法主體不會繼承

dart
class Cat implements Consumer {
  @override
  consume() {
    print('Eating mice...');
    super.consume(); 
    // Invalid. The superclass `Object` has no `consume` method.
  }
}

抽象類別和方法

#

若要確保類別只能擴充或實作其介面,但禁止建構任何執行個體,請將其標記為 abstract

標記為 abstract 的類別可以具有抽象方法,這些方法不需要主體,而是需要在擴充類別或實作其介面時實作

dart
abstract class Consumer {
  consume();
}
// Extending the full class
class Dog extends Consumer {
  consume() {
    print('Eating cookies...');
  }
}
// Just implementing the interface
class Cat implements Consumer {
  consume() {
    print('Eating mice...');
  }
}
Consumer consumer;
consumer = Dog();
consumer.consume(); // Eating cookies...
consumer = Cat();
consumer.consume(); // Eating mice...

混入

#

Mixins 用於在類別之間分享功能。您可以在類別中使用 mixin 的欄位和方法,使用它們的功能,就像它們是類別的一部分一樣。一個類別可以使用多個 mixins。這有助於多個類別分享相同的功能,而不需要彼此繼承或分享共同的祖先。

使用 with 關鍵字將一個或多個以逗號分隔的 mixins 加入類別。

JavaScript 沒有等效的關鍵字。JavaScript 可以使用 Object.assign 在實例化後將其他物件合併到現有物件中。

以下範例顯示 JavaScript 和 Dart 如何達成類似的行為

js
class Animal {}

// Defining the mixins
class Flyer {
  fly = () => console.log('Flaps wings');
}
class Walker {
  walk = () => console.log('Walks on legs');
}
 
class Bat extends Animal {}
class Goose extends Animal {}
class Dog extends Animal {}

// Composing the class instances with
// their correct functionality.
const bat =
  Object.assign(
    new Bat(),
    new Flyer()
    );
const goose =
  Object.assign(
    new Goose(),
    new Flyer(),
    new Walker()
    );
const dog =
  Object.assign(
    new Dog(),
    new Walker()
    );

// Correct calls
bat.fly();
goose.fly();
goose.walk();
dog.walk();
// Incorrect calls
bat.walk(); // `bat` lacks the `walk` method
dog.fly(); // `dog` lacks the `fly` method
dart
abstract class Animal {}

// Defining the mixins
class Flyer {
  fly() => print('Flaps wings');
}
class Walker {
  walk() => print('Walks on legs');
}
 
class Bat extends Animal with Flyer {}
class Goose extends Animal with Flyer, Walker {}
class Dog extends Animal with Walker {}

// Correct calls
Bat().fly();
Goose().fly();
Goose().walk();
Dog().walk();
// Incorrect calls
Bat().walk(); // Not using the Walker mixin
Dog().fly(); // Not using the Flyer mixin

或者,您可以將 class 關鍵字替換為 mixin 以防止 mixin 被用作一般類別

dart
mixin Walker {
  walk() => print('Walks legs');
}
// Not possible, as Walker is no longer a class.
class Bat extends Walker {}

由於您可以使用多個 mixins,因此當它們用於同一個類別時,它們可以有重疊的方法或欄位。它們甚至可以與使用它們的類別或該類別的超類別重疊。它們被加入類別的順序很重要。

舉例來說

dart
class Bird extends Animal with Consumer, Flyer {

當在 Bird 的實例上呼叫方法時,Dart 會從自己的類別 Bird 開始,它優先於其他實作。如果 Bird 沒有實作,則會檢查 Flyer,接著是 Consumer,直到找到實作為止。父類別 Animal 會在最後被檢查。

擴充

#

擴充類別、實作介面或使用 mixins 都可以在受影響的類別可編輯時使用。不過,有時擴充一個已經存在的類別或屬於另一個函式庫或 Dart SDK 的類別會很有用。

在這些情況下,Dart 提供了撰寫現有類別擴充的功能。

舉例來說,以下 Dart SDK 中的 String 類別擴充允許解析整數

dart
extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }
}

若要讓擴充可用,它必須存在於同一個檔案中,或必須匯入它的檔案。

使用方式如下

dart
import 'string_apis.dart'; // Import the file the extension is in
var age = '42'.parseInt(); // Use the extension method.

取得器和設定器

#

Dart 中的 getter 和 setter 的運作方式與它們的 JavaScript 對應項完全相同

js
class Person {
  _age = 0;

  get age() {
    return this._age;
  }

  set age(value) {
    if (value < 0) {
      throw new Error(
        'age cannot be negative'
        );
    }
    this._age = value;
  }
}

var person = new Person();
person.age = 10;
console.log(person.age);
dart
class Person {
  int _age = 0;
 
  int get age {
    return _age;
  }
 
  set age(int value) {
    if (value < 0) {
      throw ArgumentError(
        'Age cannot be negative'
        );
    }
    _age = value;
  }
}

void main() {
  var person = Person();
  person.age = 10;
  print(person.age);
}

公開和私人成員

#

與 JavaScript 類似,Dart 沒有存取修飾子關鍵字:預設所有類別成員都是公開的。

JavaScript 將在 EcmaScript 標準的下次實際修訂中包含私有類別成員。因此,這方面的實作已經在各種瀏覽器和執行環境中可用了一段時間。

要在 JavaScript 中將類別成員設為私有,請在名稱前面加上井號 (或雜湊) 符號 (#)。

js
class Animal {
  eyes; // Public field
  #paws; // Private field

  #printEyes() { // Private method
    print(this.eyes);
  }

  printPaws() { // Public method
    print(this.#paws);
  }
}

要在 Dart 中將類別成員設為私有,請在名稱前面加上底線 (_)。

dart
class Animal {
  int eyes; // Public field
  int _paws; // Private field

  void _printEyes() { // Private method
    print(this.eyes);
  }

  void printPaws() { // Public method
    print(this._paws);
  }
}

JavaScript 使用雜湊作為慣例。Dart 編譯器強制使用底線來執行此功能。

Dart 會將私有成員設為對程式庫私有,而非對類別私有。這表示您可以從同一個程式庫中的程式碼存取私有成員。預設情況下,Dart 會將私有類別成員的存取權限限制在同一個檔案中的程式碼。若要將程式庫的範圍擴充到一個檔案以外,請加入 part 指令。若可行,避免使用 part。保留 part 供程式碼產生器使用。

延遲變數

#

若要指出 Dart 會在稍後初始化類別欄位,請將 late 關鍵字指定給這些類別欄位。這些類別欄位會保持非可為空的狀態。當變數不需要立即觀察或存取,且可以在稍後初始化時執行此動作。這與將欄位標示為可為空不同。

  • (非可為空)的 late 欄位無法在稍後指定為 null。

  • (非可為空)的 late 欄位在初始化之前存取時會擲回執行時期錯誤。應避免發生這種情況。

dart
class PetOwner {
  final String name;
  late final Pet _pet;
  PetOwner(this.name, String petName) {
    // Cyclic object graph, cannot set _pet before owner exists.
    _pet = Pet(petName, this);
  }
  Pet get pet => _pet;
}
class Pet {
  final String name;
  final PetOwner owner;
  Pet(this.name, this.owner);
}

僅當不明確的程式碼導致編譯器無法判斷程式碼是否初始化變數時,才對區域變數使用 late

dart
doSomething(int n, bool capture) {
  late List<Foo> captures;
  if (capture) captures = [];
  for (var i = 0; i < n; i++) {
    var foo = something(i);
    if (capture) captures.add(foo);
  }
}

在前面的範例中,如果 capture 為 true,編譯器不知道要指定 captures。使用 late 會將正常的「已指定」檢查延遲到執行時期。

泛型

#

雖然 JavaScript 不提供泛型,但 Dart 提供泛型來改善類型安全性並減少程式碼重複。

泛型方法

#

您可以將泛型套用至方法。若要定義泛型類型參數,請將其置於方法名稱後面的尖括號 < > 之間。然後,您可以在方法中將此類型用作回傳類型或在方法的參數中使用。

dart
Map<Object?, Object?> _cache = {};
T cache<T>(T value) => (_cache[value] ??= value) as T;

透過逗號分隔,定義多個泛型類型。

dart
// Defining a method with multiple generics.
T transform<T, Q>(T param1, Q param2) {
   ...
}
// Calling the method with explicitly defined types.
transform<int, String>(5, 'string value');
// Types are optional when the analyzer can infer them.
transform(5, 'string value');

泛型類別

#

泛型也可以套用至類別。您可以在呼叫建構函式時包含要使用的類型。這讓您可以針對特定類型調整可重複使用的類別。

在以下範例中,Cache 類別會快取特定類型。

dart
class Cache<T> {
  T getByKey(String key) {}
  void setByKey(String key, T value) {}
}
// Creating a cache for strings
var stringCache = Cache<String>(); // stringCache has type Cache<String>
stringCache.setByKey('Foo', 'Bar'); // Valid, setting a string value.
stringCache.setByKey('Baz', 5); // Invalid, int type does not match generic.

如果您省略類型宣告,執行時期類型會變成 Cache<dynamic>,而且對 setByKey 的兩個呼叫都是有效的。

限制泛型

#

您可以使用泛型,透過 extends 將程式碼限制在類型家族中。這可確保您的類別會使用延伸特定類型的泛型類型進行實例化。

dart
class NumberManager<T extends num> {
   ...
}
// Valid.
var manager = NumberManager<int>();
var manager = NumberManager<double>();
// Invalid, String nor its parent classes extend num.
var manager = NumberManager<String>();

文字中的泛型

#

MapSetList 文字可以接受類型引數。這有助於 Dart 無法推斷類型或正確推斷類型的情況。

例如,List 類別有一個泛型定義:class List<E>。類型參數 E 指的是清單內容的類型。通常,此類型會自動推論,並用於某些 List 類別的成員類型。(例如,它的第一個 getter 會傳回 E 類型的值。)定義 List 文字時,您可以明確定義泛型類型,如下所示

dart
// Automatic type inference
var objList = [5, 2.0]; // Type: List<num>
// Explicit type definition:
var objList = <Object>[5, 2.0]; // Type: List<Object>
// Sets work identically:
var objSet = <Object>{5, 2.0};

這也適用於 Map,它也使用泛型定義它們的鍵和值類型 (class Map<K, V>)

dart
// Automatic type inference
var map = {
  'foo': 'bar'
}; // Type: Map<String, String>
// Explicit type definition:
var map = <String, Object>{
  'foo': 'bar'
}; // Type: Map<String, Object>

文件註解

#

常規註解在 Dart 中的工作方式與在 JavaScript 中相同。使用 // 註解會註解掉剩餘行中其後的所有內容,而您可以使用 /* ... */ 來區塊註解跨多行。

除了常規註解之外,Dart 還具有與 文件註解 協同工作的 dart doc:一個第一方工具,可為 Dart 套件產生 HTML 文件。將文件註解置於所有公開成員的宣告上方被視為最佳實務。

使用三個正斜線而不是兩個 (///) 來定義文件註解

dart
/// The number of characters in this chunk when unsplit.
int get length => ...

後續步驟

#

本指南已向您介紹 Dart 和 JavaScript 之間的主要差異。在這個時候,請考慮閱讀 Dart 文件。您也可以閱讀 Flutter 文件。Flutter 是使用 Dart 建置的開源架構,它使用 Dart 從單一程式碼庫建置原生編譯的多平台應用程式。這些文件提供有關語言和實際入門方法的深入資訊。

一些可能的下一步