阅读 106

30分钟掌握Dart语言

语言概览

Dart 是谷歌开发的计算机编程语言,被应用于 Web、服务器、移动应用和物联网等领域的开发。Dart 是面向对象、类定义的、单继承的语言。它的语法支持接口(interfaces)、混入(mixins)、抽象类(abstract classes)、具体化泛型(reified generics)、可选类型(optional typing)。

重要概念

  • 在 Dart 中,一切皆对象。所有变量引用的都是 对象,每个对象都是一个 的实例。数字、函数以及 null 都是对象。除去 null 以外(如果你开启了 空安全), 所有的类都继承于 [Object][] 类。
  • 尽管 Dart 是强类型语言,但是在声明变量时指定类型是可选的,因为 Dart 可以进行类型推断。
  • Dart 支持泛型,比如 List<int>(表示一组由 int 对象组成的列表)或 List<Object>(表示一组由任何类型对象组成的列表)。
  • Dart 支持顶级函数(例如 main 方法),同时还支持定义属于类或对象的函数(即 静态实例方法)。你还可以在函数中定义函数(嵌套局部函数)。
  • Dart 支持顶级 变量,以及定义属于类或对象的变量(静态和实例变量)。实例变量有时称之为域或属性。
  • Dart 与Java不同的是 没有 类似于 Java 那样的 publicprotectedprivate 成员 访问限定符。如果一个标识符以下划线 (_) 开头则表示该标识符在 库内是私有的。可以查阅 库和可见性 获取更多相关信息。
  • 标识符 可以以字母或者下划线 (_) 开头,其后可跟字符和数字的组合。
  • Dart 中 表达式语句 是有区别的,表达式有值而语句没有。比如条件表达式 expression condition ? expr1 : expr2 中含有值 expr1expr2。与 if-else 分支语句相比,if-else 分支语句则没有值。一个语句通常包含一个或多个表达式,但是一个表达式不能只包含一个语句。

空安全

Dart 2.x之后版本已支持健全的空安全(null safety)机制,空安全已在 Dart 2.12 和 Flutter 2 中可用。

  • 如果你开启了 空安全,变量在未声明为可空类型时不能为 null。你可以通过在类型后加上问号 (?) 将类型声明为可空。例如,int? 类型的变量可以是整形数字或 null。如果你 明确知道 一个表达式不会为空,但 Dart 不这么认为时,你可以在表达式后添加 ! 来断言表达式不为空(为空时将抛出异常)。例如:int x = nullableButNotNullInt!
  • 如果你想要显式地声明允许任意类型,使用 Object?(如果你 开启了空安全)、 Object 或者 特殊类型 dynamic 将检查延迟到运行时进行。

变量

变量的定义

下面的示例代码将创建一个变量并将其初始化:

  var name = 'Eren'; // 声明变量时可不指定类型,Dart 会自动类型推断
  var lineCount = 0;
复制代码

其中 name 变量的类型被推断为 String , 变量 lineCount 的类型被被推断为 int, 但是你可以为其指定类型。

  String name = 'Eren';
  int lineCount = 0;
复制代码

Tips:

可以通过 varName.runtimeType.toString() 打印变量的类型,如:

print(name.runtimeType.toString());
// String
复制代码

变量的默认值

未初始化以及可空类型的变量拥有一个默认的初始值 null,即便 int 类型也是如此,因为在 Dart 中一切皆为对象,数字也不例外。这里要注意引入空安全机制时变量的声明。

常量Final 和 Const

如果你不想更改一个变量,可以使用关键字 final 或者 const 来修饰变量,也可以加在一个具体的类型前,但不能是 var 关键字。一个 final 变量只可以被赋值一次;一个 const 变量是一个编译时常量(const 变量同时也是 final 的)。final 变量是惰性初始化,在其第一次使用的时候才被初始化。可以这么理解:final修饰的变量是不可改变的,而const修饰的表示一个常量。

注意:实例变量可以是 final 的但不可以是 const 的

下面的示例中我们创建并设置两个 final 变量:

  final name = 'Eren'; // 不指定具体类型
  final String nickname = 'Eren Jaeger';

  final var name = "Eren"; // Error: Members can't be declared to be both 'final' and 'var'.
复制代码

使用关键字 const 修饰变量表示该变量为 编译时常量 ,在声明 const 变量时可以直接为其赋值,也可以使用其它的 const 变量为其赋值

  const lineCount = 100; 
  const double atm = 1.01325 * lineCount; 
复制代码

内置类型

Dart 语言支持以下数据类型:

Numbers

Dart 支持两种 Number 类型:

int

  var x = 0;// 变量 x 的类型被推断为 int 类型
  int y = 1;
复制代码

double

  var x = 1.0;
  double y = 2;// 整型字面量将会在必要的时候自动转换成浮点数字面量
  // 2.0
复制代码

intdouble 都是 num 的子类。

Tips:

字符串和数字之间的转换

  // String -> int
  var intNum = int.parse("1");

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

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

  // double -> String
  String doubleStr = 3.14159.toString();
复制代码

String

在 Dart 中可以使用 单引号 或者 双引号 来创建字符串:

  var s1 = 'Single quotes work well for string literals.';
  var s2 = "Double quotes work just as well.";
复制代码

在字符串中,请以 ${表达式} 的形式使用表达式,如果表达式是一个标识符,可以省略掉 {}。如果表达式的结果为一个对象,则 Dart 会调用该对象的 toString 方法来获取一个字符串

  String s1 = 'Hello';
  String s2 = '${s1.toUpperCase()}, Dart!';
  // HELLO, Dart!

  int s3 = 2021;
  String s4 = "你好,$s3!";// 可以省略掉 {}
  // 你好,2021!
复制代码

使用 + 运算符拼接字符串

  var s = 'The + "operator" ' + 'works, as well.';
  // The 'operator' works, as well.
复制代码

使用三个单引号或者三个双引号也能创建多行字符串:

在字符串前加上 r 作为前缀创建 “raw” 字符串(即不会被做任何处理(比如转义)的字符串):

  var s = r'不会被做任何处理(比如转义)\n的字符串 会直接输出 “\n” 而不是转义为换行。';
  // 不会被做任何处理(比如转义)\n的字符串 会直接输出 “\n” 而不是转义为换行。
复制代码

布尔类型

Dart 使用 bool 关键字表示布尔类型,布尔类型只有两个对象 truefalse,两者都是编译时常量。

  var real = true;
  // true
  bool untrue = false;
  // false
复制代码

Lists

在 Dart 中数组由 List 对象表示。通常称之为 List

  var list = [1, 2, 3, 4]; // 不指定类型,自动推断为 List<int> 类型;
  // [1, 2, 3, 4]

  // 等同
  var list = <int>[1, 2, 3, 4];
  // 或
  List<int> list = [1, 2, 3, 4];
复制代码

你也可以在最后一个元素后添加逗号,这个逗号并不会影响集合,但它能有效避免「复制粘贴」的错误。

  var list = [1, 2, 3, 4,];
  // [1, 2, 3, 4]
复制代码

不指定类型时,列表中可以放入任意类型元素,此时list会被推断为 List<Object>

  var list = [1, 2, 3, 4, "5", true];
  // [1, 2, 3, 4, 5, true]
复制代码

添加 const 关键字会创建一个编译时常量

  var list = const [1, 2, 3, 4];
  // [1, 2, 3, 4]
复制代码

Sets

在 Dart 中,set 是一组 无重复 元素的无序集合。在 Dart 2.2 中才具备 set 核心库。

创建一个 Set 集合的方法

  var fruits = {'tomato', 'orange', 'tomato', 'apple'};
  // {tomato, orange, apple}
复制代码

可以使用在 {} 前加上类型参数的方式创建一个空的 Set,或者将 {} 赋值给一个 Set 类型的变量,或者new Set() 空的对象。

  var set1 = <String>{};
  // {}
  Set<String> set2 = {}; // This works, too.
  Set set3 = new Set(); // This works, too.
复制代码

Maps

Map 是用来关联 keys 和 values 的对象。其中键和值都可以是任何类型的对象。每个 只能出现一次但是 可以重复出现多次

创建 Map 的例子

  var rectangle = {
    'width': 200, 
    'height': 300
  };
  // {width: 200, height: 300}
复制代码

使用 Map 的构造器创建 Map

  var rectangle = Map<String, int>();
  rectangle['width'] = 200; // 向 Map 中添加键值对操作
  rectangle['height'] = 300;
  // {width: 200, height: 300}
复制代码

从一个 Map 中获取一个值的操作

  var width = rectangle['width']; // 如果检索的 Key 不存在于 Map 中则会返回一个 null
  // 200
复制代码

函数

Dart 是一种 正面向对象的语言,所以 函数也是对象 并且类型为 Function,函数可以被赋值给变量或者作为其它函数的参数。

下面是定义一个函数

  int operator(x, y){
    return x + y;
  }
复制代码

虽然 Dart 建议给 函数添加返回类型,不过即便不定义,该函数也依然有效

  // 不声明返回类型
  operator(x, y){
    return x + y;
  }
复制代码

如果函数体内只包含一个表达式,可以使用简写语法

  int operator(x, y) => x + y;
复制代码

语法 => 表达式{ return 表达式; } 的简写, => 有时也称之为 箭头 函数。

参数

函数可以有两种形式的参数:必要参数可选参数。必要参数定义在参数列表前面,可选参数则定义在必要参数后面。可选参数可以是 命名的位置的

命名参数

命名参数默认为可选参数,除非他们被特别标记为 required 时需必传。

定义函数时,使用 {参数1, 参数2, …} 来指定命名参数:

  // ? 标识表明这个参数可传入空值null,空安全机制引入后 ? 必须添加或者标记为 required
  void enableFlags({bool? bold, bool? hidden}) {...} 
复制代码

当你调用函数时,可以使用 参数名: 参数值 的形式来指定命名参数。例如

  enableFlags(bold: true, hidden: false);
复制代码

如果参数可选,但又不能为空,可以 指定默认参数

  void enableFlags({bool? bold, bool hidden = false}) {...} 
复制代码

虽然命名参数是可选参数的一种类型,但是你仍然可以使用 required 来标识一个命名参数是必须的参数

  const Scrollbar({Key? key,  required Widget child})
复制代码

如果调用者想要通过 Scrollbar 的构造函数构造一个 Scrollbar 对象而不提供 child 参数,则会导致编译错误。

可选的位置参数

使用 [] 包裹起来一系列参数是函数的可选位置参数,该参数可以不传,可选的位置参数只能放在函数的参数列表的最后面。例如

  String say(String from, String msg, [String? local]) {
    var result = '$from says $msg';
    if (local != null) {
      result = '$result in $local.';
    }
    return result;
  }
复制代码

默认参数值

可以用 = 为函数的 命名参数可选位置参数 定义默认值,默认值必须为编译时常量。

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

  // 命名参数设置默认值
  void enableFlags({bool? bold, bool hidden = false}) {...} 

  // 可选位置参数设置默认值
  String say(String from, String msg, [String local = 'Wall-Maria']) {
    var result = '$from says $msg';
    if (local != null) {
      result = '$result in $local.';
    }
    return result;
  }
复制代码

main() 函数

每个 Dart 或 Flutter 程序都必须有一个 main() 顶级函数作为程序的入口, main() 函数返回值为 void 并且有一个 List<String> 类型的可选参数。

下面是一个简单 main() 函数:

void main() {
  print('Hello, Dart!');
}
复制代码

函数是一级对象

你可以将函数作为参数传递给另一个函数。例如:

  // 声明一个函数用于打印列表元素
  void sysprint(int element){
    print(element);
  }
  
  var list = [1, 2, 3];
  // 把定义的函数作为参数传递给另一个函数
  list.forEach(sysprint);
  // 1
	 2
	 3
复制代码

你也可以将函数赋值给一个变量,比如:

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

  // 把函数赋值给一个变量
  var pri = sysprint;
  // 调用函数
  pri(100);
  // 100
复制代码

匿名函数

大多数方法都是有名字的,比如 main()sysprint()。你可以创建一个没有名字的方法,称之为 匿名函数。 匿名函数和Lambda 表达式很相似。

匿名方法看起来与命名方法类似,在括号之间可以定义参数,参数之间用逗号分割。

后面大括号中的内容则为函数体:

  ([[类型] 参数[, …]]) {
    函数体;
  };
复制代码

下面代码定义了只有一个参数 defstr 且没有参数类型的匿名方法。

  void sysprint(Function fun1){
     fun1("Hello, Dart!");

  }

  sysprint((defstr){
      print(defstr);
  });
  // Hello, Dart!
复制代码

把上面代码拆分后如下

  // 匿名函数
  var nonfunc = (defstr) {
    print(defstr);
  };

  void sysprint(Function fun1) {
    fun1("Hello, Dart!");
  }
  // 调用
  sysprint(nonfunc);
复制代码

如果函数体内只有一行返回语句,你可以使用胖箭头缩写法。

  sysprint((defstr) => print(defstr));
复制代码

箭头函数

如果函数体内只包含一个表达式,可以使用简写语法

  int operator(x, y) => x + y;
复制代码

语法 => 表达式{ return 表达式; } 的简写, => 有时也称之为 箭头 函数。

函数返回值

所有的函数都有返回值。没有显示返回语句的函数,最后一行默认为执行 return null;

闭包

闭包 是一个函数对象,即使函数对象的调用在它原始作用域之外,依然能够访问在它词法作用域内的变量。

  • 闭包是一个函数(对象)
  • 闭包定义在其他函数内部
  • 闭包能够访问外部函数内的局部变量,并持有其状态,使其常驻内存,又不污染全局(这是闭包最大的作用,可以通过闭包的方式,将其暴露出去,提供给外部访问)

下面代码定义了一个函数嵌套函数的词法闭包。

  // 声明一个无参函数,函数返回类型可省略
  Function sysprint() {
    int count = 100;
    return (int args) => print(count + args);
  }

  // 获取闭包,并持有其外部函数内的局部变量状态
  var fun1 = sysprint();

  // 调用闭包函数
  fun1(1);
  // 101
  fun1(2);
  // 102
复制代码

掌握了闭包词法后,我们再看下官方给出的例子:

  // 声明一个有参的函数,并返回一个函数对象
  Function makeAdder(int addBy) {
    return (int i) => addBy + i;
  }

  void main() {
    // 获取闭包,并持有参数 2 的状态。即参数 2 在函数makeAdder中常驻,可通过闭包对象 add2 访问参数2
    var add2 = makeAdder(2);

    // 获取闭包,并持有参数 4 的状态
    var add4 = makeAdder(4);

    // add2(3) 即调用函数 (int i) => addBy + i; addBy为2
    assert(add2(3) == 5);
    assert(add4(3) == 7);
  }
复制代码

自执行函数

自执行函数,即定义和调用合为一体。我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,关键是这种机制不会污染全局对象。

  (() {
    print("Hello, Dart!");
  })();
  // Hello, Dart!

  // 单参数的自执行函数
  ((args) {
    print("$args, Dart!");
  })('Hello');
  // Hello, Dart!
复制代码

运算符

Dart 中的运算符与 Java 中的很多类似,比如 算术运算符 ( 加( + )减( - )乘( * )除( / )取模( % )...)、关系运算符(不等( != )大于( > )小于( < )...)、逻辑运算符(取反( ! )逻辑或( || )逻辑与( && )...)等。下面列出一些常用且不同于 Java 的运算符操作:

类型判断运算符

asisis! 运算符是在运行时判断对象类型的运算符。

OperatorMeaning
as类型转换(也用作指定 类前缀))
is如果对象是指定类型则返回 true
is!如果对象是指定类型则返回 false

如果不确定这个对象类型是不是 T,可以在转型前使用 is T 检查类型。

  var lineCount = 100;
  print(lineCount is int);
  // true
复制代码

取整运算符

Dart 中 ~/ 用作取整运算,而 / 则为除运算。

  print(1/2);
  // 0.5
  print(1~/2);
  // 0
复制代码

?? 赋值运算符

可以使用 ??= 来为值为 null 的变量赋值。

  String? lang;
  lang ??= 'Dart'; // 当lang为 null时才为其赋值Dart字串
  // Dart
复制代码

条件表达式

Dart 有两个特殊的运算符可以用来替代 if-else 语句:

条件 ? 表达式 1 : 表达式 2 如果条件为 true,执行表达式 1并返回执行结果,否则执行表达式 2 并返回执行结果。

表达式 1 ?? 表达式 2 如果表达式 1 为非 null 则返回其值,否则执行表达式 2 并返回其值。

  void sysprint(String? lang) => print(lang ?? 'Dart');
  
  sysprint(null);
  // Dart
  sysprint('Java');
  // Java
复制代码

级联运算符

级联运算符 (.., ?..) 可以让你在同一个对象上连续调用多个对象的变量或方法。类似于Java中的建造者模式。

比如下面的代码:

  var paint = Paint()
    ..color = Colors.black
    ..strokeCap = StrokeCap.round
    ..strokeWidth = 5.0;
复制代码

其他运算符

?. 条件访问成员,与上述成员访问符类似,但是左边的操作对象不能为 null,例如 foo?.bar,如果 foo 为 null 则返回 null ,否则返回 bar。

流程控制语句

你可以使用下面的语句来控制 Dart 代码的执行流程:

  • ifelse
  • for 循环
  • whiledo-while 循环
  • breakcontinue
  • switchcase
  • assert

Dart 中控制流语法和 Java 中基本类似,具体可参照 控制流语句 使用。

在 Dart 中,一切皆对象。所有对象都是一个类的实例,而除了 Null 以外的所有的类都继承自 Object 类。Dart 与Java不同的是 没有 类似于 Java 那样的 publicprotectedprivate 成员 访问限定符。如果一个标识符以下划线 (_) 开头则表示该标识符在 库内是私有的

一个简单的类定义

  class Point {
    double? x;
    double? y;
	// 默认构造函数
    Point(double x, double y) {
      this.x = x;
      this.y = y;
    }
  }
复制代码

一个类通常由属性和方法组成,定义和声明语法与Java类似。使用 this 关键字引用当前实例。

实例变量

下面是声明实例变量的示例:

  class Point {
    double? x;
    double? y;
  }
复制代码

所有未初始化的实例变量其值均为 null。所有实例变量均会隐式地声明一个 Getter 方法。

构造函数

可以使用 构造函数 来创建一个对象。构造函数的命名方式可以为 类名类名 . 标识符 的两种形式。下述代码分别使用 Point 为例:

默认构造函数简写

  class Point {
    double? x;
    double? y;
  	// 默认构造函数简写
    Point(this.x, this.y);
  }
复制代码

命名构造函数

可以为一个类声明多个命名式构造函数( 类名 . 标识符 )来表达更明确的意图

  class Point {
    double? x;
    double? y;

    Point(this.x, this.y);
	// 命名构造函数
    Point.fromJson(double x, double y) {
      this.x = x;
      this.y = y;
    }
  }
复制代码

使用构造函数

对象的 成员 由函数和数据(即 方法实例变量)组成。方法的 调用 要通过对象来完成,这种方式可以访问对象的函数和数据。

使用 构造函数 创建一个对象。分别使用 Point()Point.fromJson() 两种构造器创建了 Point 对象:

  var p1 = Point(2.0, 3.0);
  var p2 = Point.fromJson(2.0, 3.0);
复制代码

以下代码具有相同的效果,但是构造函数名前面的的 new 关键字是可选的:

  var p1 = new Point(2.0, 3.0);
  var p2 = new Point.fromJson(2.0, 3.0);
复制代码

构造函数不被继承

子类不会继承父类的构造函数,如果子类没有声明构造函数,那么只会有一个默认无参数的构造函数。

调用父类非默认构造函数

默认情况下,子类的构造函数会调用 父类的匿名无参数构造方法,并且该调用会在子类构造函数的函数体代码执行前,如果子类构造函数还有一个 初始化列表,那么该初始化列表会在调用父类的该构造函数之前被执行,总的来说,这三者的调用顺序如下:

  1. 初始化列表

    除了调用父类构造函数之外,还可以在构造函数体执行之前初始化实例变量。每个实例变量之间使用逗号分隔。

     class Point {
       double? x;
       double? y;
    
       Point(double x, double y)
           : x = 1.0,
             y = 1.0 {
          this.x = x;
          this.y = y;
        }
      }
    复制代码

    初始化列表表达式 = 右边的语句不能使用 this 关键字。

  2. 父类的无参数构造函数

  3. 当前类的构造函数

如果父类没有匿名无参数构造函数,那么 子类必须调用父类的其中一个构造函数,为子类的构造函数指定一个父类的构造函数只需在构造函数体前使用(:)指定。

下面的示例中,Employee 类的构造函数调用了父类 Person 的命名构造函数。点击运行按钮执行示例代码。

  class Person {
    String? firstName;
    Person(Map data) {
      print('in Person');
    }
  }

  class Employee extends Person {
    Employee.fromJson(Map data) : super(data) {
      print('in Employee');
    }
  }

  main() {
    var employee = Employee.fromJson({});
    print(employee);
    // in Person
    // in Employee
    // Instance of 'Employee'
  }
复制代码

重定向构造函数

有时候类中的构造函数仅用于调用类中其它的构造函数,此时该构造函数没有函数体,只需在函数签名后使用(:)指定需要重定向到的其它构造函数 (使用 this 而非类名):

  class Point {
    double? x, y;

    Point(this.x, this.y);

    Point.alongXAxis(double x) : this(x, 0);
  }
复制代码

抽象类

使用关键字 abstract 标识类可以让该类成为 抽象类,抽象类将无法被实例化。抽象类常用于声明接口方法、有时也会有具体的方法实现。如果想让抽象类同时可被实例化,可以为其定义 工厂构造函数

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

  abstract class AbstractContainer {
    void updateChildren(); // 没有方法体
  }
复制代码

继承,使用 extends 关键字来扩展一个类

  • 使用 extends 关键字来继承父类创建一个子类

  • 子类会继承父类里面可见的属性和方法,但不能继承构造函数

  • 子类可以重写父类的实例方法(包括 操作符)、 Getter 以及 Setter 方法。你可以使用 @override 注解来表示你重写了一个成员

接口,可以通过关键字 implements 来实现一个或多个接口

  • Dart 中接口没有 interface 关键字定义,而是普通类或抽象类都可以作为接口被实现。

  • 通过关键字 implements 来实现一个或多个接口并实现每个接口定义的 API

  • 无论时实现一个普通类或抽象类作为一个接口,都需要将其中的属性的方法全部覆写一遍

  • 建议使用抽象类定义接口


  class Person {

    final String _name;

    Person(this._name);

    String greet(String who) => 'Hello, $who. I am $_name.';
  }

  class Impostor implements Person {
    String get _name => 'Eren';

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

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

  void main() {
    print(greetBob(Person('Kathy')));
    print(greetBob(Impostor()));
    // Hello, Bob. I am Kathy.
    // Hello, Bob. I am Eren.
  }
复制代码

枚举类型

枚举类型是一种特殊的类型,也称为 enumerationsenums,用于定义一些固定数量的常量值。

枚举类型有如下两个限制:

  • 枚举不能成为子类,也不可以 mix in,你也不可以实现一个枚举。
  • 不能显式地实例化一个枚举类。

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

  enum Color { red, green, blue }

  void main() {
    print(Color.red.index);
    // 0
  }
复制代码

每一个枚举值都有一个名为 index 成员变量的 Getter 方法,该方法将会返回以 0 为基准索引的位置值。例如,第一个枚举值的索引是 0 ,第二个枚举值的索引是 1。以此类推。

使用 Mixin 为类添加功能

在 Dart 中可以使用 with 关键字实现类似多继承的功能。

关键字 mixin 在 Dart 2.1 中才被引用支持,由于 mixin 使用的条件随着dart的版本一直在变,在 Dart2.x 中使用mixins的条件:

  • 作为 mixin 的类只能继承自 Object,不能继承其他类

  • 作为 mixin 的类不能有 构造函数

  • 一个类可以 mixin 多个 mixin

  • mixin 绝不是继承也不是接口,而是一种全新的特性

使用 with 关键字并在其后跟上 Mixin 类的名字来使用 Mixin 模式:

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

  class Musical {
    // ...
  }
复制代码

想要实现一个 Mixin,请创建一个继承自 Object 且 未声明构造函数 的类。除非你想让该类与普通的类一样可以被正常地使用,否则请使用关键字 mixin 替代 class。例如:

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

  mixin Musical {
    // ...
  }
复制代码

可以使用关键字 on 来指定具体哪些类可以使用该 Mixin 类

  class Musician {
    // ...
  }
  mixin MusicalPerformer on Musician {
    // ...
  }
  class SingerDancer extends Musician with MusicalPerformer {
    // ...
  }
复制代码

泛型

如果你查看数组的 API 文档,你会发现数组 List 的实际类型为 List<E>。 <…> 符号表示数组是一个 泛型(或 参数化类型通常 使用一个字母来代表类型参数,比如 E、T、S、K 和 V 等等。

为什么使用泛型?

泛型常用于需要要求类型安全的情况,但是它也会对代码运行有好处:

  • 适当地指定泛型可以更好地帮助代码生成。
  • 使用泛型可以减少代码重复。

泛型就是解决类、接口、方法的复用性以及对不特定数据类型的支持(类型校验)。

Tips:

Dart的泛型类型是 固化的,这意味着即便在运行时也会保持类型信息。与 Java 不同的是,Java 中的泛型是类型 擦除 的,这意味着泛型类型会在运行时被移除。在 Java 中你可以判断对象是否为 List 但不可以判断对象是否为 List<String>

库和可见性

**每个 Dart 程序都是一个库。**Dart 中通过importlibrary 关键字可以帮助你创建一个模块化和可共享的代码库。

使用库

使用 import 来指定命名空间以便其它库可以访问。

比如你可以导入代码库 dart:html 来使用 Dart Web 中相关 API:

  import 'dart:html';
复制代码

Tips:

Pub包管理系统中的库:pub.dev/packages;ht…

指定库前缀

如果你导入的两个代码库有冲突的标识符,你可以为其中一个指定前缀。比如如果 library1 和 library2 都有 Element 类,那么可以这么处理:

  import 'package:lib1/lib1.dart';
  import 'package:lib2/lib2.dart' as lib2;

  // Uses Element from lib1.
  Element element1 = Element();

  // Uses Element from lib2.
  lib2.Element element2 = lib2.Element();
复制代码

导入库的一部分

如果你只想使用代码库中的一部分,你可以有选择地导入代码库。例如:

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

  // Import all names EXCEPT foo.
  import 'package:lib2/lib2.dart' hide foo;
复制代码

异步支持

Dart 代码库中有大量返回 FutureStream 对象的函数,这些函数都是 异步 的,它们会在耗时操作(比如I/O)执行完毕前直接返回而不会等待耗时操作执行完毕。

asyncawait 关键字用于实现异步编程,并且让你的代码看起来就像是同步的一样。

处理 Future

可以通过下面两种方式,获得 Future 执行完成的结果:

  • 使用 asyncawait
  • 使用 Future API,具体描述参考 库概览

使用 asyncawait 的代码是异步的,但是看起来有点像同步代码。例如,下面的代码使用 await 等待异步函数的执行结果。

  await lookUpVersion();
复制代码

必须在带有 async 关键字的 异步函数 中使用 await

  Future<void> checkVersion() async {
    var version = await lookUpVersion();
    // Do something with version
  }
复制代码

你可以在异步函数中多次使用 await 关键字。例如,下面代码中等待了三次函数结果:

  var entrypoint = await findEntryPoint();
  var exitCode = await runExecutable(entrypoint, args);
  await flushThenExit(exitCode);
复制代码

await 表达式的返回值通常是一个 Future 对象;如果不是的话也会自动将其包裹在一个 Future 对象里。 Future 对象代表一个“承诺”, await 表达式会阻塞直到需要的对象返回。

参考资料:

Dart 开发语言概览

文章分类
Android
文章标签