一、前言

Flutter 是 Google 开源的 UI 工具包,帮助开发者通过一套代码库高效构建多平台精美应用,Flutter 开源、免费,拥有宽松的开源协议,支持移动、Web、桌面和嵌入式平台。

Flutter是使用Dart语言开发的跨平台移动UI框架,通过自建绘制引擎,能高性能、高保真地进行Android和IOS开发。Flutter采用Dart语言进行开发,而并非Java,Javascript这类热门语言,这是Flutter团队对当前热门的10多种语言慎重评估后的选择。因为Dart囊括了多数编程语言的优点,它更符合Flutter构建界面的方式。

本文主要就是简单梳理一下Dart语言的一些基础知识和语法。关于编程语言的基本语法无外乎那么些内容,注释、变量、数据类型、运算符、流程控制、函数、类、异常、文件、异步、常用库等内容,相信大部分读者都是有一定编程基础的,所以本文就简单地进行一个梳理,不做详细的讲解。大家也可以参考 Dart编程语言中文网

上一篇文章主要是写了Dart语言的一些基本语法,本文将接着上一篇文章继续往后写。

二、Dart中的流程控制

流程控制涉及到的基本语法其实很简单,但是这一块也是编程语言基础中最难的一部分,主要难点在于解决问题的逻辑思路,流程控制知识实现我们解决问题的逻辑思路的一种表达形式。所以,大家在学习编程语言的过程中,学习基本语法是一部分,更重要的部分其实是锻炼自己解决问题的逻辑能力,而这一块的加强,必须加以大量的练习才能熟练掌握。本文主要是给大家罗列一下Dart中的流程控制相关的基本语法。

流程控制主要涉及到的内容无外乎条件分支结构、switch分支结构和循环结构,此外,还有一些特殊的语法break、continue等。对于有过编程经验的同学而言,这些内容都是so easy。下面就简单给大家罗列一下。

2.1 条件分支结构 

Dart 中的条件分支结构就是 if - else 语句,其中 else 是可选的,Dart 的if判断条件必须是布尔值,不能是其他类型。比如下面的例子。

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

2.2 switch分支结构

在 Dart 中 switch 语句使用 == 比较整数,字符串,或者编译时常量。 比较的对象必须都是同一个类的实例(并且不可以是子类), 类必须没有对 == 重写。 枚举类型 可以用于 switch 语句。在 case 语句中,每个非空的 case 语句结尾需要跟一个 break 语句。 除 break 以外,还有可以使用 continuethrow,者 return。当没有 case 语句匹配时,执行 default 代码

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();
}
// case 程序示例中缺省了 break 语句,导致错误
switch (command) {
  case 'OPEN':
    print('open');
    // ERROR: 丢失 break

  case 'CLOSED':
    print('close');
    break;
}
// Dart 支持空 case 语句, 允许程序以 fall-through 的形式执行
var command = 'CLOSED';
switch (command) {
  case 'CLOSED': // Empty case falls through.
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

// 在非空 case 中实现 fall-through 形式, 可以使用 continue 语句结合 lable 的方式实现
var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed;
  // Continues executing at the nowClosed label.

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

2.3 循环结构

和其他编程语言中的循环结构一样,Dart中的循环结构也是有for、while、do...while三种,这三种循环结构可以相互转换,大家根据自己的编程习惯进行选择即可。

2.3.1 for循环

进行迭代操作,可以使用标准 for 语句。 例如:

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

闭包在 Dart 的 for 循环中会捕获循环的 index 索引值, 来避免 JavaScript 中常见的陷阱。 请思考示例代码:

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());

和期望一样,输出的是 0 和 1。 

// 如果要迭代一个实现了 Iterable 接口的对象, 可以使用 forEach() 方法, 如果不需要使用当前计数值, 使用 forEach() 是非常棒的选择
candidates.forEach((candidate) => candidate.interview());

