dart 语法基础

白子昂
2023-12-01

 

一些重要概念

  • 所有的东西都是对象,所有的对象都是类的实例。即使 数字、函数、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)

abstractcontinuefalsenewthis
asdefaultfinalnullthrow
assertdeferredfinallyoperatortrue
asyncdoforparttry
async*dynamicgetrethrowtypedef
awaitelseifreturnvar
breakenumimplementssetvoid
caseexportimportstaticwhile
catchexternalinsuperwith
classextendsisswitchyield
constfactorylibrarysyncyield

内置类型(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)

泛型用于List 和 Map 类型参数化。

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

库和可见性

使用import 和 library 指令可以方便的创建一个模块或分享代码。一个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 可以使用break 或returen 语句,跳出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代码中可用的三个注解:

定义自己的元数据

通过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) {
    // ...
  }
}

 

 类似资料: