dart语言基础

96
soojade
0.5 2016.12.06 18:36* 字数 4908

此文章是v1.0+时编写,年代久远,小心有毒,谨慎食用!!!

一些重要概念

  • 所有的东西都是对象,所有的对象都是类的实例。即使 数字、函数、null
    也都是对象。所有的对象都继承自 Object 类。
  • 指定静态类型表明你的意图,并使检查类型检查成为可能。在 Dart 1.x 指定类型是可选的,然而 Dart 正在朝着完全类型安全的语言发展。
  • 在 Dart 1.x 中,强类型 strong mode 是可选的,但在 Dart 2 中,默认就是 strong mode
  • Dart 在运行前解析所有的代码,可以使用些小技巧,例如:通过使用类型或编译时常量,来捕捉错误或使代码运行的更快。
  • Dart 支持顶级的函数,也支持类或对象的静态和实例方法。也可以在函数内部嵌套函数或本地函数。
  • 同样,Dart 支持顶级的变量,也支持类或对象的静态变量和实例变量(也被称作字段或属性)。
  • Dart没有 publicprotectedprivate 等关键字,如果一个标识符以 _开头则表示私有。
  • 标识符以小写字母或下划线_开头,后面跟着字符和数字的任意组合。
  • 明确区分表达式和语句。
  • Dart tools 会报告两种类型的问题:警告(warnings)和错误(errors)。警告仅标志着你的代码可能不会工作,但并不会阻止程序执行;错误可能是编译时错误,也可能是运行时错误。编译时错误会阻止程序执行;运行时错误会在程序执行时抛出异常。
  • Dart 1.x 有两种运行时模式:生产模式和检查模式。推荐在开发和 debug 时使用检查模式,部署到生产模式。生产模式时 Dart 程序默认的运行时模式。
  • Dart 2 废弃了 checked mode 默认 strong mode
  • Dart 2 不再使用 Dartium,改为 dartdevc(DDC)。
  • 没有初始化的变量都会被赋予默认值 null
  • final的值只能被设定一次。const 是一个编译时的常量,可以通过 const 来创建常量值,var c=const[];,这里 c 还是一个变量,只是被赋值了一个常量值,它还是可以赋其它值。实例变量可以是 final,但不能是 const

关键字(Keywords)

abstract continue false new this
as default final null throw
assert deferred finally operator true
async do for part try
async* dynamic get rethrow typedef
await else if return var
break enum implements set void
case export import static while
catch external in super with
class extends is switch yield
const factory library sync yield

内置类型(Built-in types)

  • numbers
  • strings
  • booleans
  • lists (also known as arrays)
  • maps
  • runes (for expressing Unicode characters in a string)
  • symbols

is is!操作符判断对象是否为指定类型,如num、String等。

as用来类型断言。

  • num
    • int 取值范围:-2^53 to 2^53
    • double
// String -> int
var one = int.parse('1');

// String -> double
var onePointOne = double.parse('1.1');

// int -> String
String oneAsString = 1.toString();

// double -> String 注意括号中要有小数点位数,否则报错
String piAsString = 3.14159.toStringAsFixed(2);
  • String

    • Dart 的String 是 UTF-16 编码的一个队列。
    • '...',"..."表示字符串。
    • '''...''',"""..."""表示多行字符串。
    • r'...',r"..."表示“raw”字符串。
  • bool

    • Dart 是强 bool 类型检查,只有 bool 类型的值是true 才被认为是 true。
  • List

// 使用List的构造函数,也可以添加int参数,表示List固定长度,不能进行添加 删除操作
var vegetables = new List();

// 或者简单的用List来赋值
var fruits = ['apples', 'oranges'];

// 添加元素
fruits.add('kiwis');

// 添加多个元素
fruits.addAll(['grapes', 'bananas']);

// 获取List的长度
assert(fruits.length == 5);

// 获取第一个元素
fruits.first;

// 获取元素最后一个元素
fruits.last;

// 利用索引获取元素
assert(fruits[0] == 'apples');

// 查找某个元素的索引号
assert(fruits.indexOf('apples') == 0);

