天天看点

1.学习 dart 语言的基本语法一段 Dart 的基础代码学习 Dart 的注意事项Dart 语言中的保留关键字变量默认值不可更变的量Dart 的内置变量类型函数运算符控制流语句异常类泛型库和可见性异步支持生成器回调类隔离器类型定义元数据注释总结

学习主干:一名PHP程序员的Flutter学习之路

学习 dart 语言的基本语法,Dart 版本 2.5

  • 一段 Dart 的基础代码
  • 学习 Dart 的注意事项
  • Dart 语言中的保留关键字
  • 变量
  • 默认值
  • 不可更变的量
    • final
    • const
  • Dart 的内置变量类型
    • numbers
      • int
      • double
    • strings
    • booleans
    • sets
    • lists
    • maps
    • Runes
    • symbols
  • 函数
    • 可选参数
      • 命名参数
      • 位置参数
      • 默认参数值
    • 主函数
    • 第一类对象
    • 匿名函数
    • 词法范围
    • 语法闭包
    • 检查是否相等的测试函数
    • 返回值
  • 运算符
    • 算术运算符
    • 平等与关系运算符
    • 类型测试运算符
    • 赋值运算符
    • 按位和移位运算符
    • 条件表达式
    • 级联符号
    • 其它运算符
  • 控制流语句
    • if and else
    • for loops
    • while and do-while loops
    • break and continue
    • switch and case
    • assert
  • 异常
    • Throw
    • Catch
    • Finally
    • 使用类成员
    • 使用构造函数
    • 获取一个对象类型
    • 实例变量
    • 构造函数
      • 默认构造函数
      • 非继承构造函数
      • 命名构造函数
      • 调用父类非默认构造函数
      • 初始化列表
      • 重定向构造函数
      • 常量构造函数
      • 工厂构造函数
    • 方法
      • 实例方法
      • 获取器和修改器
      • 抽象方法
    • 抽象类
    • 隐式接口
    • 扩展类
      • 重载成员
      • 重写运算符
      • noSuchMethod()
    • 枚举类型
      • 使用枚举
    • 向类里添加功能:mixins
    • 类变量及方法
      • 静态变量
      • 静态方法
  • 泛型
    • 为什么使用泛型
    • 使用集合文字
    • 在构造函数中使用参数类型
    • 限制参数类型
    • 通用方法
  • 库和可见性
    • 使用库
      • 指定库前缀
      • 只引入库的部分
      • 懒加载一个库
    • 实现库
  • 异步支持
    • Handling Futures
    • 声明异步函数
    • Handling Streams
  • 生成器
  • 回调类
  • 隔离器
  • 类型定义
  • 元数据
  • 注释
    • 单行注释
    • 多行注释
    • 文档注释
  • 总结

我们先来了解一下 Dart,它是一种适用于万维网的开放源代码编程语言,由 Google 主导开发,于2011年10月公开,目标在于成为下一代结构化Web开发语言。不过很可惜这个语言一直不温不火,相信很多人之前甚至都没听过这个语言,直到 Flutter 出现后才被众人知晓。

文档参考:https://dart.dev/guides/language/language-tour

一段 Dart 的基础代码

// 定义一个函数
printNumber(num aNumber) {
  print('The number is $aNumber.'); // 打印内容
}

// 主程序执行入口
main() {
  var number = 42; // 定义并初始化一个变量
  printNumber(number); // 调用自定义的函数
}

// 打印结果
// The number is 42.
           

*让上述代码在 DartPad 中试运行

学习 Dart 的注意事项

  1. 所有能够使用变量引用的都是对象,每个对象都是一个类的实例。在 Dart 中 甚至连数字、函数和 null 都是对象,所有的对象都继承于 Object 类。
  2. 使用静态类型(例如前面示例中的 num ) 可以更清晰的表明你的意图,并且可以让静态分析工具来分析你的代码,但这并不是强制性的(在调试代码的时候你可能注意到,没有指定类型的变量的类型为 dynamic)。
  3. Dart 在运行之前会先解析你的代码,你可以通过使用类型或者编译时常量来帮助 Dart 去捕获异常来让代码运行的更高效。
  4. Dart 支持顶级函数 (例如 main()),同时还支持在类中定义函数,如:静态函数 和 实例函数。 你还可以在函数中定义再函数(嵌套函数 或 局部函数)。
  5. Dart 还支持顶级变量,以及在类中定义变量(静态变量 和 实例变量)。实例变量有时候被称之为字段(Fields)或者属性(Properties)。
  6. 和 Java 不同的是,Dart 没有 public、protected 和 private 关键字。如果一个标识符以 (_) 开头,则该标识符在库内是私有的。详情请参考:库和可见性。
  7. 标识符必须为字母或者下划线开头,后面可以是其他字符和数字的组合。
  8. 有时候表达式 expression 和语句 statement 是有区别的,所以我们会针对不同的情况去说明,例如,条件表达式 condition ? expr1 : expr2 的值为 expr1 或 expr2。将其与 if-else 语句进行比较是没有价值的。一条语句通常包含了一个或多个表达式,但是一个表达式不能直接包含一条语句。
  9. Dart 开发时仅提示两个等级的问题,分别是 警告 和 错误。

Dart 语言中的保留关键字

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. 右上角角标为 1 的单词是上下文关键字,只在特定的地方才具有意义,这些关键字在代码的任何地方都是有效的。
  2. 右上角角标为 2 的单词是内置关键字,为了简化将 JavaScript 代码移植到 Dart,这些关键字在大多数地方都是有效的标识符,但不能用在 类(class) 或 类型名称(Type names) 中,也不能用作导入前缀。
  3. 右上角角标为 3 的单词是新发布的关键字,是 Dart 语言的保留关键字。与 Dart 1.0 版本之后添加的异步支持有关。在任何函数中中不能使用 await 或 yield 来修饰 async,async*,或者 sync*。
  4. 表格中无角标的单词都是系统保留关键字,不能作为标识符。

变量

变量实例:

// 向变量 name 赋值 'uSee',那么 name 就是一个字符串变量
var name = 'uSee';

// 动态变量,通过赋值来指定类型,不像单一变量只能是一种变量类型
dynamic name = 'uSee';
dynamic name = 1;
dynamic name = true;

// 单一类型变量
String name = 'uSee';
           

默认值

Int lineCount;
assert(lineCount == null); // 真实写代码时不用写 assert 这一行
           

未初始化的变量的缺省值为 null,就算是数字类型的变量的缺省值也是 null,因为在 数字(与Dart中的所有其他内容一样)都是对象。

不可更变的量

如果你声明了一个变量但是又不想它被再赋值,那么就可以使用 final 或者 const 来修饰。

注意:实例变量可以用 final 声明而不能使用 const

final

被 final 声明的变量只能在声明的时候赋值一次,实例代码如下:

final name = 'uSee';
name = 'iSee';

// 无法修改 name 的值
// Error: a final variable can only be set once.
           

const

const 是常量, 如果 const 声明是在类中,则需要定义为 static const。可以直接定义一个常量的值,也 可以定义一个常量并使用它来初始化其值。

const bar = 1000000;
const double atm = 1.01325 * bar;
           

const 不仅仅只用来定义常量,也可以用来定义一个变量的类型,这种类型是不可改变的。

var foo = const [];
final bar = const [];
const baz = [];

foo = [1, 2, 3]; // Was const []
baz = [42]; // Error: Constant variables can't be assigned a value.
           

Dart 的内置变量类型

numbers

数值类型,该类型支持两种子类型 int 和 double。

int

整数型,其取值通常位于 -253 和 253 之间。

double

64-bit (双精度) 浮点型,符合 IEEE 754 标准。

strings

字符串类型,strings是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}这种方式将表达式的值放在字符串中。

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!');
           

可以使用 ”+“ 操作符来把多个字符串连接为一个字符串。

booleans

布尔值,布尔值只有两种结果 true 或 false。

var name = 'uSee';
if (name) {
  // 当 if 判断 name 时,返回的就是布尔值,当前返回 true
  print('You have a name!');
}
           

sets

无序集合,集合中的值唯一。

如果需要创建一个空集,则要在类型参数之前使用{},或将{}分配给Set类型的变量。

var names = <String>{};
// 或者
Set<String> names = {};

// var names = {}; // 这样写创建的则是 map 而不是 set
           

lists

列表,可以看作 PHP 中的数组 Array。

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

list[1] = 1;
assert(list[1] == 1);
           
var constantList = const [1, 2, 3];
// constantList[1] = 1; // 取消注释会导致错误
           

maps

Map 是一个键值对相关的对象。 键和值可以是任何类型的对象,键名唯一,键值可重复。看起来似乎和 Javascript 中的对象一样。

var gifts = {
  // Key  : Value
  'first' : 'partridge',
  'second': 'turtledoves',
  'fifth' : 'golden rings'
};

var nobleGases = {
  2 : 'helium',
  10: 'neon',
  18: 'argon',
};

// Dart 中 gifts 跟 nobleGases 的区别是其键(key)
// gifts 的 key 为 String,nobleGases 的 key 为 int
           

Runes

在 Dart 中,runes 是 UTF-32 的字符串代码点。

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

*让上述代码在 DartPad 中试运行

symbols

暂时忽略,可参考 官方文档

函数

Dart 是一种真正的面向对象的语言,连函数甚至都是对象,并且是具有类型函数。 这意味着可以将函数分配给变量或作为参数传递给其他函数。 您也可以将Dart类的实例称为函数。

有关详细信息,请参见可调用类(Callable classes)。

下面是实现函数的示例:

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

// 当然你也可以不写函数的定义
isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}
           

对于仅有一个表达式的函数,可使用缩写语法:

=> expr 是 { return expr; } 形式的缩写,我们也称之为箭头语法。

可选参数

可选参数可以是 命名参数 或 位置参数,但不能同时选择。

命名参数

调用函数时,你可以使用 paramName:value 来指定命名参数。 例如:

在定义函数的时候,使用 {param1, param2, …} 的形式来指定命名参数。例如:

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

尽管命名参数是一种可选参数,但是您可以使用 @required 对其进行标注,以声明该参数是强制性的,必须为该参数提供一个值。 例如:

当创建滚动条而不指定子参数时,则分析器将报告错误。

这是则需要使用 @required 进行标注,请取决于 meta package 且导入 package:meta/meta.dart

位置参数

在参数装在 中括号[] 中就表示该参数是位置参数。

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

如何为位置参数设置默认值的示例:

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

assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');
           

还可以将 list 或 map 作为默认值传递。

void doStuff(
    {List<int> list = const [1, 2, 3],
    Map<String, String> gifts = const {
      'first' : 'paper',
      'second': 'cotton',
      'third' : 'leather'
    }}) {
  print('list:  $list');
  print('gifts: $gifts');
}
           

主函数

每个应用程序都必须含有顶级 main() 函数,该函数充当该应用程序的入口。 main() 函数返回 void,并具有可选的 List <String> 参数作为 main() 函数的参数。

下面是一个 web 应用程序中的 main() 函数示例:

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

这是一个带有参数的命令行应用程序的 main() 函数示例:

// Run the app like this: dart args.dart 1 test
void main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');
}
           

您可以使用 args library 来定义和解析命令行参数。

第一类对象

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

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

var list = [1, 2, 3];

// Pass printElement as a parameter.
list.forEach(printElement);
           

还可以将函数赋值给变量,例如:

var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');
           

匿名函数

大多数的函数是已经命名好的,例如 main() 或 printElement(),不过还可以创建一个没有名称的函数,称之为 匿名函数,有时也可以创建一个 lambda 或 闭包。 你可以将匿名函数分配给变量,这样可以更方便你操作它。

匿名函数看起来类似于命名函数,示例:

([[Type] param1[, …]]) {
  codeBlock;
};
           

下面的示例为一个匿名函数,该匿名函数传入了个参数 item,为列表中的每个子项调用的函数将打印一个字符串,其中包括指定索引处的值。

void main() {
  var list = ['apples', 'bananas', 'oranges'];
  list.forEach((item) {
    print('${list.indexOf(item)}: $item');
  });
}
           

如果该函数仅包含一个语句,则可以使用箭头符号将其缩短。

void main() {
  var list = ['apples', 'bananas', 'oranges'];
  list.forEach(
    (item) => print('${list.indexOf(item)}: $item'));
}
           

*让上述代码在 DartPad 中试运行

词法范围

Dart 是一种词法范围的语言,这意味着变量的范围是静态确定的,只需通过代码的布局即可。 你可以“由内向外扩展花括号”以查看变量是否在范围内。

bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;

    void nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}
           

注意:nestedFunction() 可以使用各个层级的变量。

语法闭包

闭包是可以在其语法范围内访问变量的函数对象,即使该函数在其原始范围之外使用也是如此。

闭包可以在其范围中自定义变量,在以下示例中,makeAdder() 捕获变量 addBy。无论返回的函数到哪里,它都会记住 addBy。

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

检查是否相等的测试函数

这是测试顶级函数,静态方法和实例方法是否相等的示例:

void foo() {} // A top-level function

class A {
  static void bar() {} // A static method
  void baz() {} // An instance method
}

void main() {
  var x;

  // Comparing top-level functions.
  x = foo;
  assert(foo == x);

  // Comparing static methods.
  x = A.bar;
  assert(A.bar == x);

  // Comparing instance methods.
  var v = A(); // Instance #1 of A
  var w = A(); // Instance #2 of A
  var y = w;
  x = w.baz;

  // These closures refer to the same instance (#2),
  // so they're equal.
  assert(y.baz == x);

  // These closures refer to different instances,
  // so they're unequal.
  assert(v.baz != w.baz);
}
           

返回值

所有函数都返回一个值,如果未定义返回值则返回 null,这是 Dart 语言隐式的添加到了函数体里。

foo() {}
assert(foo() == null);
           

运算符

Dart 定义了下表中显示的运算符。 你可以重载其中的许多运算符,请参考 可重载的运算符

Description (简介) Operator (运算符)
unary postfix expr++    expr--    ()    []    .    ?.
unary prefix -expr    !expr    ~expr    ++expr    --expr
multiplicative *    /    %    ~/
additive +    -
shift <<    >>    >>>
bitwise AND &
bitwise XOR ^
bitwise OR |
relational and type test >=    >    <=    <    as    is    is!
equality == !=
logical AND &&
logical OR ||
if null ??
conditional expr1 ? expr2 : expr3
cascade . .
assignment =    *=    /=    +=    -=    &=    ^=    etc.

警告:运算符优先级请查阅 Dart语言规范。

使用运算符时即创建表达式,以下是一些运算符表达式的示例:

a++
a + b
a = b
a == b
c ? a : b
a is T
           

在运算符表中,每个运算符的优先级都比其后面各行中的运算符高。 例如,乘法运算符 % 的优先级高于相等运算符 ==,其优先级高于逻辑运算符 && 。 该优先级意味着以下两行代码以相同的方式执行:

// Parentheses improve readability.
if ((n % i == 0) && (d % i == 0)) ...

// Harder to read, but equivalent.
if (n % i == 0 && d % i == 0) ...
           

算术运算符

Operator (运算符) Meaning (含义)
+
-expr 一元减号,也称之为负号(反转表达式符号)
*
/
~/ 取商(整数结果)
% 取模(余数的整数结果)

示例:

assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // Result is a double
assert(5 ~/ 2 == 2); // Result is an int
assert(5 % 2 == 1); // Remainder

assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');
           

Dart 还支持前缀和后缀的增量和减量运算符。

Operator (运算符) Meaning (含义)
++var var = var + 1 (表达式的值 = var + 1)
var++ var = var + 1 (表达式的值 = var)
−−var var = var – 1 (表达式的值 = var – 1)
var−− var = var – 1 (表达式的值 = var)

示例:

var a, b;

a = 0;
b = ++a; // Increment a before b gets its value.
assert(a == b); // 1 == 1

a = 0;
b = a++; // Increment a AFTER b gets its value.
assert(a != b); // 1 != 0

a = 0;
b = --a; // Decrement a before b gets its value.
assert(a == b); // -1 == -1

a = 0;
b = a--; // Decrement a AFTER b gets its value.
assert(a != b); // -1 != 0
           

平等与关系运算符

下表中列出了相等运算符和关系运算符的含义。

Operator (运算符) Meaning (含义)
== 等于
!= 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于

要测试两个对象 X 和 Y是否相等,请使用 == 运算符。在极少数情况下,您需要知道两个对象是否是完全相同的对象,请使用 identical() 函数。运算符的使用方式如下:

assert(2 == 2);
assert(2 != 3);
assert(3 >  2);
assert(2 <  3);
assert(3 >= 3);
assert(2 <= 3);
           

类型测试运算符

as,is 和 is! 运算符在运行时用来检查类型非常方便。

Operator (运算符) Meaning (含义)
as Typecast(也用于指定库前缀)
is 如果检验对象是指定的类型,则返回 True
is! 如果检验对象非指定的类型,则返回 True

如果 obj 是由 T 的接口实现的,那么表达式 obj is T 则返回 True。例如:obj is Object 肯定总是返回 True。

使用 as 运算符将对象转换为特定类型。一般情况下,你应将其用在检测对象语句的简写。 例如:

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

使用 as 运算符可以缩短代码:

如果 emp 为 null 或者不是 Person,则第一个示例不执行任何操作,而第二个示例则会引发异常。

赋值运算符

你可以使用赋值号 = 运算符来对变量进行赋值。在变量为原本为 null 的情况下赋值则需要使用 ??= 运算符。

// Assign value to a
a = value;
// Assign value to b if b is null; otherwise, b stays the same
b ??= value;
           

复合赋值运算符 将运算与赋值结合在一起。

= –= /= %= >>= ^=
+= *= ~/= <<= &= |=

逻辑运算符的示例:

if (!done && (col == 0 || col == 3)) {
  // ...Do something...
}
           

按位和移位运算符

Operator (运算符) Meaning (含义)
&
||
^ 异或 (同为 0,异为 1)
~expr 一元按位补码(0变为1,1变为0)
<< 往左位移
>> 往右位移

一个使用按位和移位运算符的示例:

final value = 0x22;
final bitmask = 0x0f;

assert((value & bitmask) == 0x02); // AND
assert((value & ~bitmask) == 0x20); // AND NOT
assert((value | bitmask) == 0x2f); // OR
assert((value ^ bitmask) == 0x2d); // XOR
assert((value << 4) == 0x220); // Shift left
assert((value >> 4) == 0x02); // Shift right
           

条件表达式

Dart 有两种条件运算符,在你使用 if-else 表达式时更方便:

condition ? expr1 : expr2
// 如果条件为真,则结果为(返回) expr1,否则结果为 expr2
           
expr1 ?? expr2
// 如果 expr1 非空则结果为(返回) expr1,否则结果为 expr2
           

如果需要基于布尔表达式来判断分配值时,请考虑使用 condition ? expr1 : expr2。

如果布尔表达式的结果为 null,请考虑使用 expr1 ?? expr2。

前面的示例至少可以用两种其它其他方式编写,但不那么简洁:

// Slightly longer version uses ?: operator.
String playerName(String name) => name != null ? name : 'Guest';

// Very long version uses if-else statement.
String playerName(String name) {
  if (name != null) {
    return name;
  } else {
    return 'Guest';
  }
}
           

级联符号

级联(..)允许对同一对象执行一系列操作。除了函数调用,还可以访问同一对象上的字段。这通常可以节省创建临时变量的步骤,并允许编写更多流畅的代码。

思考以下代码:

querySelector('#confirm') // Get an object.
  ..text = 'Confirm' // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));
           

第一个方法调用 querySelector() 返回选择器的对象。 遵循级联表示法的代码在此选择器对象上运行,而忽略了可能返回的其它后续值。

上一个示例等效于:

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
           

也可以嵌套级联, 例如:

final addressBook = (AddressBookBuilder()
      ..name = 'jenny'
      ..email = '[email protected]'
      ..phone = (PhoneNumberBuilder()
            ..number = '415-555-0100'
            ..label = 'home')
          .build())
    .build();
           

注意在返回实际对象的函数上构造级联,例如以下的错误代码:

var sb = StringBuffer();
sb.write('foo')
  ..write('bar'); // Error: method 'write' isn't defined for 'void'.
           

因为你不能在 void 上构建级联,所以sb.write() 调用返回 void,在此处构建级联所以错误。

提示: 严格来说,级联的 “双点” 符号不是运算符。它只是Dart语法的一部分。

其它运算符

在其他示例中,我们已经认识了大多数的运算符,接下来我们展示剩下的一些运算符:

Operator (运算符) Name(名称) Meaning (含义)
() Function application 代表一个函数调用
[] List access 引用列表中指定索引处的值
. Member access 引用表达式的属性,例如 foo.bar,意思是从表达式 foo 中选择其属性 bar
?. Conditional member access 类似于 ., 但是最左边的操作数可以为 null; 例如 foo?.bar,意思是从表达式 foo 中选择属性bar,除非 foo 为 null(在这种情况下 foo?.bar 的值为 null)

有关 (.,|?.|..) 的更多信息,请参见 Classes。

控制流语句

你可以使用以下任意方式控制 Dart 代码的流程:

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

你还可以使用 try-catch 和 throw 来影响控制流,参考 Exceptions

if and else

Dart 支持 if 语句和可选的 else 语句,如下例所示, 也可参阅 条件表达式。

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

不同于 JavaScript,条件表达式必须使用布尔值。 有关更多信息请参见 布尔值。

for loops

你可以使用标准的 for 循环进行迭代,例如:

var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
  message.write('!');
}
           

Dart 语言种 for 循环内的闭包捕获了索引的值,让我们避免了 js 中常见的坑, 例如:

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());
           

如预期的那样,先输出 0,然后输出 1。相反,该示例将在 js 中先打印 2,然后还是打印 2。

如果要迭代的对象是一个 Iterable,则可以使用 forEach() 方法。 如果不需要了解当前的迭代计数器,则使用 forEach() 是一个不错的选择:

List 和 Set 之类的可迭代类也支持 for-in 形式的迭代:

var collection = [0, 1, 2];
for (var x in collection) {
  print(x); // 0 1 2
}
           

while and do-while loops

while 循环,在循环之前会先判断开始条件:

while (!isDone()) {
  doSomething();
}
           

do-while 循环则是先执行一次循环代码后再来判断条件:

do {
  printLine();
} while (!atEndOfPage());
           

break and 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(例如列表或集合),则可以用不同的方式来编写该示例:

candidates
    .where((c) => c.yearsExperience >= 5)
    .forEach((c) => c.interview());
           

switch and case

Dart 中的 switch 语句使用 == 比较整数、字符串或常量, 被比较的对象必须全部是同一类的实例(而不是其任何子类型的实例),并且该类不得覆盖 == 。 枚举类型 在switch语句中表现不错。

提示: Dart 中的 switch 语句仅在有限的情况下使用,例如在解释器或扫描器中。

通常,每个非空的 case 子句都以 break 语句结尾。 结束非空的 case 子句还有其它的方式,如 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: Missing break

  case 'CLOSED':
    executeClosed();
    break;
}
           

当然,Dart 也支持空子句,允许使用以下形式:

var command = 'CLOSED';
switch (command) {
  case 'CLOSED': // Empty case falls through.
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}
           

如果你想跳过失败,可以使用 continue 语句和标签:

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 子句可以具有局部变量,这些局部变量仅在该子句范围内可使用。

assert

在开发过程中,使用断言 - assert(condition, optionalMessage),如果条件为假,则中断执行。你可以在本教程中找到断言语句的示例。以下也是段验示例:

// Make sure the variable has a non-null value.
assert(text != null);

// Make sure the value is less than 100.
assert(number < 100);

// Make sure this is an https URL.
assert(urlString.startsWith('https'));
           

要将消息附加到断言,需要添加字符串作为断言的第二个参数。

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

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

断言再什么时候起作用取决于你使用的工具和框架:

  • Flutter 在调试模式下启用
  • 默认情况下,开发工具(例如 dartdevc)启用断言
  • 某些工具(例如 dart 和 dart2js)通过命令行标志 – enable-asserts 支持断言。

    在生产模式时,断言将被忽略且不会判断断言的参数。

异常

Dart 代码可以引用并捕获异常。异常即错误,指示发生了意外情况。 如果未捕获到异常,则引发异常的隔离将被挂起,并且通常隔离将终止程序。

与 Java 相比,Dart 的所有异常都是未经检查的异常。 方法不声明它们可能会引发哪些异常,并且不需捕获任何异常。

Dart 提供 Exception 和 Error 类型,以及许多预定义的子类型。 当然,你可以定义自己的异常。 但是 Dart 程序可以将任何非 null 对象(不仅仅是 Exception 和 Error 对象)作为异常抛出。

Throw

以下时引发或引发异常的示例:

还可以抛出任意对象:

提示: 生产质量代码通常会抛出已实例化的 Error 或 Exception 的类型。

因为抛出异常是一个表达式,所以可以在 => 语句以及允许表达式的其他任何地方抛出异常:

Catch

捕获异常会阻止该异常的传播(除非重新抛出该异常),捕获异常后你就可以按自己的心意处理它:

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

要处理可能引发不止一种类型的异常的代码,可以指定多个 catch 子句。 第一个与抛出的对象类型匹配的 catch 子句处理异常。 如果 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) {
  // No specified type, handles all
  print('Something really unknown: $e');
}
           

正如上述代码,你可以同时使用 on 或 catch。在需要指定异常类型时使用,当异常处理程序需要异常对象时,则使用catch。

你可以往 catch() 中加入一个或两个参数。第一个参数是引发的异常,而第二个参数是堆栈跟踪(一个 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 关键字。

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

finally 子句在任何 catch 子句之后运行:

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

你可以类库导览来学习了解更多关于 Exceptions 的信息。

Dart 是一种具有类且基于 Mixin 的继承的面向对象语言。 每个对象都是一个类的实例,并且所有类都来自Object。基于混合继承,意味着尽管每个类(对象除外)都只有一个父类,但是一个类的主体可以在多个类层次结构中复用。

使用类成员

对象具有由函数和数据(分别为方法和实例变量)组成的成员。调用方法时,您可以在对象上调用它:该方法可以访问该对象的功能和数据。

使用点 (.) 来调用实例的变量或方法:

var p = Point(2, 2);

// Set the value of the instance variable y.
p.y = 3;

// Get the value of y.
assert(p.y == 3);

// Invoke distanceTo() on p.
num distance = p.distanceTo(Point(4, 4));
           

在赋值时,采用 (?.) 代替 (.),这是为了避免在最左边的操作数为 null 时发生异常:

// If p is non-null, set its y value to 4.
p?.y = 4;
           

使用构造函数

你可以使用构造函数来创建对象,构造函数名称可以是 ClassName 或 ClassName.identifier。例如以下代码使用 Point() 和 Point.fromJson() 构造函数创建 Point 对象:

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

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

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

版本提示: New 关键字在 Dart 2 中成为了可选关键字。

一些类提供常量构造函数, 要使用常量构造函数创建编译时常量,需要将 const 关键字放在构造函数名称之前:

构造两个相同的编译时常量会产生一个规范的实例:

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

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

在常量上下文中,可以在构造函数或文字之前省略 const。 例如面的代码,它创建了一个 const 映射:

// Lots of const keywords here.
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
           

你可以省略除首次使用 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!
           

版本提示: 在 Dart 2 的恒定上下文中,const 关键字变为可选。

获取一个对象类型

要在运行时获取对象的类型,可以使用 Object 的 runtimeType 属性,该属性返回 Type 对象。

到目前为止,你已经了解了如何使用课程, 本节的其余部分将展示如何实现类。

实例变量

让我们来看看如何声明实例变量:

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 方法。非最终实例变量也会自动生成的 setter 方法。有关详细信息,请参见 Getters 和 Setters。

class Point {
  num x;
  num y;
}

void main() {
  var point = Point();
  point.x = 4; // Use the setter method for x.
  assert(point.x == 4); // Use the getter method for x.
  assert(point.y == null); // Values default to null.
}
           

如果你在声明了实例变量的地方(不是在构造函数或方法中)初始化实例变量,则在创建实例时(即在构造函数及其初始化程序列表执行之前)设置值。

构造函数

通过创建一个与其类名相同的函数来声明一个构造函数(另加一个可选的标识符,如“命名构造函数”中所述)。构造函数的最常见形式是 生成构造函器(the generative constructor),它创建类的新实例:

class Point {
  num x, 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 将忽略它。

将构造函数参数分配给实例变量的模式非常普遍,Dart 具有的语法糖将使这种方式的实现变得简单:

class Point {
  num x, y;

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

默认构造函数

如果不声明构造函数,系统将会为你提供默认的构造函数。默认构造函数没有参数,并在父类中调用无参数构造函数。

非继承构造函数

子类不会从其父类中继承构造函数。声明没有构造函数的子类仅具有默认(没有参数,没有名称)构造函数。

命名构造函数

使用命名构造函数可以为一个类实现多个构造函数或提供额外的 思路(clarity 不知道怎么翻译 ?):

class Point {
  num x, y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin() {
    x = 0;
    y = 0;
  }
}
           

记住,构造函数是不能继承的,这意味着父类的命名构造函数不会被子类继承。 如果要使用超类中定义的命名构造函数创建子类,则必须在子类中先实现父类的构造函数。

调用父类非默认构造函数

默认情况下,子类中的构造函数会调用父类的未命名且无参数的构造函数。父类的构造函数在构造函数主体的开头被调用。如果还使用了初始化的 list,它将在调用父类之前执行。详细执行顺序如下:

  • 初始化列表
  • 父类无参数构造函数
  • 主类无参数构造函数

如果父类没有未命名、无参数的构造函数,则必须手动调用父类中的构造函数之一。在构造函数主体(如果有)之前,在冒号 (:) 之后指定父类构造函数。

在下面的示例中,Employee 类的构造函数为其父类 Person 调用命名的构造函数。

让下述代码在 DartPad 中试运行

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(getDefaultData());
  // ···
}
           

警告: 父类构造函数的参数无权限访问此函数。 例如,参数可以调用静态方法,而不能调用实例方法。

初始化列表

除了调用父类构造函数外,还可以在构造函数主体运行之前初始化实例变量,用逗号分隔初始化程序。

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

警告: 初始化程序的右侧无权访问此方法。

在开发过程中,你可以通过使用初始化列表中的 assert 来验证输入。

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}
           

在设置最终字段时使用初始化列表很方便。以下示例在初始化列表中初始化三个最终字段。

让下述代码在 DartPad 中试运行

import 'dart:math';

class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = new Point(2, 3);
  print(p.distanceFromOrigin);
}
           

重定向构造函数

有时,构造函数的唯一目的是重定向到同一类中的另一个构造函数。重定向构造函数的主体为空,构造函数调用出现在冒号 (:) 后面。

class Point {
  num x, 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 构造函数,并确保所有实例变量都是 最终变量。

class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final num x, y;

  const ImmutablePoint(this.x, this.y);
}
           

常量构造函数并不总是创建常量。有关详细信息,请参考有关 使用构造函数 的部分。

工厂构造函数

在实现并非总是创建其类的新实例的构造函数时,请使用 factory 关键字。 例如,工厂构造函数可能从缓存返回一个实例,或者可能返回一个子类型的实例。

以下示例演示了工厂构造函数从缓存中返回对象的方法:

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) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }

  Logger._internal(this.name);

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

提示: 工厂构造函数不允许使用 this 关键字

就像调用其他构造函数一样,调用工厂构造函数:

var logger = Logger('UI');
logger.log('Button clicked');
           

方法

方法是提供对象行为的函数。

实例方法

对象上的实例方法可以访问实例变量。 以下示例中的 distanceTo() 方法是一个实例方法的示例:

import 'dart:math';

class Point {
  num x, y;

  Point(this.x, this.y);

  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}
           

获取器和修改器

获取器和修改器是特殊的方法,可提供对对象属性的读写访问权限。回想一下,每个实例变量都有一个隐式的获取器,如果可以的话还有一个修改器。你就可以通过使用 get 和 set 关键字实现获取器和修改器来创建其他属性:

class Rectangle {
  num left, top, width, 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;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}
           

使用获取器和修改器时你可以从实例变量开始,然后使用方法将它们包装起来而无需更改客户端代码。

提示: 不管是否明确定义了获取器,增量(++)之类的运算符都以预期的方式工作。为避免任何意外的副作用,操作员只调用一次获取器,并将其值保存在一个临时变量中。

抽象方法

实例,获取器或修改器方法可以是抽象的,仅定义一个接口,将其留给其他类实现。抽象方法只能存在于 抽象类 中。

要使方法抽象,请使用分号(;)来代替方法主体:

abstract class Doer {
  // Define instance variables and methods...
  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide an implementation, so the method is not abstract here...
  }
}
           

抽象类

使用 abstract 修饰符定义一个抽象类 - 一个无法实例化的类。 抽象类通常用于某些实现中,用于定义接口。 如果您希望抽象类看起来可实例化,请定义一个工厂构造函数。

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

// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
  // Define constructors, fields, methods...

  void updateChildren(); // Abstract method.
}
           

隐式接口

每个类都隐式定义一个接口,该接口包含该类及其实现的所有接口的所有实例成员。如果你要创建一个支持B类 API 的A类而不继承B的实现,则A类应实现B接口。

一个类通过在 Implements 子句中声明一个或多个接口,然后提供接口所需的 API 来实现一个或多个接口。例如:

// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

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

// An implementation of the Person interface.
class Impostor implements Person {
  get _name => '';

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

// greetBob 方法传入名称为 person(类型为 Person 类)的参数
String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}
           

以下是指定一个类实现多个接口的示例:

扩展类

使用 extends 来创建一个子类,在子类中使用 super 来父类:

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

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

重载成员

子类可以覆盖实例方法,获取器和修改器。你可以使用 @override 来声明你需要覆盖的成员:

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

缩小

代码类型安全的方法参数或实例变量的类型,可以使用 covariant 关键字。

重写运算符

你可以重载以下表中的运算符。例如,如果定义一个 Vector 类,则可以定义一个 + 方法来添加两个向量。

< + | []
> / ^ []=
<= ~/ & ~
>= * << ==
% >>

提示: 你可能已经注意到,= 是不可重载的运算符。表达式

e1 != e2 只是 !(e1 == e2)

的语法糖。

下面是一个覆盖 + 和 - 运算符的类的示例:

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // Operator == and hashCode not shown. For details, see note below.
  // ···
}

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

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}
           

如果你重载 ==,则还应该覆盖

Object's hashCode getter

。有关重写 == 和 hashCode 的示例,请参考Implementing map keys。

更多有关 重载 的信息,请参考 扩展类。

noSuchMethod()

想要在使用不存在的方法或实例变量时进行检测或作出反应,则可以重写

noSuchMethod()

class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}
           

除非满足以下条件之一,否则不能调用未实现的方法:

  • 接收器具有静态类型的

    dynamic

    (??? 我也没懂)
  • 接收器具有定义未实现方法的静态类型(抽象也是可以的),接收器的动态类型具有

    noSuchMethod()

    的实现,该实现与

    Object

    类中的实现不同。

更多信息请参考 NoSuchMethod Forwarding Specification。

枚举类型

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

使用枚举

使用

enum

关键字声明枚举类型:

枚举中的每个值都有一个索引获取器,它返回该值在枚举声明中从零开始的位置。例如,第一个值的索引为0,第二个值的索引为1。

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

           

要获取枚举中所有值的列表,请使用枚举的值常量。

List<Color> 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: // Without this, you see a WARNING.
    print(aColor); // 'Color.blue'
}
           

枚举类型具有以下限制:

  • 你不能继承,混入或实现一个枚举。
  • 你不能显式实例化枚举。

有关更多信息,请参见 Dart语言规范。

向类里添加功能:mixins

Mixins 是一种在多个类层次结构中重用类代码的方法。

要使用 mixin,请使用 with 关键字,后跟一个或多个 mixin 名称。以下示例显示了两个使用 mixins 的类:

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

// 用起来有点像 trait
class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}
           

要实现 mixin,请创建一个扩展 Object 且不声明构造函数的类。除非你想要 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 可以调用未定义的方法),请使用

on

来指定所需的父类:

mixin MusicalPerformer on Musician {
  // ···
}
           

版本提示: Dart 2.1 中引入了对 mixin 关键字的支持。早期版本中的代码通常使用抽象类。 有关 2.1 版本中关于 mixin 的更多信息,请参阅 Dart SDK 更改日志 和 2.1 mixin 规范。

类变量及方法

使用

static

关键字实现类范围变量和方法。

静态变量

静态变量(类变量)对于类范围的状态和常量是很有用的:

class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}
           

静态变量在使用之前不会初始化。

提示: 该页面遵循 样式指南建议,建议使用 lowerCamelCase 这种格式来命名常量。

静态方法

静态方法(类方法)不能在实例上操作,因此你无法访问它。例如:

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

提示: 对于常见或广泛使用的实用程序和功能,请考虑使用顶级函数而非静态方法。

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

泛型

如果你了在 API 文档中看了基本数组类型、List,你会发现这些类型实际上是

List <E>

。<…> 标记将 List 标记为通用(或参数化)类型 - 一种具有正式类型参数的类型。按照惯例,大多数类型变量都具有单字母名称,例如 E,T,S,K 和 V。

  • E 代表 Element 元素
  • T 代表 Type 类型
  • S 没有找到这个资料
  • K 代表 Key 键
  • V 代表 Value 值

为什么使用泛型

泛型通常是实现类型安全所必需的,但是泛型不仅具有允许运行代码的优点,而且还具有更多的优点:

  • 正确指定泛型类型可以生成更优质的代码。
  • 你可以使用泛型来减少代码重复。

如果你打算一个列表只包含字符串,则可以将其声明为

List <String>

(将其称为 “字符串列表”)。这样其他程序员和工具可以检测到将非字符串类型的变量分配给该列表是错误的。

// static analysis: error/warning
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error
           

使用泛型的另一个原因是减少代码重复。泛型让你可以在多种类型之间共享单个接口和实现,同时仍可以利用静态分析。假设你创建了一个用于缓存对象的接口:

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

你会发现这个接口只能使用在对象类型上,但这时你又想要字符串的接口,为此你又创建了另一个接口:

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

后来,你又决定要想要一个针对数字的接口…

泛型可以省去创建所有这些接口的麻烦。相反,你可以创建一个带有类型参数的接口:

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}
           

在此代码中,T 是替代类型。你可以将其视为占位符,以供后续开发人员定义。

使用集合文字

List,Set 和 Map文字 可以被参数化。 参数化文字与你已经看到的文字一样,只不过你在左括号之前添加了

<type>

(用于 Lists 和 Sets)或

<keyType,valueType>

(用于 Maps)。这是使用类型文字的示例:

var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <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 = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
           

限制参数类型

在实现泛类型时,你可能希望限制其参数的类型,那么就可以使用继承来做到这一点。

class Foo<T extends SomeBaseClass> {
  // Implementation goes here...
  String toString() => "Instance of 'Foo<$T>'";
}

class Extender extends SomeBaseClass {...}
           

可以将

SomeBaseClass

或其任何子类用作通用参数:

var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();

           

也可以不指定泛类型参数:

var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'
           

指定任何非 SomeBaseClass 类型的参数都会导致错误:

通用方法

最初,Dart 的通用支持仅限于类。一种称为通用方法的较新语法,允许在方法和函数上使用类型参数:

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

在这里,第一个泛类型参数(

<T>

)允许你在多个地方使用类型参数 T:

  • 在函数的返回类型中 (

    T

    )
  • 在参数类型中 (

    List<T>

    )
  • 在方法变量中 (

    T tmp

    )

更多有关泛类型的信息,请参考 Using Generic Methods

库和可见性

导入

import

和 库指令

library

可以帮助你创建模块化且可共享的代码库。库不仅提供 API,而且是私有的单位:以下划线 (_) 开头的标识符仅在库的内部可见。每个 Dart 应用程序都是一个库,即使它不使用库指令。

可以使用 软件包 分发库。

使用库

使用

import

指定如何在另一个库的作用域中使用一个库中的命名空间。

要导入的唯一必需参数是指定库的 URI。对于内置库,其 URI 是特别的 dart:scheme。 对于其他库,可以使用文件系统路径或 package:scheme。

package

:方案指定由包管理器(例如 pub 工具)提供的库。 例如:

提示: URI 代表统一资源标识符。 URLs(统一资源定位符)是一种常见的URI。

指定库前缀

如果导入两个标识符冲突的库,则可以为其中一个库指定前缀。例如,如果 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;
           

懒加载一个库

延迟加载(也称为懒加载)允许 Web 应用程序在需要库时以及在需要时按需加载库。在某些情况下,你可能会使用到延迟加载:

  • 为了减少网络应用的初始启动时间。
  • 执行 A/B 测试 - 例如,尝试算法的替代实现。
  • 加载很少使用的功能,例如可选的屏幕和对话框。

只有 dart2js 支持延迟加载。 Flutter、Dart VM 和 dartdevc 不支持延迟加载。更多相关信息,参考 issue #33118 和 issue #27776

要延迟加载一个库时,首先必须使用

deferred as

导入它。

当你需要该库时,请使用库的标识符调用并

loadLibrary()

方法。

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

在前面的代码中,

await

关键字将暂停执行,直到加载该库为止。有关

async

await

的更多信息,请参考 asynchrony support.。

你可以在一个库上多次调用

loadLibrary()

而不会出现问题。因为该库仅加载一次。

使用延迟加载时,请注意以下几点:

  • 延迟库的常量不是导入文件中的常量。请记住,这些常量在加载延迟的库之前不存在。
  • 你不能在导入文件中使用延迟库中的类型。而是考虑将接口类型移到由延迟库和导入文件导入的库中。
  • Dart 将

    deferred

    作为命名空间隐式地将

    loadLibrary()

    插入到您定义的命名空间中。

    loadLibrary()

    函数返回 Future。

实现库

请参阅 创建库包 以获取有关如何实现库包的建议,包括:

  • 如何构建库的源代码
  • 如何使用

    export

    指令
  • 何时使用

    part

    指令
  • 何时使用

    library

    指令

异步支持

Dart 库充是返回 Future 或 Stream 对象的函数。这些函数是异步的,它们被设置在可能耗时的操作(例如 I/O)之后返回,而无需等待该操作完成。

async

await

关键字支持异步编程,使你可以编写出看似同步实则异步的代码。

Handling Futures

当你需要完成 Future 的结果时,有两种选择:

  • 使用

    async

    await

  • 如 in the library tour 中所述,使用 Future API

使用

async

await

的代码是异步的,但看起来很像是同步代码。例如下面是一些使用

await

等待异步函数结果的代码:

要使用

await

,代码必须位于

async

函数中,该函数被标记为

async

Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}
           

提示: 尽管异步功能可能会执行耗时的操作,但它不会等待这些操作。相反,异步函数仅在遇到第一个

await

表达式(details)之前执行。 然后它返回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 对象指示返回一个对象的承诺。

await

表达式的值是返回对象。

await

表达式使执行暂停,直到该对象可用为止。

如果在使用

await

时收到编译错误,请确保

await

在异步函数中。

例如,要在应用程序的

main()

函数中使用

await

,必须将

main()

的正文标记为

async

Future main() async {
  checkVersion();
  print('In main: version is ${await lookUpVersion()}');
}
           

声明异步函数

异步函数是一个函数,其主体带有

async

修饰符。

向函数添加

async

关键字使其返回 Future。例如,考虑以下同步函数,该函数返回一个 String:

如果将其更改为异步函数,则返回的值将为 Future:

请注意,该函数的主体不需要使用 Future API。 Dart 会在必要时创建 Future 对象。 如果你的函数没有返回有用的值,请使其返回类型

Future <void>

有关使用 futures,

async

await

的交互式介绍,请参见 异步编程代码实验室。

Handling Streams

当需要从

Stream

中获取值时,有两种选择:

  • 使用

    async

    和 异步for循环(

    await for

    )。
  • 使用 Stream API,如 Stream 文档 中所述。

提示: 在使用

await

之前,请确保它使代码清晰,并且你确定确实要等待所有 stream 的结果。例如,你通常不应将

await

用于UI事件侦听器,因为UI框架会发送无休止的事件流。

异步 for 循环具有以下形式:

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

表达式的值必须具有 Stream 类型,执行过程如下:

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

要停止监听 Stream,可以使用

break

return

语句,该语句会脱离 for 循环并取消 Stream。

如果在实现异步 for 循环时遇到编译错误,请确保

await for

位于

async

函数中。

例如,要在应用程序的 main() 函数中使用异步 for 循环,必须将

main()

的正文标记为

async

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

更多有关异步编程的信息,请参考文档的 dart:async 部分。

生成器

当需要延迟生成值序列时,请考虑使用生成器函数。Dart 具有对两种生成器功能的内置支持:

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

要实现同步生成器函数,请将功能主体标记为

sync *

,并使用

yield

语句传递值:

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

要实现异步生成器函数,请将函数主体标记为

async *

,并使用

yield

语句传递值:

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

方法。

在下面的示例中,

WannabeFunction

类定义了一个 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');
}
           

*让上述代码在 DartPad 中试运行

隔离器

大多数计算机甚至在移动平台上,都具有多核CPU。为了利用这些内核,开发人员通常使用并发运行共享内存线程。但是,共享状态并发容易出错,并且可能导致你需要一些复杂的代码。

所有 Dart 代码都在隔离器内运行,而不是线程。每个隔离区都有自己的内存堆,从而确保隔离区之间无法互相访问状态。

有关更多信息,请参考以下内容:

  • Dart asynchronous programming: Isolates and event loops
  • dart:isolate API reference, including Isolate.spawn() and TransferableTypedData
  • Background parsing cookbook on the Flutter site
  • Isolate sample app

类型定义

在 Dart 中,函数是对象,就像字符串和数字也是对象一样。类型定义 或 函数类型别名 为函数类型提供一个名称,你可以在声明字段和返回类型时使用该名称。当将函数类型分配给变量时,类型定义会保留类型信息。

以下代码不使用

typedef

class SortedCollection {
  Function compare;

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

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

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

  // All we know is that compare is a function,
  // but what type of function?
  assert(coll.compare is Function);
}
           

分配

f

进行比较时,类型信息会丢失。

f

的类型是 (

Object

Object

) →

int

(where → means return),而比较的类型是 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);
}
           

提示: 当前,typedef 仅限于函数类型。我们希望这种情况会改变。

因为 typedef 只是别名,所以它们提供了一种检查任何函数类型的方法。例如:

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

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

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

元数据

使用元数据提供有关你的代码的其他信息。元数据声明以字符

@

符号开头,后跟对编译常量的引用(例如

deprecated

)或对常量构造函数的调用。

所有 Dart 代码都可以使用两个注释:

@deprecated

@override

。 有关使用

@override

的示例,请参见扩展类。以下是使用

@deprecated

注释的示例:

class Television {
  /// _Deprecated: Use [turnOn] instead._
  @deprecated
  void activate() {
    turnOn();
  }

  /// Turns the TV's power on.
  void turnOn() {...}
}
           

你可以定义自己的元数据注释。以下定义带有两个参数的 @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');
}
           

元数据可以出现在库,类,typedef,类型参数,构造函数,工厂,函数,字段,参数或变量声明之前,也可以出现在导入或导出指令之前。你可以在运行时使用反射来检索元数据。

注释

Dart 支持单行注释、多行注释及文档注释。

单行注释

单行注释以

//

开头。Dart 编译器会自动忽略 // 开始到和行尾之间的所有内容。

void main() {
  // TODO: refactor into an AbstractLlamaGreetingFactory?
  print('Welcome to my Llama farm!');
}

           

多行注释

多行注释以

结尾。 Dart 编译器会自动忽略 之间的所有内容(*除非注释为文档注释)。多行注释可以嵌套。

void main() {
  /*
   * This is a lot of work. Consider raising chickens.

  Llama larry = Llama();
  larry.feed();
  larry.exercise();
  larry.clean();
   */
}

           

文档注释

文档注释是以

///

/**

开头的多行或单行注释。在连续的行上使用 /// 与多行 doc 注释具有相同的效果。

在文档注释中,除非括在方括号中,否则 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) {
    // ...
  }
}
           

在生成的文档中,[Food] 成为指向

Food

类的API文档的链接。

要解析 Dart 代码并生成 HTML 文档,你可以使用 SDK 的文档生成工具。有关生成的文档的示例,请参阅 Dart API 文档。 有关如何构建注释的建议,请参阅 Dart Doc 注释准则。

总结

该页面总结了 Dart 语言的常用功能。我们也正在实现更多功能,但希望它们不会破坏现有代码。有关更多信息,请参见 Dart语言规范 和 Effective Dart。

要了解有关 Dart 核心库的更多信息,请参阅 A Tour of the Dart Libraries。

至此我们已经初步地了解了 Dart 语言,接下来我们要开始学习 2.学习大部分基础 Widget 的使用方式