Dart 2官方文档最新中文版(Dart 编程语言导览)完整版

Dart编程语言导览 Part 1

本文展示如何使用各个主要Dart功能,从变量、运算符到类和库,这里假定你已经至少了解如何使用一种其它编程语言。

要了解更多有关 Dart核心库的知识,请参见核心库导览。

小贴士: 你可以借助DartPad来使用Dart语言的大部分功能 (了解更多。

打开DartPad

一个Dart基础程序

以下代码使用了Dart的大量最基础的功能:

// 定义一个函数

printInteger(int  aNumber)  {

  print('The number is $aNumber.');  // 打印到控制台

}

// 这是应用开始执行的地方。

main()  {

  var  number  =  42;  // 声明和初始化一个变量

  printInteger(number);  // 调用函数

}

以下是这个程序所使用到的可用于所有(或几乎所有)的Dart应用的部分:

// 这是一条注释

单行注释。Dart还支持多行注释和文件注释。更多详情,请参见 注释.

int

一种类型。其它的一些 内置类型 有 String, Listbool

42>

一个数字字面量。数字字面量是一种编译时常量。

print()

一种显示输出的方便的方式。

'...' (or "...")

一个字符串字面量。

$*variableName* (或 ${*expression*})

插值字符串:包含等价于字符串字面量的变量或表达式的字符串。更多相关信息,请参见 字符串.

main()

在应用执行开始处的特殊的、必须的顶级函数。更多相关信息,请参见 main()函数.

var

一种无需指定类型声明变量的方式。

Note: 本站的代码遵循 Dart 样式指南中的准则。

重要概念

在学习Dart编程语言的过程中,请在心中保持以下事实和概念:

  • 在变量中放置的所有内容都是对象,并且每个对象都是一个类的实例。包括数字、函数和 null 都是对象。所有的对象都继承自 Object 类。
  • 虽然Dart是强类型语言,类型标注却是可选的,因为Dart可以推导类型。在以上的代码中, number 所推导的类型是 int。在你想要显式地说明无需设定类型, 使用特殊类型 dynamic.
  • Dart支持泛型,如 List<int> (整型列表) 或 List<dynamic> (任意类型对象的列表)。
  • Dart支持顶级函数(如 main()),以及与类或对象绑定的函数 (分别为静态和实例方法)。你也可以在函数内创建函数(嵌套或局部函数)。
  • 类型地,Dart支持顶级变量,以及与类或对象绑定的变量(静态和实例变量)。实例变量有时也称为字段或属性。
  • 不同于Java,Dart没有public, protectedprivate这些关键字。 如果一个标识符以下划线(_)开头,它是对库私有的。详情请参见 库和可见性.
  • 标识符可以字母或下划线 (_)开头,后接这些字符和数字的任意组合。
  • Dart既有表达式(拥有运行时值)也有语句(没有运行时值)。例如, 条件表达式则没有值。一条语句通常包含一个或多个表达式,但一个表达式不能直接包含一条语句。
  • Dart工具可以报出两类问题:警告(warning)和错误(error) 。警告只是表明你的代码可能无法运行,但并不会阻止程序的执行。错误可以是编译时或运行时的。编译时错误会完全阻止代码的执行,运行时错误会在代码执行时导致 异常 的抛出。

关键字

下表中列出了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 的词为上下文关键字,仅在指定位置具有含义。它们在任何地方都是有效的标识符。
  • 上标为 2 的词是内置标识符。为简化将JavaScript代码移植到Dart的任务,这些关键字在大部分地方是有效的关键字,但不能用于类或类型名称,或者是作为导入的前缀。
  • 上标为 3 的词是在Dart 1.0版本之后添加的与异步支持相关的更新的、有限的保留词。你无法使用async, async*sync*标记的函数体内使用 awaityield 作为标识符。

表中的其它词都是保留词,无法用作标识符。

变量

以下是一个创建变量并初始化的示例:

var  name  =  'Bob';

变量存储引用。名为name的变量包含一个对值为”Bob”的String对象的引用。

变量name的类型推导为String,但你可以通过指定类型来进行修改。如果对象不仅限于单个类型,按照设计指南将其指定为Objectdynamic类型。

dynamic name  =  'Bob';

另一个选项是显式地将其声明为所要推导的类型:

String  name  =  'Bob';

Note: 本页中对局部变量按照 样式指南推荐 使用 var,而非类型注解。

默认值

未初始化过的值有一个初始值null。即使是带有数值类型的变量初始值也是null,因为数值类型和Dart中的所有内容一样,都是对象。

int  lineCount;

assert(lineCount  ==  null);

Note: 生产代码会忽略 assert() 调用。而在开发期间,当 if 条件为假时 assert(*condition*) 会抛出一个异常。详情请参见 Assert。

Final和const

如果你不想要修改一个变量,使用 finalconst来替代 var 或加前类型前。final变量只能设置一次,const变量是编译时常量。(const变量是隐式的final)。final顶级或类变量在初次使用时进行初始化。

Note: 实例变量可以是 final ,但不能为 const。final实例变量必须在构造函数体开始之前进行初始化,通过构造函数参数在变量声明中或在构造函数的初始化程序列表中。

以下是一个创建和设置final变量的示例:

final  name  =  'Bob';  // 无类型标注

final  String  nickname  =  'Bobby';

你无法修改final变量的值:


name  =  'Alice';  // Error: a final variable can only be set once.

对你希望是编译时常量的变量使用const。如果const变量是类级别的,将其标记为 static const。在声明变量之处,设置值为编译时常量,如数值或字符串字面量、const变量或对常数的算术运算结果:

const  bar  =  1000000;  // 单位压强 (dynes/cm2)

const  double  atm  =  1.01325  *  bar;  // 标准大气

const 关键字不只是为声明常变量的。你还可以使用它来创建常量值,以及声明创建常量值的构造函数。任意变量可拥有常量值。

var  foo  =  const  [];

final  bar  =  const  [];

const  baz  =  [];  // 等价于 const []

你可以在const声明的初始化表达式中省略 const,像上面的 baz 。详情请参见 不要重复使用const.

你可以修改一个非final、非const变量的值,即使它曾经是一个const值:

foo  =  [1,  2,  3];  // 曾经是const []

但是不能修改const变量的值:


baz  =  [42];  // Error: Constant variables can't be assigned a value.

更多有关使用 const 创建常量值的内容,请参见 列表。

内置类型

Dart语言拥有对如下类型的特别支持:

  • 数值
  • 字符串
  • 布尔型
  • 列表 (也称为数组)
  • 映射
  • rune (用于在字符串表示Unicode字符串)
  • 符号

你可以使用字面量初始化任意这些特殊类型的对象。例如, 'this is a string' 是一个字符串字面量, true 是一个布尔型字面量。

因为Dart中的每个变量都引用一个对象 – 一个类的实例,通常可以使用构造函数来初始化变量。一些内置类型有它们自己的构造函数。例如,你可以使用 Map() 构造函数来创建一个映射。

数值

Dart有两种数值类型:

int

整型值根据平台不大于64位。在 Dart VM中,值可以为 -263 到 263 – 1。编译为JavaScript的Dart使用 JavaScript数值, 允许的值为 -253 到 253 – 1。

double

64位(双精度)浮点数值,如IEEE 754 标准中所描述。

intdoublenum 库。

整型是不包含小数点的数字。以下是一些定义整型字面量的示例:

var  x  =  1;

var  hex  =  0xDEADBEEF;

如果数值中包含小数点,则为double类型。以下是一些定义double字面量的示例:


var  y  =  1.1;

var  exponents  =  1.42e5;

在 Dart 2.1中,整型字面在需要时会自动转化为double值:

double  z  =  1;  // 等价于 z = 1.0.

Version note: 在Dart 2.1之前,在double上下文中使用整型字面量会报错。

以下是如何将字符串转化为数字及其反向操作:

// String -> int

var  one  =  int.parse('1');

assert(one  ==  1);

// String -> double

var  onePointOne  =  double.parse('1.1');

assert(onePointOne  ==  1.1);

// int -> String

String  oneAsString  =  1.toString();

assert(oneAsString  ==  '1');

// double -> String

String  piAsString  =  3.14159.toStringAsFixed(2);

assert(piAsString  ==  '3.14');

int类型指定了传统的按钮移动 (<<, >>)、与 (&)和 或 (|)运算符。例如:


assert((3  <<  1)  ==  6);  // 0011 << 1 == 0110

assert((3  >>  1)  ==  1);  // 0011 >> 1 == 0001

assert((3  |  4)  ==  7);  // 0011 | 0100 == 0111

字面量数字是编译时常量。很多算术表达式也是编译时常量,只要它们的运算项是运算为数值的编译时常量。

const  msPerSecond  =  1000;

const  secondsUntilRetry  =  5;

const  msUntilRetry  =  secondsUntilRetry *  msPerSecond;

字符串

Dart字符串是一个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}来将表达式的值放到字符串中。如果表达式是一个标识符,可以省略{}。要获取一个对象对应的字符串,Dart中调用对象的toString()方法。

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

Note: == 运算符用于测试两个对象是否相等。如果两个字符串拥有相同序列的代码单元则相等。

可以使用相邻字符串字面量或者了 + 运算符来拼接字符串:

var  s1  =  'String '

    'concatenation'

    " works even over line breaks.";

assert(s1  ==

    'String concatenation works even over '

        'line breaks.');

var  s2  =  'The + operator '  +  'works, as well.';

assert(s2  ==  'The + operator works, as well.');

另一种创建多行字符串的方式是使用三个单引号或三个双引号标记:


var  s1  =  '''

You can create

multi-line strings like this one.

''';

var  s2  =  """This is also a

multi-line string.""";

可以通过r前缀来创建“原生”字符串:

var  s  =  r'In a raw string, not even \n gets special treatment.';

参见 Rune 来获取如何在字符串中表达Unicode字符的详情。

字符串字面量是编译时常量,仅需编译时常量中的插值表达式运行结果为null或数值、字符串或布尔值。

// 这些可以在一个const字符串中使用

const  aConstNum  =  0;

const  aConstBool  =  true;

const  aConstString  =  'a constant string';

// 这些不可以在一个const字符串中使用

var  aNum  =  0;

var  aBool  =  true;

var  aString  =  'a string';

const  aConstList  =  [1,  2,  3];

const  validConstString  =  '$aConstNum $aConstBool $aConstString';

// const invalidConstString = '$aNum $aBool $aString $aConstList';

更多有关使用字符串的信息,参见 字符串和正则表达式。

布尔型

要表现布尔值,Dart中有一个名为bool的类型。仅有两个对象的类型为bool:布尔型字面量truefalse,它们都是编译时常量。

Dart的类型安全意味着你不能使用if (*nonbooleanValue*)assert (*nonbooleanValue*)这样的代码。而是要显式的检查值,类似这样:

// 检查空字符串

var  fullName  =  '';

assert(fullName.isEmpty);

// 检查0值

var  hitPoints  =  0;

assert(hitPoints  <=  0);

// 检查null值

var  unicorn;

assert(unicorn  ==  null);

// 检查NaN

var  iMeantToDoThis  =  0  /  0;

assert(iMeantToDoThis.isNaN);

列表

或许在几乎所有编程语言中最常见的集合(collection)都是数组,或有序的对象组。在Dart,数组是List对象,因此大部分会称其为列表。

Dart的列表字面量和JavaScript中的数组字面量很像。以下是一个简单的Dart列表:

var  list  =  [1,  2,  3];

Note: Dart推导 list 的类型为 List<int>。如果你尝试向这个列表中添加非整型对象,分析器或运行时会抛出错误。更多信息请阅读 类型推导。

列表使用基于0的索引,即0是第1个元素的索引,并且 list.length - 1 是最后一个元素的索引。你可以完全像JavaScript中那样获取一个列表的长度并引用列表元素:

var  list  =  [1,  2,  3];

assert(list.length  ==  3);

assert(list[1]  ==  2);

list[1]  =  1;

assert(list[1]  ==  1);

要创建一个为运行是常量的列表,在列表字面量之前添加 const


var  constantList  =  const  [1,  2,  3];

// constantList[1] = 1; // 取消这行的注释会导致报错

Dart 2.3 引入了展开运算符(...) 及判空展开运算符 (...?),提供一种向集合插入多个元素的简洁方式。

例如,你可以使用展开运算符 (...) 来将列表中的所有元素插入到另一个列表中:


var  list  =  [1,  2,  3];

var  list2  =  [0,  ...list];

assert(list2.length  ==  4);

如果展开运算符右侧的表达式有可能为null,可通过使用可判空展开运算符(...?)来避免异常:

var  list;

var  list2  =  [0,  ...?list];

assert(list2.length  ==  1);

更多有关使用展开运算符的详情和示例,参见展开运算符提议。

Dart 2.3 还引入了collection ifcollection for,可以使用条件(if)和循环 (for)来构建使用条件(if)和循环 (for)的集合。

以下是使用collection if来创建一个包含3项或4项的列表::

var  nav  =  [

  'Home',

  'Furniture',

  'Plants',

  if  (promoActive)  'Outlet'

];

以下是使用collection for来在将它们添加到另一个列表之前操作列表项的示例:


var  listOfInts  =  [1,  2,  3];

var  listOfStrings  =  [

  '#0',

  for  (var  i  in  listOfInts)  '#$i'

];

assert(listOfStrings[1]  ==  '#1');

更多有关使用collection if和for的示例,参见 控制流collection建议。

列表类型有很多操作列表的便捷的方法。有关列表更多的信息,请参见泛型.

Dart中的集(set)是一个包含独立项的无序集合。Dart对集的支持由集字面量和Set类型所提供。

Version note: 虽然Set类型一直是Dart的核心部分,集字面量在Dart 2.2中才引入。

以下是一个简单的Dart集,使用集字面量创建:


var  halogens  =  {'fluorine',  'chlorine',  'bromine',  'iodine',  'astatine'};

Note: Dart推导 halogens 的类型为 Set<String>。 如果你尝试向集添加错误类型的值,分析器或运行时会抛出错误。更多信息,请阅读类型推导部分。

要创建空集,使用带有类型前缀的{}或向类型为Set的变量赋值{}


var  names  =  <String>{};

// Set<String> names = {}; // 这也同样起作用

// var names = {}; // 创建一个映射,而非集

集或映射? 映射字面量的语法类型集字面量。因为映射字面量的优先级更高, {} 的默认为 Map 类型。如果你忘记对 {} 进行类型标注,或者它所赋值的变量,那么Dart会创建一个类型为 Map<dynamic, dynamic>的对象。

使用add()addAll() 方法向已有集添加子项:


var  elements  =  <String>{};

elements.add('fluorine');

elements.addAll(halogens);

使用 .length 来获取集中子项的数量:


var  elements  =  <String>{};

elements.add('fluorine');

elements.addAll(halogens);

assert(elements.length  ==  5);

创建为运行时常量的集,在集字面量前添加const


final  constantSet  =  const  {

  'fluorine',

  'chlorine',

  'bromine',

  'iodine',

  'astatine',

};

// constantSet.add('helium'); // 取消本行注释会导致报错

在Dart 2.3中,集也像列表一样支持展开运算符 (......?) 以及 collection if 和 for。更多信息,参见 列表展开运算符 部分的讨论。

有关集的更多知识,请参见 泛型.

映射

总的来说,映射是一个关联键和值的对象。键和值都可以为任意对象类型。每个键仅出现一次,但相同值可出现多次。Dart对映射的支持由映射字面量和 Map 类型所提供。

以下是一些简单的Dart映射,使用映射字面量创建:

var  gifts  =  {

  // Key:    Value

  'first':  'partridge',

  'second':  'turtledoves',

  'fifth':  'golden rings'

};

var  nobleGases  =  {

  2:  'helium',

  10:  'neon',

  18:  'argon',

};

Note: Dart推导 gifts 的类型为 Map<String, String> ,而nobleGases 的类型为 Map<int, String>。如果你尝试对任一映射添加错误类型的值,分析器或运行时会抛出错误。更多相关信息,请参阅 类型推导。

可以使用Map构造函数创建相同的对象:

var  gifts  =  Map();

gifts['first']  =  'partridge';

gifts['second']  =  'turtledoves';

gifts['fifth']  =  'golden rings';

var  nobleGases  =  Map();

nobleGases[2]  =  'helium';

nobleGases[10]  =  'neon';

nobleGases[18]  =  'argon';

Note: 你可能会希望看到 new Map() 而非仅仅是 Map()。从Dart 2中,关键字 new 为可选部分。更多详情,参见使用构造函数。

和JavaScript中一样向已有映射添加键值对:

var  gifts  =  {'first':  'partridge'};

gifts['fourth']  =  'calling birds';  // 添加一个键值对

以JavaScript同样的方式从映射中接收值:

var  gifts  =  {'first':  'partridge'};

assert(gifts['first']  ==  'partridge');

如果你在寻找一个不在映射中的键,会在返回中获取到null:

var  gifts  =  {'first':  'partridge'};

assert(gifts['fifth']  ==  null);

使用 .length来获取映射中键值对的数量:

var  gifts  =  {'first':  'partridge'};

gifts['fourth']  =  'calling birds';

assert(gifts.length  ==  2);

创建为编译时常量的映射,在映射字面量之前添加const


final  constantMap  =  const  {

  2:  'helium',

  10:  'neon',

  18:  'argon',

};

// constantMap[2] = 'Helium'; // 取消本行注释会导致报错

在Dart 2.3中,映射像列表一样支持展开运算符 (......?) 及collection if 和 for。有关详情及示例,参见 展开运算符提议。

更多有关映射的信息,参见 泛型.

runes

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

Unicode为全球书写系统中的每个字母、数字和符号定义了一个独立的数值。因为Dart字符串是一个UTF-16代码单元的序列,在字符串中表达32位Unicode值要求有特殊的语法。

通常表达一个Unicode代码点的方式为 \uXXXX,其中XXXX是一个4位数的16进制值。例如,心形字符串 (:heart:) 为 \u2665。要指定比4位16进制更多或更少的位数,需将值放在花括号中。例如,笑脸emoji (:laughing:) 为 \u{1f600}

String 类有多个可用于提取rune信息的属性。 codeUnitAtcodeUnit属性返回16-位代码单元。使用 runes 属性来获取字符串的rune。

以下示例描述runes、16-位代码单元以及32-位代码单元之间的关系。点击 Run 来实时查看rune。

Note: 在使用列表运算符操作runes时要小心。这种方法根据具体语言、字符集和操作可能会很容易崩溃。更多相关信息,参见Stack Overflow上的 如何倒排Dart中的字符串?

符号

符号(Symbol) 对象表示在Dart程序中声明的一个运算符或标识符。你可能永远都不会需要用到符号,但它们对于通过名称引用标识符的 API 有无限的价值,因为最小化会修改标识符的名称但不修改标识符符号。

使用符号字面量获取一个标识符的符号,它只是#后接标识符。

#radix

#bar<code class="language-nocode">

符号字面量是编译时常量。

本文首发地址:Alan Hou的个人博客

感谢分享!! :grinning:

你的帖子已经被社区标记并被临时隐藏。

Dart编程语言导览 Part 3

断言

在开发过程中,可以使用断言语句 assert(*condition*, *optionalMessage*); 来在布尔条件为false时打断正常的执行。可以通过本导览找到断言语句的很多示例。以下是另外一些示例:


// 确保变量值为非null

assert(text  !=  null);

// 确保值小于100

assert(number  <  100);

// 确保这是一个https链接

assert(urlString.startsWith('https'));

要为断言关联消息,对assert添加一个字符串作为第2个参数。

assert(urlString.startsWith('https'),

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

assert 的第1个参数可以是解析为布尔值的任意表达式。如果表达式的值为true,断言成功且执行继续。如果它为false,断言失败且会抛出异常(一个[AssertionError)。

断言到底做了什么呢?这取决于你所使用的工具和框架:

  • Flutter在[调试模式中启用断言。
  • 仅针对开发的工具如 [dartdevc 通常默认启用断言。
  • 一些工具,如 [dart,支持通过命令行标记的断言: --enable-asserts.

在生产模式下,会忽略断言, assert 的参数不会被执行。

异常

Dart代码可以抛出和捕获异常。异常是表示预期外的事情发生时的报错。如未捕获到异常,抛出异常的 [隔离(isolate)被挂起,并且通常隔离及其程序会被终止。

不同睛Java,Dart的异常都是非检查型异常。方法未声明它们所要抛出的异常,那么也不要求你捕获任何异常。

Dart提供 [Exception 类型,以及很多预定义的子类型。当然也可以定义自己的异常。但是Dart程序可抛出任意非空对象(不只是Exception和Error对象)来作为异常。

Throw

以下是一个抛出异常的示例:

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

你也可以抛出自己的对象:


throw  'Out of llamas!';

Note: 生产质量的代码通常抛出实现[Error的类型。

因为抛出异常是一个表达式,你可以在=>及其它允许使用表达式的任何地方抛出异常:

try  {

  breedMoreLlamas();

}  on  OutOfLlamasException  {

  buyMoreLlamas();

}

处理会抛出一个类型以上异常的代码,可以指定多个捕获分分钟。第一个匹配所抛出对象类型的catch从句会处理该异常。如果catch从句未指定类型,该分分钟可处理任意类型抛出的对象:

try  {

  breedMoreLlamas();

}  on  OutOfLlamasException  {

  // 一个具体异常

  buyMoreLlamas();

}  on Exception catch  (e)  {

  // 任何其它异常

  print('Unknown exception: $e');

}  catch  (e)  {

  // 无具体类型,处理所有类型

  print('Something really unknown: $e');

}

如以上代表所示,可以使用 oncatch 或者同时使用。在需要指定异常类型时使用 on 。在你的异常处理器需要异常对象时使用 catch

可以为catch()指定一个或两个参数。第1个是抛出的异常,第二是栈跟踪 (一个 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++);  // 运行时错误

  }  catch  (e)  {

    print('misbehave() partially handled ${e.runtimeType}.');

    rethrow;  // 允许调用者查看该异常

  }

}

void  main()  {

  try  {

    misbehave();

  }  catch  (e)  {

    print('main() finished handling ${e.runtimeType}.');

  }

}

Finally

要确保一些代码不管是否抛出异常时都执行,使用 finally 从句。如果没有 catch 从句匹配该异常,在finally从句执行后会推出异常。

try  {

  breedMoreLlamas();

}  finally  {

  // 即使在没有抛出异常时也会清理

  cleanLlamaStalls();

}

finally 从任意匹配的catch 从句之后执行:

try  {

  breedMoreLlamas();

}  catch  (e)  {

  print('Error: $e');  // 首先处理异常

}  finally  {

  cleanLlamaStalls();  // 然后进行清理

}

阅读库导览的 [异常 的一节了解更多信息。

Dart是一个带有类和基于mixin继承的面向对象语言。每个对象都是类的实例,所有类都继承自 [Object.。* 基于mixin的继承* 表示虽然每个类(除Object外)都只有一个超类,一个类主体可在多个类级别中复用。

使用类成员

对象有由函数和数据(分别为方法和实例变量)组成的成员。在调用方法时,对一个对象进行调用:该方法可访问对象的函数和数据。

使用点号 (.) 来引用于实例变量或方法:

var  p  =  Point(2,  2);

// 设置实例变量y的值

p.y  =  3;

// 获取 y 的值

assert(p.y  ==  3);

// 对 p 调用 distanceTo()

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

使用 ?. 代替 . 来避免最左侧操作项为null时的异常:


// 如果p 不为null, 设置它的y 值为 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});

Version note: 关键字 new Dart 2中成为可选项。

一些类提供 [常量构造函数。要使用常量构造函数创建编译时常量,在构造函数名前放置关键字 const

var  p  =  const  ImmutablePoint(2,  2);

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

var  a  =  const  ImmutablePoint(1,  1);

var  b  =  const  ImmutablePoint(1,  1);

assert(identical(a,  b));  // 它们是相同的实例!

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

// 这里有很多const 关键字

const  pointAndLine  =  const  {

  'point':  const  [const  ImmutablePoint(0,  0)],

  'line':  const  [const  ImmutablePoint(1,  10),  const  ImmutablePoint(-2,  11)],

};

可以省略掉第一次使用之外的所有 const 关键字:


// 仅一个const, 它创建常量上下文

const  pointAndLine  =  {

  'point':  [ImmutablePoint(0,  0)],

  'line':  [ImmutablePoint(1,  10),  ImmutablePoint(-2,  11)],

};

如果常量构造函数在常量上下文之外且未使用 const调用,它创建一个非常量对象。

var  a  =  const  ImmutablePoint(1,  1);  // 创建一个常量

var  b  =  ImmutablePoint(1,  1);  // 不创建常量

assert(!identical(a,  b));  // 不是相同的实例!

Version note: 关键字 const 在Dart 2的常量上下文成为可选。

获取一个对象的类型

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


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

到这里,你已学习了如何使用类。本节后面的部分展示如何实现类。

实例变量

以下是如何声明实例变量:


class  Point  {

  num  x;  // 声明实例变量 x, 初始值为 null

  num  y;  // 声明 y, 初始值为 null

  num  z  =  0;  // 声明 z, 初始值为 0

}

所有未初始化的实例变量的值为 null

所有的实例变量生成一个getter 方法。非final实例变量也生成一个隐式setter 方法。更多详情,参见[getters和setters。

class  Point  {

  num  x;

  num  y;

}

void  main()  {

  var  point  =  Point();

  point.x  =  4;  // 使用针对x 的setter方法

  assert(point.x  ==  4);  // 使用针对x 的getter方法

  assert(point.y  ==  null);  // 默认值为null

}

如果在其声明处实例化实例变量(而非在构造函数或方法中进行),在实例创建时设置值,即在构造函数及期初始化程序列表执行前。

构造函数

通过和类相同的名称创建函数来声明构造函数(以及命名构造函数中所描述的可选额外标识符)。最常见形式的构造函数,生成构造函数,创建一个类的新实例:

class  Point  {

  num  x,  y;

  Point(num  x,  num  y)  {

    // 有更好的实现方式,请保持关注

    this.x  =  x;

    this.y  =  y;

  }

}

this 关键字引用的是当前实例。

Note: 仅在有名称冲突时使用 this 。否则Dart 的样式省略 this

为实例变量赋值构造函数的参数非常普通 ,Dart提供了语法糖来进行简化:

class  Point  {

  num  x,  y;

  // 在构造函数体运行前用于

  // 设置 x 和 y 的语法糖

  Point(this.x,  this.y);

}

默认构造函数

如未声明构造函数,会为你提供默认的构造函数。默认构造函数没有参数并在超类中调用无参数构造函数。

未继承构造函数

子类不会从超类中继承构造函数。未声明构造函数的子类仅拥有默认构造函数(无参数,无名称)。

命名构造函数

使用命令构造函数来为类实现多个构造函数或增强清晰度:

class  Point  {

  num  x,  y;

  Point(this.x,  this.y);

  // 命名构造函数

  Point.origin()  {

    x  =  0;

    y  =  0;

  }

}

记住构造函数并不会继承,也就表示超类的构造函数不会由子类继承。如果希望通过超类中定义的命名构造函数创建子类 ,必须在子类中实现这个构造函数。

调用非默认超类构造函数

默认,子类中的构造函数调用超类中的未命名、无参数的构造函数。超类的构造函数在构造函数体的开头调用。如果还使用了 初始化程序列表,它在超类调用前执行。总之,执行的顺序如下:

  1. 初始化程序列表initializer list
  2. 超类的无参构造函数
  3. 主类的无参构造函数

如果超类中没有未命名的无参构造函数,那么就必须手动调用超类的一个构造函数。在冒号(:)后、刚好在构造函数体前(如有)指定超类构造函数。

在下例中,Employee类的构造函数调用其超类Person的命名构造函数。点击 Run 来执行代码。

因为超类构造函数的参数在调用构造函数前运行,参数可以是像函数调用这样的表达式:

class  Employee  extends  Person  {

  Employee()  :  super.fromJson(getDefaultData());

  // ···

}

Warning: 超类构造函数的参数无法访问 this。例如,参数可调用静态方法但无法调用实例方法。

Dart编程语言导览 Part 4

初始化程序列表

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

// 初如化程序列表在构造函数体运行前

// 设置实例变量

Point.fromJson(Map<String,  num>  json)

    :  x  =  json['x'],

      y  =  json['y']  {

  print('In Point.fromJson(): ($x, $y)');

}

Warning: 初始化程序的右侧无法访问 this

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

Point.withAssert(this.x,  this.y)  :  assert(x  >=  0)  {

  print('In Point.withAssert(): ($x, $y)');

}

初始化程序列表在设置final字段时非常方便。以下示例在初始化程序列表中初始化了3个final字段。点击 Run 来执行代码。

重定向构造函数

有时构造函数的目的仅是重定向到相同类中的其它构造函数。重定向构造函数体为空,构造函数调用出现在冒号(:)之后。

class  Point  {

  num  x,  y;

  // 这个类的主构造函数

  Point(this.x,  this.y);

  // 代理重定向至主构造函数

  Point.alongXAxis(num  x)  :  this(x,  0);

}

常量构造函数

如果类产生永不修改的对象,可以让这些对象成为编译时常量。此时,定义一个 const 构造函数并确保所有的实例变量为 final


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是库私有的,这主要借助于

  // 名称前的下划线_

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

  }

}

Note: 工厂构造函数无法访问 this

可以像调用其它构造函数那样调用工厂构造函数:

var  logger  =  Logger('UI');

logger.log('Button clicked');

方法

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

实例方法

对象的实例方法可以访问实例变量和 this。下面示例中的 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);

  }

}

getter和setter

getter和setter是提供对象属性的读写访问的特殊方法。记得每个实例变量有一个隐式getter,以及如若合适时的setter。可以使用getset关键字通过实现getter和setter来创建额外的属性:

class  Rectangle  {

  num left,  top,  width,  height;

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

  // 定义两个计算属性: right 和 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);

}

通过 getter和setter,可以以实例变量开始,然后通过方法封装它们,都无需修改客户端模式。

Note: 不论是否显式的定义getter递增 (++)等运算符都以预期的方式运行。 为避免任意预期的副作用,运算符仅调用getter一次,在临时变量中w 保存它的值。

抽象方法

实例、getter和setter方法可以是抽象的,定义接口并将其实现留给其它类。抽象方法仅能存在于 抽象类中。

要让方法为抽象的,使用分号 (:wink: 而非方法体:

abstract  class  Doer  {

  // 定义实例变量和方法...

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

}

class  EffectiveDoer  extends  Doer  {

  void  doSomething()  {

    // 提供一个实现,因此这里该方法不是抽象的...

  }

}

抽象类

使用 abstract 修饰符来定义一个抽象类 – 一个无法实例化的类。抽象类对于定义接口非常有用,通常伴有一些实现。如果希望抽象类为可实例化的,定义一个 工厂构造函数。

抽象类通常有 抽象方法。这下是一个声明拥有抽象方法的抽象类的示例:


// 这个类声明为抽象类,

// 因此无法实例化。

abstract  class  AbstractContainer  {

  // 定义构造函数、字段、方法...

  void  updateChildren();  // 抽象方法

}

隐式接口

每个类隐式定义包含类的所有实例成员的接口及它实现的任意接口。如果希望创建支持类B而不继承B的应用的类A,类 A 应实现 B 的 接口。

类A通过在implements从句中通过声明它们来实现一个或多个接口,然后中提供这些接口所要求的 API。例如:

// A person. 包含greet()的隐式接口

class  Person  {

  // 在接口中,但仅在本库中可见

  final  _name;

  // 不在接口中,因为这是一个构造函数

  Person(this._name);

  // 在接口中

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

}

// 一个Person接口的实现

class  Impostor  implements  Person  {

  get _name  =>  '';

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

}

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

void  main()  {

  print(greetBob(Person('Kathy')));

  print(greetBob(Impostor()));

}

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

class  Point implements  Comparable,  Location  {...}

继承类

使用 extends 来创建子类,并用 super 来引用超类:

class  Television  {

  void  turnOn()  {

    _illuminateDisplay();

    _activateIrSensor();

  }

  // ···

}

class  SmartTelevision  extends  Television  {

  void  turnOn()  {

    super.turnOn();

    _bootNetworkInterface();

    _initializeMemory();

    _upgradeApps();

  }

  // ···

}

重载成员

子类可重载实例方法、getters和setters。可以使用 @override 标注来表明你想要重载一个成员:


class  SmartTelevision  extends  Television  {

  @override

  void  turnOn()  {...}

  // ···

}

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

可重载运算符

可以重载下表中所显示的运算符。例如,如果你定义了一类Vector类,可以定义一个+ 方法来对两个向量进行想加。

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

Note: 你可能流到到 != 并不是一个可重载运算符。表达式 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);

  // 未显示运算符 == 和 hashCode。更多详情,参见下方文字。

  // ···

}

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的 hashCode getter。有关重载 ==hashCode的示例,参见 实现映射的键。

更多有关重载总的信息,参见 继承类。

noSuchMethod()

要在代码尝试使用不存在的方法或实例变量时进行监测或回应,可以重载noSuchMethod()

class  A  {

  // 除非你重载noSuchMethod,使用一个不存在的

  // 成员会导致NoSuchMethodError报错

  @override

  void  noSuchMethod(Invocation invocation)  {

    print('You tried to use a non-existent member: '  +

        '${invocation.memberName}');

  }

}

无法调用一个未实现的方法,以下情况除外:

  • 接收者有一个静态类型dynamic
  • 接收者有一个定义未实现方法的静态类型(抽象方法没有问题),并且接收者的动态类型有一个不同于Object类中的对 noSuchMethod() 的实现。

更多信息,参数非正式的 noSuchMethod 推送规范。

枚举类型

枚举类型,常称为枚举或enum, 是一种用于展示固定数量的常量值的特殊的类。

使用enum

使用enum关键字声明一个枚举类型:

assert(Color.red.index  ==  0);

assert(Color.green.index  ==  1);

assert(Color.blue.index  ==  2);

获取枚举中所有值的列表,可使用enum的 values 常量。


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:  // 没有这条,会看到一条WARNING

    print(aColor);  // 'Color.blue'

}

枚举类型有如下的限制:

  • 不能有子类、mixin实现一个enum。
  • 不能显式地实例化一个 enum。

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

为类添加功能: mixin

mixin是在多级类中利用类的代码的一种方式。

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

class  Musician  extends  Performer  with  Musical  {

  // ···

}

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  {

  // ···

}

Version note:mixin 关键字的支持在Dart 2.1中引入。更早发行版本中的代码通常使用abstract class来进行代替。更多有关2.1对mixinw 修改的信息,参见 Dart SDK修改记录 和 [2.1 mixin规范。

类变量和方法

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

静态变量

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

class  Queue  {

  static  const  initialCapacity  =  16;

  // ···

}

void  main()  {

  assert(Queue.initialCapacity  ==  16);

}

静态变量直到使用时才进行初始化。

Note: 本文遵循 样式指南推荐 的推荐,使用 lowerCamelCase (首字母小写的驼峰命名法)来作为常量名。

静态方法

静态方法(类方法)不对实例进行操作,因此无法访问 this。例如:

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

}

Note: 考虑对常用或广泛使用的工具和功能使用顶级函数来代替静态方法。

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

泛型

如果你在查看基本数组类型List的 API 文档,实际上看到的类型是 List<E>。<…>标记表示 List是一种泛型(或参数化)类型 – 使用拥有正式类型参数的类型。[按照惯例,大部分类型变量都有单字母名称,如 E, T, S, K和 V。

为什么使用泛型?

泛型通常是类型安全所需要的,但它们有比允许你的代码运行更多的好处:

  • 合理地指定泛型会产生更好的生成代码。
  • 可以使用泛型来减少代码重复。

如果你想要列表仅包含字符串,可以声明它为List<String> (将其读作“字符串列表”)。这样你、你的程序员小伙伴和你的工具都会监测到为该列表赋非字符串值可能是错误的。以下是一个示例:

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  Cache<T>  {

  T  getByKey(String  key);

  void  setByKey(String  key,  T  value);

}

在以上代码中,T是占位类型。它是一个可以看作类型的占位符,开发者可以在稍后定义这个类型。

使用集合字面量

列表、集和映射字面量可以是参数化的。参数字面量和我们所看到的其它字面量一样,只是可以在方括号(或花括号)之前添加 <*type*> (针对列表和集) 或 <*keyType*, *valueType*> (针对映射)。下面是一个使用带类型字面量的示例:

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  nameSet  =  Set<String>.from(names);

以下代码创建一个键为整型值为View类型的映射:

var  views  =  Map<int,  View>();

泛型集合及它们所包含的类型

Dart的泛型是实化(reified)类型,表示它们可以在运行时携带类型信息。例如,可以测试一个集合的类型:

var  names  =  List<String>();

names.addAll(['Seth',  'Kathy',  'Lars']);

print(names is  List<String>);  // true

Note: 不同的是,Java中的泛型使用的是擦除(erasure)类型,表示泛型参数在运时会被删除。在 Java 中可以测试一个对象是否为List,但无法测试它是否为 List<String>

限制参数化类型

在实现泛型时,你可能会希望限制它的参数的类型。可以使用 extends来做到:


class  Foo<T  extends  SomeBaseClass>  {

  // 实现代码在这里...

  String  toString()  =>  "Instance of 'Foo<$T>'";

}

class  Extender  extends  SomeBaseClass  {...}

使用 SomeBaseClass 或任意其它子类作为泛型的参数都是可以的:


var  someBaseClassFoo  =  Foo<SomeBaseClass>();

var  extenderFoo  =  Foo<Extender>();

也可以不指定泛型参数:


var  foo  =  Foo();

print(foo);  // 'Foo<SomeBaseClass>'的实例

指定任意非SomeBaseClass 类型会导致报错:

var  foo  =  Foo<Object>();

使用泛型方法

一开始Dart对泛型的支持仅限于类。一种新的语法,称为泛型方法(generic method),允许在方法和函数中使用类型参数:

first<T>(List<T>  ts)  {

  // 做一些初始化工作或错误检查,然后...

   tmp  =  ts[0];

  // 做一些其它检查或处理...

  return  tmp;

}

这里对first (<T>) 的泛型参数允许我们在多处使用类型参数 T

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

更多有关泛型的信息,参见使用泛型方法。

库和可见性

importlibrary 指令有且于我们创建模块化和可分享的代码基。库不仅提供API,也是一个私有单元:以下划线(_)开头的标识符仅在库内可见。每个 Dart 应用都是库,即使它不使用 library 指令。

库可以使用包进行分发。

使用库

使用 import 来指定一个库中的命名空间如何在另一个库的作用域中使用。例如,Dart web应用通常使用 dart:html 库,可以像这样导入:

import  'dart:html';

import 所要求的唯一参数是一个指定库的URI。对于内置库,URI有一个特殊的 dart: 协议。对于其它库,可以使用文件系统路径或package: 协议。 package: 协议指定由像pub工具这样的包管理器所提供的库。例如:

import  'package:test/test.dart';

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

指定库的前缀

如果导入两个带有冲突标识符的库,可以对其中之一或两者指定一个前缀。例如,如果library1 和 library2 都有一个Element类,那么可以有如下这s 样的代码:

import  'package:lib1/lib1.dart';

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

// 使用lib1中的Element

Element element1  =  Element();

// 使用lib2中的Element

lib2.Element element2  =  lib2.Element();

仅导入库的部分内容

如果只想要使用库的部分内容,可以选择性的导入库,例如:


// 仅导入foo

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

// 导入除foo以外的所有名称

import  'package:lib2/lib2.dart'  hide foo;

懒加载库

延时加载(也称作懒加载) 允许web应用按照需求在需要用到库时加载库。以下是一些可能会用到延时加载的情况:

  • 为减少web应用的初始启动时间。
  • 执行A/B测试 – 比如尝试一种算法的可选实现。
  • 加载很少使用到的功能,如可选界面和对话框。

**仅有dart2js支持延时加载。**Flutter, Dart VM和dartdevc 均不支持延时加载。更多内容,参见issue #33118 和 [issue #27776。

要对库进行懒加载,必须首先使用 deferred as导入它。

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

在需要该库时,使用库的标识符调用 loadLibrary()

Future greet()  async  {

  await hello.loadLibrary();

  hello.printGreeting();

}

以上代码中, await 关键字暂停代码执行直到库被加载。更多有关asyncawait的内容,参见 异步支持。

可以对库进行多次 loadLibrary() 调用,并不会产生问题。该库只会加载一次。

使用延时加载时记住如下各点:

  • 延时加载库的常量在导入文件中不是常量。记住,这些量直至延时库加载后才存在。
  • 不能在导入文件中使用延时库中的类型。而是考虑将接口类型移到由延时库和导入文件所共同导入的库中。
  • Dart在你使用deferred as *namespace*所定义的命名空间中隐式地插入 loadLibrary()loadLibrary() 函数返回Future。

实现库

参见创建库软件包 来获取有关如何实现一个库软件包的建议,包括:

  • 如何组织库的源码
  • 如何使用 export 指令
  • 何时使用part 指令
  • 何时使用 library 指令

异步支持

Dart库中有大量返回 Future 或 [Stream 对象的函数。这些函数是异步的,它们在设置一个可能很耗时的操作(如 I/O)后返回,无需等待操作完成。

asyncawait 关键字支持异步编程,让你可以编写看起来类似同步代码的异步代码。

处理Future

在需要完成的Future结果时,有两个选择:

  • 使用 asyncawait
  • 使用在库导览中所描述的Future API

使用asyncawait 的是异步代码,但和同步代码很像。例如,下面的代码使用了await 来等待异步函数的执行结果:

await lookUpVersion();

要使用 await,代码必须放在一个 async 函数中,一个标记为 async的函数中:

Future checkVersion()  async  {

  var  version  =  await lookUpVersion();

  // Do something with version

}

Note: 虽然 async 函数可能会执行些耗时的操作,但它不会等待这些操作。而是 async函数仅在其遇到第一个await 表达式 (详情)时才执行.。然后它返回一个Future对象,仅在 await 表达式完成后才恢复执行。

使用和 try, catchfinally 来在使用了await的代码中处理错误和清理:

try  {

  version  =  await lookUpVersion();

}  catch  (e)  {

  // React to inability to look up the version

}

可以在一个async函数中多次使用 await 。例如,以下代码对函数的执行结果等待3次:


var  entrypoint  =  await findEntrypoint();

var  exitCode  =  await runExecutable(entrypoint,  args);

await flushThenExit(exitCode);

await表达式 *expression*中, *表达式*的值通常为Future,如若不是,那么其值自动在Future中进行封装。这个Future对象表明会返回一个对象。await表达式 的值是那么返回的对象。await表达式让执行暂停,直至对象可用。

**如果你在使用 await时得到了一个编译时错误, 确保 await 出现在async 函数中。**例如,在应用的main()函数中使用 awaitmain()函数体必须标记为 async

Future main()  async  {

  checkVersion();

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

}

声明async函数

async 函数是一个通过async修饰符标记函数体的函数。

对函数添加 async 关键字会让其返回一个Future。例如,思考下面这个返回一个字符串的同步函数:

String  lookUpVersion()  =>  '1.0.0';

如果你将其修改为异步 函数, 例如因为未来实现会非常耗时 – 返回的值就是一个 – Future:

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

注意函数体不需要使用Future API。 Dart在必要时会创建Future对象。如果函数不返回有用的值,设置其返回类型为 Future<void>

对于使用futures, asyncawait交互入门介绍,参数 异步编程codelab.

处理流(Stream)

在需要从Stream获取值时,有两种选择:

  • 使用 async 和一个异步for循环 (await for)。
  • 使用Stream API,参见 在库的导览中所述。

Note: 在使用 await for之前,确保它会让代码更清晰并且你确实需要等待所有stream的结果。例如,通常对于 UI 事件监听器不应使用await for ,因为 UI框架会发送无数的事件流。

异步 for循环有如下的形式:

await for  (varOrType identifier in  expression)  {

  // 在每次流发射值时执行

}

*expression* 的值必须为 Stream类型。执行流程如下:

  1. 等流发射值时
  2. 执行 for循环体,将变量设置为所射的值
  3. 重复1 和 2 直到流关闭

要停止监听流,可以使用 breakreturn 语句,它会跳出for循环并取消对流的监听。

**如果在实现异步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);

  }

}

可调用类

实现 call() 方法来允许Dart类的实例可以像函数一样调用。

在下例中,WannabeFunction 类定义一个接收3个字符串并进行拼接的call()函数,每个字符串间以空格分隔,最后接感叹号。点击 Run 来执行代码。

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

注: 受论坛所限,仅提供代码如下

隔离(Isolate)

大部分电脑,甚至是移动平台,都有多核 CPU。为利用这些多核,开发者的传统做法是使用共享内存线程并发运行。但共享状态并发很容易出错并导致复杂的代码。

代替线程,所有的Dart代码在isolate内运行。每个isolate有其自己的内存堆,确保没有isolate的状态可通过另一个isolate访问。

更多内容,参见:

  • Dart异步编程: Isolate和事件循环
  • dart:isolate API手册 包含 [Isolate.spawn() 和 [TransferableTypedData
  • Flutter网站上的后台解析 指南

Typedef

在Dart中,函数像字符串和数值一样都是对象。typedef,或函数类型别名,给函数类型一个可在声明字段和返回类型时使用的名称。typedef在函数类型赋值给变量时保留类型信息。

思考如下未使用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);

  // 我们只知道compare是一个函数,

  // 但什么类型的函数呢?

  assert(coll.compare is  Function);

}

在将f 赋值给 compare时类型信息丢失了。 f的类型为 (Object, ``Object)int (→ 表示返回),而compare 的类型是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);

}

Note: 当前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!

}

元数据(metadata)

使用metadata来为代码提供更多信息。metadata标以字符 @开始,后接对编译时常量的引用(如 deprecated) 或对常量构造函数的调用。

Dart代码中有两个可用的标:@deprecated@override。 对于使用 @override的示例,参见 继承类。以下是一个使用 @deprecated 标注的示例:

class  Television  {

  /// _Deprecated:  使用 [turnOn] 来代替。_

  @deprecated

  void  activate()  {

    turnOn();

  }

  /// 打开电视的电源

  void  turnOn()  {...}

}

可以定义自己的metadata标。下面是定义接收两个参数的@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');

}

metadata 可以出现在库、类、typedef、类型参数、构造函数、工厂、函数、字段、参数或变量声明之前,以及放在import或export指令之前。可以在运行时使用反射来获取metadata。

注释

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

   */

}

文档注释

文档注释是以////**开头的多行或单行注释。对连续行使用/// 与多行文档注释异曲同工。

在文档注释内部, 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文档注释指南。

总结

本页总结了Dart语言的常用特性。更多功能正在实现中,但我们预计它们不会破坏现有代码。更多信息,参见Dart语言规范 和 [高效Dart.

要了解Dart核心库的更多知识,参见 Dart库导览.

本文首发地址:Alan Hou的个人博客