Function 函式

在 Dart 語言中,函式本身也是物件,這一點和其他程式語言略有不同。

函式屬於一級物件

Dart 是純粹的物件導向語言,函式本身就是物件,型別為 Function

可指派給變數、做為參數傳入其他函式;

類別實體可以直接像函式一樣呼叫

1 標準函式定義

// 完整標註回傳值、參數型別
bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}
Code language: Dart (dart)

完整範例

// 稀有氣體原子序對照表
final Map<int, String> _nobleGases = {
  2: 'He',
  10: 'Ne',
  18: 'Ar',
  36: 'Kr',
  54: 'Xe',
  86: 'Rn',
};

// 完整標註回傳值、參數型別
// int 不可為空,無法傳入 null,改為 int? 支援可空參數
bool isNoble(int? atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

void main() {
  print(isNoble(null));       // null 不在 Map 鍵值內 → false
  print(isNoble(2));          // 存在稀有氣體 → true
  print(isNoble(9));          // 不存在 → false
  print(isNoble(86));         // 存在 → true
}
Code language: Dart (dart)
false
true
false
trueCode language: JavaScript (javascript)

2 省略型別標註

Effective Dart 程式碼規範建議,對外開放的公開介面(公開 API)必須完整手動標註型別。

如果是內部使用的函式,則可以省略型別

範例


final Map<int, String> _nobleGases = {
  2: 'He',
  10: 'Ne',
  18: 'Ar',
  36: 'Kr',
  54: 'Xe',
  86: 'Rn',
};


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

void main() {
  print(isNoble(null));       // null 不在 Map 鍵值內 → false
  print(isNoble(2));          // 存在稀有氣體 → true
  print(isNoble(9));          // 不存在 → false
  print(isNoble(86));         // 存在 → true
}

Code language: Dart (dart)

上面範例的 isNoble 函式,移除了回傳型別 bool,參數也省略 int? 型別,依舊可以正常執行。

當你沒有撰寫型別時,Dart 會自動推斷型別

  • 參數 atomicNumber:無顯式型別 → 推斷型別為 dynamic(動態型別,任意值皆可傳入:數字、字串、null 都允許)
  • 回傳值:前方未標註型別 → Dart 依據 return 右側運算式推斷回傳型別為 bool,只是程式碼不會顯示標註
為什麼公開 API 一定要標註型別?

可讀性:其他開發者呼叫你的函式時,一眼就能看懂參數、回傳值的型別,不用閱讀內部實作猜測;

型別安全dynamic 動態型別會關閉靜態檢查,傳入錯誤型別不會觸發編譯錯誤,容易造成上線後崩潰;

IDE 提示:標註型別後編輯器會自動補全、顯示參數提示;若無型別標註,IDE 無法精準提示。

什麼時候可以省略型別?

僅建議用於區域私有程式碼,函式只在當前檔案內使用、不會對外被呼叫。

箭頭簡寫語法(=>)

若函式主體(大括號 {} 內的程式碼)只有單一行回傳運算式,可簡寫為箭頭函式。

範例

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
Code language: Dart (dart)
  • => expr 等同於 { return expr; }
  • 箭頭與分號之間只能撰寫運算式,不能撰寫陳述式(例如 if,可改用三元運算式替代)。
// 稀有氣體原子序對照表
final Map<int, String> _nobleGases = {
  2: '氦',
  10: '氖',
  18: '氬',
  36: '氪',
  54: '氙',
  86: '氡',
};

// 箭頭簡寫函式:單行運算式直接回傳,等同帶有 return 的區塊函式
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

void main() {
  // 測試存在的稀有氣體
  print(isNoble(2));   // true
  print(isNoble(18));  // true

  // 測試一般元素
  print(isNoble(6));   // false(碳)
  print(isNoble(99));  // false(無對應元素)
}
Code language: Dart (dart)

Parameters 參數

函式支援必要位置參數,其後二選一使用:具名參數 / 選擇性位置參數,兩者不可混用。

定義或呼叫參數時允許尾隨逗號 ,,提升程式碼可讀性。

1. 具名參數 Named parameters

使用 {} 包裹,預設為選填;加上 required 強制必須傳入。

1)基礎可空具名參數(無預設值,預設為 null,型別必須標註可空 ?

void enableFlags({bool? bold, bool? hidden}) {}
// 呼叫時明確指定參數名稱
enableFlags(bold: true, hidden: false);
Code language: Dart (dart)

完整範例

/// 開啟文字樣式標記:粗體、隱藏
void enableFlags({bool? bold, bool? hidden}) {
  print('粗體:$bold,隱藏:$hidden');
}

void main() {
  // 完整傳入所有具名參數
  enableFlags(bold: true, hidden: false);

  // 只傳其中一個具名參數,另一個為 null
  enableFlags(bold: true);

  // 只傳 hidden
  enableFlags(hidden: true);

  // 不傳任何參數,兩個值都是 null
  enableFlags();
}
Code language: Dart (dart)

執行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
粗體:true,隱藏:false
粗體:true,隱藏:null
粗體:null,隱藏:true
粗體:null,隱藏:nullCode language: JavaScript (javascript)

2)設定編譯期常數預設值(不需可空標註,已有預設值故不用標註 ?

void enableFlags({bool bold = false, bool hidden = false}) {}
enableFlags(bold: true); // hidden 自動使用預設 false

Code language: Dart (dart)

範例

/// 開啟文字樣式標記:粗體、隱藏
void enableFlags({bool bold = false, bool hidden = false}) {
  print('粗體:$bold,隱藏:$hidden');
}

void main() {
  enableFlags();//不傳參數,兩者使用預設 false → 粗體:false,隱藏:false
}
Code language: Dart (dart)

3)強制必填具名參數 required(備註:Flutter 元件建構子經常使用)以下範例為 Flutter 程式,可至 Flutter 課程深入學習。

