不止前端
首发于不止前端

Dart语言官网翻译中

方法

Dart是纯面向对象的语言,所以方法也有一个类型——Function。意味着方法可以被赋给变量或者作为参数传递给其他方法。你也可以调用方法类的实例。

下面是方法实现的例子

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

可以略去参数的类型(就像JavaScript一样,但不被推荐)

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

对于只有一行表达式的方法,可以使用箭头函数语法。

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

=> expr表示{ return expr; }

但和JavaScript箭头函数不同的是,Dart里只能包含一行表达式

方法里可以传入必须和可选的参数。先列出必须的参数,然后才是可选的。

可选参数

可选参数要么是基于位置的,要么是基于名字的 ,但不能同时存在。

可选的命名参数

在调用方法时,你可以指定参数名字

enableFlags(bold: true, hidden: false);

定义方法时,使用{param1, param2, ...}来指定参数名字。

void enableFlags({bool bold, bool hidden}) {...}

Flutter实例创建的表达式可能比较复杂,所以widget的构造函数完全使用命名参数。这使得实例创建表达式易读。

你可以用@required注解加到命名参数前来表明这是个必传的参数。

const Scrollbar({Key key, @required Widget child})

当创建Scrollbar时,不传child就会报错。

required在meta包里定义,要么直接引入package:meta/meta.dart,要么引入一个导出meta的包,比如Flutter的package:flutter/material.dart

可选的位置参数

用[ ]来包裹参数标记这是可选的位置参数。

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。

//设置命名参数的默认值
void enableFlags({bool bold = false, bool hidden = false}) {...}

// 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;
}

assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');

可以传递list或者map作为默认值。

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');
}

main( )方法

每个应用都有顶层的main( )方法,用来作为应用的入口。main()方法返回void并有一个可选的List<String>参数。

void main() {
  querySelector('#sample_text_id')
    ..text = 'Click me!'
    ..onClick.listen(reverseText);
}
语法被称为cascade(级联)。有了cascade后,可以对单个对象执行多次操作。
// 带参数的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];

// 把方法作为参数传给另一个参数
list.forEach(printElement);

//可以把方法赋给一个变量
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

一等公民的方法

可以把方法作为参数传递,以及传给变量(对于JavaScript开发者来说没什么特别的)。

void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

list.forEach(printElement);

var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

匿名方法

匿名方法也称之为lambda闭包。匿名方法是这个样子的。

([[Type] param1[, ]]) {   
	codeBlock;
}; 

下面是list遍历的一个例子(没有=>箭头)

var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

如果方法只含有一个参数,可以用箭头函数来简化。

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()可以使用每一层直至顶层的变量

词法闭包

闭包就是可以拿到词法作用域内变量的方法对象,即便方法是在原始作用域之外被调用(跟JavaScript里以及其他现代有闭包语法的语言都一样)。

/// 返回一个方法用来给方法的参数加上[addBy]
Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

void main() {
  // 创建一个方法用来加2
  var add2 = makeAdder(2);

  // 创建一个方法用来加4
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

测试方法的相等性

void foo() {} // 顶层方法

class A {
  static void bar() {} // 静态方法
  void baz() {} // 实例方法
}

void main() {
  var x;

  // 比较顶层方法
  x = foo;
  assert(foo == x);

  // 比较静态方法
  x = A.bar;
  assert(A.bar == x);

  // 比较实例方法
  var v = A(); // A实例1
  var w = A(); // A实例2
  var y = w;
  x = w.baz;

  // 指向相同的实例,所以相等
  assert(y.baz == x);

  // 指向不同的实例,所以不相等
  assert(v.baz != w.baz);
}

返回值

所有的方法都返回值,如果没有明确的值返回,那就相当于return null的语句被插在了方法体底部。

foo() {}
assert(foo() == null);

运算符

Dart里可以对运算符重写的。

//一些运算符表达式的例子
a++
a + b
a = b
a == b
c ? a : b
a is T

代码考虑易读。

// 易读
if ((n % i == 0) && (d % i == 0)) ...

// 不易读,但其实相等
if (n % i == 0 && d % i == 0) ...

算术运算符

assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // 结果是double
assert(5 ~/ 2 == 2); // 结果是int
assert(5 % 2 == 1); // 求余

assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');

//Dart能支持++ --(印象中就Python、swift不支持)
var a, b;

a = 0;
b = ++a;
assert(a == b); // 1 == 1

a = 0;
b = a++;
assert(a != b); // 1 != 0

a = 0;
b = --a;
assert(a == b); // -1 == -1

a = 0;
b = a--;
assert(a != b); // -1 != 0

关系运算符

==运算符的规则

  1. 如果都是null,返回true,如果只有一个是null,返回false
  2. 都不是null,返回x.==(y)方法调用的结果,==其实是一次方法调用,你可以重写它。
assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(2 <= 3);

类型检查运算符

如果obj是实现T的接口,那么obj is T返回true,比如obj is Object永远返回true。

使用as做强转,可以作为is检测的简化。

Dart里还多了一个is!用来作为is取反。

if (emp is Person) {
  // Type check
  emp.firstName = 'Bob';
}
//上面的语句可以简化为
(emp as Person).firstName = 'Bob'; //前提是emp不是null,否则抛出异常,而上面的语句不受影响

赋值运算符

a = value;
// b是null的时候把value赋给b,否则不变
b ??= value;

var a = 2;
a *= 3; // a = a * 3
assert(a == 6);

//Dart里不像Java在复合赋值语句里有默认的强制类型转换
var a = 2;
a *= 3.0; //会抛出异常

逻辑运算符

if (!done && (col == 0 || col == 3)) {
  // ...Do something...
}

位和移位运算

final value = 0x22;
final bitmask = 0x0f;

assert((value & bitmask) == 0x02); // AND
assert((value & ~bitmask) == 0x20); // AND NOT
assert((value | bitmask) == 0x2f); // OR
assert((value ^ bitmask) == 0x2d); // XOR
assert((value << 4) == 0x220); // Shift left
assert((value >> 4) == 0x02); // Shift right

条件表达式

expr1 ?? expr2,如果expr1不是null,返回expr1的值,否则返回expr2(这种形式的语法在swift里也有)。

var visibility = isPublic ? 'public' : 'private';
String playerName(String name) => name ?? 'Guest';

级联标识

..可以让你在同一个对象上连续操作。除了方法调用,也可以获取属性。可以节约临时变量的声明,写出更流畅的代码。

考虑下面的代码

querySelector('#confirm') // Get an object.
  ..text = 'Confirm' // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

等同于

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

还可以创建级联的级联

final addressBook = (AddressBookBuilder()
      ..name = 'jenny'
      ..email = 'jenny@example.com'
      ..phone = (PhoneNumberBuilder()
            ..number = '415-555-0100'
            ..label = 'home')
          .build())
    .build();

对于一些方法返回不是对象的情况,要小心些。

var sb = StringBuffer();
sb.write('foo')
  ..write('bar'); // Error: write返回的是null

其他运算符

  • ( )代表方法调用
  • [ ]代表获取数组元素
  • . 代表获取成员
  • ?.代表条件获取成员,比如foo?.bar在foo不为null的时候返回foo.bar,在foo为null的时候返回null

流程控制

  • if与else
  • for循环
  • while与do-while循环
  • break与continue
  • switch与case
  • assert

if与else

与其他语言没什么区别,除了if里必须为布尔值(不像JavaScript)。

if (isRaining()) {
  you.bringRainCoat();
} else if (isSnowing()) {
  you.wearJacket();
} else {
  car.putTopDown();
}

for循环

var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
  message.write('!');
}

Dart的for循环里声明的循环变量与JavaScript里let声明的循环变量等同。

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c()); //输出0 1如果是JavaScript里用var声明i,结果则是2 2

如果对象是可迭代的,可以用forEach( )方法

candidates.forEach((candidate) => candidate.interview());

可迭代的类比如List和Set也支持for-in的用法

var collection = [0, 1, 2];
for (var x in collection) {
  print(x); // 0 1 2
}

while与do-while循环

while (!isDone()) {
  doSomething();
}

do {
  printLine();
} while (!atEndOfPage());

break与continue

while (true) {
  if (shutDownRequested()) break;
  processIncomingRequests();
}

for (int i = 0; i < candidates.length; i++) {
  var candidate = candidates[i];
  if (candidate.yearsExperience < 5) {
    continue;
  }
  candidate.interview();

  //可以用迭代器
  candidates
    .where((c) => c.yearsExperience >= 5)
    .forEach((c) => c.interview());

好像没有看到有带标签的用法,简单试了一下,也是语法报错。

switch与case

Dart里的switch比较整型、字符串或编译时常量时使用==。被比较的对象必须是相同类的实例(不能是子类型),类不能重写过==。枚举类型也可以用在switch语句中。

每一个非空case条件都以break结尾,其他可以结尾的关键词有continue,throw或return。使用default分支来执行没有case满足的情况。

var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClosed();
    break;
  case 'PENDING':
    executePending();
    break;
  case 'APPROVED':
    executeApproved();
    break;
  case 'DENIED':
    executeDenied();
    break;
  case 'OPEN':
    executeOpen();
    break;
  default:
    executeUnknown();
}

Dart里必须要有break,或者空的case语句也行。

var command = 'OPEN';
switch (command) {
  case 'OPEN':
    executeOpen();
    // ERROR: Missing break

  case 'CLOSED':
    executeClosed();
    break;
}

//空的case语句
var command = 'CLOSED';
switch (command) {
  case 'CLOSED': 
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

如果确实想要在非空case里继续执行,使用continue加上标签

var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed;

  nowClosed:
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

case里可以含有本地变量,它只在那个分支里可见。

Assert

开发阶段assert在false情况下会阻止正常执行下去,抛出一个AssertionError的异常。

// 确保text不是null
assert(text != null);

// 确保number小于100.
assert(number < 100);

// 确保是https的URL
assert(urlString.startsWith('https'));

第二个参数可以带上消息

assert(urlString.startsWith('https'), 'URL ($urlString) should start with "https".');

异常

异常本身概念与其他语言一致。和Java对比,Dart里的异常都是unchecked异常。方法不会声明抛出异常,在语法编译上不是必须要去catch任何异常的。

Dart提供了Exception和Error类型,以及它们一些预定义的子类型。当然你也可以定义你自己的异常。但是Dart可以抛出任何非null对象,而不仅仅是Exception或Error对象来作为异常(但生产环境质量的代码不会这么做)。

Throw

throw FormatException('Expected at least 1 section');
throw 'Out of llamas!'; //抛一个字面对象也行
void distanceTo(Point other) => throw UnimplementedError(); //在箭头函数里抛异常,因为throw是一个表达式而不是语句

Catch

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}

对于处理多个异常的情况,可以写多条catch分支。只有第一个符合抛出异常的分支来进行处理。如果不指定类型,那就可以处理任何抛出的对象。

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // 特定的异常
  buyMoreLlamas();
} on Exception catch (e) {
  // 其他任何异常
  print('Unknown exception: $e');
} catch (e) {
  // 没有特定的类型,处理所有的情况
  print('Something really unknown: $e');
}

如上面代码所示,可以用on或者catch或者两者都用。一般在特定的异常类型里用on,在异常对象的处理时用catch。

catch里可以有两个参数,第一个是抛出的异常,第二个是跟踪栈(stack trace,一个StackTrace对象)。

try {
  // ···
} on Exception catch (e) {
  print('Exception details:\n $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

在部分处理的情况下,使用rethrow重新抛出。

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // 运行时错误
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // 允许调用者看到
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}

Finally

使用finally分支确保一些代码在异常抛出后运行。如果没有catch符合,异常会在finally运行后传播出去。

try {
  breedMoreLlamas();
} finally {
  cleanLlamaStalls(); // 永远会进行清理
}

try {
  breedMoreLlamas();
} catch (e) {
  print('Error: $e'); // 先处理
} finally {
  cleanLlamaStalls(); // 再清理
}

发布于 2019-10-19

文章被以下专栏收录