1. 主页
  2. 文档
  3. Dart 2 中文文档
  4. 语言
  5. 导览

导览

Dart编程语言导览

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

要了解更多有关 Dart核心库的知识,请参见核心库导览。在想要了解更多有关语言功能的时候,请查询Dart语言规范

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

打开DartPad

一个Dart基础程序

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

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

// 这是一条注释
单行注释。Dart还支持多行注释和文件注释。更多详情,请参见 注释.
int
一种类型。其它的一些 内置类型 有 StringListbool
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没有publicprotectedprivate这些关键字。 如果一个标识符以下划线(_)开头,它是对库私有的。详情请参见 库和可见性.
  • 标识符可以字母或下划线 (_)开头,后接这些字符和数字的任意组合。
  • Dart既有表达式(拥有运行时值)也有语句(没有运行时值)。例如, 条件表达式 condition ? expr1 : expr2 有一个值expr1 或 expr2。对比 if-else 语句则没有值。一条语句通常包含一个或多个表达式,但一个表达式不能直接包含一条语句。
  • 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版本之后添加的与异步支持相关的更新的、有限的保留词。你无法使用asyncasync*sync*标记的函数体内使用 await 或 yield 作为标识符。

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

变量

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

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

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

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

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

默认值

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

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

Final和const

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

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

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

你无法修改final变量的值:

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

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

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

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

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

更多有关使用 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 标准中所描述。

int 和 doublenum的子类型。num类型包含基本运算符如 +, -, / 和 *,也包含其它方法中的 abs(),ceil()floor()。(位相关的运算符,如 >>,在 int 类中定义)。 如果num及其子类型中没有你所要寻找的,可以使用 dart:math 库。

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

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

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

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

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

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

字符串

Dart字符串是一个UTF-16代码单元。你可以使用单引号或双引号来创建字符串:

可以通过使用${expression}来将表达式的值放到字符串中。如果表达式是一个标识符,可以省略{}。要获取一个对象对应的字符串,Dart中调用对象的toString()方法。

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

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

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

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

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

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

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

布尔型

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

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

列表

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

映射

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

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

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

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

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

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

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

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

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

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

Runes

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

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

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

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

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

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

符号

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

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

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

函数

Dart是一个真面向对象语言,因此即使是函数也是对象,并且拥有一个类型Function。这表示函数可赋值给变量或作为参数传递给其它函数。也可以调用将Dart类的实例看作函数来进行调用。更多详情,参见 可调用类

以下是实现一个函数的示例:

虽然高效Dart推荐 为公有 API 进行类型标注,函数则省略类型时依然有效:

对于仅包含一个表达式的函数,可以使用简写语法:

=> expr 语法是{ return expr; }的简写。 => 标记有时被称为箭头语法。

函数可以有两种类型的参数:必选和可选。必选参数放在前面,后接可选参数。可选参数可为命名参数或位置参数。

可选参数

可选参数可以为命名参数或位置参数,或者是两者都有。

命名参数

在调用一个函数时,你可以使用paramNamevalue指定命名参数。例如:

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

虽然命名参数是一种可选参数,通过可以@required 来标记它们以表示该参数是必须 – 即用户必须为该参数提供一个值。例如:

如果有人尝试不指定child参数就创建一个Scrollbar ,那么分析器会报出问题。

使用@required 标注,需要依赖meta 包并导入 package:meta/meta.dart

位置参数

将一组函数参数封装在 [] 中,会将它们标记为可选位置参数:

以下是无添加可选参数调用该函数的示例:

以下是使用了第3个参数调用该函数的示例:

默认参数值

函数可以使用 = 来为命名参数和位置参数定义默认值。默认值必须为编译时常量。如未提供默认值,则默认值为 null

以下是一个为命名参数设置默认值的示例:

Deprecation note: 老代码中可能会使用冒号 (:) 代替 = 来设置命名函数的默认值。原因是原来命名函数中仅支持 : 。该支持可能会被淘汰,所以我们推荐使 用= 来指定默认值。

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

也可以传递列表或映射作为默认值。以下示例定义了一个函数doStuff(),它为 list参数指定了一个默认列表并为 gifts 参数指定了一个默认映射。

main()函数

每个应用必须有一个顶级的 main() 函数,它作为应用的入口。 main() 函数返回 void 并有一个可选参数l List<String>

以下是一个针对web应用的 main() 函数示例:

Note: 以下代码中 .. 语法称作一个 级联。通过级联,你可以对单个对象的成员执行多个操作。

以下是一个针对接收参数的命令行应用的main() 函数示例:

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

函数作为一等对象

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

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

该例使用了匿名函数,更多相关内容请见下一节。

匿名函数

大部分函数都是命名函数,如 main() 或 printElement()。你也可创建一个无名称函数,称为匿名函数,有时也称为lambda 或闭包。可以将匿名函数赋值给变量,那么例如就可以从集合中添加或删除它。

匿名函数类似于命名函数 – 0或多个参数,在括号中由逗号和可选选类型标注分隔。

后面的代码块中包含函数体:

([[Typeparam1[, …]]) {
codeBlock;
};

以下示例定义了一个带有无类型参数 item 的匿名函数。对列表中的第一项所调用的函数,打印包含在指定索引处的值的字符串。

点击 Run 执行下面的代码。

如果函数仅包含一条语句,可将其简化为使用箭头标记。将如下行拷贝到DartPad中并点击Run来验证它的功能是相同的。

词法作用域

Dart是一个词法作用域语言,表示变量的作用域仅由代码的布局静态决定。你可以“按照外部花括号” 来查看变量是否在作用域中。

以下是一个变量在各自作用域中嵌套函数的示例:

注意nestedFunction() 是如何使用每个级别的变量的,一路到顶级的变量。

词法闭包

闭包是一个即使函数位置原作用域之外也可在词法作用域中访问变量的函数对象。

函数可封闭周围作用域中所定义的变量。在以下示例中, makeAdder() 捕获变量 addBy。不论返回的函数放在哪里,它都能记住 addBy

测试函数是否相等

下面是一个测试顶级函数、静态方法和实例方法是否相等的示例:

返回值

所有的函数都会返回值。如果未指定返回值,语句 return null; 会隐式地附加到函数体中。

运算符

Dart定义了下表中显示的运算符。你可以重载很多运算符,在重载运算符中进行了描述。

描述 运算符
一元后置 expr++    expr--    ()    []    .    ?.
一元前置 -expr    !expr    ~expr    ++expr    --expr
乘除 *    /    %  ~/
加减 +    -
按位移 <<    >>    >>>
按位与 &
按位异或 ^
按位或 |
关系和类型测试 >=    >    <=    <    as    is    is!
等于 ==    !=
逻辑与 &&
逻辑或 ||
判空(null) ??
条件 expr1 ? expr2 : expr3
级联 ..
赋值 =    *=    /=   +=   -=   &=   ^=   etc.

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

运算符表格中, 每个运算符都比下面一行的运算符优先级要高。比如乘法运算符 % 的优先级高于(因此先执行)等号运算符 ==,而它的优先级又高于逻辑与运算符 &&。这个优先级表示以下两行代码的执行方式相同:

Warning: 对于使用两个运算项的运算符,最左侧的运算项决定使用哪个版本的运算符。例如,如果有一个Vector对象和一个Point对象, aVector + aPoint 使用Vector版本的+。

算术运算符

Dart支持常见算术运算符,如下表所示:

运算符 含义
+
-expr 一元减,也称为否定(反转表达式的符号)
*
/
~/ 整除,返回一个整数结果
% 获取整数相除的余数(模)

示例:

Dart也同时支持前置和后置递增及递减运算符。

运算符 含义
++var var = var + 1 (表达式什为 var + 1)
var++ var = var + 1 (表达式值为 var)
--var var = var – 1 (表达式值为 var – 1)
var-- var = var – 1 (表达式值为 var)

示例:

比较和关系运算符

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

运算符 含义
== 等于,参见下面的讨论
!= 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于

测试两个对象x 和 y 是否表示相同的内容,使用 == 运算符。(在极少的情况中,需要知道两个对象是否是完全相同的对象,转而使用 identical() 函数。) 以下展示 == 运算符如何运行:

  1. 如果x 或 y 为null,如果两者都为null则返回true,如果只一个为null则返回false。
  2. 返回x.==(y)方法运行的结果 。(没错,==这样的运算符是对它们的第一项所调用的方法。你甚至可以重载很多运算符,包括 ==, 可参见 重载运算符。)

以下是使用每个比较和关系运算符的示例:

类型测试运算符

asis和  is! 运算符对于在运行时检查类型非常方便。

运算符 含义
as 对象类型转换 (也用于指定 库前缀)
is 如果对象有指定的类型时为true
is! 如果对象有指定的类型时为false

如果obj实现了T所指定的接口的话则 obj is T 的结果为 true。例如,obj is Object 永远为true。

使用 as 运算符将对象转换为指定类型。通常,应使用它作为对象is测试的简写,后接使用该对象的表达式。例如,考虑使用如下代码:

可以使用 as 运算符让代码更精简:

Note: 两种代码并不等同。如果 emp 为null 或者不是Person,第1个例子(带is)不会做任何事,第二个(带as)抛出异常。

赋值运算符

如你所见,可以使用=运算符进行赋值。仅为值为null的变量赋值时,使用 ??= 运算符。

复合赋值运算符如 += 将运算和赋值合并在一起。

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

以下是复合赋值运算符运行的方式:

复合赋值 比较表达式
对于运算符 op op= b a = a op b
示例: a += b a = a + b

下例中使用赋值和复合赋值运算符:

逻辑运算符

可以使用逻辑运算符取反或合并布尔表达式。

运算符 含义
!expr 对紧接着的表达式进行取反(修改false 为 true,反之亦然)
|| 逻辑或
&& 逻辑与

以下是一个使用逻辑运算符的示例:

按位及左移右移运算符

可以在Dart中操作数字的单个位。通常,会对整型使用这些按位和移动运算符。

运算符 含义
&
|
^ 异或
~expr 一元按位取反(0变成1,1变成0)
<< 左移
>> 右移

以下是使用按位和移动运算符的示例:

条件表达式

Dart有两个运算符可以让我们简洁地运行可能会要求使用 if-else 语句的表达式:

condition ? expr1 : expr2
若if条件为true, 运行 expr1 (并返回它的值),否则运行并返回 expr2的值。
expr1 ?? expr2
右 expr1 为非空,返回其值,否则运行并返回 expr2的值。

在需要根据布尔表达式进行赋值时,考虑使用 ?:

如果要测试布尔表达式是否为null,考虑使用 ??

前例至少可通过另外两种方式进行编写,但都没有它简洁:

级联标记 (..)

级联 (..) 允许我们对同一对象进行一系列操作。除调用函数外,你还可以对相同的对象访问字段。这通常可节省创建临时变量的步骤并让我们能编写列流畅的代码。

思考如下代码:

第1个方法调用querySelector(),返回一个选择器对象。紧接级联标记的代码对选择器对象进行操作,它忽略所有后续可能返回的值。

前例等同于:T

可以内嵌级联。例如:

对于返回实际对象的函数构建级联时要非常小心。例如,如下代码会失败:

sb.write() 调用返回void,而无法对 void构建级联。

Note: 严格的说,对级联使用的“双点号”标记并非运算符。它只是Dart语法的一部分。

其它运算符

我们已经在其它示例中看到过大部分剩余的运算符:

运算符 名称 含义
() 函数应用 表示一个函数调用
[] 列表访问 引用列表中所指定索引处的值
. 成员访问 引用一个表达式的属性;例如:foo.bar 从表达式foo中选择属性 bar
?. 条件成员访问 类似 ., 但最左侧的运算项可以为null;例如: foo?.bar 从foo中选择属性 bar ,除非 foo 为 null (这时 foo?.bar 的值为 null)

更多有关.?. 和 .. 运算符的信息,参见

流程控制语句

可以使用以下方式来控制Dart 代码中的流程

  • if 和 else
  • for 循环
  • while 和 dowhile 循环
  • break 和 continue
  • switch 和 case
  • assert

也可以使用try-catch 和 throw 来影响控制流程,在 异常中有进行讲解。

if和else

Dart 支持带有可选的else语句的 if语句,如下例中所示。同时请参见 条件表达式

不同于JavaScript,条件必须使用布尔值,其它的都不行。参见 布尔型 获取更多信息。

for循环

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

Dart for 循环中的装饰捕获索引的值,避免JavaScript中所发现的常见问题。例如,思考:

会如所预期的先输出 0 再输出 1。但对比在JavaScript中则会先打印 2 再打印 2

如果所迭代的对象是一个 Iterable,可以使用forEach() 方法。如果无需知道当前的迭代计数器的话使用 forEach() 是一个很好的选择:

Iterable类如List 和 Set 还把持迭代的 for-in 形式:

while和do-while

while 循环在循环之前运行条件:

dowhile 在循环之后运行条件:

break和continue

使用 break 来停止循环:

使用 continue 来跳至下一次迭代:

如果在使用列表或集这样的Iterable时可以会以不同的方式编写该示例:

switch和case

Dart中的switch 语句使用==比较整型、字符串或编译时常量。 比较的对象必须都是相同类的实例(而非其子类型的),并且该类不能重载 ==.。枚举类型 在 switch 语句中可以良好运行。

Note: Dart中的switch语句针对受限的环境中,如解释器或扫描器。

每个非空 case 从句按照规则以break语句结束。其它结束非空 case 从名的有效方式有 continuethrow或 return 语句。

在没有匹配的case从句时使用  default 从句来执行代码:

下例在一个case从句中省略了 break 语句,因此产生了一个报错:

但是,Dart确实支持空的 case 从句允许越过的形式:

如果你真的希望越过,可以使用continue 语句及一个标签:

case 从句可以有局部变量,仅在从句作用域内部可见。

断言

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

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

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

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

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

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

异常

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

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

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

Throw

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

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

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

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

Catch

捕获或抓取异常停止异常的传导(除非你重新抛出了该异常)。捕获异常提供了一个处理它的机会:

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

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

可以为catch()指定一个或两个参数。第1个是抛出的异常,第二是栈跟踪 (一个 StackTrace对象).

要部分处理异常,但允许其执行,使用 rethrow 关键字。

Finally

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

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

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

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

使用类成员

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

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

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

使用构造函数

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

以下代码有同样的效果,但在构造函数名前使用可选的关键字 new

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

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

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

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

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

获取一个对象的类型

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

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

实例变量

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

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

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

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

构造函数

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

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

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

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

默认构造函数

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

未继承构造函数

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

命名构造函数

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

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

调用非默认超类构造函数

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

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

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

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

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

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

初始化程序列表

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

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

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

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

重定向构造函数

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

常量构造函数

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

常量构造函数并不总是创建常量。更多详情,请参见 使用构造函数一节。

工厂构造函数

在实现不会一直新建类的实例的构造函数时使用factory 关键字。你不能吃,一个工厂构造函数可能会从缓存返回一个实例,或者它可能返回一个子类型的实例。

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

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

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

方法

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

实例方法

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

getter和setter

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

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

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

抽象方法

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

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

抽象类

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

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

隐式接口

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

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

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

继承类

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

重载成员

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

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

可重载运算符

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

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

Note: 你可能流到到 != 并不是一个可重载运算符。表达式 e1 != e2 只是针对 !(e1 == e2)的语法糖。

以下是一个重载重载 + 和 - 运算符的类的示例:

如果重载==,还应当重载 Object的 hashCode getter。有关重载 == 和 hashCode的示例,参见 实现映射的键

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

noSuchMethod()

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

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

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

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

枚举类型

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

使用enum

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

枚举中的每个值有一个 index getter,它返回在枚举声明中以0位置开始的值。例如,第一个值的索引为0,第二个值的索引为1。

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

可以在switch语句中使用枚举,并如果未处理所有的枚举值时会收到警告:

枚举类型有如下的限制:

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

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

为类添加功能: mixin

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

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

要实现一个mixin,创建继承Object和未声明构造函数的类。除非你希望让mixin和普通类一样可用,否则请使用 mixin 关键字来替代 class。例如:

要指定只有某些类型可以使用该mixin – 例如,这样你的mixin可以调用它未定义的方法 – 使用 on 来指定所要求的超类:

类变量和方法

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

静态变量

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

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

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

静态方法

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

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

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

泛型

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

为什么使用泛型?

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

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

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

另一个使用泛型的原因是减少代码重复量。泛型让我们可以在多个类型中共享单个接口和实现,而又仍拥有静态分析的好处。例如,假设你创建接口来捕获一个对象:

发现你想要该接口的字符串版本,所以创建了另一个接口:

稍后,你又决定要一个该接口的数值类型版本… 读者应该明白了。

泛型可以省却创建所有这些接口的麻烦。取而代之的是,你可以创建单个接口来接收类型参数:

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

使用集合字面量

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

使用带有构造函数的参数化类型

在使用构造函数时要指定一个或多个类型,将类型放在类名后的尖括号 (<...>) 中。例如:

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

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

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

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

限制参数化类型

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

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

也可以不指定泛型参数:

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

使用泛型方法

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

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

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

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

库和可见性

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

库可以使用进行分发。

使用库

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

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

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

指定库的前缀

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

仅导入库的部分内容

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

懒加载库

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

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

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

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

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

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

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

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

实现库

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

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

异步支持

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

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

处理Future

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

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

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

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

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

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

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

声明async函数

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

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

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

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

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

处理流(Stream)

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

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

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

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

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

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

如果在实现异步for 循环时得到了一个编译时错误,确保 await for 出现在一个 async 函数中。例如,要在应用的main() 中使用异步 for循环,main()函数体必须标记为 async

更多有关异步编程的信息,参见库导览的 dart:async 一节。

生成器

在需要便利地生成一系列值时,考虑使用生成器函数。Dart内置支持两种生成器函数:

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

要实现一个同步生成器函数,将函数体标记为 sync*,并使用 yield 语句来传送值:

要实现一个异步生成器函数,将函数体标记为 async*, 并使用 yield 语句来传送值:

如果生成器是递归的,可惟通过使用 yield*来提升性能:

可调用类

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

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

隔离(Isolate)

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

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

更多内容,参见:

Typedef

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

思考如下未使用typedef的代码:

在将f 赋值给 compare时类型信息丢失了。 f的类型为 (Object, Object) → int (→ 表示返回),而compare 的类型是Function。如果我们修改代码使用显式的名称并保留类型信息,开发人员和工具都可以使用该信息。

因为typedef只是别名,它们提供一种检查任意函数类型的方式。例如:

元数据(metadata)

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

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

可以定义自己的metadata标。下面是定义接收两个参数的@todo标注的示例:

以下是使用该 @todo 标注的示例:

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

注释

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

单行注释

单行注释以 //开头。 // 和行尾之间的所有内容都会被Dart编译器所忽略

多行注释

多行注释以 /* 开头,并以 */结束。 /* 和 */ 之间的所有内容都被Dart编译器所忽略(除非注释是文档注释,参见下一节)。多行注释可以嵌套。

文档注释

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

在文档注释内部, Dart编译器忽略方括号所包裹之外的文件。使用方括号,可以引用类、方法、字段、顶级变量、函数和参数。方括号中的名称在文档记录的程序元素词法作用域中解析。

以下是一个带有其它类和参数的文档注释的示例:

在所生成的文档中, [Food] 成为针对Food类的API文件的链接。

解析Dart代码及生成HTML文档,可以使用SDK的文档生成工具。 有关生成的文档的示例,参见 Dart API文档。有关如何架构注释的建议,参见 Dart文档注释指南

总结

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

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

这篇文章对您有用吗?
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址