// 删除指定位置的元素,返回删除的元素
fruits.removeAt(index);

// 删除指定元素,成功返回true,失败返回false
fruits.remove('apples');

// 删除最后一个元素,返回删除的元素
fruits.removeLast();

// 删除指定范围元素,含头不含尾,成功返回null
fruits.removeRange(start,end);

// 删除指定条件的元素,成功返回null
fruits.removeWhere((item) => item.length >6);

// 删除所有的元素
fruits.clear();
assert(fruits.length == 0);

// sort()对元素进行排序,传入一个函数作为参数,return <0表示由小到大, >0表示由大到小
fruits.sort((a, b) => a.compareTo(b));
  • Map
// Map的声明
var hawaiianBeaches = {
    'oahu' : ['waikiki', 'kailua', 'waimanalo'],
    'big island' : ['wailea bay', 'pololu beach'],
    'kauai' : ['hanalei', 'poipu']
};
var searchTerms = new Map();

// 指定键值对的参数类型
var nobleGases = new Map<int, String>();

// Map的赋值,中括号中是Key,这里可不是数组
nobleGase[54] = 'dart';

//Map中的键值对是唯一的
//同Set不同,第二次输入的Key如果存在,Value会覆盖之前的数据
nobleGases[54] = 'xenon';
assert(nobleGases[54] == 'xenon');

// 检索Map是否含有某Key
assert(nobleGases.containsKey(54));

//删除某个键值对
nobleGases.remove(54);
assert(!nobleGases.containsKey(54));
  • Runes
    Dart 中 runes 是UTF-32字符集的string 对象。
string = 'Dart';
string.codeUnitAt(0); // 68 返回指定位置index的16位UTF-16编码单元
string.codeUnits;     // [68, 97, 114, 116] 返回一个只读的List,包含字符串中所有的UTF-16编码单元。 
  • Symbol

symbol字面量是编译时常量,在标识符前面加#。如果是动态确定,则使用Symbol构造函数,通过new来实例化。

函数(Functions)

所有的函数都会有返回值。如果没有指定函数返回值,则默认的返回值是null
。没有返回值的函数,系统会在最后添加隐式的return 语句。

函数可以有两种类型的参数:

  • 必须的——必须的参数放在参数列表的前面。
  • 可选的——可选的参数跟在必须的参数后面。
可选的参数

可以通过名字位置指定可选参数,但是两个不能同时存在。

  • 命名可选参数使用{},默认值使用冒号:,调用时需指明参数名,与顺序无关。
  • 位置可选参数使用[],默认值使用等号=,调用时参数按顺序赋值。

操作符(Operators)

表中,操作符的优先级依次降低。

描述 操作符
一元后置操作符 expr++ expr-- () [] . ?.
一元前置操作符 -expr !expr ~expr ++expr --expr
乘除 * / % ~/
加减 + -
位移 << >>
按位与 &
按位异或 ^
按位或
关系和类型判断 >= > <= < as is is!
== !=
逻辑与 &&
逻辑或
若为null ??
条件表达式 expr1 ? expr2 : expr3
级联 ..
赋值 = *= /= ~/= %= += -= <<= >>= &= ^= = ??=

流程控制语句(Control flow statements)

  • if...else
  • for
  • while do-whild
  • break continue
  • switch...case
  • assert(仅在checked模式有效)

异常(Exceptions)

throw
  • 抛出固定类型的异常:
throw new FormatException('Expected at least 1 section');
  • 抛出任意类型的异常:
throw 'Out of llamas!';
  • 因为抛出异常属于表达式,可以将throw语句放在=>语句中,或者其它可以出现表达式的地方:
distanceTo(Point other) =>
    throw new UnimplementedError();
catch

将可能出现异常的代码放置到try语句中,可以通过 on语句来指定需要捕获的异常类型,使用catch来处理异常。

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

可以向catch()传递1个或2个参数。第一个参数表示:捕获的异常的具体信息,第二个参数表示:异常的堆栈跟踪(stack trace)。

rethrow

rethrow语句用来处理一个异常,同时希望这个异常能够被其它调用的部分使用。

final foo = '';

