类
Dart是一种面向对象的语言,具有类和基于mixin的继承。每个对象都是一个类的实例,所有类都来自Object。 _基于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;
使用构造函数
构造函数用来创建对象. 构造函数的名称可以为类名或者其他类方法. 比如你可以用Point的Point()构造函数 或者Point.fromJson():
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关键字Dart2成为可选项。
有些类提供常量构造函数。要使用常量构造函数创建编译时常量,请将const
关键字放在构造函数名称之前:
有些类提供常量构造函数。要使用常量构造函数创建编译时常量,请将const
关键字放在构造函数名称之前:
var p = const ImmutablePoint(2, 2);
构造两个相同的编译时常量会产生一个规范的实例:
常量构造函数如果传递相同的参数,仅仅会存在一个对象,不会重复创建!!!
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!
获取对象的类型
要在运行时获取对象的类型,可以使用Object的runtimeType
属性,该属性返回Type对象。
print('The type of a is ${a.runtimeType}');
到目前为止,您已经了解了如何_使用_类。本节的其余部分将介绍如何_实现_类。
实例变量
以下是声明实例变量的方法:
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和setter。
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.
}
如果声明时初始化了实例变量(而不是构造函数或方法),则在创建实例时设置该值,该操作在构造函数及其初始化列表执行之前。
构造函数
通过创建与其类同名的函数来声明构造函数(另外,可选地,如命名构造函数中所述的附加标识符 )。最常见的构造函数形式,即生成构造函数,创建一个类的新实例:
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风格省略了this。
将构造函数参数赋值给实例变量的模式是如此常见,Dart具有语法糖,使其变得简单:
class Point {
num x, y;
// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}
默认构造函数
如果您未声明构造函数,则会为您提供默认构造函数。默认构造函数没有参数,并在超类中调用无参数构造函数。
构造函数不能继承的
子类不从其超类继承构造函数。声明没有构造函数的子类只有默认(无参数,无名称)构造函数。
命名构造函数
使用命名构造函数为类实现多个构造函数:
class Point {
num x, y;
Point(this.x, this.y);
// Named constructor
Point.origin() {
x = 0;
y = 0;
}
}
请记住,构造函数不是继承的,这意味着超类的命名构造函数不会被子类继承。如果希望使用超类中定义的命名构造函数创建子类,则必须在子类中实现该构造函数。
调用非默认的超类构造函数
默认情况下,子类中的构造函数调用超类的未命名的无参数构造函数。超类的构造函数在构造函数体的开头被调用。如果 还使用初始化列表,则在调用超类之前执行。总之,执行顺序如下:
- 初始化列表
- 超类的无参数构造函数
- 主类的无参数构造函数
如果超类没有未命名的无参数构造函数,则必须手动调用超类中的一个构造函数。在冒号(:)之后,在构造函数体(如果有)之前指定超类构造函数。
在下面的示例中,Employee类的构造函数为其超类Person调用命名构造函数。
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());
// ···
}
警告: 超类构造函数的参数无权访问this。例如,参数可以调用静态方法,但不能调用实例方法。
初始化列表
除了调用超类构造函数之外,还可以在构造函数体运行之前初始化实例变量。用逗号分隔初始化程序。
// 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)');
}
警告: 初始化程序的右侧无权访问this。
在开发期间,您可以通过assert在初始化列表中使用来验证输入。
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
设置最终字段时,初始化程序列表很方便。以下示例初始化初始化列表中的三个最终字段。
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构造函数并确保所有实例变量都是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 is library-private, thanks to
// the _ in front of its name.
static final Map<String, Logger> _cache =
<String, Logger>{};
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
注意: 工厂构造函数无权访问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);
}
}
Getters and setters
getter和setter是提供对象属性的读写访问权限的特殊方法。回想一下,每个实例变量都有一个隐式getter,如果合适的话还有一个setter。您可以使用get和set关键字通过实现getter和setter来创建其他属性 :
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);
}
使用getter和setter,您可以从实例变量开始,稍后使用方法包装它们,而无需更改客户端代码。
注意: 无论是否明确定义了getter,增量(++)等运算符都以预期的方式工作。为避免任何意外的副作用,操作员只需调用一次getter,将其值保存在临时变量中。
抽象方法
实例,getter和setter方法可以是抽象的,定义一个接口,但将其实现留给其他类。抽象方法只能存在于抽象类中。 要使方法成为抽象,请使用分号(;)而不是方法体:
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实现的情况下创建支持B类API的A类,则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?';
}
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();
}
// ···
}
复写成员
子类可以覆盖实例方法,getter和setter。您可以使用@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的hashCode
getter。用于覆盖的一个例子==
和hashCode
,请参见 实施映射键。
有关覆盖的更多信息,请参阅 扩展类。
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。
接收器有一个静态类型,它定义了未实现的方法(抽象是OK),接收器的动态类型的实现与类noSuchMethod() 中的实现不同Object。
有关更多信息,请参阅非正式 noSuchMethod转发规范。
枚举类型
枚举类型(通常称为枚举或枚举)是一种特殊类,用于表示固定数量的常量值。
使用枚举
使用enum关键字声明枚举类型:
enum Color { red, green, blue }
枚举中的每个值都有一个indexgetter,它返回枚举声明中值的从零开始的位置。例如,第一个值具有索引0,第二个值具有索引1。
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
要获取枚举中所有值的列表,请使用枚举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: // Without this, you see a WARNING.
print(aColor); // 'Color.blue'
}
枚举类型具有以下限制:
- 您不能子类化,混合或实现枚举。
- 您无法显式实例化枚举。
有关更多信息,请参阅Dart语言规范。
向类添加元素:mixins
Mixins是一种在多个类层次结构中重用类代码的方法。
要使用 mixin,请使用with关键字后跟一个或多个mixin名称。以下示例显示了两个使用mixins的类:
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 {
// ···
}
版本说明:
mixin
Dart 2.1中引入了对关键字的支持。通常使用早期版本中的代码abstract class
。有关2.1 mixin更改的更多信息,请参阅 Dart SDK changelog和2.1 mixin规范。
类变量和方法
使用static关键字实现类范围的变量和方法。
静态变量
静态变量(类变量)对于类范围的状态和常量很有用:
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
静态变量在使用之前不会初始化。
注意: 此页面遵循 首选的常量名称的样式指南建议
驼峰体
。
静态方法
静态方法(类方法)不对实例进行操作,因此无权访问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);
}
注意: 对于常用或广泛使用的实用程序和功能,请考虑使用顶级函数而不是静态方法。
您可以使用静态方法作为编译时常量。例如,您可以将静态方法作为参数传递给常量构造函数。
泛型
如果您查看基本数组类型的API文档 List,您会看到该类型实际上是List<E>
。<...>表示法将List标记为 _通用_(或_参数化_)类型 - 具有正式类型参数的类型。按照惯例,大多数类型变量都有单字母名称,例如E,T,S,K和V.
为什么要使用泛型
类型安全通常需要泛型,但它们比仅允许代码运行有更多好处:
正确指定泛型类型会产生更好的生成代码。
- 您可以使用泛型来减少代码重复。
- 如果您希望列表只包含字符串,则可以将其声明为List
(将其读作“字符串列表”)。这样,您,您的程序员和您的工具可以检测到将非字符串分配给列表可能是一个错误。这是一个例子:
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是替身类型。它是一个占位符,您可以将其视为开发人员稍后定义的类型。
使用集合文字
可以参数化列表,集和地图文字。参数化文字就像你已经看到的文字一样,除了你在开始括号之前添加 (对于列表和集合)或 (对于地图)。以下是使用类型文字的示例:<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泛型类型的具体化,这意味着他们随身携带的类型信息在运行时。例如,您可以测试集合的类型:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
注意: 相反,Java中的泛型使用擦除,这意味着在运行时删除泛型类型参数。在Java中,您可以测试对象是否为List,但您无法测试它是否为
a List<String>
。
限制参数化类型
实现泛型类型时,您可能希望限制其参数的类型。你可以使用extends。
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类型会导致错误:
var foo = Foo < Object >();
使用通用方法
最初,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;
}
这里on first
(<T>
)的泛型类型参数允许你T
在几个地方使用type参数:
- 在函数的返回类型(
T
)中。 - 在参数类型(
List<T>
)中。 - 在局部变量的类型(
T tmp
)。
有关泛型的更多信息,请参阅 使用泛型方法。
库与可见性
该import
和library
指令可以帮助您创建一个模块化的,可共享的代码库。库不仅提供API,还是隐私单元:以下划线(_)开头的标识符仅在库内可见。_每个Dart应用程序都是一个库_,即使它不使用library
指令。
可以使用包来分发库。 有关pub(包含在SDK中的包管理器)的信息,请参阅 Pub Package和Asset Manager。
使用库
使用import
指定如何从一个库中的命名空间在另一个库的范围内使用。
例如,Dart Web应用程序通常使用dart:html 库,它们可以像这样导入:
import 'dart:html';
唯一需要的参数import是指定库的URI。对于内置库,URI具有特殊dart:方案。对于其他库,您可以使用文件系统路径或package: 方案。该package:方案指定由包管理器(如pub工具)提供的库。例如:
import 'package:test/test.dart';
注意: URI代表统一资源标识符。 URL(统一资源定位符)是一种常见的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;
懒惰地加载一个库
延迟加载(也称为延迟加载)允许应用程序根据需要加载库,如果需要的话。以下是您可能使用延迟加载的一些情况:
- 减少应用程序的初始启动时间。
- 例如,执行A / B测试 - 尝试算法的替代实现。
- 加载很少使用的功能,例如可选的屏幕和对话框。
要懒加载库,必须先使用它导入它deferred as。
import 'package:greetings/hello.dart' deferred as hello;
当您需要库时,loadLibrary()使用库的标识符进行调用 。
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
在前面的代码中,await
关键字暂停执行,直到加载库。有关详细信息async
,并await
请参阅异步支持。
您可以loadLibrary()
在库上多次调用而不会出现问题。该库只加载一次。
使用延迟加载时请记住以下内容:
- 延迟库的常量不是导入文件中的常量。请记住,在加载延迟库之前,这些常量不存在。
- 您不能在导入文件中使用延迟库中的类型。相反,请考虑将接口类型移动到由延迟库和导入文件导入的库。
- Dart隐式插入
loadLibrary()
您使用的命名空间。该函数返回Future。deferred as *namespace*``loadLibrary()
Dart VM差异: 即使在调用之前,Dart VM也允许访问延迟库的成员loadLibrary()
。此行为可能会更改,因此 不要依赖于当前的VM行为。 有关详细信息,请参阅问题#33118。
实现库
有关 如何实现库包的建议,请参阅 创建库包,包括:
- 如何组织库源代码。
- 如何使用该
export
指令。 - 何时使用该
part
指令。 - 何时使用该
library
指令。
异步支持
Dart库中包含许多返回Future或Stream对象的函数。这些函数是_异步的_:它们在设置可能耗时的操作(例如I / O)后返回,而不等待该操作完成。
在async
和await
关键字支持异步编程,让你写异步代码看起来类似于同步代码。
Handling Futures
当您需要完成Future的结果时,您有两个选择:
- 使用
async
和await
。 - 使用Future API,如 库浏览中所述。
使用async
和await
异步的代码,但它看起来很像同步代码。例如,这里有一些代码await
用于等待异步函数的结果:
await lookUpVersion();
要使用await,代码必须在异步函数中 - 标记为的函数async
:
Future checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
注意: 虽然异步函数可能会执行耗时的操作,但它不会等待这些操作。相反,异步函数只在遇到第一个
await
表达式(详细信息)时执行。然后它返回一个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对象象征返回object的承诺。await 表达式
的值是返回的object对象。await表达式
阻塞直到返回object值为止。
如果在使用时出现编译时错误await,请确保await处于异步功能中。 例如,要await在您的应用程序的main()功能中使用,main()必须将正文标记为async:
Future main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}
声明异步函数
一个异步函数是一个函数体标有async修改。
将async关键字添加到函数使其返回Future。例如,考虑这个同步函数,它返回一个String:
String lookUpVersion() => '1.0.0';
如果将其更改为异步函数 - 例如,因为将来的实现将非常耗时 - 返回的值是Future:
```dart
Future < String > lookUpVersion ()async => '1.0.0' ;
请注意,函数的主体不需要使用Future API。如有必要,Dart会创建Future对象。
如果您的函数没有返回有用的值,请设置其返回类型Future
处理流
当您需要从Stream获取值时,您有两个选择:
- 使用
async
和_异步for循环_(await for
)。 - 使用Stream API,如 库浏览中所述。
注意: 在使用之前await for,请确保它使代码更清晰,并且您确实希望等待所有流的结果。例如,你通常应该不使用await for的UI事件侦听器,因为UI框架发送无尽的事件流。
异步for循环具有以下形式:
await for (varOrType identifier in expression) {
// Executes each time the stream emits a value.
}
值expression必须具有Stream类型。执行过程如下:
- 等到流发出一个值。
- 执行for循环的主体,将变量设置为该发出的值。
- 重复1和2,直到关闭流。
要停止侦听流,可以使用breakor return语句,该for语句会从for循环中断开并从流中取消取消。
如果在实现异步for循环时遇到编译时错误,请确保await for它处于异步函数中。 例如,要在应用程序的main()函数中使用异步for循环,main()必须将正文标记为async:
Future main() async {
// ...
await for (var request in requestServer) {
handleRequest(request);
}
// ...
}
有关异步编程的更多信息,请参阅 库浏览的 dart:async部分。另请参阅文章 Dart语言异步支持:阶段1 和 Dart语言异步支持:阶段2和Dart语言规范。
Generators
当您需要懒惰地生成一系列值时,请考虑使用_生成器函数_。Dart内置支持两种发电机功能:
要实现同步生成器函数,请将函数体标记为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语言异步支持:阶段2。
Callable classes
要允许像函数一样调用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');
}
有关处理函数类的更多信息,请参阅 在Dart中模拟函数。
Isolates
大多数计算机,即使在移动平台上,也有多核CPU。为了利用所有这些核心,开发人员传统上使用并发运行的共享内存线程。但是,共享状态并发容易出错,并且可能导致代码复杂化。
所有Dart代码都在_隔离_区内运行,而不是线程。每个隔离区都有自己的内存堆,确保不会从任何其他隔离区访问隔离区的状态。
有关更多信息,请参阅 dart:isolate库文档。
Typedefs
在Dart中,函数是对象,就像字符串一样,数字是对象。一个类型定义,或功能型的别名,给出了一个函数类型声明字段时,您可以使用和返回类型的名称。当函数类型分配给变量时,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);
// All we know is that compare is a function,
// but what type of function?
assert(coll.compare is Function);
}
当分配类型信息丢失f到compare。类型 f是(Object, Object)→ int(其中→表示返回),但类型compare是功能。如果我们将代码更改为使用显式名称并保留类型信息,则开发人员和工具都可以使用该信息。
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!');
}
多行注释
多行注释以... /结尾/。介于两者之间的/,并/用飞镖编译器忽略(除非该注释是一个文档注释;见下一节)。多行注释可以嵌套。
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 Doc注释指南。
摘要
本页概述了Dart语言中常用的功能。正在实施更多功能,但我们希望它们不会破坏现有代码。有关更多信息,请参阅Dart语言规范和 Effective Dart。