跳到主內容

術語表

以下是 Dart 文件中使用的術語定義。

輔助操作

一種自動化的原生代碼編輯,旨在對程式碼進行常見改進。

輔助操作是一種自動化的原生代碼編輯,旨在對程式碼進行常見改進。輔助操作的例子包括將 switch 語句轉換為 switch 表示式,反轉 if 語句中的 thenelse 塊,以及將 widget 插入到 widget 結構中。

相關文件與資源

常量上下文

程式碼中 const 關鍵字隱式存在,並且該區域內的一切都必須是常量的區域。

常量上下文是程式碼中的一個區域,在該區域內無需包含 const 關鍵字,因為該區域中的所有內容都必須是常量,const 關鍵字是隱式存在的。以下位置是常量上下文:

  • const 關鍵字為字首的列表、對映或集合字面量中的所有內容。例如:

    dart
    var l = const [/*constant context*/];
  • 常量建構函式呼叫中的引數。例如:

    dart
    var p = const Point(/*constant context*/);
  • const 關鍵字為字首的變數的初始化表示式。例如:

    dart
    const v = /*constant context*/;
  • 註解。

  • case 子句中的表示式。例如:

    dart
    void f(int e) {
      switch (e) {
        case /*constant context*/:
          break;
      }
    }

確定性賦值

判斷一個變數在使用前是否已被確定性地賦值。

確定性賦值分析是針對程式碼中每個點的每個區域性變數,判斷以下哪個條件成立的過程:

  • 變數已被確定性地賦值(確定性已賦值)。
  • 變數已被確定性地未賦值(確定性未賦值)。
  • 變數可能已被賦值,也可能未被賦值,取決於到達該點的執行路徑。

確定性賦值分析有助於發現程式碼中的問題,例如引用可能未被賦值的變數的位置,或者在變數可能已被賦值之後再次對其進行賦值(該變數只能賦值一次)的位置。

例如,在以下程式碼中,當變數 s 作為引數傳遞給 print 時,它是確定性未賦值的:

dart
void f() {
  String s;
  print(s);
}

但在以下程式碼中,變數 s 是確定性已賦值的:

dart
void f(String name) {
  String s = 'Hello $name!';
  print(s);
}

確定性賦值分析甚至可以在存在多條可能的執行路徑時判斷變數是否確定性已賦值(或未賦值)。在以下程式碼中,如果執行透過 if 語句的 true 分支或 false 分支,都會呼叫 print 函式,但由於無論選擇哪個分支 s 都會被賦值,因此在將其傳遞給 print 之前,它已被確定性地賦值。

dart
void f(String name, bool casual) {
  String s;
  if (casual) {
    s = 'Hi $name!';
  } else {
    s = 'Hello $name!';
  }
  print(s);
}

在控制流分析中,if 語句的末尾被稱為一個匯合點(join)——即兩條或多條執行路徑重新合併的地方。在匯合點,如果一個變數在所有合併的路徑上都確定性已賦值,那麼該變數就是確定性已賦值的;如果它在所有路徑上都確定性未賦值,那麼它就是確定性未賦值的。

有時一個變數在一條路徑上被賦值,但在另一條路徑上沒有,這種情況下該變數可能已被賦值,也可能未被賦值。在以下示例中,if 語句的 true 分支可能執行,也可能不執行,因此變數可能已被賦值,也可能未被賦值:

dart
void f(String name, bool casual) {
  String s;
  if (casual) {
    s = 'Hi $name!';
  }
  print(s);
}

如果存在一個不給 s 賦值的 false 分支,情況也一樣。

迴圈的分析稍微複雜一些,但遵循同樣的基本原理。例如,while 迴圈中的條件總是執行的,但迴圈體可能執行,也可能不執行。因此,就像 if 語句一樣,在 while 語句的末尾,條件為 true 的路徑與條件為 false 的路徑之間存在一個匯合點。

函式

一個總稱,指代頂層函式、區域性函式、靜態方法和例項方法。

不可反駁模式

總是匹配的模式。

不可反駁模式是總是匹配的模式。不可反駁模式是唯一可以在不可反駁上下文中出現的模式:宣告賦值模式上下文。

Late

一個關鍵字,用於延遲初始化變數,通常用於非空變數。

Dart 中的 late 關鍵字用於指示一個變數將在其宣告之後、使用之前被初始化。當你確定變數最終會獲得一個值,只是不是立即獲得時,這有助於避免將其宣告為可空 (?)。

使用 late 會延遲初始化,使你能夠編寫更靈活、更易讀的程式碼,尤其是在處理依賴項或複雜設定時。

例如:

dart
late String description;

void setup() {
  description = 'This will be initialized before use.';
}

在使用作為公共 API 一部分的 late 變數時要小心。如果客戶端在變數初始化之前訪問它,將會遇到 LateInitializationError,它提供的上下文資訊很少。在這種情況下,考慮使用一個私有的可空變數,並提供一個公共 getter,如果在訪問過早時丟擲描述性錯誤(例如 StateError),這樣可以為 API 使用者提供更清晰的反饋,儘管會增加複雜性。

當變數只需要設定一次時,你也可以使用 late final。這在物件構造時值不可用的場景中很有用,例如物件圖中的迴圈依賴。

示例:

dart
class LinkedQueue<T> {
  late final QueueLink<T> _head;
  
  LinkedQueue() {
    _head = QueueLink<T>._head(owner: this); // Cyclic reference between objects
  }
}

注意:如果在 late 變數初始化之前或完全未初始化的情況下訪問它,將會導致執行時錯誤。

Mixin 應用

當一個 mixin 應用於一個類時建立的類。

Mixin 應用是當一個 mixin 應用於一個類時建立的類。例如,考慮以下宣告:

dart
class A {}

mixin M {}

class B extends A with M {}

B 是 Mixin M 應用於類 A 的 Mixin 應用(有時表示為 A+M)的子類。類 A+MA 的子類,幷包含從 M 複製的成員。

你可以透過如下定義為 Mixin 應用賦予實際名稱:

dart
class A {}

mixin M {}

class A_M = A with M;

給定 A_M 的這個宣告,以下對 B 的宣告與原始示例中對 B 的宣告是等效的:

dart
class B extends A_M {}

相關文件與資源

覆蓋推斷

方法宣告中缺失型別如何被推斷。

覆蓋推斷是根據方法覆蓋的一個或多個相應方法的型別來推斷方法宣告中任何缺失型別的過程。

如果候選方法(缺失型別資訊的方法)覆蓋了單個繼承方法,則會推斷出被覆蓋方法中的相應型別。例如,考慮以下程式碼:

dart
class A {
  int m(String s) => 0;
}

class B extends A {
  @override
  m(s) => 1;
}

Bm 的宣告是一個候選者,因為它同時缺少返回型別和引數型別。由於它覆蓋了一個方法(A 中的方法 m),因此將使用被覆蓋方法中的型別來推斷缺失的型別,就好像 B 中的方法被宣告為 int m(String s) => 1; 一樣。

如果候選方法覆蓋了多個方法,並且其中一個被覆蓋方法 Ms 的函式型別是所有其他被覆蓋方法的函式型別的超型別,則使用 Ms 來推斷缺失的型別。例如,考慮以下程式碼:

dart
class A {
  int m(num n) => 0;
}

class B {
  num m(int i) => 0;
}

class C implements A, B {
  @override
  m(n) => 1;
}

Cm 的宣告是覆蓋推斷的候選者,因為它同時缺少返回型別和引數型別。它覆蓋了 A 中的 mB 中的 m,因此編譯器需要選擇其中一個來推斷缺失的型別。但由於 Am 的函式型別(int Function(num))是 Bm 的函式型別(num Function(int))的超型別,因此使用 A 中的函式來推斷缺失的型別。結果與將 C 中的方法宣告為 int m(num n) => 1; 相同。

如果沒有被覆蓋方法的函式型別是所有其他被覆蓋方法的超型別,則會報錯。

相關文件與資源

分部檔案

包含 part of 指令的 Dart 原始檔。

分部檔案是包含 part of 指令並透過 part 指令包含在庫中的 Dart 原始檔。

潛在非空

明確為非空或由於是型別引數而為非空的型別。

一個型別是潛在非空的,如果它明確為非空,或者它是一個型別引數。

一個型別是明確非空的,如果它是後面沒有問號 (?) 的型別名稱。請注意,有一些型別總是可空的,例如 Nulldynamic,並且 FutureOr 只有在後面沒有問號並且型別引數為非空時(例如 FutureOr<String>)才為非空。

型別引數是潛在非空的,因為實際執行時型別(作為型別引數指定的型別)可能為非空。例如,給定 class C<T> {} 的宣告,型別 C 可以與非空型別引數一起使用,如 C<int>

相關文件與資源

公共庫

位於包的 lib 目錄中,但不在 lib/src 目錄內的庫。

公共庫是位於包的 lib 目錄內但不在 lib/src 目錄內的庫。

快速修復

一種自動化的原生代碼編輯,旨在修復特定診斷報告的問題。

重構

一種程式碼編輯,旨在進行非本地性修改或需要使用者互動的修改。

重構是一種程式碼編輯,旨在進行非本地性修改或需要使用者互動的修改。重構的例子包括重新命名、刪除或提取程式碼。

相關文件與資源

可反駁模式

可以針對一個值進行測試的模式。

可反駁模式是可以針對一個值進行測試以確定模式是否與該值匹配的模式。如果不匹配,模式會反駁或拒絕該匹配。可反駁模式出現在匹配上下文中。

子類

繼承另一個類實現的類。

子類是使用 extends 關鍵字或透過mixin 應用繼承另一個類實現的類。

dart
// A is a subclass of B; B is the superclass of A.
class A extends B {}

// B1 has the superclass `A with M`, which has the superclass A.
class B1 extends A with M {}

子類關係也意味著相關的子型別關係。例如,class A 隱式定義了一個關聯型別 A,類 A 的例項屬於該型別。因此,class A extends B 不僅聲明瞭類 AB 的子類,還確立了型別 A 是型別 B子型別

子類關係是子型別關係的子集。當文件說“S 必須是 T 的子型別”時,ST 的子類是沒問題的。然而,反過來不成立:並非所有子型別都是子類。

子型別

可以在任何需要其超型別值的地方使用的型別。

子型別關係是指在需要某種型別(超型別)值的地方,可以使用另一種型別的值進行替換。例如,如果 ST 的子型別,則可以在需要型別 T 值的地方替換使用型別 S 的值。

子型別支援其超型別的所有操作(以及可能的一些額外操作)。實際上,這意味著你可以將子型別的值賦給任何期望超型別的位置,並且超型別的所有方法都可以在子型別上使用。

這至少在靜態層面是成立的。特定的 API 在執行時可能不允許這種替換,這取決於其操作。

一些子型別關係基於型別的結構,例如可空型別(例如,intint? 的子型別)和函式型別(例如,String Function()void Function() 的子型別)。

對於類,子型別也可以透過實現繼承(直接或間接)引入。

dart
// A is a subtype of B, but NOT a subclass of B.
class A implements B {}

// C is a subtype AND a subclass of D.
class C extends D {}

變型與變型位置

改變一個型別的型別引數如何影響原始型別和結果型別之間的關係。

在 Dart 中,改變型別宣告(如類)或函式返回型別的型別引數,會使整體型別關係向同一方向變化(協變)。

然而,改變函式引數型別的型別,會使整體型別關係向相反方向變化(逆變)。

當一個類(或其他型別宣告,如 mixin)的型別引數與實際型別引數“協變”時,該型別引數被稱為協變的。換句話說,如果型別引數被替換為子型別,則整個型別也是子型別。

例如,類 List 的型別引數是協變的,因為列表型別與其型別引數協變:List<int>List<Object> 的子型別,因為 intObject 的子型別。

在 Dart 中,所有類、mixin、mixin class 和列舉宣告的所有型別引數都是協變的。

然而,函式型別是不同的:函式型別在其返回型別上是協變的,但在其引數型別上是相反的(稱為逆變)。例如,型別 int Function(int) 是型別 Object Function(int) 的子型別,但它是 int Function(Object) 的超型別。

如果你考慮它們的可替換性,這就說得通了。如果你呼叫一個靜態型別為 int Function(int) 的函式,該函式在執行時實際上可以是型別 int Function(Object)。根據靜態型別,你期望能夠傳遞一個 int 給它。這沒有問題,因為該函式實際上接受任何 Object,這包括所有型別為 int 的物件。類似地,返回的結果將是 int 型別,這也與你基於靜態型別的期望一致。

因此,int Function(Object)int Function(int) 的子型別。

請注意,對於引數型別來說,一切都顛倒了。特別是,函式型別之間的這種子型別關係要求引數型別存在相反的子型別關係。例如,void Function(Object)void Function(int) 的子型別,因為 intObject 的子型別。

對於像 List<void Function(int)> 這樣更復雜的型別,你必須考慮型別中的位置。為了實現這一點,將型別的一部分變成一個佔位符,然後考慮當將不同型別放置在該位置時,型別會發生什麼變化。

例如,將 List<void Function(_)> 視為一個型別模板,你可以在佔位符 _ 的位置放入不同的型別。這種型別在該佔位符出現的位置是逆變的。

以下透過將 Objectint 替換 _ 來闡述這一點。List<void Function(Object)>List<void Function(int)> 的子型別,因為 void Function(Object)void Function(int) 的子型別,因為 voidvoid 的子型別(返回型別),並且 intObject 的子型別(引數型別,順序相反)。因此,位於 _ 的型別與整體型別 List<void Function(_)> 的變化方向相反,而根據定義,這種“相反方向”使其成為一個逆變位置

協變位置的定義類似。例如,_ 在型別 List<_> 中處於協變位置,並且 _ 在型別 _ Function(int) 中也處於協變位置。

還有另一種位置稱為不變,但它出現的頻率低得多,因此此處省略了其細節。

實際上,通常只需要知道類、mixin 等的型別引數處於協變位置,函式型別的返回型別也處於協變位置,而引數型別處於逆變位置就足夠了。

萬用字元

在模式和其他上下文中使用的一個符號 (_),用於代替變數名錶示未使用的值。

萬用字元是下劃線字元 (_),用於忽略值或表明值是有意未使用的。它常用於模式、解構和 switch 表示式中,用於匹配任何值而不將其繫結到名稱。

萬用字元透過明確標記在特定上下文不需要的值來幫助程式碼更具意圖性。

示例:

dart
// Ignoring the value in a for-each loop.
var names = ['Alice', 'Bob', 'Charlie'];
for (var _ in names) {
  print('Someone is here!');
}

萬用字元模式在以下情況特別有用:

  • 你只需要解構值中的某些部分。
  • 你想明確表明某些值被忽略了。
  • 你在模式匹配中需要一個捕獲所有情況。

Zone

一種在不修改非同步程式碼本身的情況下自定義非同步程式碼行為的機制。

Zone 是一種執行上下文,允許你執行程式碼時自定義處理計時器、微任務和未捕獲錯誤等非同步事件的行為。

Zone 對於以下方面很有用:

  • 日誌記錄
  • 錯誤跟蹤
  • 在非同步間隙中維護特定請求的狀態(例如,在伺服器應用中)
  • 測試和除錯非同步行為

Zone 提供了一種跟蹤和影響非同步執行的方式,而無需非同步程式碼瞭解 Zone 的存在。

你可以使用 runZoned(或 runZonedGuarded)建立一個新的 zone,並覆蓋特定於 zone 的行為,例如錯誤處理和計時器。甚至 print 也可以被覆蓋,儘管它不是非同步的,只是為了方便而包含在內。

示例:

dart
import 'dart:async';

void main() {
  runZonedGuarded(() {
    Future.delayed(Duration(seconds: 1), () {
      throw 'Zone caught this error!';
    });
  }, (error, stackTrace) {
    print('Caught error: $error');
  });
}

在前面的示例中,非同步回撥內部的未捕獲錯誤被自定義 zone 攔截。