const Scrollbar({super.key, required Widget child});
// required 參數也支援可空型別
const Scrollbar({super.key, required Widget? child});

Code language: Dart (dart)

Dart 不強制具名參數必須放在參數列表最後,可穿插在任意位置

repeat(times: 2, () {})Code language: CSS (css)
2. 選擇性位置參數 Optional positional parameters

使用 [] 包裹,未傳入時預設為 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');

Code language: Dart (dart)

完整範例

// [String? device] 選擇性位置參數,無預設值,未傳入則為 null
String say(String from, String msg, [String? device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device'; //若 device 不為 null,就在訊息後追加裝置資訊
  }
  return result;
}

void main() {
  // 不傳入第三個選擇性參數
  String res1 = say('Bob', 'Howdy');
  print(res1);

  // 傳入第三個選擇性位置參數
  String res2 = say('Bob', 'Howdy', 'smoke signal');
  print(res2);

  // 手動傳入 null 給選擇性參數
  String res3 = say('Alice', 'Hi', null);
  print(res3);
}
Code language: Dart (dart)

設定預設值:

String say(String from, String msg, [String device = 'carrier pigeon']) {
  return '$from says $msg with a $device';
}
assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');

Code language: Dart (dart)

完整範例

// 選擇性位置參數,設定預設值 carrier pigeon(信鴿)
String say(String from, String msg, [String device = 'carrier pigeon']) {
  return '$from says $msg with a $device';
}

void main() {
  // 不傳入第三個選擇性參數,自動使用預設值 carrier pigeon
  String res1 = say('Bob', 'Howdy');
  print(res1); //Bob says Howdy with a carrier pigeon

  // 手動傳入自訂裝置,覆蓋預設值
  String res2 = say('Bob', 'Howdy', 'smoke signal');
  print(res2); //Bob says Howdy with a smoke signal

  String res3 = say('Alice', 'Hello', 'mobile phone');
  print(res3); // Alice says Hello with a mobile phone
  
  String res4 = say('Jack', 'Tom', 'website foxdevelop.com');
  print(res4); // Jack says Tom with a website foxdevelop.com
}
Code language: Dart (dart)

執行結果

Built firstdart:firstdart.
Bob says Howdy with a carrier pigeon
Bob says Howdy with a smoke signal
Alice says Hello with a mobile phone
Jack says Tom with a website foxdevelop.comCode language: JavaScript (javascript)


main () 進入點函式

所有 Dart 程式都必須存在頂層 main(),是程式唯一執行入口。

  1. 無參數基礎版本
void main() {
  print('Hello, welcome to foxdevelop.com!');
}

Code language: Dart (dart)
Hello, welcome to foxdevelop.com!Code language: CSS (css)
  1. 命令列程式接收啟動參數
// 執行指令:dart run firstdart.dart 1 test
void main(List<String> arguments) {
  print(arguments); // [1, test]
}

Code language: Dart (dart)

複雜命令列參數解析可使用官方 args 函式庫。

上方的 firstdart.dart 請替換成你的 Dart 檔案名稱


函式做為一級物件 First-class objects

1. 將函式做為參數傳遞

void printElement(int element) => print(element);
var list = [1, 2, 3];
list.forEach(printElement); // 直接傳入函式

Code language: Dart (dart)

完整範例

void main() {
    void printElement(int element) => print(element);
	var list = [1, 2, 3];
	list.forEach(printElement); // 直接傳入函式
}
Code language: Dart (dart)

輸出

1
2
3

2. 匿名函式指派給變數

void main() {
    var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
	print(loudify('hello')); //!!! HELLO !!! 
}
Code language: Dart (dart)

函式型別 Function Type

可顯式宣告函式變數的型別

格式:回傳值型別 Function(參數列表, {具名參數})

void greet(String name, {String greeting = 'Hello'}) => print('$greeting $name!');
// 定義函式型別變數
void Function(String, {String greeting}) g = greet;
g('Dash', greeting: 'Howdy');

Code language: Dart (dart)

可透過 typedef 替函式型別建立別名,提升重複使用性與可讀性。

{String greeting}) → 代表一個具名參數 greeting,型別為 String

void main() {
    void greet(String name, {String greeting = 'Hello'}) => print('$greeting $name!');
	// 定義函式型別變數
	void Function(String, {String greeting}) g = greet;
	g('Jack', greeting: 'Howdy');
}
Code language: Dart (dart)

輸出

Howdy Jack!

匿名函式(Lambda / Closure 閉包)

沒有函式名稱的函式,有兩種寫法:

  1. 區塊寫法(多行)
void main() {
    const list = ['apples', 'bananas'];
	var uppercaseList = list.map((item) {
	  return item.toUpperCase();
	}).toList();
	print(uppercaseList);
}
Code language: Dart (dart)

上方程式碼中的 (item) { return item.toUpperCase(); } 即為匿名函式(lambda)

輸出:[APPLES, BANANAS]

  1. 箭頭簡寫(單行運算式)
void main() {
    const list = ['fox', 'develop','foxdevelop.com'];
    var uppercaseList = list.map((item) => item.toUpperCase()).toList();
    uppercaseList.forEach((item) => print('$item: ${item.length}'));
}

Code language: Dart (dart)

輸出

FOX: 3
DEVELOP: 7
FOXDEVELOP.COM: 14Code language: HTTP (http)

詞法作用域 Lexical scope

Dart 採用靜態詞法作用域:變數作用域由程式大括號層級決定,內層可存取所有外層變數。

bool topLevel = true;
void main() {
  var insideMain = true;
  void myFunction() {
    var insideFunction = true;
    void nestedFunction() {
      var insideNestedFunction = true;
      // 全部皆可存取
      assert(topLevel && insideMain && insideFunction && insideNestedFunction);
    }
  }
}

Code language: Dart (dart)

詞法閉包 Lexical closures

閉包:函式物件可以捕獲並持有外層作用域變數,脫離原本作用域後仍可正常存取。

Function makeAdder(int addBy) {
  // 捕獲外部變數 addBy
  return (int i) => addBy + i;
}

