跳至主要內容

針對 JavaScript 開發人員的 Dart 學習指南

本指南旨在您學習 Dart 時,能善用您的 JavaScript 程式設計知識。它展示了這兩種語言的主要相似之處與差異,並介紹 JavaScript 不支援的 Dart 概念。身為 JavaScript 開發人員,Dart 應讓您感到相當熟悉,因為這兩種語言有許多共通概念。

和 JavaScript 一樣,Dart 也在事件迴圈上執行,因此這兩種語言執行程式碼的方式很相似。例如,非同步概念,像是 futures (JavaScript 中的 promises) 和 async/await 語法都非常相似。

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

Dart 預設啟用 Null Safety (空值安全)。JavaScript 不支援 Null Safety (空值安全)。身為 JavaScript 開發人員,您可能需要一些時間學習如何編寫 Null Safety (空值安全) 程式碼,但其優點是能更好地防範 Null 參考例外,這些例外甚至能在編譯 Dart 程式碼之前就被偵測到。(因此能避免在 JavaScript 變數結果為 null 時,執行運算而發生的那些可怕的 TypeError。)

慣例與 Linting

#

JavaScript 和 Dart 都有 Linting 工具來強制執行標準慣例。雖然 JavaScript 提供了許多工具、標準和組態,但 Dart 只有一套官方的版面配置和風格慣例,以及一個 Linter 來簡化遵循。Dart 分析器會 Lint 程式碼,同時提供更多分析功能。若要為您的專案自訂 Lint 規則,請依照「自訂靜態分析」的指示操作。

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)
  • Lists (列表) (List,也稱為陣列)
  • Sets (集合) (Set)
  • Maps (映射) (Map)
  • Symbols (符號) (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 中的布林值都表示二元條件。這兩個值表示值或表達式是 true 還是 false。您可以使用字面值 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 Safety (空值安全)

#

與 JavaScript 不同,Dart 支援 Null Safety (空值安全)。在 Dart 中,所有型別預設為不可為空。這對 Dart 開發人員有利,因為 Dart 在編寫程式碼時 (而不是在執行階段) 捕捉 Null 參考例外。

可空型別 vs 不可空型別

#

以下程式碼範例中的任何變數都不能為 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-aware 運算子 (空值感知運算子)

#

Dart 支援多個運算子來處理可空性。如同 JavaScript,Dart 支援 Null 賦值運算子 (??=)、Null 聯合運算子 (??) 和選用鏈結運算子 (?.)。這些運算子的運作方式與 JavaScript 相同。

! 運算子

#

在可空變數或表達式可能不為 null 的情況下,您可以告知編譯器使用 (!) 運算子來抑制任何編譯時期錯誤。將此運算子放在表達式之後。

請勿將此與 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));
}

Arrow 語法 (箭頭語法)

#

Dart 和 JavaScript 都支援 Arrow 語法 (=>),但在支援方式上有所不同。在 Dart 中,只有當函式包含單一表達式或 return 陳述式時,才能使用 Arrow 語法。

例如,以下 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. 參數已命名且未標記為必要。

若要定義選用位置參數,請將它們括在方括號中,放在任何必要位置參數之後。您不能在選用參數之後接續必要參數。

由於 Null Safety (空值安全),選用位置參數必須具有預設值或標記為可空。若要瞭解更多資訊,請參閱前一節關於 Null Safety (空值安全) 的內容。

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

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)

再次強調,在 Null Safety (空值安全) 的情況下,未標記為必要的命名參數需要具有預設值或標記為可空。

以下程式碼定義了具有命名參數的函式

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

First-class 函式 (一級函式)

#

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. 使用 Arrow 語法 (箭頭語法)

同樣地,Dart 也有兩種宣告匿名函式的方式。兩者的運作方式都與 JavaScript Arrow 表達式類似。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();

Generator 函式 (產生器函式)

#

這兩種語言都支援Generator 函式 (產生器函式) 。這些函式傳回可迭代的項目集合,這些項目經過計算以避免不必要的工作。

若要在 Dart 中編寫 Generator 函式 (產生器函式) ,請在函式參數後新增 sync* 關鍵字,並傳回 Iterable。使用 yield 關鍵字將項目新增至最終的可迭代物件,或使用 yield* 新增整組項目。

以下範例顯示如何編寫基本 Generator 函式 (產生器函式)

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

您也可以定義非同步 Generator 函式 (產生器函式) ,它們會傳回串流而不是可迭代物件。在即將到來的「非同步」章節中瞭解更多資訊。

陳述式

#

本節說明 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 時,您可以將其與放在 case 上的標籤結合使用

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 允許在需要布林值的地方使用任何值。然後,它會將這些值轉換為 true 或 false。JavaScript 認為空字串和數字 0 是「falsy」值。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-aware 變體 (??=)。

若要瞭解更多資訊,請參閱 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.

串聯 (Cascades, .. 運算子)

#

Dart 允許您在單一物件上串聯多個方法呼叫、屬性賦值或兩者兼具。Dart 將此稱為串聯 (cascading),並使用串聯語法 (..) 來執行此動作。

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 中類似的類型進行比較。

Lists (列表)

#

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

Sets (集合)

#

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

Maps (映射)

#

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

Spread 運算子 (展開運算子)

#

與 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 對應項目的關係。

Futures (期貨)

#

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

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

以下範例顯示,在 Dart 中處理 Future 的方式與在 JavaScript 中處理 Promise 的方式相同。

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

Async/Await

#

如果您熟悉 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 函式)中才有可能。

以下範例顯示如何等待 Future 以取得其值。

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

若要瞭解有關 Futureasync/await 語法的更多資訊,請參閱非同步程式設計教學課程。

Streams (串流)

#

Dart 非同步工具箱中的另一個工具是 Stream。雖然 JavaScript 有自己的串流概念,但 Dart 的串流更類似於常用的 rxjs 庫中找到的 Observable。如果您碰巧熟悉此庫,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');
}

建立 Streams (串流)

#

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

非同步程式設計文件中瞭解有關 Future、串流和其他非同步功能的更多資訊。

類別

#

從表面上看,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 建構函式。具有 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);
}

Factory 建構函式

#

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

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 用於在類別之間共用功能。您可以在類別中使用 mixin 的欄位和方法,就像使用它們的功能就像是類別的一部分一樣。一個類別可以使用多個 mixin。這在多個類別共用相同功能時很有幫助,而無需彼此繼承或共用共同的祖先。

使用 with 關鍵字將一個或多個逗號分隔的 mixin 新增至類別。

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

由於您可以使用多個 mixin,因此當在同一個類別上使用時,它們可以彼此具有重疊的方法或欄位。它們甚至可以與使用它們的類別或該類別的超類別重疊。它們新增到類別的順序很重要。

舉例來說

dart
class Bird extends Animal with Consumer, Flyer {

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

擴充

#

當受影響的類別可編輯時,擴展類別、實作介面或使用 mixin 都有效。但是,有時擴展已存在的類別或屬於另一個庫或 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.

Getters 與 setters (取值器與賦值器)

#

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 用於程式碼產生器。

Late 變數 (延遲初始化變數)

#

若要指示 Dart 在稍後初始化類別欄位,請將 late 關鍵字指派給這些類別欄位。這些類別欄位保持不可為 null。當變數不需要立即觀察或存取,並且可以稍後初始化時,請執行此操作。這與將欄位標記為可為 null 不同。

  • (不可為 null)late 欄位稍後不能指派 null。

  • (不可為 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 也是如此,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 從單一程式碼庫建構原生編譯的多平台應用程式。這些文件提供了有關語言的深入資訊和入門的實用方法。

一些可能的後續步驟