函式
Dart 是一種真正的面嚮物件語言,因此即使是函式也是物件,並具有 Function 型別。這意味著函式可以賦值給變數,也可以作為引數傳遞給其他函式。你也可以像呼叫函式一樣呼叫 Dart 類的例項。詳情請參閱可呼叫物件。
這是一個實現函式的例子:
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}雖然 Effective Dart 建議為公共 API 新增型別註解,但如果你省略型別,函式仍然可以工作:
isNoble(atomicNumber) {
return _nobleGases[atomicNumber] != null;
}對於只包含一個表示式的函式,你可以使用簡寫語法:
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;=> expr 語法是 { return expr; } 的簡寫。=> 符號有時被稱為箭頭語法。
引數
#函式可以有任意數量的必需位置引數。這些引數後面可以跟命名引數或可選位置引數(但不能同時出現)。
在向函式傳遞引數或定義函式引數時,可以使用尾隨逗號。
命名引數
#命名引數是可選的,除非它們被明確標記為 required。
定義函式時,使用 {param1, param2, …} 來指定命名引數。如果你沒有提供預設值或將命名引數標記為 required,它們的型別必須是可為空的,因為它們的預設值將是 null:
/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool? bold, bool? hidden}) {
...
}呼叫函式時,可以使用 paramName: value 來指定命名引數。例如:
enableFlags(bold: true, hidden: false);要為命名引數定義 null 以外的預設值,請使用 = 來指定預設值。指定的值必須是編譯時常量。例如:
/// 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 進行註解:
const Scrollbar({super.key, required Widget child});如果有人嘗試在不指定 child 引數的情況下建立 Scrollbar,分析器將報告問題。
你可能希望將位置引數放在前面,但 Dart 不要求這樣做。Dart 允許命名引數在引數列表中的任何位置,以適應你的 API 需求:
repeat(times: 2, () {
...
});可選位置引數
#將一組函式引數用 [] 括起來,將其標記為可選位置引數。如果你不提供預設值,它們的型別必須是可為空的,因為它們的預設值將是 null:
String say(String from, String msg, [String? device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}以下是一個不帶可選引數呼叫此函式的示例:
assert(say('Bob', 'Howdy') == 'Bob says Howdy');以下是一個帶第三個引數呼叫此函式的示例:
assert(
say('Bob', 'Howdy', 'smoke signal') ==
'Bob says Howdy with a smoke signal',
);要為可選位置引數定義 null 以外的預設值,請使用 = 來指定預設值。指定的值必須是編譯時常量。例如:
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() 函式:
void main() {
print('Hello, World!');
}這是一個接受引數的命令列應用程式的 main() 函式示例:
// 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 庫來定義和解析命令列引數。
函式作為一等物件
#你可以將函式作為引數傳遞給另一個函式。例如:
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
// Pass printElement as a parameter.
list.forEach(printElement);你也可以將函式賦值給變數,例如:
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');此示例使用匿名函式。更多內容請參閱下一節。
函式型別
#你可以指定函式的型別,這被稱為函式型別。函式型別是透過將函式宣告頭中的函式名替換為關鍵字 Function 來獲得的。此外,你可以省略位置引數的名稱,但命名引數的名稱不能省略。例如:
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 或閉包。
匿名函式類似於命名函式,因為它具有:
- 零個或多個引數,以逗號分隔。
- 括號內可選的型別註解。
以下程式碼塊包含函式體:
([[Type] param1[, ...]]) {
codeBlock;
}以下示例定義了一個帶有未型別化引數 item 的匿名函式。該匿名函式將其傳遞給 map 函式。map 函式對列表中的每個專案進行呼叫,將每個字串轉換為大寫。然後,傳遞給 forEach 的匿名函式列印每個轉換後的字串及其長度。
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 中並點選執行以驗證其功能等效性。
var uppercaseList = list.map((item) => item.toUpperCase()).toList();
uppercaseList.forEach((item) => print('$item: ${item.length}'));詞法作用域
#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。
/// 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。
var charCodes = [68, 97, 114, 116];
var buffer = StringBuffer();// Function tear-off
charCodes.forEach(print);
// Method tear-off
charCodes.forEach(buffer.write);// Function lambda
charCodes.forEach((code) {
print(code);
});
// Method lambda
charCodes.forEach((code) {
buffer.write(code);
});測試函式相等性
#以下是測試頂級函式、靜態方法和例項方法相等性的示例:
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; 會隱式新增到函式體中。
foo() {}
assert(foo() == null);要在函式中返回多個值,請將這些值聚合到記錄中。
(String, int) foo() {
return ('something', 42);
}生成器
#當你需要延遲生成一系列值時,考慮使用生成器函式。Dart 內建支援兩種生成器函式:
要實現同步生成器函式,請將函式體標記為 sync*,並使用 yield 語句來傳遞值:
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}要實現非同步生成器函式,請將函式體標記為 async*,並使用 yield 語句來傳遞值:
Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}如果你的生成器是遞迴的,你可以透過使用 yield* 來提高其效能:
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}外部函式
#外部函式是其函式體與宣告分開實現的函式。在函式宣告前包含 external 關鍵字,如下所示:
external void someFunc(int i);外部函式的實現可以來自另一個 Dart 庫,或者更常見的是來自另一種語言。在互操作上下文中,external 為外部函式或值引入型別資訊,使其可在 Dart 中使用。實現和用法嚴重依賴於平臺,因此請檢視互操作文件,例如C或JavaScript以瞭解更多資訊。
外部函式可以是頂級函式、例項方法、getter 或 setter,或非重定向建構函式。例項變數也可以是 external,這相當於一個外部 getter 和(如果變數不是 final)一個外部 setter。