//实现了 Iterable 的类(比如, List 和 Set)同样也支持使用 for-in 进行迭代操作 iteration 
var collection = [0, 1, 2];
for (var x in collection) {
  print(x); // 0 1 2

2.3.2 while和do...while循环

// while 循环在执行前判断执行条件:
while (!isDone()) {
  doSomething();
}

// do-while 循环在执行后判断执行条件:
do {
  printLine();
} while (!atEndOfPage());

2.4 break和continue语句

使用 break 停止程序循环:

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

使用 continue 跳转到下一次迭代:

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

如果对象实现了 Iterable 接口 (例如,list 或者 set)。 那么上面示例完全可以用另一种方式来实现:

candidates
    .where((c) => c.yearsExperience >= 5)
    .forEach((c) => c.interview());

2.5 assert语句

如果 assert 语句中的布尔条件为 false , 那么正常的程序执行流程会被中断。  下面是一些示例:

// 确认变量值不为空。
assert(text != null);

// 确认变量值小于100。
assert(number < 100);

// 确认 URL 是否是 https 类型。
assert(urlString.startsWith('https'));
  • assert 的第一个参数可以是解析为布尔值的任何表达式。 如果表达式结果为 true , 则断言成功,并继续执行。 如果表达式结果为 false , 则断言失败,并抛出异常 (AssertionError) 。
  • assert 的第二个参数可以为其添加一个字符串消息。
assert(urlString.startsWith('https'),
    'URL ($urlString) should start with "https".');

三、Dart中的函数

Dart 是一门真正面向对象的语言, 甚至其中的函数也是对象,并且有它的类型 Function 。 这也意味着函数可以被赋值给变量或者作为参数传递给其他函数。 也可以把 Dart 类的实例当做方法来调用。

3.1 函数的定义

下面是函数实现的示例:

// 模板
returnType funcName(paramsList) {
    // function code
    // return statement
}

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

3.1.1 可选参数

函数有两种参数类型: required(必需参数,函数调用时不传就会报错) 和 optional(可选参数,函数调用时可以不传)。 required 类型参数在参数最前面, 随后是 optional 类型参数。 命名的可选参数也可以标记为 “@required” 。

可选参数可以是命名参数或者位置参数,但一个参数只能选择其中一种方式修饰。

  • 命名可选参数:定义函数时,使用 {param1param2, …} 来指定命名参数,并且可以使用 @required 注释表示参数是 required 性质的命名参数。调用函数时,可以使用指定命名参数 paramNamevalue
    // 定义函数是,使用 {param1, param2, …} 来指定命名参数:
    void enableFlags({bool bold, bool hidden}) {...}
    
    // 调用函数时,可以使用指定命名参数 paramName: value。 例如:
    enableFlags(bold: true, hidden: false);
    
    // 使用 @required 注释表示参数是 required 性质的命名参数, 该方式可以在任何 Dart 代码中使用(不仅仅是Flutter)。
    // 此时 Scrollbar 是一个构造函数, 当 child 参数缺少时,分析器会提示错误。
    const Scrollbar({Key key, @required Widget child})
  • 位置可选参数:将参数放到 [] 中来标记参数是可选的,调用函数时,按位置顺序传递参数。

    // 将参数放到 [] 中来标记参数是可选的:
    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'); 

3.1.2 默认参数

在定义方法的时候,可以使用 = 来定义可选参数的默认值。 默认值只能是编译时常量。 如果没有提供默认值,则默认值为 null。

注意:旧版本代码中可能使用的是冒号 (:) 而不是 = 来设置参数默认值。 原因是起初命名参数只支持 : 。 这种支持可能会被弃用。 建议 使用 = 指定默认值。

下面是设置可选参数默认值示例:

/// 设置 [bold] 和 [hidden] 标志 ...
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');

3.1.3 返回值

所有函数都会返回一个值。 如果没有明确指定返回值, 函数体会被隐式的添加 return null; 语句。

foo() {}

assert(foo() == null);

3.2 main()函数

任何应用都必须有一个顶级 main() 函数,作为应用服务的入口。 main() 函数返回值为空,参数为一个可选的 List<String> 。

下面是 web 应用的 main() 函数:

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

下面是一个命令行应用的 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');
}

3.3 匿名函数

多数函数是有名字的, 比如 main() 和 printElement()。 也可以创建没有名字的函数,这种函数被称为 匿名函数。 匿名函数可以赋值到一个变量中, 举个例子,在一个集合中可以添加或者删除一个匿名函数。

匿名函数和命名函数看起来类似— 在括号之间可以定义一些参数或可选参数,参数使用逗号分割。后面大括号中的代码为函数体:

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

// 下面例子中定义了一个包含一个无类型参数 item 的匿名函数。 list 中的每个元素都会调用这个函数,打印元素位置和值的字符串。
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

3.4 箭头函数

不管是匿名函数还是命名函数,如果函数中只有一句表达式,可以使用箭头语法,简写如下:

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

提示: 在箭头 (=>) 和分号 (;) 之间只能使用一个 表达式 ,不能是 语句 。 例如:不能使用 if 语句 ,但是可以是用 条件表达式.

3.5 函数是一等对象

一个函数可以作为另一个函数的参数。 例如:

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

var list = [1, 2, 3];

// 将 printElement 函数作为参数传递。
list.forEach(printElement);

同样可以将一个函数赋值给一个变量,例如:

// 使用匿名函数
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

3.6 变量的作用域

Dart 是一门词法作用域的编程语言,就意味着变量的作用域是固定的, 简单说变量的作用域在编写代码的时候就已经确定了。 花括号内的是变量可见的作用域。下面示例关于多个嵌套函数的变量作用域:

bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;

    // 注意 nestedFunction() 可以访问所有的变量, 一直到顶级作用域变量。
    void nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}

3.7 闭包

3.7.1 闭包的概念

闭包这个概念好难理解,身边朋友们好多都稀里糊涂的,我也是学习了很久才理解这个概念。下面请大家跟我一起理解一下,如果在一个函数的内部定义了另一个函数,外部的我们叫他外函数,内部的我们叫他内函数。

函数可以封闭定义到它作用域内的变量。 接下来的示例中, makeAdder() 捕获了变量 addBy。 无论在什么时候执行返回函数,函数都会使用捕获的 addBy 变量。

/// 返回一个函数,返回的函数参数与 [addBy] 相加
Function makeAdder(num addBy) {
  // //返回的函数就是一个闭包,封闭了局部变量 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);
}

3.7.2 闭包的特点

由于变量的作用域的限制,全局变量可以在整个代码范围内使用,但是带来的问题就是任何地方都可以修改该全局变量,函数内局部变量又只能在函数内部使用。所以闭包就让外部访问函数内部变量成为可能,同时也让局部变量可以常驻在内存中。

  • 让外部访问函数内部变量成为可能;
  • 局部变量会常驻在内存中;
  • 可以避免使用全局变量,防止全局变量污染;
  • 会造成内存泄漏(有一块内存空间被长期占用,而不被释放)

闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。闭包会发生内存泄漏,每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。但凡是当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象。

闭包内存泄漏为: key = value,key 被删除了 value 常驻内存中; 局部变量闭包升级版(中间引用的变量) => 自由变量;

四、异常

Dart 代码可以抛出和捕获异常。 异常表示一些未知的错误情况。 如果异常没有被捕获, 则异常会抛出, 导致抛出异常的代码终止执行。和 Java 有所不同, Dart 中的所有异常是非检查异常。 方法不会声明它们抛出的异常, 也不要求捕获任何异常。

Dart 提供了 Exception 和 Error 类型, 以及一些子类型。 当然也可以定义自己的异常类型。 但是,此外 Dart 程序可以抛出任何非 null 对象, 不仅限 Exception 和 Error 对象。

4.1 抛出异常 throw

下面是关于抛出或者 引发 异常的示例:

throw FormatException('Expected at least 1 section');

也可以抛出任意的对象:

throw 'Out of llamas!';

提示: 高质量的生产环境代码通常会实现 Error 或 Exception 类型的异常抛出。

因为抛出异常是一个表达式, 所以可以在 => 语句中使用,也可以在其他使用表达式的地方抛出异常:

void distanceTo(Point other) => throw UnimplementedError();

4.2 异常处理 try...catch...finally

Dart中的异常处理和Java中的比较类似,也是使用try...catch...finally的语句进行处理,不同的是,Dart中海油一个特殊的关键字on。使用 on 来指定异常类型, 使用 catch 来 捕获异常对象,捕获语句中可以同时使用 on 和 catch ,也可以单独分开使用

捕获异常可以避免异常继续传递(除非重新抛出( rethrow )异常)。 可以通过捕获异常的机会来处理该异常:

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

通过指定多个 catch 语句,可以处理可能抛出多种类型异常的代码。 与抛出异常类型匹配的第一个 catch 语句处理异常。 如果 catch 语句未指定类型, 则该语句可以处理任何类型的抛出对象:

// 捕获语句中可以同时使用 on 和 catch ,也可以单独分开使用。 使用 on 来指定异常类型, 使用 catch 来 捕获异常对象。
try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // 一个特殊的异常
  buyMoreLlamas();
} on Exception catch (e) {
  // 其他任何异常
  print('Unknown exception: $e');
} catch (e) {
  // 没有指定的类型,处理所有异常
  print('Something really unknown: $e');
}

catch() 函数可以指定1到2个参数, 第一个参数为抛出的异常对象, 第二个为堆栈信息 ( 一个 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++); // Runtime error
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // Allow callers to see the exception.
  }
}

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

不管是否抛出异常, finally 中的代码都会被执行。 如果 catch 没有匹配到异常, 异常会在 finally 执行完成后,再次被抛出。如果catch捕获到异常,那么先执行catch中的处理代码,然后再执行finally中的代码。总而言之,finally语句块中的代码一定会被执行,并且是在最后被执行

try {
  breedMoreLlamas();
} finally {
  // Always clean up, even if an exception is thrown.
  cleanLlamaStalls();
}

// 任何匹配的 catch 执行完成后,再执行 finally 
try {
  breedMoreLlamas();
} catch (e) {
  print('Error: $e'); // Handle the exception first.
} finally {
  cleanLlamaStalls(); // Then clean up.
}
02-02 16:45