Dart中的函数

1,920 阅读4分钟

定义

Dart是一种真正的面向对象语言,因此即使是函数也是对象并且具有类型Function。这意味着函数可以分配给变量或作为参数传递给其他函数。也可以调用Dart类的实例,就像普通函数调用的方式一样。详情可参考 Callable classes

定义函数:

// 最前面的bool表示函数返回bool值,参数atomicNumber前的int表示接收类型为int,{}内为函数体
bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}
// 推荐在任何时候都标注函数的返回类型,如果不标注函数依然可以执行
isNoble(atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

箭头函数

如果函数只包含一个表达式,可以简写成如下形式,=> 后面的表达式将作为函数的返回结果:

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

只有表达式允许出现在箭头函数中,也就是说三元表达式可以用在箭头函数中,但是 if等语句无法在箭头函数中使用。

参数

函数可以有两种类型的参数:必需参数(required)和可选参数(optional)。首先列出所需参数,然后列出可选参数。可选参数可以是命名的参数或占位参数。

命名参数

命名参数是可选参数的一种,非必填:

// 定义两个叫bold和hidden的参数
void enableFlags({bool bold, bool hidden}) {...}
// 执行时传入对应名称和值
enableFlags(bold: true, hidden: false);

尽管命名参数是一种可选参数,但可以使用@required对它们进行标注,以表明该参数是必需的:

// 如果用户在传入参数时没有指定child,那么编译器将会发出一个问题。
const Scrollbar({Key key, @required Widget child})

使用@required需要引入import package:meta/meta.dart

占位参数

占位参数也是可选参数的一种,通过[]将它们标记为可选的占位参数:

String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}
// 不传入可选参数执行
say('Bob', 'Howdy') // 'Bob says Howdy'
// 传入可选参数执行
say('Bob', 'Howdy', 'smoke signal') // 'Bob says Howdy with a smoke signal'

默认参数值:

函数可以使用=来定义命名和占位参数的默认值。默认值必须是编译时常量。如果未提供默认值,则默认值为null

命名参数的默认值

// 定义bold和hidden的默认参数为false
void enableFlags({bool bold = false, bool hidden = false}) {...}
// 这里传入了bold的值为true,所以函数中bold为true,hidden为默认值false
enableFlags(bold: true);

占位参数默认值

String say(String from, String msg,
    [String device = 'carrier pigeon', String mood]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  if (mood != null) {
    result = '$result (in a $mood mood)';
  }
  return result;
}
// 不传入占位参数, device等于默认值carrier pigeon
say('Bob', 'Howdy') // 'Bob says Howdy with a carrier pigeon');

前面的例子中都是字符串和bool等简单的类型,对于MapsList占位参数和命名参数也是支持的:

void doStuff(
    {List<int> list = const [1, 2, 3],
    Map<String, String> gifts = const {
      'first': 'paper',
      'second': 'cotton',
      'third': 'leather'
    }}) {
  print('list:  $list');
  print('gifts: $gifts');
}
doStuff();
/// 结果如下,由于未传值,所以list和gifts都等于默认值。
/// list:  [1, 2, 3]
/// gifts: {first: paper, second: cotton, third: leather} 

main函数

每个应用程序都必须具有顶级main函数,该函数用作应用程序的入口点。main函数返回void,并为参数提供可选的List 参数。
webapp的main函数例子:

void main() {
  querySelector('#sample_text_id')
    ..text = 'Click me!'
    ..onClick.listen(reverseText);
}

..级联操作符允许对一个对象进行连续操作,这里的textonClick都是对querySelector('#sample_text_id')进行的操作。
main函数还可以接收命令行参数来执行:

// 在命令行如下操作: dart 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');
}

函数是一等公民

也就是说你可以把函数当做普通类型进行参数传递和赋值给变量:

void printElement(int element) {
  print(element);
}
var list = [1, 2, 3];
// 这里printElement作为参数传递给了forEach方法
list.forEach(printElement);
// 赋值方法给变量
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

匿名函数

大多数函数都是命名函数,例如main()printElement()。但是也可以创建没有名字的匿名函数、lambda或闭包。或者为变量分配匿名函数,以便操作匿名函数。

var list = ['apples', 'bananas', 'oranges'];
// 这里的forEach接收了一个匿名函数,传入参数是list的每一项
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});
// 结果
// 0: apples
// 1: bananas
// 2: oranges
// 因为这个匿名函数只有一个表达式所以可以简写成箭头函数
list.forEach(
    (item) => print('${list.indexOf(item)}: $item'));

作用域

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 可以访问所有这里定义变量
// myFunction 除了insideNestedFunction变量,其他都可以访问。

闭包

闭包是指一个函数可以访问其词法范围中的变量,即使该函数在其原始范围之外执行也是如此。
函数可以留存其作用域内定义的变量。在以下示例中,makeAdder()中定义了变量addBy。无论返回的函数在哪里,它都会记住addBy:

/// 这里定义了一个匿名函数,并作为结果返回,这个函数记住了变量addBy
Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

void main() {
  // add2 将会记住 2
  var add2 = makeAdder(2);

  // add4 将会记住 4
  var add4 = makeAdder(4);
 
  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

返回值

所有函数都返回一个值。如果未指定返回值,则该语句返回null, 隐式附加到函数体中:

foo() {}

assert(foo() == null);