目錄

身為 JavaScript 開發者學習 Dart

本指南旨在於您學習 Dart 時,運用您的 JavaScript 程式設計知識。它展示了這兩種語言的主要異同,並介紹了 JavaScript 中不支援的 Dart 概念。身為 JavaScript 開發者,Dart 應該會讓您感到相當熟悉,因為這兩種語言都有許多共同的概念。

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

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

Dart 預設啟用空值安全。JavaScript 不支援空值安全。身為 JavaScript 開發者,可能需要一段時間來學習如何撰寫空值安全程式碼,但其好處是更能防範在編譯 Dart 程式碼之前就偵測到的空值參考例外。(從而避免在對結果為 null 的 JavaScript 變數執行操作時發生的可怕 TypeError。)

慣例與程式碼檢查

#

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

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

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

Dart 支援以逗號分隔的集合、參數或引數列表的尾隨逗號。當您新增尾隨逗號時,格式化工具會將每個列表項目放在自己的行上。當您認為您的列表稍後可能會新增更多項目時,請新增尾隨逗號。避免僅為了格式化好處而新增尾隨逗號。

JavaScript 僅在列表和 Map 字面值中支援尾隨逗號。

內建類型

#

JavaScript 和 Dart 都將它們的資料分類為類型。每個變數都有一個相關聯的類型。類型決定了變數可以儲存的值類型以及可以對這些值執行的操作。Dart 與 JavaScript 的不同之處在於,它會為每個表達式和變數指定靜態類型。靜態類型預測變數值或表達式值的執行階段類型。這表示 Dart 應用程式具有健全的靜態型別。

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

Dart 支援下列內建類型

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

若要深入瞭解,請查看內建類型中的Dart 語言導覽

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

例如:對於數字類型的相同值,equals 運算子 ==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 不允許。

若要了解更多資訊,請參閱 類別 章節。

空值安全

#

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

可空值與不可空值類型

#

下列程式碼範例中的任何變數都不能為 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!';
}

空值感知運算子

#

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

如果在執行階段 myObjectnull,則會發生執行階段錯誤。

函式

#

雖然 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 對於作為建構子的函式表達式的支援,或建立此物件的自訂繫結。

若要了解更多資訊,請參閱 類別 章節。

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 關鍵字將項目新增至最終的可迭代項目,或使用 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 時,您可以將其與放在 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(表達式值為 xx++x++
x = x - 1(表達式值為 x - 1--x--x
x = x - 1(表達式值為 xx--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 也包含一個 ~/ 運算子(稱為截斷除法運算子),它會除以雙精度浮點數並輸出一個無條件捨去的整數

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 是「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 的變體 (??=)。

若要深入了解,請參閱 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);

Map

#

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',
];

它對於映射(maps)和集合(sets)的作用方式類似。

集合 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 有自己的 stream 概念,但 Dart 的 stream 更像是 Observable,如常用 rxjs 函式庫中找到的。如果您碰巧熟悉這個函式庫,則 Dart 的 stream 應該會讓您感到熟悉。

對於那些不熟悉這些概念的人:Stream 的作用基本上像 Future,但有多個值隨時間分散,就像一個事件匯流排。您的程式碼可以監聽 stream,而且它可以完成或達到失敗狀態。

監聽

#

若要監聽 stream,請呼叫其 listen 方法並提供回呼方法。每當 stream 發出一個值時,Dart 就會呼叫此方法。

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

listen 方法包含用於處理錯誤或在 stream 完成時的選用回呼。

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

listen 方法會回傳一個 StreamSubscription 的實例,您可以使用它來停止監聽 stream。

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

這不是監聽 stream 的唯一方式。與 Futureasync/await 語法類似,您可以在 async 環境中將 stream 與 for-in 迴圈結合。for 迴圈會針對發出的每個項目叫用回呼方法,並且當 stream 完成或發生錯誤時結束。

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

當以這種方式監聽 stream 時發生錯誤時,該錯誤會在包含 await 關鍵字的那行重新擲回。您可以使用 try-catch 語句處理此錯誤。

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

建立 streams

#

如同 Future 一樣,您有幾種不同的方式可以建立 stream。Stream 類別具有實用建構函式,可用於從 FutureIterable 建立 stream,或建立以定時間隔發出值的 stream。若要進一步了解,請參閱Stream API 頁面。

StreamController
#

實用類別 StreamController 可以建立和控制 stream。它的 stream 屬性會公開它所控制的 stream。它的方法提供將事件新增至該 stream 的方法。

例如,add 方法可以發出新項目,而 close 方法會完成 stream。

以下範例顯示 stream 控制器的基本用法。

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');
});
非同步產生器
#

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

在非同步產生器函式中,yield 關鍵字會將給定的值發送到 stream。但是,yield* 關鍵字會與 stream 而非其他可迭代物件一起運作。這允許將其他 stream 的事件發送到此 stream。在以下範例中,函式會在新產生的 stream 完成後繼續執行。

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、stream 和其他非同步功能。

類別

#

從表面上看,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...

Mixin

#

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.

Getter 和 Setter

#

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 也是如此,它們也使用泛型來定義其鍵和值類型 (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 從單一程式碼庫建置原生編譯的多平台應用程式。這些文件提供了有關該語言的深入資訊,以及入門的實用方法。

一些可能的後續步驟