Dart Functions
A function is the basic unit for organizing code; it encapsulates a piece of reusable logic that can be called whenever needed.
Dart's function system is very flexible, supporting features such as named parameters, optional parameters, arrow functions, and higher-order functions.
Function Definition and Return Value
A function consists of a return type, a function name, a parameter list, and a function body.
Example
// String is the return type, greet is the function name, (String name) is the parameter list
String greet(String name) {
return 'Hello, $name! Welcome to EXAMPLE.';
}
// Functions with no return value use void
void printWelcome() {
print('=== EXAMPLE Dart Tutorial ===');
}
void main() {
printWelcome();
// Call the function and receive the return value
String message = greet('Xiao Ming');
print(message);
}
=== EXAMPLE Dart 教程 === 你好,小明!欢迎来到 EXAMPLE。
Return Value
Every function has a return value.
If there is no explicit return, the function implicitly returns null (but under null safety this would cause a type mismatch, so void is usually used to indicate no return value).
Example
int add(int a, int b) {
return a + b;
}
// Use arrow syntax as shorthand for single-expression functions
int multiply(int a, int b) => a * b;
// void indicates no meaningful return value
void log(String msg) {
print('[EXAMPLE Log] $msg');
}
void main() {
print('3 + 5 = ${add(3, 5)}');
print('3 × 5 = ${multiply(3, 5)}');
log('Function learning complete');
}
3 + 5 = 8 3 × 5 = 15 [EXAMPLE 日志] 函数学习完成
Named Parameters and Optional Parameters
Dart parameters are divided into two types: required parameters and optional parameters.
Optional parameters are further divided into named parameters and positional parameters.
Named Parameters
Named parameters are wrapped in curly braces {}, and when calling, they are passed by parameter name, so the order can be arbitrary.
Example
// The required keyword marks them as required
String createUser({
required String name, // Required named parameter
int age = 0, // Optional, defaults to 0
String? email, // Optional, can be null
bool isVip = false, // Optional, defaults to false
}) {
var info = 'Username: $name, Age: $age';
if (email != null) {
info += ', Email: $email';
}
if (isVip) {
info += ' [VIP User]';
}
return info;
}
void main() {
// Named parameters: passed by parameter name, order doesn't matter
print(createUser(name: 'example', age: 10));
print(createUser(name: 'admin', email: 'admin@example.com', isVip: true));
// Required parameters cannot be omitted
// createUser(); // Error: missing required parameter name
}
用户名: example, 年龄: 10 用户名: admin, 邮箱: admin@example.com [VIP用户]
Positional Parameters
Positional parameters are wrapped in square brackets [], and when calling, they are passed in order.
Example
String buildUrl(String host, [String path = '/', int port = 80]) {
return 'http://$host:$port$path';
}
void main() {
// Only pass required parameters, optional parameters use default values
print(buildUrl('www.example.com'));
// Pass 2 parameters
print(buildUrl('www.example.com', '/dart'));
// Pass 3 parameters
print(buildUrl('localhost', '/api', 8080));
}
http://www.example.com:80/ http://www.example.com:80/dart http://localhost:8080/api
Named parameters vs positional parameters: how to choose
| Scenarios | Recommended approach | Reason |
|---|---|---|
| Many parameters (more than 3) | Named parameters | The parameter names are self-documenting at the call site, making it hard to pass them in the wrong order. |
| The meaning of the parameter is unclear | Named parameters | Values such as true/false do not explain their purpose; adding a name makes them clearer. |
| Few parameters and clear meaning | Positional parameters | Such as add(1, 2), concise and clear. |
| Flutter Widget construction | Named parameters | Flutter standard style |
A function cannot use both optional positional parameters and optional named parameters. You can only choose one.
Default parameter values
Optional parameters can be given default values, which are used when the caller does not pass that parameter.
Example
String formatMessage(
String content, {
String prefix = '[EXAMPLE]',
String suffix = '',
bool uppercase = false,
}) {
var result = '$prefix $content $suffix';
return uppercase ? result.toUpperCase() : result;
}
void main() {
// Use all default values
print(formatMessage('Dart tutorial updated'));
// Override some default values
print(formatMessage('Important notice', prefix: '[Announcement]', suffix: '!!!'));
// Override default values + enable uppercase
print(formatMessage('error', prefix: [Error], uppercase: true));
}
[EXAMPLE] Dart 教程更新了 [公告] 重要通知 !!! [错误] ERROR
Default values must be compile-time constants. That is, the default value cannot be DateTime.now() or the return value of a function (unless the function is const). If you need to determine the default value at runtime, you can handle it with ?? inside the function body.
Arrow Functions and Anonymous Functions
When the function body contains only a single expression, arrow syntax (=>) can be used as shorthand.
An anonymous function is a function without a name, usually passed as an argument to another function.
Example
var numbers = [1, 2, 3, 4, 5];
// Anonymous function (full syntax)
numbers.forEach((number) {
print(EXAMPLE number: $number);
});
print('---');
// Arrow function (single-expression shorthand)
var doubled = numbers.map((n) => n * 2);
print(Doubled: $doubled);
// Using arrow functions in function definitions
// When the function body has only one return statement, you can use => as shorthand
int square(int x) => x * x;
print(5 squared: ${square(5)});
// Store an anonymous function in a variable
var sayHello = (String name) => Hello, $name!;
print(sayHello('example'));
}
EXAMPLE 数字: 1 EXAMPLE 数字: 2 EXAMPLE 数字: 3 EXAMPLE 数字: 4 EXAMPLE 数字: 5 --- 翻倍: (2, 4, 6, 8, 10) 5 的平方: 25 你好,example!
Higher-order functions and closures
A higher-order function is a function that can receive a function as an argument, or return a function as a result.
A closure is a function that can access variables in its lexical scope, even when the function is called outside that scope.
Higher-order functions
Example
List<int> filterList(
List<int> items, bool Function(int) predicate) {
return items.where(predicate).toList();
}
// Higher-order function: returns a function
Function makeMultiplier(int factor) {
// Returns a closure: remembers the outer factor
return (int n) => n * factor;
}
void main() {
var numbers = [10, 15, 20, 25, 30];
// Pass an anonymous function as a filter condition
var bigNumbers = filterList(numbers, (n) => n > 18);
print(EXAMPLE numbers greater than 18: $bigNumbers);
// Get the returned function
var doubleIt = makeMultiplier(2);
var tripleIt = makeMultiplier(3);
print('5 × 2 = ${doubleIt(5)}');
print('5 × 3 = ${tripleIt(5)}');
}
EXAMPLE 大于18的数: [20, 25, 30] 5 × 2 = 10 5 × 3 = 15
Closure
A closure is a function object that can access variables in its lexical scope, even when the function is called outside the original scope.
Example
Function makeCounter() {
int count = 0; // This variable is "captured" by the returned function
return () {
count++;
return count;
};
}
void main() {
// counterA and counterB each have their own independent count
var counterA = makeCounter();
var counterB = makeCounter();
print(EXAMPLE Counter A: ${counterA()}); // 1
print(EXAMPLE Counter A: ${counterA()}); // 2
print(EXAMPLE Counter B: ${counterB()}); // 1 (independent count)
print(EXAMPLE Counter A: ${counterA()}); // 3
}
EXAMPLE 计数器 A: 1 EXAMPLE 计数器 A: 2 EXAMPLE 计数器 B: 1 EXAMPLE 计数器 A: 3
other extensionsThe key to closures is that the inner function "remembers" the outer function's variables, even after the outer function has finished executing. Each call to makeCounter() creates a brand new count variable and closure, so multiple counters do not affect each other.