跳至主要內容

函式

Dart 是一種真正的面嚮物件語言,因此即使是函式也是物件,並具有 Function 型別。這意味著函式可以賦值給變數,也可以作為引數傳遞給其他函式。你也可以像呼叫函式一樣呼叫 Dart 類的例項。詳情請參閱可呼叫物件

這是一個實現函式的例子:

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

雖然 Effective Dart 建議為公共 API 新增型別註解,但如果你省略型別,函式仍然可以工作:

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

對於只包含一個表示式的函式,你可以使用簡寫語法:

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

=> expr 語法是 { return expr; } 的簡寫。=> 符號有時被稱為箭頭語法。

引數

#

函式可以有任意數量的必需位置引數。這些引數後面可以跟命名引數或可選位置引數(但不能同時出現)。

在向函式傳遞引數或定義函式引數時,可以使用尾隨逗號

命名引數

#

命名引數是可選的,除非它們被明確標記為 required

定義函式時,使用 {param1, param2, …} 來指定命名引數。如果你沒有提供預設值或將命名引數標記為 required,它們的型別必須是可為空的,因為它們的預設值將是 null

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

呼叫函式時,可以使用 paramName: value 來指定命名引數。例如:

dart
enableFlags(bold: true, hidden: false);

要為命名引數定義 null 以外的預設值,請使用 = 來指定預設值。指定的值必須是編譯時常量。例如:

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

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

如果你希望命名引數成為強制性引數,要求呼叫者為該引數提供值,請使用 required 進行註解:

dart
const Scrollbar({super.key, required Widget child});

如果有人嘗試在不指定 child 引數的情況下建立 Scrollbar,分析器將報告問題。

你可能希望將位置引數放在前面,但 Dart 不要求這樣做。Dart 允許命名引數在引數列表中的任何位置,以適應你的 API 需求:

dart
repeat(times: 2, () {
  ...
});

可選位置引數

#

將一組函式引數用 [] 括起來,將其標記為可選位置引數。如果你不提供預設值,它們的型別必須是可為空的,因為它們的預設值將是 null

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

以下是一個不帶可選引數呼叫此函式的示例:

dart
assert(say('Bob', 'Howdy') == 'Bob says Howdy');

以下是一個帶第三個引數呼叫此函式的示例:

dart
assert(
  say('Bob', 'Howdy', 'smoke signal') ==
      'Bob says Howdy with a smoke signal',
);

要為可選位置引數定義 null 以外的預設值,請使用 = 來指定預設值。指定的值必須是編譯時常量。例如:

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

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

main() 函式

#

每個應用程式都必須有一個頂級 main() 函式,它作為應用程式的入口點。main() 函式返回 void,並有一個可選的 List<String> 引數用於接收引數。

這是一個簡單的 main() 函式:

dart
void main() {
  print('Hello, World!');
}

這是一個接受引數的命令列應用程式的 main() 函式示例:

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

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

你可以使用 args 庫來定義和解析命令列引數。

函式作為一等物件

#

你可以將函式作為引數傳遞給另一個函式。例如:

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

var list = [1, 2, 3];

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

你也可以將函式賦值給變數,例如:

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

此示例使用匿名函式。更多內容請參閱下一節。

函式型別

#

你可以指定函式的型別,這被稱為函式型別。函式型別是透過將函式宣告頭中的函式名替換為關鍵字 Function 來獲得的。此外,你可以省略位置引數的名稱,但命名引數的名稱不能省略。例如:

dart
void greet(String name, {String greeting = 'Hello'}) =>
    print('$greeting $name!');

// Store `greet` in a variable and call it.
void Function(String, {String greeting}) g = greet;
g('Dash', greeting: 'Howdy');

匿名函式

#

儘管你為大多數函式命名,例如 main()printElement(),你也可以建立沒有名稱的函式。這些函式被稱為匿名函式lambda閉包

匿名函式類似於命名函式,因為它具有:

  • 零個或多個引數,以逗號分隔。
  • 括號內可選的型別註解。

以下程式碼塊包含函式體:

dart
([[Type] param1[, ...]]) {
  codeBlock;
}

以下示例定義了一個帶有未型別化引數 item 的匿名函式。該匿名函式將其傳遞給 map 函式。map 函式對列表中的每個專案進行呼叫,將每個字串轉換為大寫。然後,傳遞給 forEach 的匿名函式列印每個轉換後的字串及其長度。

