Flutter_一小时入门Dart

本文适合有代码基础的人,如果没在代码基础请文章底部来源从头开始学习

示例

// 定义一个函数
printInteger(int aNumber) {
  print('The number is $aNumber.'); // 打印到控制台。
}

// 应用从这里开始执行。
// 程序开始执行函数,该函数是特定的、必须的、顶级函数。
main() {
  //定义变量,通过这种方式定义变量不需要指定变量类型。
  var number = 42;
  printInteger(number); // 调用函数。
}

重要的概念

在学习 Dart 语言时, 应该基于以下事实和概念:

  • 任何保存在变量中的都是一个 对象 , 并且所有的对象都是对应一个 类 的实例。 无论是数字,函数和 null 都是对象。所有对象继承自 Object 类。

  • 尽管 Dart 是强类型的,但是 Dart 可以推断类型,所以类型注释是可选的。 在上面的代码中, number 被推断为 int 类型。 如果要明确说明不需要任何类型, 需要使用特殊类型 dynamic 。

  • Dart 支持泛型,如 List (整数列表)或 List (任何类型的对象列表)。

  • Dart 支持顶级函数(例如 main() ), 同样函数绑定在类或对象上(分别是 静态函数 和 实例函数 )。 以及支持函数内创建函数 ( 嵌套 或 局部函数 ) 。

  • 类似地, Dart 支持顶级 变量 , 同样变量绑定在类或对象上(静态变量和实例变量)。 实例变量有时称为字段或属性。

  • 与 Java 不同,Dart 没有关键字 “public” , “protected” 和 “private” 。 如果标识符以下划线(_)开头,则它相对于库是私有的。 有关更多信息,参考 库和可见性。

  • 标识符 以字母或下划线(_)开头,后跟任意字母和数字组合。

  • Dart 语法中包含 表达式( expressions )(有运行时值)和 语句( statements )(没有运行时值)。 例如,条件表达式 condition ? expr1 : expr2 的值可能是 expr1 或 expr2 。 将其与 if-else 语句 相比较,if-else 语句没有值。 一条语句通常包含一个或多个表达式,相反表达式不能直接包含语句。

  • Dart 工具提示两种类型问题:警告错误。 警告只是表明代码可能无法正常工作,但不会阻止程序的执行。 错误可能是编译时错误或者运行时错误。 编译时错误会阻止代码的执行; 运行时错误会导致代码在执行过程中引发 [异常](#exception)。

Dart关键字

key 作用域 备注
abstract 2 抽象方法/抽象类
dynamic 2
implements 2
show 1
as 2
else
import 2
static 2
assert
enum
in
super
async 1
export 2
interface 2
switch
await 3
extends
is
sync 1
break
external 2
library 2
this
case
factory 2
mixin 2
throw
catch
false
new
true
class
final
null
try
const
finally
on 1
typedef 2
continue
for
operator 2
var
covariant 2
Function 2
part 2
void
default
get 2
rethrow
while
deferred 2
hide 1
return
with
do
if
set 2
yield 3

避免使用这些单词作为标识符。 但是,如有必要,标有上标的关键字可以用作标识符:

  • 带有 1 上标的单词为 上下文关键字, 仅在特定位置具有含义。 他们在任何地方都是有效的标识符。

  • 带有 2 上标的单词为 内置标识符, 为了简化将 JavaScript 代码移植到 Dart 的工作, 这些关键字在大多数地方都是有效的标识符, 但它们不能用作类或类型名称,也不能用作 import 前缀。

  • 带有 3 上标的单词是与 Dart 1.0 发布后添加的异步支持相关的更新,作为限制类保留字。
    不能在标记为 async ,async* 或 sync* 的任何函数体中使用 await 或 yield 作为标识符。

关键字表中的剩余单词都是保留字。 不能将保留字用作标识符。

abstract

  • 抽象方法
    实例方法, getter, 和 setter 方法可以是抽象的, 只定义接口不进行实现,而是留给其他类去实现。 抽象方法只存在于 抽象类 中。

定义一个抽象函数,使用分号 (;) 来代替函数体:

abstract class Doer {
  // 定义实例变量和方法 ...

  void doSomething(); // 定义一个抽象方法。
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // 提供方法实现,所以这里的方法就不是抽象方法了...
  }
}

调用抽象方法会导致运行时错误。

  • 抽象类
    使用 abstract 修饰符来定义 抽象类 — 抽象类不能实例化。 抽象类通常用来定义接口,以及部分实现。 如果希望抽象类能够被实例化,那么可以通过定义一个 工厂构造函数 来实现。

抽象类通常具有 抽象方法。 下面是一个声明具有抽象方法的抽象类示例:

// 这个类被定义为抽象类,
// 所以不能被实例化。
abstract class AbstractContainer {
  // 定义构造行数,字段,方法...

  void updateChildren(); // 抽象方法。
}

dynamic

