函数在Dart中是对象,因此,可以将函数作为参数传递给另一个函数、作为一个'值'赋值给一个变量。下面主要介绍匿名函数、内联函数类型的使用,以及了解下作用域和闭包、类型别名...
匿名函数(Anonymous function
,lambda表达式
)
在创建函数时通常都是有名字的,如果创建一个没有名字的方法,则称之为“匿名函数”,有时也被称为lambda
或闭包(Closure
)。
可以将匿名函数赋值给一个变量、将其添加到集合或从中删除。
与命名函数的的定义一样,只是没有函数名:
([[Type] param1[, …]]) {
codeBlock;
};
下面是一个参数为item且没有参数类型的匿名方法,作为参数传递给List的forEach
方法:
const list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
如果函数体只有一句,可以使用箭头表示法简写:
list.forEach((item) => print('${list.indexOf(item)}: $item'));
作用域和闭包
Lexical scope 词法作用域、Lexical closure 词法闭包
Dart是有词法作用域的语言,也就是,变量的作用域在写代码的那一刻就确定了,{}
大括号定义一个完整的块级作用域,大括号内定义的变量只能在大括号内访问。
块内的变量无法在外部被访问:
内层可以访问外层(任何一级)的变量。
闭包通俗来讲就是访问了函数外部变量的函数,在使用时获取了该函数,同时由于引用了外部变量,也要维持一份包含函数外部变量的环境。
闭包是一个可以访问其词法作用域内的变量的函数对象,即使在它原始作用域之外被调用时。
闭包可以封闭定义在其作用域内的变量。下面的示例,函数 makeAdder()
捕获了变量 addBy
,其内的子函数无论何时返回,变量都会被记录保持。
/// 返回一个子函数,可以在其他地方调用,调用时使用子函数所在作用域中的addBy
Function makeAdder(int addBy) {
return (int i) => addBy + i;
}
void main() {
// 加2的函数
var add2 = makeAdder(2);
// 加6的函数
var add6 = makeAdder(6);
print(add2(3) == 5); // true
print(add6(3) == 9); // true
}
也可以是使用了局部变量的闭包。
Function makeAdder2(int addBy) {
var k=10;
return (int i) => (addBy + i)*k;
}
typedef 类型别名(类型定义,v2.13开始可用于任何类型)
typedef
关键字用于为某一个类型起别名,通过类型别名可以非常方便的引用某一类型,常常也被称为类型定义,类型别名就表示了一种类型。
比如,为List<int>
声明一个类型别名IntList
,然后就是使用该别名代替原来的类型:
typedef IntList = List<int>;
IntList il = [1, 2, 3];
同时,类型别名也可以用于泛型类型的定义。
在 2.13 版本之前,typedef
仅限于函数类型,从v2.13开始可以更广泛的应用于其他对象类型。
使用内联函数类型声明(不推荐typedef
)
在Dart1中,如果想要将函数类型作为字段、变量或泛型参数使用,必须首先通过typedef
为其定义一个类型别名。
比如下面,为double Function(double)
类型的函数定义别名Operation
typedef Operation = double Function(double);
然后将改类型的函数作为变量定义,并调用:
main(){
Operation op = square;
// 调用
op(10); 100.0
op.call(15); 225.0
}
double square(double a)=> a * a;
作为Operation
类型的op
变量可以在代码中指代任何其他符合double Function(double)
签名的函数。
但着却有很大的一个问题,即真实的函数类型不够明确,使用者更希望知道函数使用时的真实类型。
内联函数类型作为变量类型
Dart2引入了内联函数类型(inline function type
)。即直接使用函数类型声明而不是typedef
别名【推荐做法】
double Function(double) op = square;
// 调用
print(op(10));
print(op.call(15));
赋值给op
变量其他的函数实现,比如下面计算三次方:
double Function(double) op = square;
op = cube;
print(op(10)); // 1000
double cube(double a) => a * a * a;
函数类型作为函数的参数
内联函数类型也可用于函数的参数中。
如下,add
方法中声明了double Function(double)?
类型的可选命名参数opArg
,其中 ?
表示该类型可为空,不为空时用于操作每个加法元素之后再相加。
// opArg 操作每个加法元素
double add(double a, double b, {double Function(double)? opArg}) {
if (opArg == null) return a + b;
return opArg(a) + opArg(b);
}
将square
作为opArg
命名参数传入:
double result = add(8, 6, opArg: square);
print(result); // 100.0
同样的,满足函数类型的匿名函数,也可以作为变量的值或函数的入参参数。
double result2 = add(8, 6, opArg: (double e) => e * e * e);
print(result2); // 728.0
或
double result3 = add(8, 6, opArg: (e) => e * e * e);
print(result3); // 28.0
函数的封装作用
函数用于将可以完成一定功能的一段代码集成在一起,通过逻辑算法完成某项任务,这就是封装的概念。
函数最大的作用就是对功能的封装,使用者不需要关注其中具体的实现细节,只在需要时进行调用即可,该函数对于使用者来说就是一个实现某个功能的黑盒。
在此基础上,还可以引申出类的封装、程序包或程序库的封装,通过使用官方或第三方提供的工具库,我们可以很方便的实现各种功能,如果有能力,还可以完善这些功能的实现细节或过程。
当然,需要注意的是,避免过渡依赖其他库或实现。