dart
const list = ['apples', 'bananas', 'oranges'];

var uppercaseList = list.map((item) {
  return item.toUpperCase();
}).toList();
// Convert to list after mapping

for (var item in uppercaseList) {
  print('$item: ${item.length}');
}

點選執行執行程式碼。

void main() {
  const list = ['apples', 'bananas', 'oranges'];

  var uppercaseList = list.map((item) {
    return item.toUpperCase();
  }).toList();
  // Convert to list after mapping

  for (var item in uppercaseList) {
    print('$item: ${item.length}');
  }
}

如果函式只包含一個表示式或 return 語句,可以使用箭頭符號縮短它。將以下行貼上到 DartPad 中並點選執行以驗證其功能等效性。

dart
var uppercaseList = list.map((item) => item.toUpperCase()).toList();
uppercaseList.forEach((item) => print('$item: ${item.length}'));

詞法作用域

#

Dart 根據其程式碼的佈局確定變數的作用域。具有此功能的程式語言被稱為詞法作用域語言。你可以“向外跟蹤大括號”以檢視變數是否在作用域內。

示例:一系列巢狀函式,每個作用域級別都有變數:

dart
bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;

    void nestedFunction() {
      var insideNestedFunction = true;

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

nestedFunction() 方法可以使用所有級別的變數,一直到頂級。

詞法閉包

#

一個能夠訪問其詞法作用域中變數的函式物件(當函式位於該作用域之外時)被稱為閉包

函式可以捕獲在周圍作用域中定義的變數。在以下示例中,makeAdder() 捕獲了變數 addBy。無論返回的函式走到哪裡,它都記得 addBy

dart
/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(int addBy) {
  return (int i) => addBy + i;
}

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

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

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

Tear-offs

#

當你引用函式、方法或命名建構函式時,不帶括號,Dart 會建立一個 tear-off。這是一個閉包,它接受與該函式相同的引數,並在你呼叫它時呼叫底層函式。如果你的程式碼需要一個閉包,它呼叫一個與閉包接受相同引數的命名函式,請不要將呼叫封裝在 lambda 中。使用 tear-off。

dart
var charCodes = [68, 97, 114, 116];
var buffer = StringBuffer();
dart
// Function tear-off
charCodes.forEach(print);

// Method tear-off
charCodes.forEach(buffer.write);
不好dart
// Function lambda
charCodes.forEach((code) {
  print(code);
});

// Method lambda
charCodes.forEach((code) {
  buffer.write(code);
});

測試函式相等性

#

以下是測試頂級函式、靜態方法和例項方法相等性的示例:

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

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

void main() {
  Function x;

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

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

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

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

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

返回值

#

所有函式都會返回值。如果未指定返回值,則語句 return null; 會隱式新增到函式體中。

dart
foo() {}

assert(foo() == null);

要在函式中返回多個值,請將這些值聚合到記錄中。

dart
(String, int) foo() {
  return ('something', 42);
}

生成器

#

當你需要延遲生成一系列值時,考慮使用生成器函式。Dart 內建支援兩種生成器函式:

  • 同步生成器:返回 Iterable 物件。
  • 非同步生成器:返回 Stream 物件。

要實現同步生成器函式,請將函式體標記為 sync*,並使用 yield 語句來傳遞值:

dart
Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

要實現非同步生成器函式,請將函式體標記為 async*,並使用 yield 語句來傳遞值:

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

如果你的生成器是遞迴的,你可以透過使用 yield* 來提高其效能:

dart
Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

外部函式

#

外部函式是其函式體與宣告分開實現的函式。在函式宣告前包含 external 關鍵字,如下所示:

dart
external void someFunc(int i);

外部函式的實現可以來自另一個 Dart 庫,或者更常見的是來自另一種語言。在互操作上下文中,external 為外部函式或值引入型別資訊,使其可在 Dart 中使用。實現和用法嚴重依賴於平臺,因此請檢視互操作文件,例如CJavaScript以瞭解更多資訊。

外部函式可以是頂級函式、例項方法getter 或 setter,或非重定向建構函式例項變數也可以是 external,這相當於一個外部 getter 和(如果變數不是 final)一個外部 setter。