void misbehave() {
  try {
    foo = "1";
  } catch (e) {
    print('2');
    rethrow;// 如果不重新抛出异常,main函数中的catch语句执行不到
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('3');
  }
}
finally

Dart 的finally用来执行那些无论异常是否发生都执行的操作。

final foo = '';

void misbehave() {
  try {
    foo = "1";
  } catch (e) {
    print('2');
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('3');
  } finally {
    print('4'); // 即使没有rethrow最终都会执行到
  }
}

类(Classes)

Dart 是一种面向对象的语言,并且支持基于mixin的继承方式:一个类可以继承自多个父类

使用new语句来构造一个类。构造函数的名字可能是ClassName,也可以是ClassName.identifier。例如:

var jsonData = JSON.decode('{"x":1, "y":2}');

// Create a Point using Point().
var p1 = new Point(2, 2);

// Create a Point using Point.fromJson().
var p2 = new Point.fromJson(jsonData);
  • 使用.来调用实例的变量或者方法。
  • 使用 ?. 来避免左边操作数为null引发异常。
  • 使用const替代new来创建编译时的常量构造函数。
  • 两个使用const构建的同一个构造函数,实例相等。
  • 获取对象的运行时类型使用:o.runtimeType
实例化变量

在类定义中,所有没有初始化的变量都会被初始化为null

所有实例变量会生成一个隐式的getter方法,不是finalconst的实例变量也会生成一个隐式的setter方法。

构造函数
class Point {
  num x;
  num y;

  Point(num x, num y) {
    // 这不是最好的方式.
    this.x = x; // this关键字指向当前类的实例
    this.y = y;
  }
}
class Point {
  num x;
  num y;

  // 推荐方式
  Point(this.x, this.y);
}
默认构造函数

没有声明构造函数,dart会提供默认构造函数。默认构造函数没有参数,并且调用父类的无参数构造函数。

构造函数不能被继承

子类不会继承父类的构造函数。如果不显式提供子类的构造函数,系统就提供默认的构造函数。

命名构造函数
class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // 命名构造函数
  Point.fromJson(Map json) {
    x = json['x'];
    y = json['y'];
  }
}

使用命名构造函数可以实现一个类多个构造函数。构造函数不能被继承,父类中的命名构造函数不能被子类继承。如果想要子类也拥有一个父类一样名字的构造函数,必须在子类实现这个构造函数。

调用父类的非默认构造函数

默认情况下,子类调用父类的无参数的非命名构造函数。父类的构造函数会在子类的构造函数体之前(大括号前)调用。如果也使用了初始化列表 ,则会先执行初始化列表 中的内容,即下面的顺序:

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

如果父类不显式提供无参的非命名构造函数,在子类中必须手动调用父类的一个构造函数。在子类构造函数名后,大括号{前,使用super.调用父类的构造函数,中间使用:分割。

父类构造函数参数不能访问this,例如,参数可以调用静态方法但不能调用实例方法。

class Person {
  String firstName;

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

class Employee extends Person {
  // 父类没有无参数的非命名构造函数,必须手动调用一个构造函数 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()); // 一个函数调用作为参数
}

当在构造函数初始化列表中使用super()时,要把它放在最后。

View(Style style, List children)
    : _children = children,
      super(style) {}
初始化列表

除了调用父类的构造函数,也可以通过初始化列表 在子类的构造函数体前(大括号前)来初始化实例的变量值,使用逗号,分隔。如下所示:

一个初始化器的右边不能访问this

class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // 在构造函数体前 初始化列表 设置实例变量
  Point.fromJson(Map jsonMap)
      : x = jsonMap['x'],
        y = jsonMap['y'] {
    print('In Point.fromJson(): ($x, $y)');
  }
}
重定向构造函数

有时候构造函数的目的只是重定向到该类的另一个构造函数。重定向构造函数没有函数体,使用冒号:分隔。

class Point {
  num x;
  num y;

  // 主构造函数
  Point(this.x, this.y) {
    print("Point($x, $y)");
  }

  // 重定向构造函数,指向主构造函数,函数体为空
  Point.alongXAxis(num x) : this(x, 0);
}

void main() {
  var p1 = new Point(1, 2);
  var p2 = new Point.alongXAxis(4);
}
常量构造函数

如果类的对象不会发生变化,可以构造一个编译时的常量构造函数。定义格式如下:

  • 定义所有的实例变量是final
  • 使用const声明构造函数。
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;
  static final Map<String, Logger> _cache = <String, Logger>{}; // 缓存保存对象
  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = new Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }
  Logger._internal(this.name);// 命名构造函数
  void log(String msg) {
    if (!mute) {
      print(msg);
    }
  }
}