使用 dynamic 注解替换推断失败的情况。
Dart 允许在许多地方省略类型注解,并尝试推断类型。在某些情况下,如果推断失败了,会默认指定为 dynamic 类型。如果 dynamic 类型与期望相同,那么从技术的角度来讲,这是获取类型最简洁 的方式。

但是,这种方式是最不清晰的。任何一个阅读代码的人,当看到一个类型确实的成员时,是没有办法 知道,编写的人是希望它是 dynamic 类型,还是期望它是其他的什么类型,或者阅读的人就简单的 认为是编写的人忘记了指定类型。

当 dynamic 是你期望的类型,就应该指明它,这样能让你的意图更清晰。

dynamic mergeJson(dynamic original, dynamic changes) => ...

在Dart 2之前,本规则恰恰是相反的:不要 为隐性类型的成员指定 dynamic 注解。基于强类型系统 和类型推断,现在的开发者更希望 Dart 的行为类似于推断的静态类型语言。基于这种心理模型,我们发现 代码区域慢慢地失去了静态类型所具有的安全及性能。

implements

每个类都隐式的定义了一个接口,接口包含了该类所有的实例成员及其实现的接口。 如果要创建一个 A 类,A 要支持 B 类的 API ,但是不需要继承 B 的实现, 那么可以通过 A 实现 B 的接口。

一个类可以通过 implements 关键字来实现一个或者多个接口, 并实现每个接口要求的 API。 例如:

// person 类。 隐式接口里面包含了 greet() 方法声明。
class Person {
  // 包含在接口里,但只在当前库中可见。
  final _name;

  // 不包含在接口里,因为这是一个构造函数。
  Person(this._name);

  // 包含在接口里。
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// person 接口的实现。
class Impostor implements Person {
  get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}
下面示例演示一个类如何实现多个接口: Here’s an example of specifying that a class implements multiple interfaces:

class Point implements Comparable, Location {...}

show hide

导入库的一部分
如果你只使用库的一部分功能,则可以选择需要导入的 内容。例如:

// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;

as

  • 类型判定运算符
    as, is, 和 is! 运算符用于在运行时处理类型检查:
|Operator|  Meaning|
| --- | ---| 
|as|    Typecast (也被用于指定库前缀)|
|is |True if the object has the specified type|
|is!|   False if the object has the specified type|

例如, obj is Object 总是 true。 但是只有 obj 实现了 T 的接口时, obj is T 才是 true。

使用 as 运算符将对象强制转换为特定类型。 通常,可以认为是 is 类型判定后,被判定对象调用函数的一种缩写形式。 请考虑以下代码:

```
if (emp is Person) {
  // Type check
  emp.firstName = 'Bob';
}
```
使用 as 运算符进行缩写:

```
(emp as Person).firstName = 'Bob';
```
######提示:以上代码并不是等价的。 如果 emp 为 null 或者不是 Person 对象, 那么第一个 is 的示例,后面将不回执行; 第二个 as 的示例会抛出异常。
  • 指定库前缀
    如果导入两个存在冲突标识符的库, 则可以为这两个库,或者其中一个指定前缀。 例如,如果 library1 和 library2 都有一个 Element 类, 那么可以通过下面的方式处理:

    import 'package:lib1/lib1.dart';
    import 'package:lib2/lib2.dart' as lib2;
    
    // 使用 lib1 中的 Element。
    Element element1 = Element();
    
    // 使用 lib2 中的 Element。
    lib2.Element element2 = lib2.Element();
    

if else

Dart 支持 if - else 语句,其中 else 是可选的, 比如下面的例子, 另参考 conditional expressions.

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

和 JavaScript 不同, Dart 的判断条件必须是布尔值,不能是其他类型。

import

使用库
通过 import 指定一个库命名空间中的内如如何在另一个库中使用。 例如,Dart Web应用程序通常使用 dart:html 库,它们可以像这样导入:

import 'dart:html';

import 参数只需要一个指向库的 URI。 对于内置库,URI 拥有自己特殊的dart: 方案。 对于其他的库,使用系统文件路径或者 package: 方案 。 package: 方案指定由包管理器(如 pub 工具)提供的库。例如:

import 'package:test/test.dart';
提示: URI 代表统一资源标识符。 URL(统一资源定位符)是一种常见的URI。

static

类变量和方法
使用 static 关键字实现类范围的变量和方法。

  • 静态变量
    静态变量(类变量)对于类级别的状态是非常有用的:

    class Queue {
      static const initialCapacity = 16;
      // ···
    }
    
    void main() {
      assert(Queue.initialCapacity == 16);
    }
    

静态变量只到它们被使用的时候才会初始化。

提示: 代码准守风格推荐指南 中的命名规则, 使用 lowerCamelCase 来命名常量。
  • 静态方法
    静态方法(类方法)不能在实例上使用,因此它们不能访问 this 。 例如:

    import 'dart:math';
    
    class Point {
      num x, y;
      Point(this.x, this.y);
    
      static num distanceBetween(Point a, Point b) {
        var dx = a.x - b.x;
        var dy = a.y - b.y;
        return sqrt(dx * dx + dy * dy);
      }
    }
    
    void main() {
      var a = Point(2, 2);
      var b = Point(4, 4);
      var distance = Point.distanceBetween(a, b);
      assert(2.8 < distance && distance < 2.9);
      print(distance);
    }
    
提示: 对于常见或广泛使用的工具和函数, 应该考虑使用顶级函数而不是静态方法。

静态函数可以当做编译时常量使用。 例如,可以将静态方法作为参数传递给常量构造函数。

asset

如果 assert 语句中的布尔条件为 false , 那么正常的程序执行流程会被中断。 在本章中包含部分 assert 的使用, 下面是一些示例:

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

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

// 确认 URL 是否是 https 类型。
assert(urlString.startsWith('https'));
提示: assert 语句只在开发环境中有效, 在生产环境是无效的; Flutter 中的 assert 只在 debug 模式 中有效。 开发用的工具,例如 dartdevc 默认是开启 assert 功能。 其他的一些工具, 例如 dart 和 dart2js, 支持通过命令行开启 assert : --enable-asserts。

assert 的第二个参数可以为其添加一个字符串消息。

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

assert 的第一个参数可以是解析为布尔值的任何表达式。 如果表达式结果为 true , 则断言成功,并继续执行。 如果表达式结果为 false , 则断言失败,并抛出异常 (AssertionError) 。

enum

枚举类型
枚举类型也称为 enumerations 或 enums , 是一种特殊的类,用于表示数量固定的常量值。

使用枚举
使用 enum 关键字定义一个枚举类型:

enum Color { red, green, blue }

枚举中的每个值都有一个 index getter 方法, 该方法返回值所在枚举类型定义中的位置(从 0 开始)。 例如,第一个枚举值的索引是 0 , 第二个枚举值的索引是 1。

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

使用枚举的 values 常量, 获取所有枚举值列表( list )。

List colors = Color.values;
assert(colors[2] == Color.blue);

可以在 switch 语句 中使用枚举, 如果不处理所有枚举值,会收到警告:

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // 没有这个,会看到一个警告。
    print(aColor); // 'Color.blue'
}

枚举类型具有以下限制:

  • 枚举不能被子类化,混合或实现。
  • 枚举不能被显式实例化。
    有关更多信息,参考 Dart language specification 。

for in

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。 但是示例中的代码在 JavaScript 中会连续输出两个 2 。

I如果要迭代一个实现了 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
}

extends super

扩展类(继承)
使用 extends 关键字来创建子类, 使用 super 关键字来引用父类:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}

async await

  • 异步支持
    Dart 库中包含许多返回 Future 或 Stream 对象的函数. 这些函数在设置完耗时任务(例如 I/O 曹组)后, 就立即返回了,不会等待耗任务完成。 使用 async 和 await 关键字实现异步编程。 可以让你像编写同步代码一样实现异步操作。
- 处理 Future
可以通过下面两种方式,获得 Future 执行完成的结果:

使用 async 和 await.
使用 Future API,具体描述,参考 库概览.
使用 async 和 await 关键字的代码是异步的。 虽然看起来有点想同步代码。 例如,下面的代码使用 await 等待异步函数的执行结果。

```
await lookUpVersion();
```
要使用 await , 代码必须在 异步函数(使用 async 标记的函数)中:

```
Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}
```
提示: 虽然异步函数可能会执行耗时的操作, 但它不会等待这些操作。 相反,异步函数只有在遇到第一个 await 表达式(详情见)时才会执行。 也就是说,它返回一个 Future 对象, 仅在await表达式完成后才恢复执行。

使用 try, catch, 和 finally 来处理代码中使用 await 导致的错误。

```
try {
  version = await lookUpVersion();
} catch (e) {
  // React to inability to look up the version
}
```
在一个异步函数中可以多次使用 await 。 例如,下面代码中等待了三次函数结果:

```
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
```

在 await 表达式 中, 表达式 的值通常是一个 Future 对象; 如果不是,这是表达式的值会被自动包装成一个 Future 对象。 Future 对象指明返回一个对象的承诺(promise)。 await 表达式 执行的结果为这个返回的对象。 await 表达式会阻塞代码的执行,直到需要的对象返回为止。

如果在使用 await 导致编译时错误, 确认 await 是否在一个异步函数中。 例如,在应用的 main() 函数中使用 await , main() 函数的函数体必须被标记为 async :

Future main() async {
  checkVersion();
  print('In main: version is ${await lookUpVersion()}');
}
  • 声明异步函数
    函数体被 async 标示符标记的函数,即是一个异步函数。 将 async 关键字添加到函数使其返回Future。 例如,考虑下面的同步函数,它返回一个 String :

     String lookUpVersion() => '1.0.0';
    

    例如,将来的实现将非常耗时,将其更改为异步函数,返回值是 Future 。

    Future lookUpVersion() async => '1.0.0';
    
    注意,函数体不需要使用Future API。 如有必要, Dart 会创建 Future 对象。

    如果函数没有返回有效值, 需要设置其返回类型为 Future

  • 处理 Stream
    当需要从 Stream 中获取数据值时, 可以通过一下两种方式:

    使用 async 和 一个 异步循环 (await for)。
    使用 Stream API, 更多详情,参考 in the library tour。
    提示: 在使用 await for 前,确保代码清晰, 并且确实希望等待所有流的结果。 例如,通常不应该使用 await for 的UI事件侦听器, 因为UI框架会发送无穷无尽的事件流。

    一下是异步for循环的使用形式:

    await for (varOrType identifier in expression) {
      // Executes each time the stream emits a value.
    }
    

    上面 表达式 返回的值必须是 Stream 类型。 执行流程如下:

    1. 等待,直到流发出一个值。
    2. 执行 for 循环体,将变量设置为该发出的值
    3. 重复1和2,直到关闭流。

    使用 break 或者 return 语句可以停止接收 stream 的数据, 这样就跳出了 for 循环, 并且从 stream 上取消注册。 如果在实现异步 for 循环时遇到编译时错误, 请检查确保 await for 处于异步函数中。 例如,要在应用程序的 main() 函数中使用异步 fo r循环, main() 函数体必须标记为 async` :

    Future main() async {
      // ...
      await for (var request in requestServer) {
        handleRequest(request);
      }
      // ...
    }
    

有关异步编程的更多信息,请参考 dart:async 部分。 同时也可参考文章 Dart Language Asynchrony Support: Phase 1 和 Dart Language Asynchrony Support: Phase 2, 以及 Dart language specification 。

switch case

在 Dart 中 switch 语句使用 == 比较整数,字符串,或者编译时常量。 比较的对象必须都是同一个类的实例(并且不可以是子类), 类必须没有对 == 重写。 枚举类型 可以用于 switch 语句。

提示: 在 Dart 中 Switch 语句仅适用于有限的情况下, 例如在 interpreter 或 scanner 中。

在 case 语句中,每个非空的 case 语句结尾需要跟一个 break 语句。 除 break 以外,还有可以使用 continue, throw,者 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 语句,导致错误:

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

  case 'CLOSED':
    executeClosed();
    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;
}

case 语句可以拥有局部变量, 这些局部变量只能在这个语句的作用域中可见。

sync

生成器
当您需要延迟生成( lazily produce )一系列值时, 可以考虑使用生成器函数。 Dart 内置支持两种生成器函数:

  • Synchronous 生成器: 返回一个 Iterable 对象。
  • Asynchronous 生成器: 返回一个 Stream 对象。

通过在函数体标记 sync*, 可以实现一个同步生成器函数。 使用 yield 语句来传递值:

Iterable naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
通过在函数体标记 async*, 可以实现一个异步生成器函数。 使用 yield 语句来传递值:

Stream asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

如果生成器是递归的,可以使用 yield* 来提高其性能:

Iterable naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

有关生成器的更多信息,请参考文章 Dart Language Asynchrony Support: Phase 2 。

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());

Mixin

为类添加功能: Mixin

Mixin 是复用类代码的一种途径, 复用的类可以在不同层级,之间可以不存在继承关系。

通过 with 后面跟一个或多个混入的名称,来 使用 Mixin , 下面的示例演示了两个使用 Mixin 的类:

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

通过创建一个继承自 Object 且没有构造函数的类,来 实现 一个 Mixin 。 如果 Mixin 不希望作为常规类被使用,使用关键字 mixin 替换 class 。 例如:

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

指定只有某些类型可以使用的 Mixin - 比如, Mixin 可以调用 Mixin 自身没有定义的方法 - 使用 on 来指定可以使用 Mixin 的父类类型:

mixin MusicalPerformer on Musician {
  // ···
}

版本提示: mixin 关键字在 Dart 2.1 中被引用支持。 早期版本中的代码通常使用 abstract class 代替。 更多有关 Mixin 在 2.1 中的变更信息,请参见 Dart SDK changelog 和 2.1 mixin specification 。

提示: 对 Mixin 的一些限制正在被移除。 关于更多详情,参考 proposed mixin specification.

有关 Dart 中 Mixin 的理论演变,参考 A Brief History of Mixins in Dart.

Final Const

Final 和 Const
使用过程中从来不会被修改的变量, 可以使用 final 或 const, 而不是 var 或者其他类型, Final 变量的值只能被设置一次; Const 变量在编译时就已经固定 (Const 变量 是隐式 Final 的类型.) 最高级 final 变量或类变量在第一次使用时被初始化。

提示: 实例变量可以是 final 类型但不能是 const 类型。 必须在构造函数体执行之前初始化 final 实例变量 —— 在变量声明中,参数构造函数中或构造函数的初始化列表中进行初始化。

创建和设置一个 Final 变量:

final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';

final 不能被修改:

name = 'Alice'; // Error: 一个 final 变量只能被设置一次。

如果需要在编译时就固定变量的值,可以使用 const 类型变量。 如果 Const 变量是类级别的,需要标记为 static const。 在这些地方可以使用在编译时就已经固定不变的值,字面量的数字和字符串, 固定的变量,或者是用于计算的固定数字:

const bar = 1000000; // 压力单位 (dynes/cm2)
const double atm = 1.01325 * bar; // 标准气压

Const 关键字不仅可以用于声明常量变量。 还可以用来创建常量值,以及声明创建常量值的构造函数。 任何变量都可以拥有常量值。

var foo = const [];
final bar = const [];
const baz = []; // Equivalent to `const []`

声明 const 的初始化表达式中 const 可以被省略。 比如上面的 baz。 有关更多信息,参考 DON’T use const redundantly。

非 Final , 非 const 的变量是可以被修改的,即使这些变量 曾经引用过 const 值。

foo = [1, 2, 3]; // 曾经引用过 const [] 常量值。

Const 变量的值不可以修改:

baz = [42]; // Error: 常量变量不能赋值修改。

更多关于使用 const 创建常量值,参考 Lists, Maps, 和 Classes。

throw catch finnaly on rethrow

异常
Dart 代码可以抛出和捕获异常。 异常表示一些未知的错误情况。 如果异常没有被捕获, 则异常会抛出, 导致抛出异常的代码终止执行。

和 Java 有所不同, Dart 中的所有异常是非检查异常。 方法不会声明它们抛出的异常, 也不要求捕获任何异常。

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

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

    throw FormatException('Expected at least 1 section');
    也可以抛出任意的对象:

    throw 'Out of llamas!';
    提示: 高质量的生产环境代码通常会实现 Error 或 Exception 类型的异常抛出。

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

    void distanceTo(Point other) => throw UnimplementedError();
    catch
    捕获异常可以避免异常继续传递(除非重新抛出( rethrow )异常)。 可以通过捕获异常的机会来处理该异常:

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

    通过指定多个 catch 语句,可以处理可能抛出多种类型异常的代码。 与抛出异常类型匹配的第一个 catch 语句处理异常。 如果 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 ,也可以单独分开使用。 使用 on 来指定异常类型, 使用 catch 来 捕获异常对象。

    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
    如果仅需要部分处理异常, 那么可以使用关键字 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
    不管是否抛出异常, finally 中的代码都会被执行。 如果 catch 没有匹配到异常, 异常会在 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.
    }
    

    更多详情,请参考 Exceptions 章节。

Typedefs

在 Dart 中,函数也是对象,就想字符和数字对象一样。 使用 typedef ,或者 function-type alias 为函数起一个别名, 别名可以用来声明字段及返回值类型。 当函数类型分配给变量时,typedef会保留类型信息。

请考虑以下代码,代码中未使用 typedef :

class SortedCollection {
  Function compare;

  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}

// Initial, broken implementation. // broken ?
int sort(Object a, Object b) => 0;

void main() {
  SortedCollection coll = SortedCollection(sort);

  // 虽然知道 compare 是函数,
  // 但是函数是什么类型 ?
  assert(coll.compare is Function);
}

当把 f 赋值给 compare 的时候,类型信息丢失了。 f 的类型是 (Object, Object) → int (这里 → 代表返回值类型), 但是 compare 得到的类型是 Function 。如果我们使用显式的名字并保留类型信息, 这样开发者和工具都可以使用这些信息:

typedef Compare = int Function(Object a, Object b);

class SortedCollection {
  Compare compare;

  SortedCollection(this.compare);
}

// Initial, broken implementation.
int sort(Object a, Object b) => 0;

void main() {
  SortedCollection coll = SortedCollection(sort);
  assert(coll.compare is Function);
  assert(coll.compare is Compare);
}

提示: 目前,typedefs 只能使用在函数类型上, 我们希望将来这种情况有所改变。

由于 typedefs 只是别名, 他们还提供了一种方式来判断任意函数的类型。例如:

typedef Compare = int Function(T a, T b);

int sort(int a, int b) => a - b;

void main() {
  assert(sort is Compare); // True!
}

Function

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

Deferred

延迟加载库
Deferred loading (也称之为 lazy loading) 可以让应用在需要的时候再加载库。 下面是一些使用延迟加载库的场景:

减少 APP 的启动时间。
执行 A/B 测试,例如 尝试各种算法的 不同实现。
加载很少使用的功能,例如可选的屏幕和对话框。
要延迟加载一个库,需要先使用 deferred as 来导入:

import 'package:greetings/hello.dart' deferred as hello;

当需要使用的时候,使用库标识符调用 loadLibrary() 函数来加载库:

Future greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}

在前面的代码,使用 await 关键字暂停代码执行一直到库加载完成。 关于 async 和 await 的更多信息请参考 异步支持。

在一个库上你可以多次调用 loadLibrary() 函数。但是该库只是载入一次。

使用延迟加载库的时候,请注意一下问题:

延迟加载库的常量在导入的时候是不可用的。 只有当库加载完毕的时候,库中常量才可以使用。
在导入文件的时候无法使用延迟库中的类型。 如果你需要使用类型,则考虑把接口类型移动到另外一个库中, 让两个库都分别导入这个接口库。
Dart 隐含的把 loadLibrary() 函数导入到使用 deferred as 的命名空间 中。 loadLibrary() 方法返回一个 Future。

变量类型

Dart 语言支持以下内建类型:

  1. Number
  2. String
  3. Boolean
  4. List (也被称为 Array)
  5. Map
  6. Set
  7. Rune (用于在字符串中表示 Unicode 字符)
  8. Symbol
    这些类型都可以被初始化为字面量。 例如, 'this is a string' 是一个字符串的字面量, true 是一个布尔的字面量。

因为在 Dart 所有的变量终究是一个对象(一个类的实例), 所以变量可以使用 构造函数 进行初始化。 一些内建类型拥有自己的构造函数。 例如, 通过 Map() 来构造一个 map 变量。

  • Number

    Dart 语言的 Number 有两种类型:

    • int

      整数值不大于64位, 具体取决于平台。 在 Dart VM 上, 值的范围从 -263 到 263 - 1. Dart 被编译为 JavaScript 时,使用 JavaScript numbers, 值的范围从 -253 到 253 - 1.

    • double

      64位(双精度)浮点数,依据 IEEE 754 标准。

      int 和 double 都是 num. 的亚类型。 num 类型包括基本运算 +, -, /, 和 *, 以及 abs(), ceil(), 和 floor(), 等函数方法。 (按位运算符,例如»,定义在 int 类中。) 如果 num 及其亚类型找不到你想要的方法, 尝试查找使用 dart:math 库。

      整数类型不包含小数点。 下面是定义整数类型字面量的例子:

      var x = 1;
      var hex = 0xDEADBEEF;
      

      如果一个数字包含小数点,那么就是小数类型。 下面是定义小数类型字面量的例子:

      var y = 1.1;
      var exponents = 1.42e5;
      

      从 Dart 2.1 开始,必要的时候 int 字面量会自动转换成 double 类型。

      double z = 1; // 相当于 double z = 1.0.
      
      版本提示: 在 2.1 之前,在 double 上下文中使用 int 字面量是错误的。

      以下是将字符串转换为数字的方法,反之亦然:

      // String -> int
      var one = int.parse('1');
      assert(one == 1);
      
      // String -> double
      var onePointOne = double.parse('1.1');
      assert(onePointOne == 1.1);
      
      // int -> String
      String oneAsString = 1.toString();
      assert(oneAsString == '1');
      
      // double -> String
      String piAsString = 3.14159.toStringAsFixed(2);
      assert(piAsString == '3.14');
      

      int 特有的传统按位运算操作,移位(<<, >>),按位与(&)以及 按位或(|)。 例如:

      assert((3 << 1) == 6); // 0011 << 1 == 0110
      assert((3 >> 1) == 1); // 0011 >> 1 == 0001
      assert((3 | 4) == 7); // 0011 | 0100 == 0111
      

      数字类型字面量是编译时常量。 在算术表达式中,只要参与计算的因子是编译时常量, 那么算术表达式的结果也是编译时常量。

      const msPerSecond = 1000;
      const secondsUntilRetry = 5;
      const msUntilRetry = secondsUntilRetry * msPerSecond;
      
    
    
  • String

    Dart 字符串是一组 UTF-16 单元序列。 字符串通过单引号或者双引号创建。

    var s1 = 'Single quotes work well for string literals.';
    var s2 = "Double quotes work just as well.";
    var s3 = 'It\'s easy to escape the string delimiter.';
    var s4 = "It's even easier to use the other delimiter.";
    

    字符串可以通过 ${expression} 的方式内嵌表达式。 如果表达式是一个标识符,则 {} 可以省略。 在 Dart 中通过调用就对象的 toString() 方法来得到对象相应的字符串。

    var s = 'string interpolation';
    
    assert('Dart has $s, which is very handy.' ==
        'Dart has string interpolation, ' +
            'which is very handy.');
    assert('That deserves all caps. ' +
            '${s.toUpperCase()} is very handy!' ==
        'That deserves all caps. ' +
            'STRING INTERPOLATION is very handy!'); 
    
    提示: == 运算符用来测试两个对象是否相等。 在字符串中,如果两个字符串包含了相同的编码序列,那么这两个字符串相等。 units.

    可以使用 + 运算符来把多个字符串连接为一个,也可以把多个字面量字符串写在一起来实现字符串连接:

    var s1 = 'String '
        'concatenation'
        " works even over line breaks.";
    assert(s1 ==
        'String concatenation works even over '
        'line breaks.');
    
    var s2 = 'The + operator ' + 'works, as well.';
    assert(s2 == 'The + operator works, as well.');
    

    使用连续三个单引号或者三个双引号实现多行字符串对象的创建:

    var s1 = '''
    You can create
    multi-line strings like this one.
    ''';
    
    var s2 = """This is also a
    multi-line string.""";
    

    使用 r 前缀,可以创建 “原始 raw” 字符串:

    var s = r"In a raw string, even \n isn't special.";
    

    参考 Runes 来了解如何在字符串中表达 Unicode 字符。

    一个编译时常量的字面量字符串中,如果存在插值表达式,表达式内容也是编译时常量, 那么该字符串依旧是编译时常量。 插入的常量值类型可以是 null,数值,字符串或布尔值。

    // const 类型数据
    const aConstNum = 0;
    const aConstBool = true;
    const aConstString = 'a constant string';
    
    // 非 const 类型数据
    var aNum = 0;
    var aBool = true;
    var aString = 'a string';
    const aConstList = [1, 2, 3];
    
    const validConstString = '$aConstNum $aConstBool $aConstString'; //const 类型数据
    // const invalidConstString = '$aNum $aBool $aString $aConstList'; //非 const 类型数据
    

    更多关于 string 的使用, 参考 字符串和正则表达式.

    • Boolean
      Dart 使用 bool 类型表示布尔值。 Dart 只有字面量 true and false 是布尔类型, 这两个对象都是编译时常量。

      Dart 的类型安全意味着不能使用 if (nonbooleanValue) 或者 assert (nonbooleanValue)。 而是应该像下面这样,明确的进行值检查:

      // 检查空字符串。
      var fullName = '';
      assert(fullName.isEmpty);
      
      // 检查 0 值。
      var hitPoints = 0;
      assert(hitPoints <= 0);
      
      // 检查 null 值。
      var unicorn;
      assert(unicorn == null);
      
      // 检查 NaN 。
      var iMeantToDoThis = 0 / 0;
      assert(iMeantToDoThis.isNaN);
      
  • List
    几乎每种编程语言中最常见的集合可能是 array 或有序的对象集合。 在 Dart 中的 Array 就是 List 对象, 通常称之为 List 。

    Dart 中的 List 字面量非常像 JavaScript 中的 array 字面量。 下面是一个 Dart List 的示例:

    var list = [1, 2, 3];
    
    提示: Dart 推断 list 的类型为 List 。 如果尝试将非整数对象添加到此 List 中, 则分析器或运行时会引发错误。 有关更多信息,请阅读 类型推断。

    Lists 的下标索引从 0 开始,第一个元素的索引是 0。 list.length - 1 是最后一个元素的索引。 访问 List 的长度和元素与 JavaScript 中的用法一样:

    var list = [1, 2, 3];
    assert(list.length == 3);
    assert(list[1] == 2);
    
    list[1] = 1;
    assert(list[1] == 1);
    

    在 List 字面量之前添加 const 关键字,可以定义 List 类型的编译时常量:

    var constantList = const [1, 2, 3];
    // constantList[1] = 1; // 取消注释会引起错误。
    

    List 类型包含了很多 List 的操作函数。 更多信息参考 泛型 和 集合.

  • Set
    在 Dart 中 Set 是一个元素唯一且无需的集合。 Dart 为 Set 提供了 Set 字面量和 Set 类型。

    版本提示: 虽然 Set 类型 一直是 Dart 的核心部分, 但在 Dart2.2 中才引入了 Set 字面量 。

    下面是通过字面量创建 Set 的一个简单示例:

    var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
    

    Note: Dart 推断 halogens 类型为 Set 。如果尝试为它添加一个 错误类型的值,分析器或执行时会抛出错误。更多内容,参阅 类型推断。

    要创建一个空集,使用前面带有类型参数的 {} ,或者将 {} 赋值给 Set 类型的变量:

    var names = {};
    // Set names = {}; // 这样也是可以的。
    // var names = {}; // 这样会创建一个 Map ,而不是 Set 。
    

    是 Set 还是 Map ? Map 字面量语法同 Set 字面量语法非常相似。 因为先有的 Map 字母量语法,所以 {} 默认是 Map 类型。 如果忘记在 {} 上注释类型或赋值到一个未声明类型的变量上, 那么 Dart 会创建一个类型为 Map 的对象。

    使用 add() 或 addAll() 为已有的 Set 添加元素:

    var elements = {};
    elements.add('fluorine');
    elements.addAll(halogens);
    

    使用 .length 来获取 Set 中元素的个数:

    var elements = {};
    elements.add('fluorine');
    elements.addAll(halogens);
    assert(elements.length == 5);
    

    在 Set 字面量前增加 const ,来创建一个编译时 Set 常量:

    final constantSet = const {
      'fluorine',
      'chlorine',
      'bromine',
      'iodine',
      'astatine',
    };
    // constantSet.add('helium'); // Uncommenting this causes an error.
    

    更多关于 Set 的内容,参阅 Generic 及 Set。

  • Map
    通常来说, Map 是用来关联 keys 和 values 的对象。 keys 和 values 可以是任何类型的对象。在一个 Map 对象中一个 key 只能出现一次。 但是 value 可以出现多次。 Dart 中 Map 通过 Map 字面量 和 Map 类型来实现。

    下面是使用 Map 字面量的两个简单例子:

    var gifts = {
      // Key:    Value
      'first': 'partridge',
      'second': 'turtledoves',
      'fifth': 'golden rings'
    };
    
    var nobleGases = {
      2: 'helium',
      10: 'neon',
      18: 'argon',
    };
    
    提示: Dart 会将 gifts 的类型推断为 Map, nobleGases 的类型推断为 Map 。 如果尝试在上面的 map 中添加错误类型,那么分析器或者运行时会引发错误。 有关更多信息,请阅读类型推断。。

    以上 Map 对象也可以使用 Map 构造函数创建:

    var gifts = Map();
    gifts['first'] = 'partridge';
    gifts['second'] = 'turtledoves';
    gifts['fifth'] = 'golden rings';
    
    var nobleGases = Map();
    nobleGases[2] = 'helium';
    nobleGases[10] = 'neon';
    nobleGases[18] = 'argon';
    
    提示: 这里为什么只有 Map() ,而不是使用 new Map()。 因为在 Dart 2 中,new 关键字是可选的。 有关更多信息,参考 构造函数的使用。

    类似 JavaScript ,添加 key-value 对到已有的 Map 中:

    var gifts = {'first': 'partridge'};
    gifts['fourth'] = 'calling birds'; // Add a key-value pair
    

    类似 JavaScript ,从一个 Map 中获取一个 value:

    var gifts = {'first': 'partridge'};
    assert(gifts['first'] == 'partridge');
    

    如果 Map 中不包含所要查找的 key,那么 Map 返回 null:

    var gifts = {'first': 'partridge'};
    assert(gifts['fifth'] == null);
    

    使用 .length 函数获取当前 Map 中的 key-value 对数量:

    var gifts = {'first': 'partridge'};
    gifts['fourth'] = 'calling birds';
    assert(gifts.length == 2);
    

    创建 Map 类型运行时常量,要在 Map 字面量前加上关键字 const。

    final constantMap = const {
      2: 'helium',
      10: 'neon',
      18: 'argon',
    };
    
    // constantMap[2] = 'Helium'; // 取消注释会引起错误。
    

    更名多关于 Map 的内容,参考 Generics and Maps.

  • Rune
    在 Dart 中, Rune 用来表示字符串中的 UTF-32 编码字符。

    Unicode 定义了一个全球的书写系统编码, 系统中使用的所有字母,数字和符号都对应唯一的数值编码。 由于 Dart 字符串是一系列 UTF-16 编码单元, 因此要在字符串中表示32位 Unicode 值需要特殊语法支持。

    表示 Unicode 编码的常用方法是, \uXXXX, 这里 XXXX 是一个4位的16进制数。 例如,心形符号 (♥) 是 \u2665。 对于特殊的非 4 个数值的情况, 把编码值放到大括号中即可。 例如,emoji 的笑脸 (�) 是 \u{1f600}。

String 类有一些属性可以获得 rune 数据。 属性 codeUnitAt 和 codeUnit 返回16位编码数据。 属性 runes 获取字符串中的 Rune 。

######提示: 谨慎使用 list 方式操作 Rune 。 这种方法很容易引发崩溃, 具体原因取决于特定的语言,字符集和操作。 有关更多信息,参考 How do I reverse a String in Dart? on Stack Overflow.
  • Symbol

    一个 Symbol 对象表示 Dart 程序中声明的运算符或者标识符。 你也许永远都不需要使用 Symbol ,但要按名称引用标识符的 API 时, Symbol 就非常有用了。 因为代码压缩后会改变标识符的名称,但不会改变标识符的符号。 通过字面量 Symbol ,也就是标识符前面添加一个 # 号,来获取标识符的 Symbol 。

      #radix
      #bar
    

    Symbol 字面量是编译时常量。

控制流程语句

你可以通过下面任意一种方式来控制 Dart 程序流程:

  • if and else

  • for loops

  • while and do-while loops

  • break and continue

  • switch and case

  • assert

使用 try-catch 和 throw 也可以改变程序流程, 详见 Exceptions。

文章节选总结自来源

你可能感兴趣的:(Flutter_一小时入门Dart)