构造函数
通过和类相同的名称创建函数来声明构造函数(以及命名构造函数中所描述的可选额外标识符)。最常见形式的构造函数,生成构造函数,创建一个类的新实例:
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;
}
}
记住构造函数并不会继承,也就表示超类的构造函数不会由子类继承。如果希望通过超类中定义的命名构造函数创建子类 ,必须在子类中实现这个构造函数。
调用非默认超类构造函数
默认,子类中的构造函数调用超类中的未命名、无参数的构造函数。超类的构造函数在构造函数体的开头调用。如果还使用了 初始化程序列表,它在超类调用前执行。总之,执行的顺序如下:
- 初始化程序列表initializer list
- 超类的无参构造函数
- 主类的无参构造函数
如果超类中没有未命名的无参构造函数,那么就必须手动调用超类的一个构造函数。在冒号(:
)后、刚好在构造函数体前(如有)指定超类构造函数。
在下例中,Employee类的构造函数调用其超类Person的命名构造函数。点击 Run 来执行代码。
点击查看
因为超类构造函数的参数在调用构造函数前运行,参数可以是像函数调用这样的表达式:
class Employee extends Person {
Employee() : super.fromJson(getDefaultData());
// ···
}
Warning: 超类构造函数的参数无法访问 this
。例如,参数可调用静态方法但无法调用实例方法。
初始化程序列表
除调用超类构造函数外,也可以在构造函数体运行之前初始化实例变量。将初始化程序以逗号分隔。
// 初如化程序列表在构造函数体运行前
// 设置实例变量
Point.fromJson(Map 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 _cache =
{};
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。可以使用get
和set
关键字通过实现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方法可以是抽象的,定义接口并将其实现留给其它类。抽象方法仅能存在于 抽象类中。
要让方法为抽象的,使用分号 (;) 而非方法体:
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 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
。<…>标记表示 List是一种泛型(或参数化)类型 – 使用拥有正式类型参数的类型。按照惯例,大部分类型变量都有单字母名称,如 E, T, S, K和 V。
为什么使用泛型?
泛型通常是类型安全所需要的,但它们有比允许你的代码运行更多的好处:
- 合理地指定泛型会产生更好的生成代码。
- 可以使用泛型来减少代码重复。
如果你想要列表仅包含字符串,可以声明它为List
(将其读作“字符串列表”)。这样你、你的程序员小伙伴和你的工具都会监测到为该列表赋非字符串值可能是错误的。以下是一个示例:
var names = List();
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 getByKey(String key);
void setByKey(String key, T value);
}
在以上代码中,T是占位类型。它是一个可以看作类型的占位符,开发者可以在稍后定义这个类型。
使用集合字面量
列表、集和映射字面量可以是参数化的。参数字面量和我们所看到的其它字面量一样,只是可以在方括号(或花括号)之前添加 <*type*>
(针对列表和集) 或 <*keyType*, *valueType*>
(针对映射)。下面是一个使用带类型字面量的示例:
var names = ['Seth', 'Kathy', 'Lars'];
var uniqueNames = {'Seth', 'Kathy', 'Lars'};
var pages = {
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
使用带有构造函数的参数化类型
在使用构造函数时要指定一个或多个类型,将类型放在类名后的尖括号 (<...>
) 中。例如:
var nameSet = Set.from(names);
以下代码创建一个键为整型值为View类型的映射:
var views = Map();
泛型集合及它们所包含的类型
Dart的泛型是实化(reified)类型,表示它们可以在运行时携带类型信息。例如,可以测试一个集合的类型:
var names = List();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List); // true
Note: 不同的是,Java中的泛型使用的是擦除(erasure)类型,表示泛型参数在运时会被删除。在 Java 中可以测试一个对象是否为List,但无法测试它是否为 List
。
限制参数化类型
在实现泛型时,你可能会希望限制它的参数的类型。可以使用 extends
来做到:
class Foo {
// 实现代码在这里...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
使用 SomeBaseClass
或任意其它子类作为泛型的参数都是可以的:
var someBaseClassFoo = Foo();
var extenderFoo = Foo();
也可以不指定泛型参数:
var foo = Foo();
print(foo); // 'Foo'的实例
指定任意非SomeBaseClass
类型会导致报错:
var foo = Foo
使用泛型方法
一开始Dart对泛型的支持仅限于类。一种新的语法,称为泛型方法(generic method),允许在方法和函数中使用类型参数:
first(List ts) {
// 做一些初始化工作或错误检查,然后...
tmp = ts[0];
// 做一些其它检查或处理...
return tmp;
}
这里对first
(
) 的泛型参数允许我们在多处使用类型参数 T
:
- 在函数的返回类型中 (
T
) - 在参数的类型中(
List
) - 在局部变量的类型中 (
T tmp
).
更多有关泛型的信息,参见使用泛型方法。
库和可见性
import
和 library
指令有且于我们创建模块化和可分享的代码基。库不仅提供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
关键字暂停代码执行直到库被加载。更多有关async
和 await
的内容,参见 异步支持。
可以对库进行多次 loadLibrary()
调用,并不会产生问题。该库只会加载一次。
使用延时加载时记住如下各点:
- 延时加载库的常量在导入文件中不是常量。记住,这些量直至延时库加载后才存在。
- 不能在导入文件中使用延时库中的类型。而是考虑将接口类型移到由延时库和导入文件所共同导入的库中。
- Dart在你使用
deferred as *namespace*
所定义的命名空间中隐式地插入loadLibrary()
。loadLibrary()
函数返回Future。
实现库
参见创建库软件包 来获取有关如何实现一个库软件包的建议,包括:
- 如何组织库的源码
- 如何使用
export
指令 - 何时使用
part
指令 - 何时使用
library
指令
异步支持
Dart库中有大量返回 Future 或 Stream 对象的函数。这些函数是异步的,它们在设置一个可能很耗时的操作(如 I/O)后返回,无需等待操作完成。
async
和 await
关键字支持异步编程,让你可以编写看起来类似同步代码的异步代码。
处理Future
在需要完成的Future结果时,有两个选择:
- 使用
async
和await
- 使用在库导览中所描述的Future API
使用async
和 await
的是异步代码,但和同步代码很像。例如,下面的代码使用了await
来等待异步函数的执行结果:
await lookUpVersion();
要使用 await
,代码必须放在一个 async
函数中,一个标记为 async
的函数中:
Future checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
Note: 虽然 async
函数可能会执行些耗时的操作,但它不会等待这些操作。而是 async
函数仅在其遇到第一个await
表达式 (详情)时才执行.。然后它返回一个Future对象,仅在 await
表达式完成后才恢复执行。
使用和 try
, catch
和 finally
来在使用了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()
函数中使用 await
, main()
函数体必须标记为 async
:
Future main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}
声明async函数
async
函数是一个通过async
修饰符标记函数体的函数。
对函数添加 async
关键字会让其返回一个Future。例如,思考下面这个返回一个字符串的同步函数:
String lookUpVersion() => '1.0.0';
如果你将其修改为异步
函数, 例如因为未来实现会非常耗时 – 返回的值就是一个 – Future:
Future lookUpVersion() async => '1.0.0';
注意函数体不需要使用Future API。 Dart在必要时会创建Future对象。如果函数不返回有用的值,设置其返回类型为 Future
。
对于使用futures, async
和 await
交互入门介绍,参数 异步编程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类型。执行流程如下:
- 等流发射值时
- 执行 for循环体,将变量设置为所射的值
- 重复1 和 2 直到流关闭
要停止监听流,可以使用 break
或 return
语句,它会跳出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 naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
要实现一个异步生成器函数,将函数体标记为 async*
, 并使用 yield
语句来传送值:
Stream asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}
如果生成器是递归的,可惟通过使用 yield*
来提升性能:
Iterable naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
可调用类
实现 call()
方法来允许Dart类的实例可以像函数一样调用。
在下例中,WannabeFunction
类定义一个接收3个字符串并进行拼接的call()函数,每个字符串间以空格分隔,最后接感叹号。点击 Run 来执行代码。
点击查看
隔离(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 = int Function(T a, T b);
int sort(int a, int b) => a - b;
void main() {
assert(sort is Compare); // 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的个人博客