main() {
  var p1 = new Logger("1");
  p1.log("2");

  var p2 = new Logger('22');
  p2.log('3');
  var p3 = new Logger('1');// 相同对象直接访问缓存
}

方法
实例方法

实例方法可以访问实例变量和this

Getters and setters

get()set()方法是Dart 语言提供的专门用来读取和写入对象的属性的方法。每一个类的实例变量都有一个隐式的getter和可能的setter(如果字段为finalconst,只有getter)。

++之类的操作符,无论是否明确定义了getter,都按预期的方式工作。为了避免意想不到的副作用,操作符调用getter一次,将其值保存在临时变量中。

class Rectangle {
  num left;
  num top;
  num width;
  num height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  num get right             => left + width;
      set right(num value)  => left = value - width;
  num get bottom            => top + height;
      set bottom(num value) => top = value - height;
}

main() {
  var rect = new Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}
抽象方法

抽象方法,只有函数声明,没有函数体,以分号;结尾,作为接口在其他类中实现。调用抽象方法会导致运行时错误。如果在不是抽象类中定义抽象方法会引发warning,但不会阻止实例化。

abstract class Doer {
  ...

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

class EffectiveDoer extends Doer {
  void doSomething() {
    // ...Provide an implementation, so the method is not abstract here...
  }
}
可重写的运算符

下面是可重写的运算符,在运算符前面使用operator关键字。

如果重写==,需要重写ObjecthashCodegetter。

< + { []
> / ^ []=
<= ~/ & ~
>= * << ==
% >>
抽象类

使用abstract关键字定义一个抽象类,抽象类不能实例化。抽象类通常用来定义接口。

假如需要将抽象类实例化,需要定义一个factory constructor

抽象类通常会包含一些抽象的方法。

abstract class AbstractContainer { // 抽象类
  // ....

  void updateChildren(); // 抽象方法
}
隐式接口

每一个类都隐式的定义一个接口,这个接口包含了这个类的所有实例成员和它实现的所有接口。

一个类可以实现一个或多个(用,隔开)接口,通过implements关键字。

class Person {
  final _name;
  Person(this._name);
  String greet(who) => 'hello,$who,i am $_name';
}

class Imposter implements Person {
  final _name = '';
  String greet(who) => 'hi $who.do you know who i am.';
}

greetBob(Person p) => p.greet('bob');
main(List<String> args) {
  print(greetBob(new Person('lili')));
  print(greetBob(new Imposter()));
}

继承

使用extends来创造子类,使用super来指向父类。

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

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

子类可以重载实例方法,getter和setter。使用@override注解重载方法。

class A {
  @override
  void noSuchMethod(Invocation mirror) {
    print('You tried to use a non-existent member:' +
          '${mirror.memberName}');
  }
}

如果使用noSuchMethod()来实现每一个可能的getter,setter和一个或多个类型的方法,可以使用@proxy注解来避免警告:

@proxy
class A {
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}

如果知道编译时类型,可以不使用@proxy,只需要使类实现这些类型。

class A implements SomeClass, SomeOtherClass {
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}
枚举类型

枚举类型是一种特殊的类,通常用来表示一组固定数字的常量值。

使用enum关键字声明枚举类型。

enum Color {
  red,
  green,
  blue
}

每个枚举类型都有一个index的getter,返回以0开始的位置索引,每次加1。

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

使用values关键字获取枚举的所有值,返回一个列表。

print(Color.values); // [Color.red, Color.green, Color.blue]

在switch语句中使用枚举,必须在case语句中判断所有的枚举,否则会获得警告。

enum Color {
  red,
  green,
  blue
}
// ...
Color aColor = Color.blue;
switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: //必须完成所有判断,否则会引发warning
    print(aColor);  // 'Color.blue'
}

枚举类型有以下限制:

  • 不能继承,mixin,或实现一个枚举。
  • 不能显式的实例化一个枚举。
minxins

mixins是一种在多个类层次结构中,重用一个类的代码的方法。使用with关键字后跟多个mixin名。

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

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

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

  • 静态变量

静态变量在使用时初始化。

class Color {
  static const red =
      const Color('red'); // A constant static variable.
  final String name;      // An instance variable.
  const Color(this.name); // A constant constructor.
}

main() {
  assert(Color.red.name == 'red');
}
  • 静态方法

静态方法不能被实例调用,不能访问this,只能被类名调用。

import 'dart:math';

class Point {
  num x;
  num 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);
  }
}

main() {
  var a = new Point(2, 2);
  var b = new Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  print(distance); // 2.8284271247461903
}

对于通用或广泛使用的工具和功能,应该使用顶级函数,而不是静态方法。

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

泛型(Generics)

使用<...> 的方式来定义泛型。

为什么使用泛型
  • 虽然Dart 语言中类型是可选的,但是明确的指明使用的是泛型,会让代码更好理解。
 var names = new List<String>();
 names.addAll(['Seth', 'Kathy', 'Lars']);
 // ...
 names.add(42); // Fails in checked mode (succeeds in production mode).
  • 使用泛型减少代码重复。

代码片段一:

 abstract class ObjectCache {
   Object getByKey(String key);
   setByKey(String key, Object value);
 }

代码片段二:

 abstract class StringCache {
   String getByKey(String key);
   setByKey(String key, String value);
 }

以上的两段代码可以使用泛型简化如下:

 abstract class Cache<T> {
   T getByKey(String key);
   setByKey(String key, T value);
 }
用于集合类型(Using collection literals)

泛型用于ListMap 类型参数化。

  • List: <type>
  • Map: <keyType, valueType>
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);
var views = new Map<int, View>();
泛型集合及它们所包含的类型

dart的泛型类型是具体的,在运行时包含它们的类型信息。

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

然而,is表达式检查的是集合的类型,而不是里面的对象。在生产模式下,一个List<Stirng>可能有很多不是string的条目在里面。解决办法是检查每一项,或者包裹在一个异常处理程序中。

限制参数化类型

当实现一个泛型时,如果需要限制它参数的类型,可以使用extends关键字。

// T 必须是SomeBaseClass或它的后代
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>();
}

库和可见性

使用importlibrary 指令可以方便的创建一个模块或分享代码。一个Dart 库不仅能够提供相应的API,还可以包含一些以_开头的私有变量仅在库内部可见。

每一个Dart 应用都是一个库,即使它没有使用library指令。库可以方便是使用各种类型的包。

使用库(Libraries and visibility)

使用import指定怎样的命名空间,从一个库引用另一个库。

import唯一要求的参数是指定库的URI。

  • dart内置库,URI组合dart:
  • 其他库,使用文件路径或package:组合。package:组合式通过包管理工具提供的。
import 'dart:html';
import 'package:mylib/mylib.dart';
指定一个库的前缀

如果导入的库拥有相互冲突的名字,使用as为其中一个或几个指定不一样的前缀。

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.
导入库的一部分

如果只需要使用库的一部分内容,使用showhide有选择的导入。

// 仅导入foo.
import 'package:lib1/lib1.dart' show foo;

// 除了foo都导入
import 'package:lib2/lib2.dart' hide foo;
延迟加载库

延迟加载,也叫懒加载,允许应用程序按需加载库。使用延迟加载的场景:

  • 减少程序初始化启动时间。
  • 执行A/B测试——尝试替换一个算法的实现。
  • 加载很少用的功能,比如可选的屏幕和对话框。

要延迟加载一个库,首先必须使用deferred as导入它。

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

当需要使用库的时候,使用库名调用loadLibrary()

greet() async {
  // 使用await关键字暂停执行,直到库加载
  await hello.loadLibrary();
  hello.printGreeting();
}

可以在代码中多次调用loadLibrary()方法。但是实际上它只会被执行一次。

使用延迟加载的注意事项:

  • 延迟加载的内容只有在加载后才存在。
  • Dart 隐式的将deferred as改为了deferred as namespaceloadLibrary()返回值是Future

异步支持(Asynchrony support)

使用async函数和await表达式实现异步操作。

当需要使用一个从Future返回的值时,有两个选择:

  • 使用asyncawait
  • 使用Future API

当需要从一个Stream获取值时,有两个选择:

  • 使用async和异步的循环(await for)。
  • 使用Stream API

代码使用了asyncawait就是异步的,虽然看起来像同步代码。

await lookUpVersion()

要使用await,代码必须在函数后面标记为async

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

可以使用try,catch和finally配合await处理错误。

try {
  server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044);
} catch (e) {
  // 无法绑定到端口时......
}
声明异步函数

异步函数是一个被async标记的函数。

虽然异步的函数中可能执行耗时的操作,但是函数本身在调用后将会立即返回,即使函数体一条语句也没执行。

checkVersion() async {
  // ...
}

lookUpVersion() async => /* ... */;

给函数添加async关键字将使函数返回一个Future类型。

// 修改前是同步的
String lookUpVersionSync() => '1.0.0';

// 修改后 是异步的 函数体不需要使用Future API
// dart会在必要的时候创建Future对象
Future<String> lookUpVersion() async => '1.0.0';
在 Future 中使用 await 表达式
await expression

可以在异步函数中多次使用await

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

await表达式中,表达式通常是一个Future。如果表达式不是Future 类型,它将自动被包装为Future类型。await expression的返回值是一个对象。await表达式使执行暂停,直到对象可用。

如果await不工作,确保await处于async函数中。即使是在main函数中,也要标记为async

main() async {
  checkVersion();
                          // 这里使用了 await
  print('In main: version is ${await lookUpVersion()}');
}
在Stream中使用异步循环
 // expression的值必须是Stram类型
await for (variable declaration in expression) {
  // Executes each time the stream emits a value.
}

异步循环的执行流程如下:

  1. 等待 stream 发出数据。
  2. 执行循环体,并将变量的值设置为发出的数据。
  3. 重复1.,2.直到stream 对象被关闭。

结束监听stram 可以使用breakreturen 语句,跳出for循环,取消订阅stream。

如果异步循环不工作,确保是在一个async函数中,如果使用异步循环在main()函数中,也要确保main()函数被标记为async

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

可调用类(Callable classes)

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'); // Hi there, gang!
  print(wf.runtimeType); // WannabeFunction
  print(out.runtimeType); // String
  print(wf is Function); // true
}

隔离(Isolates)

Dart 代码都运行在独立的隔离空间中,每个隔离空间都有自己的内存堆栈,确保每个隔离空间的状态不会被其它的隔离空间访问。

类型定义(Typedefs)

typedef关键字,用来声明一种类型,当一个函数类型分配给一个变量时,保留类型信息。

// 声明一种 Compare类型
typedef int Compare(int a, int b);

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

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

当前 typedef 仅用于函数类型

元数据(Metadata)

元数据是以@开始的修饰符。在@ 后面接着编译时的常量或调用一个常量构造函数。

所有dart代码中可用的三个注解:

  • @deprecated 被弃用的
  • @override 重载
  • @proxy 代理
定义自己的元数据

通过library来定义一个库,在库中定义一个相同名字的class,然后在类中定义const 构造方法。

// 定义
library todo;

class todo {
  final String who;
  final String what;

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

// 使用
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 declaration(变量声明)。

可以使用reflection反射,在运行时检查元数据。

注释(Comments)

  • 单行注释:/
  • 多行注释:/*...*/
  • 文档注释:/**...*//// 使用[]引用参考生成API文档链接
class Llama {
  String name;// 这是单行注释
  void feed(Food food) {
    /*
    这是多行注释
    /
  }

/**
这里是文档注释
*/
  /// Exercises your llama with an [activity] for
  /// [timeLimit] minutes.
  void exercise(Activity activity, int timeLimit) {
    // ...
  }
}
Dart