Dart 基础语法

编辑于2018年09月26日

本文记录了 Dart 的基本语法,如:变量、类型、函数、类等,是我在学习 Dart 时的笔记,内容摘抄自官方文档,主要是我觉得重点的内容。

学习 Dart 的原因是我想学习 Flutter,而 Flutter 使用 Dart 作为开发语言。目前仅仅是熟悉了基本的语法,还没有太多深入的了解,期待能给我带来更多惊喜。

重要的概念

  • 所有能够使用变量引用的都是对象,每个对象都是一个类的实例。在 Dart 中甚至连数字、方法和 null 都是对象。所有的对象都继承于 Object 类。
  • 使用静态类型(例如前面示例中的 num)可以更清晰的表明你的意图,并且可以让静态分析工具来分析你的代码,但这并不是强制性的。
  • Dart 在运行之前会先解析代码,可以通过静态类型或者编译时常量来帮助 Dart 去捕获异常,让代码运行的更高效。
  • Dart 支持全局方法,同时还支持在类中定义函数(静态方法和实例方法),还可以在方法中定义方法。
  • Dart 支持全局变量,以及在类中定义变量(静态变量和实例变量)。
  • Dart 没有 public、 protected、 和 private 关键字。如果一个标识符以(_)开头,则说明其是私有的。
  • 标识符可以以字母或者下划线(_)开头,后面可以是其他字符和数字的组合。
  • Dart 中有表达式(具有运行值)和语句(没有运行值)。例如条件语句condition ? expr1 : expr2 的值不是 expr1 就是 expr2,而对应的 if-else 语句则没有。语句中可以包含一个或多个表达式,而表达式不能直接包含语句。
  • Dart 中有警告和错误,错误分为编译时错误和运行时错误,编译时错误代码无法正常运行,而运行时错误会在代码运行时抛出异常。

变量

var name = 'Bob';

变量是一个引用,变量 name 包含一个 String 对象的引用,值为 “Bob”。

没有初始化的变量的默认值是 null(数字也是对象)。

int lineCount;
assert(lineCount == null);

assert() 在生成环境下会被忽略,在开发环境下,如果条件会成立会抛出异常。

使用 final 或者 const 关键字定义常量。final 变量只能被赋值一次;const 用来定义编译时常量,本身也是 final。类的实例变量不能是 const 类型,静态成员变量可以。

const 关键字除了用来定义常量,还可以用来定义不变的值或者声明构造函数以创建不可变的对象。任何变量都可以有不变的值

var foo = const []; // 值是一个不变的数组
final bar = const [];
const baz = []; // 等价于 `const []`

内置类型

  • numbers
  • strings
  • booleans
  • lists (也叫 arrays)
  • maps
  • runes (用于在字符串中表示 Unicode 字符)
  • symbols

Numbers

  • int:不超过 64 位(8 字节),取决于运行的平台。在 Dart VM 中,值为 -263 to 263 - 1,编译成 JS 后值为:-253 to 253 - 1。
  • double:64 位双精度浮点数。

intdouble 都是 num 的子类。num 类型定义了基本的运算符和方法。位操作符,例如 >> 定义在 int 类中。如果 num 或者其子类型不满足要求,可以使用 dart:math 库。

整数是不带小数点的数字:

int x = 1;
int hex = 0xDEADBEEF;

如果一个数带小数点,则其为 double 类型:

double y = 1.1;
double exponents = 1.42e5;

字符串和数字互转:

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

Strings

Dart 字符串是 UTF-16 编码的字符序列,可以使用单引号或者双引号来创建字符串。可以在字符串中使用表达式,用法是这样的: ${expression}。如果表达式是一个标识符,可以省略 {}。如果表达式的结果为一个对象,则 Dart 会调用对象的 toString() 函数来获取一个字符串。

可以用 + 拼接字符串,也可以直接把两个字符串放在一起进行拼接:

var s1 = 'String ' 'concatenation'
         " works even over line breaks.";
assert(s1 == 'String concatenation works even over '
             'line breaks.');

使用三个单引号或者双引号也可以 创建多行字符串对象:

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.";

Booleans

只有两个对象是布尔类型:true 和 false,这两个对象也是编译时常量。当 Dart 需要一个布尔值的时候,只有 true 对象才被认为是 true。

var name = 'Bob';
if (name) {
  // Prints in JavaScript, not in Dart.
  print('You have a name!');
}

List 和 Map

Dart 中数组是 List 对象。基本用法和 JS 中一样。
Map 和 JS 对象基本用法一样。

Runes

Runes 代表字符串的 UTF-32 码点。通常使用 \uXXXX 的方式来表示 Unicode 码点,这里的 XXXX 是 4 个十六进制的数。例如,心形符号 (♥) 是 \u2665。对于非 4 个数值的情况,把编码值放到大括号中即可。例如,笑脸 emoji (😆) 是 \u{1f600}

main() {
  var clapping = '\u{1f44f}';
  print(clapping);
  print(clapping.codeUnits);
  print(clapping.runes.toList());

  Runes input = new Runes(
      '\u2665  \u{1f605}  \u{1f60e}  \u{1f47b}  \u{1f596}  \u{1f44d}');
  print(new String.fromCharCodes(input));
}

Symbols

Symbol 对象代表 Dart 程序中声明的操作符或者标识符。Symbole 字面量是编译时常量:

#radix
#bar

Function

方法也是对象,可以赋值给变量,也可以当做其他方法的参数,也可以把 Dart 类的实例当做方法来调用:

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

对于只有一个表达式的方法,你可以选择 使用缩写语法来定义:

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

方法可以有两种类型的参数:必需的和可选的。必需的参数在参数列表前面,后面是可选参数。

可选的参数

可选参数可以是命名参数或者位置参数,但不能即是命令参数又是位置参数。

调用方法的时候,你可以使用这种形式 paramName: value 来指定命名参数:

enableFlags(bold: true, hidden: false);

在定义方法的时候,使用 {param1, param2, …} 的形式来指定命名参数:

// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold, bool hidden}) {...}

命令参数和 JS 中使用对象作为参数类似。

把一些方法的参数放到 [] 中就变成可选的位置参数了:

String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

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

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold = false, bool hidden = false}) {...}

// bold will be true; hidden will be false.
enableFlags(bold: true);

main 函数

每个应用都需要有个顶级的 main() 入口方法才能执行。 main() 方法的返回值为 void 并且有个可选的 List<String> 参数:

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

代码中的 .. 语法为级联调用(cascade)。使用级联调用语法,你可以在一个对象上执行多个操作。

作用域

Dart 是静态作用域语言,变量的作用域在写代码的时候就确定过了。作用域基本在 {} 内,与 java、js 中的 let/const 一样。

闭包

一个闭包是一个方法对象,不管该对象在何处被调用,该对象都可以访问其作用域内的变量。方法可以封闭定义到其作用域内的变量。

/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

void main() {
  // Create a function that adds 2.
  var add2 = makeAdder(2);

  // Create a function that adds 4.
  var add4 = makeAdder(4);

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

操作符

~/:除号,但是返回值为整数,求商。

类型判定操作符:

  • as:类型转换。
  • is:如果对象是指定的类型返回 True
  • is!:如果对象是指定的类型返回 False

使用 as 操作符把对象转换为特定的类型。一般情况下,你可以把它当做用 is 判定类型,然后调用所判定对象的函数的缩写形式,区别在于后者失败会抛出异常。例如下面的示例:

if (emp is Person) { // Type check
  emp.firstName = 'Bob';
}

// 使用 as 操作符可以简化上面的代码:
(emp as Person).firstName = 'Bob';

?? 判空。

b ??= value; // 如果 b 是 null,则赋值给 b;
             // 如果不是 null,则 b 的值保持不变

expr1 ?? expr2 如果 expr1 是不为空,返回其值;否则执行 expr2 并返回其结果。

级联操作符 .. 可以在同一个对象上 连续调用多个函数以及访问成员变量。 使用级联操作符可以避免创建 临时变量, 并且写出来的代码看起来 更加流畅。
在方法上使用级联操作符需要非常小心, 例如下面的代码就是不合法的:

// Does not work
var sb = new StringBuffer();
sb.write('foo')..write('bar');

sb.write() 函数返回一个 void, 无法在 void 上使用级联操作符。

?.. 类似,但是左边的操作对象不能为 null,例如 foo?.bar 如果 foonull 则返回 null,否则返回 bar 成员。

流程控制语句

  • if and else
  • for loops
  • while and do-while loops
  • break and continue
  • switch and case
  • assert

需要注意 Dart 中和 JavaScript 对待 true 的区别。

如果条件表达式结果不满足需要,则可以使用 assert 语句俩打断代码的执行。

异常

所有的 Dart 异常是非检查异常。方法不一定声明了它们所抛出的异常,并且你不要求捕获任何异常。

Dart 代码可以 抛出任何非 null 对象为异常,不仅仅是实现了 Exception 或者 Error 的对象:

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

throw 'Out of llamas!';

对于可以抛出多种类型异常的代码,你可以指定多个捕获语句。每个语句分别对应一个异常类型,如果捕获语句没有指定异常类型,则可以捕获任何异常类型:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');
}

函数 catch() 可以带有一个或者两个参数,第一个参数为抛出的异常对象,第二个为堆栈信息 (一个 StackTrace 对象):

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

使用 rethrow 关键字可以把捕获的异常给 重新抛出:

final foo = '';

void misbehave() {
  try {
    foo = "You can't change a final variable's value.";
  } 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 语句后,异常被抛出了:

try {
  breedMoreLlamas();
} catch(e) {
  print('Error: $e');  // Handle the exception first.
} finally {
  cleanLlamaStalls();  // Then clean up.
}

Dart 是一个面向对象编程语言,支持基于 mixin 的继承机制。每个对象都是一个类的实例,所有的类都继承于 Object。 基于 Mixin 的继承意味着尽管每个类(Object 除外)都只有一个父类,类的代码也可以在多个类中复用。

使用构造函数

使用构造函数可以创建对象,构造函数名可以是 ClassName 或者 ClassName.identifier

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

new 关键字在 Dart 2 中是可选的。

有些类提供了常量构造函数。使用常量构造函数 可以创建编译时常量,要使用常量构造函数只需要用 const 替代 new 即可:

var p = const ImmutablePoint(2, 2);

两个一样的编译时常量其实是同一个对象:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // They are the same instance!

在常量上下文中,可以省略 const 关键字:

// Only one const, which establishes the constant context.
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

如果一个常量构造函数在常量上下文之外,没有调用时没有 const 关键字,那么创建的是一个非常量对象:

var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant

assert(!identical(a, b)); // NOT the same instance!

可以使用 Object 的 runtimeType 属性来判断实例 的类型,该属性 返回一个 Type 对象:

print('The type of a is ${a.runtimeType}');

实例变量

下面是如何定义实例变量的示例:

class Point {
  num x; // Declare instance variable x, initially null.
  num y; // Declare y, initially null.
  num z = 0; // Declare z, initially 0.
}

所有没有初始化的变量值都是 null。每个实例变量都会自动生成一个 getter 方法(隐含的)。 Non-final 实例变量还会自动生成一个 setter 方法。

构造函数

定义一个和类名字一样的方法就定义了一个构造函数,还可以带有其他可选的标识符:

class Point {
  num x;
  num y;

  Point(num x, num y) {
    // There's a better way to do this, stay tuned.
    this.x = x;
    this.y = y;
  }
}

this 关键字指当前的实例,只有当名字冲突的时候才使用 this。否则的话, Dart 代码风格样式推荐忽略 this
由于把构造函数参数赋值给实例变量的场景太常见了, Dart 提供了一个语法糖来简化这个操作:

class Point {
  num x;
  num y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}

如果你没有定义构造函数,则会有个默认构造函数。默认构造函数没有参数,并且会调用父类的无参的构造函数。
子类不会继承父类的构造函数。
使用命名构造函数可以为一个类实现多个构造函数, 或者使用命名构造函数来更清晰的表明你的意图:

class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // Named constructor
  Point.fromJson(Map json) {
    x = json['x'];
    y = json['y'];
  }
}

默认情况下,子类的构造函数会自动调用父类的匿名无参构造函数,父类的构造函数在子类的构造函数体还是之前执行。如果提供了一个初始化参数列表,则初始化参数列表在父类构造函数之前执行,顺序如下:

  1. 初始化参数列表
  2. 父类的无参构造函数
  3. 子类的无参构造函数

如果父类没有匿名无参数构造函数, 则你需要手动的调用父类的其他构造函数,在构造函数参数后使用冒号 (:) 可以调用父类构造函数。下面的示例中,Employee 类的构造函数调用了父类 Person 的命名构造函数:

class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // Prints:
  // in Person
  // in Employee
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
}

由于超类构造函数的参数在构造函数执行之前执行,所以 参数可以是一个表达式或者 一个方法调用:

class Employee extends Person {
  // ...
  Employee() : super.fromJson(findDefaultData());
}

初始化列表

在构造函数执行之前除了可以调用父类构造函数之外,还可以初始化实例参数。使用逗号分隔初始化表达式:

// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

初始化列表非常适合用来设置 final 变量的值。

重定向构造函数

有时候一个构造函数会调动类中的其他构造函数。一个重定向构造函数是没有代码的,在构造函数声明后,使用冒号调用其他构造函数。

class Point {
  num x;
  num y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(num x) : this(x, 0);
}

常量构造函数

如果你的类提供一个状态不变的对象,你可以把这些对象 定义为编译时常量。要实现这个功能,需要定义一个 const 构造函数, 并且声明所有类的变量为 final

class ImmutablePoint {
  final num x;
  final num y;
  const ImmutablePoint(this.x, this.y);
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);
}

工厂构造函数

如果一个构造函数并不总是返回一个新的对象,则使用 factory 来定义这个构造函数。例如,一个工厂构造函数可能从缓存中获取一个实例并返回,或者返回一个子类型的实例。工厂构造函数无法访问 this

class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

函数

函数是类中定义的方法,是类对象的行为。

  • 实例函数:对象的实例函数可以访问 this
  • Getters/Setters:getterssetters 是用来设置和访问对象属性的特殊函数。
  • 抽象函数:用分号来替代函数体则这个函数就是抽象函数。

抽象类

使用 abstract 修饰符定义一个抽象类,抽象类无法被实例化。抽象类通常用来定义接口,以及部分实现。如果你希望你的抽象类是可实例化的,则定义一个工厂构造函数。

隐式接口

每个类都定义了一个包含所有实例成员和它实现的任何接口的隐式接口。如果你想创建类 A 来支持 类 B 的 api,而不想继承 B 的实现, 则类 A 应该实现 B 的接口。一个类可以通过 implements 关键字来实现一个或者多个接口(多个接口用逗号分隔),并实现每个接口定义的 API。

继承

使用 extends 创建子类,super 引用父类。
子类可以覆写实例函数,getter 和 setter。可以使用 @override 注解表明是有意覆写的:

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}

覆写操作符

如果你定义了一个 Vector 类, 你可以定义一个 + 函数来实现两个向量相加:

class Vector {
  final int x;
  final int y;
  const Vector(this.x, this.y);

  /// Overrides + (a + b).
  Vector operator +(Vector v) {
    return new Vector(x + v.x, y + v.y);
  }

  /// Overrides - (a - b).
  Vector operator -(Vector v) {
    return new Vector(x - v.x, y - v.y);
  }
}

main() {
  final v = new Vector(2, 3);
  final w = new Vector(2, 2);

  // v == (2, 3)
  assert(v.x == 2 && v.y == 3);

  // v + w == (4, 5)
  assert((v + w).x == 4 && (v + w).y == 5);

  // v - w == (0, 1)
  assert((v - w).x == 0 && (v - w).y == 1);
}

noSuchMethod()

使用不存在的方法或变量时,会调用 noSuchMethod(),可以覆写此方法。满足下列任意情况时,可以调用未实现的函数:

  • 接收器的数据类型是 dynamic
  • 接收器有一个静态类型定义了未实现的方法(抽象函数),并且动态类型实现了与 Object 不同的 noSuchMethod()

枚举类型

枚举类型通常称之为 enumerations 或者 enums, 是一种特殊的类,用来表示一个固定数目的常量。
使用 enum 关键字来定义枚举类型:

enum Color {
  red,
  green,
  blue
}

枚举类型中的每个值都有一个 index getter 函数,该函数返回该值在枚举类型定义中的位置(从 0 开始)。
枚举的 values 常量可以返回 所有的枚举值。
可以在 switch 语句 中使用枚举。

枚举类型具有如下的限制:

  • 无法继承枚举类型、无法使用 mixin、无法实现一个枚举类型。
  • 无法显示的初始化一个枚举类型。

使用 mixins 为类添加功能

Mixins 是一种在多个类中复用类代码的方法。

使用 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,该类没有构造函数,没有调用 super ,则该类就是一个 mixin:

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

类变量和函数

使用 static 关键字来实现类级别的变量和函数。

静态变量在第一次使用的时候才被初始化。
静态函数不再类实例上执行,所以无法访问 this。静态函数还可以当做编译时常量使用。

泛型

如果你查看 List 类型的 API 文档则可以看到实际的类型定义为 List<E>。这个 <…> 声明 list 是一个泛型(或者参数化)类型。 通常情况下,使用一个字母来代表类型参数,例如 E, T, S, K, 和 V 等。

为什么使用泛型?参考链接

  • 指定具体类型,表明意图,便于阅读和检查。
  • 减少重复代码,在多种类型之间定义同一个实现。

List 和 map 字面量也是可以参数化的:

var names = <String>['Seth', 'Kathy', 'Lars'];
var pages = <String, String>{
  'index.html': 'Homepage',
  'robots.txt': 'Hints for web robots',
  'humans.txt': 'We are people, not machines'
};

在调用构造函数的时候, 在类名字后面使用尖括号(<...>)来指定 泛型类型。例如:

var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = new Set<String>.from(names);

Dart 的泛型类型是固化的,在运行时也可以判断具体的类型。例如在运行时(甚至是生成模式)也可以检测集合里面的对象类型:

var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

注意 is 表达式只是判断集合的类型,而不是集合里面具体对象的类型。Java 中的泛型信息是编译时的,泛型信息在运行时是不存在的。

当使用泛型类型的时候,你可能想限制泛型的具体类型,使用 extends 可以实现这个功能:

// T must be SomeBaseClass or one of its descendants.
class Foo<T extends SomeBaseClass> {...}

class Extender extends SomeBaseClass {...}

void main() {
  // It's OK to use SomeBaseClass or any of its subclasses inside <>.
  var someBaseClassFoo = new Foo<SomeBaseClass>();
  var extenderFoo = new Foo<Extender>();

  // It's also OK to use no <> at all.
  var foo = new Foo();

  // Specifying any non-SomeBaseClass type results in a warning and, in
  // checked mode, a runtime error.
  // var objectFoo = new Foo<Object>();
}

在函数和方法上使用泛型:

T first<T>(List<T> ts) {
  // ...Do some initial work or error checking, then...
  T tmp ?= ts[0];
  // ...Do some additional checking or processing...
  return tmp;
}

这里的 first<T>)泛型可以在如下地方使用参数 T

  • 函数的返回值类型 (T)
  • 参数的类型 (List<T>)
  • 局部变量的类型 (T tmp)

库和可见性

使用 importlibrary 指令可以帮助你创建模块化的可分享的代码。库不仅仅提供 API, 还是一个私有单元:以下划线 (_) 开头的标识符只有在库内部可见。每个 Dart app 都是一个库, 即使没有使用 library 命令也是一个库。库可以使用 pub(Dart 的包管理工具)管理。

使用库

使用 import 关键字来导入库:import 'dart:html';

import 必须参数为库 的 URI。对于内置的库,URI 使用特殊的 dart: scheme。对于其他的库,你可以使用文件系统路径或者 package: scheme。package: scheme 指定的库通过包管理器来提供,例如 pub 工具:

import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';

如果你导入的两个库具有冲突的标识符,则你可以使用库的前缀来区分。例如,如果 library1 和 library2 都有一个名字为 Element 的类, 你可以这样使用:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// ...
Element element1 = new Element();           // Uses Element from lib1.
lib2.Element element2 = new lib2.Element(); // Uses Element from lib2.

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

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

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

Deferred loading (也称之为 lazy loading) 可以让应用在需要的时候再载入库。要延迟加载一个库,需要先使用 deferred as 来导入:

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

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

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

在一个库上你可以多次调用 loadLibrary() 函数,但该库只载入一次。
使用延迟加载库的时候,请注意以下问题:

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

如何创建库?参考链接

异步支持

Dart 库中有很多返回 Future 或者 Stream 对象的方法。 这些方法是异步的:它们在建立一个耗时操作后就返回了,不会等到操作完成。

使用 asyncawait 关键字来进行异步编程,看起来就像同步代码一样。

处理 Futures

获取 Future 完成后的结果,有两种方式:

  • 使用 asyncawait
  • 使用 Future API

要使用 await,其方法必须带有 async 关键字:

checkVersion() async {
  var version = await lookUpVersion();
  if (version == expectedVersion) {
    // Do something.
  } else {
    // Do something else.
  }
}

可以使用 try, catch, 和 finally 来处理使用 await 的异常。
在一个异步方法中可以多次使用 await
await 表达式中,表达式的值通常是 Future,如果不是,那么值会被自动包装为 Future。

声明异步方法

一个 async 方法是函数体被标记为 async 的方法,给方法添加 async 关键字使其返回 Future。注意,方法的函数体并不需要使用 Future API。Dart 会自动在需要的时候创建 Future 对象:

Future<String> lookUpVersion() async => '1.0.0';

如果没有返回值,则使用 Future<void>

处理流

从流中获取值,有两种方法:

  • 使用 async异步 for 循环await for
  • 使用 Stream API

注意:在使用 await for 前,确定使代码更整洁,并且你真的需要等待流中的所有结果。

一个异步 for 循环的例子:

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

expression 的值必须是 Stream 类型,运行过程如下:

  1. 等待流触发值
  2. 执行循环体,变量为流触发的值
  3. 重复 1、2 直到流关闭

使用 break 或者 return 语句停止监听。

生成器

当你需要延迟产生一系列的值,可以使用生成器函数。Dart 内置两种生成器函数:

  • 同步生成器:返回一个 Iterable 对象。
  • 异步生成器:返回一个 Stream 对象。

实现同步生成器:

Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

实现异步生成器:

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

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

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

可调用的类

如果 Dart 类实现了 call() 函数则可以当做方法来调用:

class WannabeFunction {
  call(String a, String b, String c) => '$a $b $c!';
}

main() {
  var wf = new WannabeFunction();
  var out = wf("Hi","there,","gang");
  print('$out');
}

Isolates

所有的 Dart 代码在 isolates 中运行而不是线程。每个 isolate 都有自己的堆内存,确保每个 isolate 的状态都不能被其他 isolate 访问。

Typedefs

typeof 或者 function-type alias 可以给定义函数类型名。当把函数赋值为变量时,typedef 会保留函数的类型信息。如果不这样做,我们就只知道该变量是一个方法,而不知道其参数和返回值。

typedef int Compare(Object a, Object b);

class SortedCollection {
  Compare compare;

  SortedCollection(this.compare);
}

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

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

元数据

使用元数据给你的代码添加其他额外信息。元数据注解是以 @ 字符开头,后面是一个编译时常量(例如 deprecated)或者调用一个常量构造函数。@deprecated@override 在所有的 Dart 代码中都可以使用。

自定义元数据注解(@todo):

library todo;

class todo {
  final String who;
  final String what;

  const todo(this.who, this.what);
}

使用 @todo

import 'todo.dart';

@todo('seth', 'make this do something')
void doSomething() {
  print('do something');
}

元数据可以在 library、class、typedef、type parameter、constructor、factory、function、field、parameter或者 variable 声明之前使用,也可以在 import 或者 export 指令之前使用。使用反射可以在运行时获取元数据信息。

注释

单行注释:单行注释以 // 开始。 // 后面的一行内容 为 Dart 代码注释。
多行注释:多行注释以 /* 开始, */ 结尾。多行注释可以嵌套。
文档注释:文档注释可以使用 /// 开始, 也可以使用 /** 开始 并以 */ 结束:

/// A domesticated South American camelid (Lama glama).
///
/// Andean cultures have used llamas as meat and pack
/// animals since pre-Hispanic times.
class Llama {
  String name;

  /// Feeds your llama [Food].
  ///
  /// The typical llama eats one bale of hay per week.
  void feed(Food food) {
    // ...
  }

  /// Exercises your llama with an [activity] for
  /// [timeLimit] minutes.
  void exercise(Activity activity, int timeLimit) {
    // ...
  }
}

在文档注释内,Dart 编译器忽略除了中括号以外的内容。使用中括号可以引用 classes、methods、fields、top-level variables、 functions和 parameters。括号中的名称在已记录的程序元素的词法范围内解析。

希望对您能有帮助,打赏随意