类 Classes
Dart是一种面向对象的语言 包含类和基于 mixin 的继承两部分。每个对象是一个类的实例, 并且 Object.是所有类的父类。 基于 mixin 的继承指的是每个类(除了 Object )都只有一个父类,类体还可以在多个类继承中被重用。
要创建一个对象,你可以使用 new
关键词并在其后跟上一个构造函数 .构造函数可以写成 ClassName
或者ClassName.identifier
. 比如:
var jsonData = JSON.decode('{"x":1, "y":2}');
// Create a Point using Point().
var p1 = new Point(2, 2);
// Create a Point using Point.fromJson().
var p2 = new Point.fromJson(jsonData);
对象拥有由函数和数据组成的成员(方法 和 实例变量).当你调用一个方法时, 是基于一个对象调用它的: 这个方法能够访问该对象的所有方法和数据.
使用一个点(.
) 引用实例变量或方法:
var p = new 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(new Point(4, 4));
使用 ?.
代替 .
来避免当最左操作数为null时产生的异常:
// If p is non-null, set its y value to 4.
p?.y = 4;
一些类提供常量构造函数。 要使用常量构造函数创建编译时常数,请使用const
代替 new
:
var p = const ImmutablePoint(2, 2);
构造两个相同的编译时常量导致一个单一的规范的实例:
代码语言:javascript复制var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // They are the same instance!
在运行时获取一个对象的类型, 你可以使用Object类的 runtimeType
属性, 该属性返回一个 Type 对象.
print('The type of a is ${a.runtimeType}');
以下部分讨论如何实现类.
变量实例
声明实例变量:
代码语言:javascript复制class Point {
num x; // Declare instance variable x, initially null.
num y; // Declare y, initially null.
num z = 0; // Declare z, initially 0.
}
所有为初始化的实例变量值为 null
.
所有实例变量都生成一个隐式的 getter 方法. 非最终实例变量也生成一个隐式 setter 方法. 更多查看 Getters 和 setters.
代码语言:javascript复制class Point {
num x;
num y;
}
main() {
var point = new 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.
}
如果你要在实例变量声明的时候为其初始化值(而不是通过构造函数或方法),那么当创建实例时就该为其设值,该值在构造函数和其初始化程序列表之前执行.
Constructors
通过创建一个与其类名相同的函数来声明构造函数 (plus, optionally, an additional identifier as described inNamed constructors). 构造函数的最常见形式是生成构造函数,创建一个类的新实例:
代码语言:javascript复制class Point {
num x;
num y;
Point(num x, num y) {
// There's a better way to do this, stay tuned.
this.x = x;
this.y = y;
}
}
this
关键字引用当前实例.
Note:只有当命名出现冲突时使用
this
. 否则,Dart风格指南建议不要用this
.
向实例变量分配构造函数的参数是很常见的一种模式,Dart语法糖让其变得容易:
代码语言:javascript复制class Point {
num x;
num y;
// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}
默认构造函数
如果您没有声明构造函数,则会为您提供默认构造函数。 默认构造函数没有参数,并调用父类中的无参数构造函数。.
构造函数不能继承
子类不会从他们的超类继承构造函数.声明没有构造函数的子类只有默认(无参数,无名称)构造函数.
命名构造函数
使用命名构造函数为类实现多个构造函数或提供额外的声明:
代码语言:javascript复制class Point {
num x;
num y;
Point(this.x, this.y);
// Named constructor
Point.fromJson(Map json) {
x = json['x'];
y = json['y'];
}
}
请记住,构造函数不是继承的,这意味着父类的命名构造函数不会被子类继承。 如果要使用父类中定义的命名构造函数创建子类,则必须在子类中实现该构造函数.
调用父类的非默认构造函数
默认情况下,子类中的构造函数调用超类的未命名的无参数构造函数。 超类的构造函数在构造函数体的起始处被调用。 如果一个 初始化器列表 也被使用,它将在超类被调用之前执行。 总而言之,执行顺序如下:
- 初始化程序列表
- 父类的无参构造
- 主类的无参构造
如果超类没有未命名的无参数构造函数,则必须手动调用超类中的一个构造函数。 在冒号 (:
)之后,在构造函数体(如果有的话)之前指定超类构造函数.
下面的例子中,Employee类的构造函数调用了其父类Person的命名构造函数. 点击运行按钮 (
) 执行代码.
代码语言:javascript复制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';
}
因为在调用构造函数之前对父类构造函数的参数进行求值,所以参数可以是一个表达式,例如函数调用:
代码语言:javascript复制class Employee extends Person {
// ...
Employee() : super.fromJson(findDefaultData());
}
Note: 在构造函数初始化列表中使用
super()
时,将其放在最后. 更多信息查看 Dart usage guide.Warning: 父类的构造函数的参数无权访问
this
. 比如, 参数能访问静态方法不能访问实例方法.
Initializer list
除了调用超类构造函数之外,还可以在构造函数体运行之前初始化实例变量,用逗号分隔初始化器.
代码语言:javascript复制class Point {
num x;
num y;
Point(this.x, this.y);
// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map jsonMap)
: x = jsonMap['x'],
y = jsonMap['y'] {
print('In Point.fromJson(): ($x, $y)');
}
}
Warning: 初始化程序的右侧无法访问
this
.
初始化器列表在设置final字段时很方便。 以下示例在初始化程序列表中初始化三个final字段。 单击运行按钮(
) 执行代码.
代码语言:javascript复制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);
}
重定向构造函数
有时,构造函数的唯一目的是重定向到同一个类中的另一个构造函数。 重定向构造函数的正文是空的,构造函数调用出现在冒号(:)之后.
代码语言:javascript复制class Point {
num x;
num 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 {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
}
Factory 构造函数
当不需要总是创建该类的新实例时,使用 factory
关键字. 例如, 一个工厂构造函数可能从缓存中返回实例或返回一个子类的实例.
下面的例子演示工厂构造函数如何从缓存中返回对象:
代码语言:javascript复制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 = new Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) {
print(msg);
}
}
}
Note: 工厂构造函数不能访问
this
.
调用工厂构造函数, 使用 new
关键字:
var logger = new Logger('UI');
logger.log('Button clicked');
方法
方法是为对象提供行为的函数.
实例方法
对象上的实例方法可以访问实例变量和 this
. 下例中 distanceTo()
方法是一个实例方法的示例:
import 'dart:math';
class Point {
num x;
num 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
Getters和setter是为对象的属性提供读写访问权限的特殊方法。 回想一下,每个实例变量都有一个隐式的getter,如果合适的话加一个setter。 您可以使用 get
和 set
关键字来实现getter和setter来创建其他属性
class Rectangle {
num left;
num top;
num width;
num 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;
}
main() {
var rect = new Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
使用getter和setter,您可以从实例变量开始,稍后用方法包装它们,而不用改变客户端代码.
Note: 如运算符( )以预期的方式工作,无论是否明确定义了getter. 为了避免任何意外的发生,操作符只调用一次getter,将其值保存在临时变量中.
抽象方法
实例,getter和setter方法可以是抽象的,定义一个接口,但将其实现交给其他类。 要使方法变成抽象方法,请使用分号 (;) 而不是方法体:
代码语言:javascript复制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 classes.
可覆盖的操作符
您可以覆盖下表中显示的运算符。 例如,如果定义Vector(向量)类,则可以定义一个
方法来添加两个向量.
下面是覆盖
和 -
操作符的实例:
class Vector {
final int x;
final int y;
const Vector(this.x, this.y);
/// Overrides (a b).
Vector operator (Vector v) {
return new Vector(x v.x, y v.y);
}
/// Overrides - (a - b).
Vector operator -(Vector v) {
return new Vector(x - v.x, y - v.y);
}
}
main() {
final v = new Vector(2, 3);
final w = new Vector(2, 2);
// v == (2, 3)
assert(v.x == 2 && v.y == 3);
// v w == (4, 5)
assert((v w).x == 4 && (v w).y == 5);
// v - w == (0, 1)
assert((v - w).x == 0 && (v - w).y == 1);
}
如果你覆盖 ==
, 你也应该覆盖Object的 hashCode
的 getter方法. 覆盖 ==
和 hashCode
的例子, 查看 Implementing map keys.
有关覆盖的更多信息, 参阅 Extending a class.
抽象类
使用 abstract
修饰符来定义一个 抽象类— 一个不能实例化的类. 抽象类对于定义接口很有用,通常有一些实现. 如果想让抽象类变得可实例化 请使用 factory constructor .
抽象类通常具有 抽象方法. 这是一个声明一个抽象类的例子,它有一个抽象方法:
代码语言:javascript复制// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
// ...Define constructors, fields, methods...
void updateChildren(); // Abstract method.
}
以下类不是抽象的,因此即使它定义了一个抽象方法也可以实例化:
代码语言:javascript复制class SpecializedContainer extends AbstractContainer {
// ...Define more constructors, fields, methods...
void updateChildren() {
// ...Implement updateChildren()...
}
// Abstract method causes a warning but
// doesn't prevent instantiation.
void doSomething();
}
隐式接口
每个类隐式定义一个包含类的所有实例成员以及它实现的任何接口的接口。 如果要创建一个支持B类API而不继承B实现的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(who) => 'Hello, $who. I am $_name.';
}
// An implementation of the Person interface.
class Imposter implements Person {
// We have to define this, but we don't use it.
final _name = "";
String greet(who) => 'Hi $who. Do you know who I am?';
}
greetBob(Person person) => person.greet('bob');
main() {
print(greetBob(new Person('kathy')));
print(greetBob(new Imposter()));
}
下面是一个指定一个类实现多个接口的例子:
代码语言:javascript复制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() {
// ...
}
// ...
}
有时缩小方法参数的类型或保证代码中实例变量是 type safe, 可以使用 covariant
关键字.
noSuchMethod()
为了检测或应对尝试使用不存在的方法或实例变量, 你可以复写 noSuchMethod()
:
class A {
// Unless you override noSuchMethod, using a
// non-existent member results in a NoSuchMethodError.
void noSuchMethod(Invocation mirror) {
print('You tried to use a non-existent member:'
'${mirror.memberName}');
}
}
如果你使用 noSuchMethod()
为一个或多个类型实现可能的getter、setter、方法, 你可以使用 @proxy
注解来避免警告:
@proxy
class A {
void noSuchMethod(Invocation mirror) {
// ...
}
}
一种替代 @proxy
的方式是, 如果在编译时你知道有那些类型, 就是类声明实现了的类.
class A implements SomeClass, SomeOtherClass {
void noSuchMethod(Invocation mirror) {
// ...
}
}
了解更多注解信息, 查看 Metadata.
枚举类型
枚举类型, 通常被称为 enumerations 或 enums, 是用于表示固定数量的常量值的特殊类.
使用枚举
使用 enum
关键字声明一个枚举类型:
enum Color {
red,
green,
blue
}
枚举中的每个值都有一个 index
getter, 它返回枚举声明中的值从零的位置开始。 例如,第一个值具有索引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 语句中使用枚举. 如果switch (e)
中的e 被明确地键入为枚举,那么如果你没有处理所有的枚举值,你会被警告:
enum Color {
red,
green,
blue
}
// ...
Color 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的类,没有声明构造函数,没有调用super
. 例如:
abstract class 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');
}
}
}
Note: 从1.13起,Dart VM已经取消了对mixin的两个限制::
- Mixins允许从Object以外的类扩展.
- Mixins可以调用
super()
.
dart2js 中还不支持这些 “super mixins” , 并且在Dart分析器中需要 --supermixin
标志.
更多信息查看 文章 Mixins in Dart.
类变量和方法
使用 static
关键字来实现类范围的变量和方法.
静态变量
静态变量(类变量)对于类范围的状态和常量很有用:
代码语言:javascript复制class Color {
static const red =
const Color('red'); // A constant static variable.
final String name; // An instance variable.
const Color(this.name); // A constant constructor.
}
main() {
assert(Color.red.name == 'red');
}
直到使用时静态变量才被初始化.
Note: 此页面遵循 风格指南建议 ,优选使用
lowerCamelCase
作为常量名称.
静态方法
静态方法(类方法)不对一个实例进行操作,因此无法访问this
. 例如:
import 'dart:math';
class Point {
num x;
num 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);
}
}
main() {
var a = new Point(2, 2);
var b = new Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(distance < 2.9 && distance > 2.8);
}
Note: 考虑使用顶级函数,而不是静态方法,用于常用或广泛使用的实用程序和功能.
您可以使用静态方法作为编译时常量。 例如,您可以将静态方法作为参数传递给常量构造函数.
泛型
如果您查看基本数组类型List的API文档, List, 您将看到类型实际上是List<E>
. <…> 符号将List标记为 通用 (或参数化)类型—一个有正规类型参数的类型. 按照惯例,类型变量具有单字母名称,例如: E, T, S, K, 和 V.
为什么要用泛型?
因为Dart中的类型是可选的,所以您不必使用泛型 . 但是,您可能希望,由于同样的原因,您可能希望在代码中使用其他类型:types(通用或不通用)可让您记录和注释代码,从而使您的意图更清晰.
例如,如果您打算列出只包含字符串,则可以将其声明为 List<String>
(将其作为“List of string). 这样你,你的同行程序员和你的工具(如IDE和Dart VM在检查模式下)可以检测到将非字符串分配给列表可能是一个错误。 以下是一个例子:
var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
// ...
names.add(42); // Fails in checked mode (succeeds in production mode).
使用泛型的另一个原因是减少代码重复.泛型让您在多个类型之间共享一个接口和实现, 同时仍然利用检查模式和静态分析预警。 例如,假设您创建一个用于缓存对象的接口:
代码语言:javascript复制abstract class ObjectCache {
Object getByKey(String key);
setByKey(String key, Object value);
}
你发现你想要这个接口的字符串特定版本,所以你创建另一个接口:
代码语言:javascript复制abstract class StringCache {
String getByKey(String key);
setByKey(String key, String value);
}
接着你想要这个接口的数字特别版本… 于是你有了个主意.
通用类型可以节省您创建所有这些接口的麻烦。 相反,您可以创建一个接受类型参数的单一接口:
代码语言:javascript复制abstract class Cache<T> {
T getByKey(String key);
setByKey(String key, T value);
}
在此代码中,T是占位类型。 这是一个占位符,您可以将其视为开发人员稍后将定义的类型.
使用集合字面量
List 和 map 字面量能被参数化.参数化字面量就像你之前见过的字面量一样,除了你在括号之前使用的 <type>
(对于list集合) 或 <keyType, valueType>
(对于map集合) . 当您想要在检查模式下输入警告时,可以使用参数化字面量。 以下是使用类型字面量的示例:
var names = <String>['Seth', 'Kathy', 'Lars'];
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
构造函数上使用参数化类型
要在使用构造函数时指定一个或多个类型,请将类型放在类名后面的尖括号(<...>
) . 例如:
var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = new Set<String>.from(names);
下例中创建了一个键为Integer类型,值为View类型的map集合:
代码语言:javascript复制var views = new Map<int, View>();
泛型集合及其包含的类型
Dart泛型类型被 修改, 意味着会附带类型信息. 例如,您可以测试集合的类型,即使在生产模式下:
代码语言:javascript复制var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
但是,is
表达式仅检查集合的类型 —而不是里面的对象. 在生产模式下, List<String>
里面可能含有非String类型的项. 解决方案是检查每一项的类型或使用异常处理程序包裹项操作代码 (查看 Exceptions).
Note: 相比之下,Java中的泛型使用擦除,这意味着泛型类型参数在运行时被删除。 在Java中,您可以测试对象是否为List,但是不能测试它是否为
List<String>
.
限制参数化类型
实现泛型类型时,可能需要限制其参数的类型。 你可以使用extends
来实现 .
// T must be SomeBaseClass or one of its descendants.
class Foo<T extends SomeBaseClass> {...}
class Extender extends SomeBaseClass {...}
void main() {
// It's OK to use SomeBaseClass or any of its subclasses inside <>.
var someBaseClassFoo = new Foo<SomeBaseClass>();
var extenderFoo = new Foo<Extender>();
// It's also OK to use no <> at all.
var foo = new Foo();
// Specifying any non-SomeBaseClass type results in a warning and, in
// checked mode, a runtime error.
// var objectFoo = new Foo<Object>();
}
使用泛型方法
最初,Dart的通用支持仅限于类。 一种较新的语法(称为泛型方法)允许在方法和函数上使用类型参数:
代码语言:javascript复制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;
}
在 first
(<T>
) 中的泛型类型参数 允许在多个地方使用参数T
:
- 函数返回类型 (
T
). - 参数类型 (
List<T>
). - 本地变量 (
T tmp
).
版本要点: SDK 1.21. 中介绍了泛型方法的新语法。 如果使用泛型方法,请选用 SDK版本为1.21或更高版本.
关于泛型的更多信息, 参阅 Dart中的可选类型 和 使用通用方法.
库及可见性
import
和 library
指令能让你创建模块化和可共享的代码库. 库不仅提供API,而且是一个隐私单位: 以下划线(_)开头的标识符只能在库内部显示。 每个Dart应用程序都是一个库, 即使它不使用library
指令.
库可以使用包分发. 参阅 Pub Package and Asset Manager 查看关于 pub 组件的更多信息, 软件包管理器包含在SDK中.
使用库
使用 import
来指定一个库的命名空间在另外一个库的作用域内被使用.
例如, Dart Web应用程序通常使用 dart:html 库, 可以这样引入:
代码语言:javascript复制import 'dart:html';
import
唯一需要的参数是指定库的URI. 对于内置库,URI具有特殊的dart:scheme。 dart:
scheme. 对于其他库,您可以使用文件系统路径或 package:
scheme. package:
scheme 指定由程序包管理器(如pub工具)提供的库. 例如:
import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';
Note: URI 代表统一的资源标识符. URLs(统一资源定位符)是一种常见的URI.
指定库前缀
如果导入两个库的标识符具有冲突,那么您可以为一个或两个库指定前缀。 例如,如果library1和library2都有一个Element类,那么你可能会有这样的代码:
代码语言:javascript复制import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// ...
Element element1 = new Element(); // Uses Element from lib1.
lib2.Element element2 = new lib2.Element(); // Uses Element from lib2.
引入库的一部分
如果您只想使用库的一部分,则可以有选择地导入库. 例如:
代码语言:javascript复制// Import only foo.
import 'package:lib1/lib1.dart' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
懒加载一个库
延迟加载 (也称为 懒加载) 允许应用程序根据需要加载库,如果需要的话。 以下是您可能会使用延迟加载的情况:
- 减少应用程序的初始启动时间.
- 要执行A / B测试 - 尝试实现,算法的替代 例如.
- 加载很少使用的功能,如可选screens 和 dialogs.
要延迟加载一个库, 引用时使用 deferred as
.
import 'package:deferred/hello.dart' deferred as hello;
当需要一个库时, 使用库的标识符调用 loadLibrary()
.
greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
在上述代码中,直到库被加载 await
关键字将暂停执行. 查看更多关于 async
和 await
的信息, 参阅 asynchrony support.
您可以在库中多次调用loadLibrary()
,而不会出现问题。 该库仅加载一次.
使用延期加载时,请记住以下几点:
- 延迟库的常量不是导入文件中的常量。 记住,这些常量在加载延迟库之前不存在.
- 您不能在导入文件中使用延迟库中的类型。 相反,请考虑将接口类型移动到由延迟库和导入文件导入的库中.
- Dart隐式地将
loadLibrary()
插入到使用deferred as namespace
定义的命名空间中。loadLibrary()
函数返回一个 Future.
实现库
查看 创建 Library Packages 关于如何实现库包的建议.
异步支持
Dart具有几种支持异步编程的语言特性。 这些功能最常用的是 async
函数和 await
表达式.
Dart库充满了返回Future或Stream对象的函数。 这些功能是异步的: 在设置可能耗时的操作(例如I / O)后返回,而不等待该操作完成.
当您需要使用由Future代表的值时,您有两个选择:
- 使用
async
和await
- 使用 Future API
同样,当您需要从Stream获取值时,您有两个选项:
- 使用
async
和一个 asynchronous for loop (await for
) - 使用 Stream API
使用async
和 await
的代码是异步的,但它看起来很像同步代码。 例如,这里有一些使用await
等待异步功能的结果的代码:
await lookUpVersion()
要使用 await
,代码必须在标记为async
的函数中:
checkVersion() async {
var version = await lookUpVersion();
if (version == expectedVersion) {
// Do something.
} else {
// Do something else.
}
}
你可以使用 try
, catch
, 和 finally
处理和清理 await
代码中的错误:
try {
server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044);
} catch (e) {
// React to inability to bind to the port...
}
声明异步函数
一个 async function是一个函数,其主体标有 async
标记. 尽管异步功能可能会执行耗时的操作, 但是在任何正文执行之前,它将立即返回.
checkVersion() async {
// ...
}
lookUpVersion() async => /* ... */;
将async
关键字添加到函数上使其返回Future. 例如,考虑这个同步函数,它返回一个String:
String lookUpVersionSync() => '1.0.0';
如果将其更改为异步函数—例如,由于将来的实现将会耗费时间—返回的值是Future:
代码语言:javascript复制Future<String> lookUpVersion() async => '1.0.0';
请注意,该函数的主体不需要使用Future API。 如有必要,Dart创建Future对象.
结合Futures使用await表达式
await表达式具有以下形式:
代码语言:javascript复制await expression
你可以在一个异步函数中多次使用 await
表达式. 例如, 下面的代码等了3次函数的结果:
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
在await 表达式
中,表达式
的值通常是一个 Future; 如果不是,则该值将自动包装在Future中. 此Future对象表示返回对象的承诺. await 表达式
的值是返回的对象. await 表达式使执行暂停,直到该对象可用.
如果await
不起作用, 请确保它处于异步函数中. 例如, 应用程序的 main()
函数中使用 await
, main()
的正文必须标记为 async
:
main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}
结合 streams 使用异步循环
异步for循环具有以下形式:
代码语言:javascript复制await for (variable declaration in expression) {
// Executes each time the stream emits a value.
}
表达式
的值必须具有Stream类型。 执行情况如下:
- 等待直到Stream发出一个值.
- 执行for循环的主体,将变量设置为该发射值.
- 重复1和2,直到Stream关闭.
要停止侦听流,您可以使用 break
或 return
语句,该语句突破了for循环,并从Stream中取消订阅.
如果异步for循环不起作用,请确保它处于异步功能 例如,要在应用程序的main()
函数中使用异步for循环 main()
的主体必须标记为 async
:
main() async {
...
await for (var request in requestServer) {
handleRequest(request);
}
...
}
有关异步编程的更多信息,请参阅库指南 dart:async . 也可以查看文章 Dart语言异步支持:阶段1和Dart语言异步支持:阶段2, 和 Dart语言规范.
可调用类
为了让你的Dart类像方法一样被调用,请实现 call()
方法.
在下例中, WannabeFunction
类定义了 call() 方法,该函数需要三个字符串并连接它们, 用空格分开,并附加感叹号. 单击运行按钮 (
) 执行代码.
代码语言:javascript复制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中的仿真函数.
隔离
现代网络浏览器甚至可以在移动平台上运行在多核CPU上。 为了利用所有这些核心,开发人员传统上使用并发运行的共享内存线程。 然而,共享状态并发是容易出错的,可能导致复杂的代码.
而不是线程,所有Dart代码都运行在 isolates内. 每个隔离区都有自己的内存堆,确保没有任何其他隔离区可以访问隔离区的状态.
Typedefs
在Dart中,函数是对象,就像字符串和数字是对象一样。typedef 或 function-type alias, 给一个函数类型一个别名,当声明字段和返回类型时,可以使用该名称。 当函数类型分配给变量时.当函数类型分配给变量时,typedef保留类型信息.
思考下列代码,哪一个没有使用 typedef:
代码语言:javascript复制class SortedCollection {
Function compare;
SortedCollection(int f(Object a, Object b)) {
compare = f;
}
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
main() {
SortedCollection coll = new 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
的类型是 Function. 如果我们更改代码以使用显式名称并保留类型信息,那么开发人员和工具都可以使用该信息.
typedef int Compare(Object a, Object b);
class SortedCollection {
Compare compare;
SortedCollection(this.compare);
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
main() {
SortedCollection coll = new SortedCollection(sort);
assert(coll.compare is Function);
assert(coll.compare is Compare);
}
Note: 目前,typedef仅限于功能类型。 我们预计这会改变.
因为typedef是简单的别名,它们提供了检查任何函数类型的方法.例如:
代码语言:javascript复制typedef int Compare(int a, int b);
int sort(int a, int b) => a - b;
main() {
assert(sort is Compare); // True!
}
元数据
使用元数据提供有关您的代码的额外信息。 元数据注解以字符 @
开始,之后是对编译时常数(例如 deprecated
)的引用或对常量构造函数的调用.
所有Dart代码都有三个注解: @deprecated
, @override
, 和 @proxy
. 有关使用 @override
和 @proxy
的示例,请参阅扩展一个类. 以下是使用 @deprecated
注解的例子:
class Television {
/// _Deprecated: Use [turnOn] instead._
@deprecated
void activate() {
turnOn();
}
/// Turns the TV's power on.
void turnOn() {
print('on!');
}
}
您可以定义自己的元数据注解。 下面是一个定义一个@todo注解的示例,它需要两个参数
代码语言:javascript复制library todo;
class todo {
final String who;
final String what;
const todo(this.who, this.what);
}
以下是使用@todo注解的示例:
代码语言:javascript复制import 'todo.dart';
@todo('seth', 'make this do something')
void doSomething() {
print('do something');
}
元数据可以出现在库,类,typedef,类型参数,构造函数,工厂,函数,字段,参数或变量声明之前,以及 import 或 export 指令之前。 您可以使用反射在运行时检索元数据.
注释
Dart 支持单行注释、多行注释、文档注释.
单行注释
单行注释以 //
开头。 Dart编译器忽略 //
和行尾之间的所有内容.
main() {
// TODO: refactor into an AbstractLlamaGreetingFactory?
print('Welcome to my Llama farm!');
}
多行注释
多行注释以/*
开始,以 */
结尾. Dart编译器忽略 /*
和 */
之间的所有内容 (除非注释是文档注释,请参阅下一节). 多行注释可以嵌套.
main() {
/*
* This is a lot of work. Consider raising chickens.
Llama larry = new Llama();
larry.feed();
larry.exercise();
larry.clean();
*/
}
文档注释
文档注释是以 ///
或 /**
开头的多行或单行注释。 在连续行上使用 ///
与多行文档注释具有相同的效果.
在文档注释中,Dart编译器忽略所有文本,除非它包含在括号中。 使用括号,可以参考类,方法,字段,顶级变量,函数和参数。 括号中的名称在已记录的程序元素的词法范围内得到解决.
以下是有关引用其他类和参数的文档注释的示例:
代码语言:javascript复制/// 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文档,可以使用 文档生成工具. 有关生成的文档的示例,请参阅 Dart API 文档. 有关如何组织您的注释,请参阅 Dart文档注解指南.
概要
本页总结了Dart语言中常用的功能。 更多的功能正在实施,但我们期望他们不会破坏现有的代码。 更多信息查看Dart 语言规范 和 有效的 Dart.
要了解有关Dart核心库的更多信息, 参阅 Dart图书馆之旅.