void main() {
  var add2 = makeAdder(2);
  var add4 = makeAdder(4);
  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

Code language: Dart (dart)

以下逐步解析上方程式執行流程

第一步:var add2 = makeAdder(2);

  1. 執行 makeAdder,傳入 addBy = 2
  2. 建立匿名函式:(int i) => 2 + i
  3. 將此函式指派給變數 add2,此時 add2 本身就是函式物件,函式主體為 (int i) => 2 + i,接收 int i 並回傳 i+2
  4. makeAdder 函式執行完畢,但匿名函式會永久記住 addBy=2

第二步:var add4 = makeAdder(4);

  1. 再次執行 makeAdder,傳入 addBy = 4
  2. 建立完全獨立全新的匿名函式:(int i) => 4 + i
  3. 指派給 add4,它單獨保存一份 addBy=4,與 add2 互不干擾;也就是 addBy 在兩個函式物件 add2、add4 內各自獨立儲存,一個存 2、一個存 4
  • 執行 makeAdder(2) 時,會產生獨立閉包,單獨綁定一份 addBy = 2,指派給 add2
  • 執行 makeAdder(4) 時,產生另一個全新閉包,單獨綁定一份 addBy = 4,指派給 add4
  • add2add4 是兩個完全互不影響的函式物件,各自私有存放捕獲的 addBy,彼此不會互相干擾。

第三步:呼叫 add2(3)

執行內部函式:2 + 3 = 5,斷言成立

第四步:呼叫 add4(3)

執行內部函式:4 + 3 = 7,斷言成立

函式截取 Tear-offs

呼叫函式 / 方法時不加括號,會產生 tear-off(輕量閉包),取代手寫匿名 lambda,程式碼更簡潔。

推薦寫法,直接傳入函式即可。

var charCodes = [68, 97, 114, 116];
var buffer = StringBuffer();
charCodes.forEach(print); // 函式截取
charCodes.forEach(buffer.write); // 方法截取

Code language: Dart (dart)

冗餘寫法

charCodes.forEach((code) => print(code));
charCodes.forEach((code) => buffer.write(code));

Code language: Dart (dart)

函式相等性判斷

  • 頂層函式、靜態方法:函式名完全相同則 == 判斷為相等;
  • 實例方法:只有綁定同一個物件實體的方法截取才會判定相等。
void foo() {}
class A {
  static void bar() {}
  void baz() {}
}
void main() {
  var x = foo;
  assert(foo == x);

  x = A.bar;
  assert(A.bar == x);

  var v = A(), w = A(), y = w;
  x = w.baz;
  assert(y.baz == x); // 同一實體,相等
  assert(v.baz != w.baz); // 不同實體,不相等
}

Code language: Dart (dart)

回傳值 Return values

  1. 所有函式必有回傳值:若沒有顯式撰寫 return,程式末尾會自動隱藏追加 return null;
foo() {}
assert(foo() == null);

Code language: Dart (dart)
  1. 回傳多個值:使用 Record 紀錄元組
(String, int) foo() {
  return ('something', 42);
}

Code language: Dart (dart)

Getter & Setter 存取器

所有屬性讀取 / 賦值本質都是呼叫 getter /setter;一般變數會自動產生隱藏存取器,也可手動明確定義。

優勢:外部統一使用 .屬性名 存取,內部邏輯可隨時修改,不用異動外部呼叫程式。

String _secret = 'Hello';

// Getter 讀取專用
String get secret {
  print('呼叫getter');
  return _secret.toUpperCase();
}

// Setter 賦值專用
set secret(String newMsg) {
  print('呼叫setter');
  if (newMsg.isNotEmpty) _secret = newMsg;
}

void main() {
  print(secret); // 讀取,觸發getter → HELLO
  secret = 'Dart is fun'; // 賦值,觸發setter
  print(secret); // DART IS FUN
}

Code language: Dart (dart)

* 產生器 Generators

延遲按需產生序列,分同步、非同步兩種,使用 yield 輸出數值。

  1. 同步產生器 sync*,回傳 Iterable
Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

Code language: Dart (dart)
  1. 非同步產生器 async*,回傳 Stream
Stream<int> asyncNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

Code language: Dart (dart)
  1. 遞迴產生器最佳化 yield*(轉發另一個產生器,提升執行效能)
Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

Code language: Dart (dart)

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *