構(gòu)造函數(shù)
通過和類相同的名稱創(chuàng)建函數(shù)來聲明構(gòu)造函數(shù)(以及命名構(gòu)造函數(shù)中所描述的可選額外標(biāo)識符)。最常見形式的構(gòu)造函數(shù),生成構(gòu)造函數(shù),創(chuàng)建一個類的新實(shí)例:
class Point {
num x, y;
Point(num x, num y) {
// 有更好的實(shí)現(xiàn)方式,請保持關(guān)注
this.x = x;
this.y = y;
}
}
this 關(guān)鍵字引用的是當(dāng)前實(shí)例。
Note: 僅在有名稱沖突時使用 this 。否則Dart 的樣式省略 this。
為實(shí)例變量賦值構(gòu)造函數(shù)的參數(shù)非常普通 ,Dart提供了語法糖來進(jìn)行簡化:
class Point {
num x, y;
// 在構(gòu)造函數(shù)體運(yùn)行前用于
// 設(shè)置 x 和 y 的語法糖
Point(this.x, this.y);
}
默認(rèn)構(gòu)造函數(shù)
如未聲明構(gòu)造函數(shù),會為你提供默認(rèn)的構(gòu)造函數(shù)。默認(rèn)構(gòu)造函數(shù)沒有參數(shù)并在超類中調(diào)用無參數(shù)構(gòu)造函數(shù)。
未繼承構(gòu)造函數(shù)
子類不會從超類中繼承構(gòu)造函數(shù)。未聲明構(gòu)造函數(shù)的子類僅擁有默認(rèn)構(gòu)造函數(shù)(無參數(shù),無名稱)。
命名構(gòu)造函數(shù)
使用命令構(gòu)造函數(shù)來為類實(shí)現(xiàn)多個構(gòu)造函數(shù)或增強(qiáng)清晰度:
class Point {
num x, y;
Point(this.x, this.y);
// 命名構(gòu)造函數(shù)
Point.origin() {
x = 0;
y = 0;
}
}
記住構(gòu)造函數(shù)并不會繼承,也就表示超類的構(gòu)造函數(shù)不會由子類繼承。如果希望通過超類中定義的命名構(gòu)造函數(shù)創(chuàng)建子類 ,必須在子類中實(shí)現(xiàn)這個構(gòu)造函數(shù)。
調(diào)用非默認(rèn)超類構(gòu)造函數(shù)
默認(rèn),子類中的構(gòu)造函數(shù)調(diào)用超類中的未命名、無參數(shù)的構(gòu)造函數(shù)。超類的構(gòu)造函數(shù)在構(gòu)造函數(shù)體的開頭調(diào)用。如果還使用了 初始化程序列表,它在超類調(diào)用前執(zhí)行。總之,執(zhí)行的順序如下:
- 初始化程序列表initializer list
- 超類的無參構(gòu)造函數(shù)
- 主類的無參構(gòu)造函數(shù)
如果超類中沒有未命名的無參構(gòu)造函數(shù),那么就必須手動調(diào)用超類的一個構(gòu)造函數(shù)。在冒號(:)后、剛好在構(gòu)造函數(shù)體前(如有)指定超類構(gòu)造函數(shù)。
在下例中,Employee類的構(gòu)造函數(shù)調(diào)用其超類Person的命名構(gòu)造函數(shù)。點(diǎn)擊 Run 來執(zhí)行代碼。
因?yàn)槌悩?gòu)造函數(shù)的參數(shù)在調(diào)用構(gòu)造函數(shù)前運(yùn)行,參數(shù)可以是像函數(shù)調(diào)用這樣的表達(dá)式:
class Employee extends Person {
Employee() : super.fromJson(getDefaultData());
// ···
}
Warning: 超類構(gòu)造函數(shù)的參數(shù)無法訪問 this。例如,參數(shù)可調(diào)用靜態(tài)方法但無法調(diào)用實(shí)例方法。
初始化程序列表
除調(diào)用超類構(gòu)造函數(shù)外,也可以在構(gòu)造函數(shù)體運(yùn)行之前初始化實(shí)例變量。將初始化程序以逗號分隔。
// 初如化程序列表在構(gòu)造函數(shù)體運(yùn)行前
// 設(shè)置實(shí)例變量
Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
Warning: 初始化程序的右側(cè)無法訪問 this。
在開發(fā)過程中,可以在初始化程序列表中使用assert來驗(yàn)證輸入。
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
初始化程序列表在設(shè)置final字段時非常方便。以下示例在初始化程序列表中初始化了3個final字段。點(diǎn)擊 Run 來執(zhí)行代碼。
重定向構(gòu)造函數(shù)
有時構(gòu)造函數(shù)的目的僅是重定向到相同類中的其它構(gòu)造函數(shù)。重定向構(gòu)造函數(shù)體為空,構(gòu)造函數(shù)調(diào)用出現(xiàn)在冒號(:)之后。
class Point {
num x, y;
// 這個類的主構(gòu)造函數(shù)
Point(this.x, this.y);
// 代理重定向至主構(gòu)造函數(shù)
Point.alongXAxis(num x) : this(x, 0);
}
常量構(gòu)造函數(shù)
如果類產(chǎn)生永不修改的對象,可以讓這些對象成為編譯時常量。此時,定義一個 const 構(gòu)造函數(shù)并確保所有的實(shí)例變量為 final。
class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
final num x, y;
const ImmutablePoint(this.x, this.y);
}
常量構(gòu)造函數(shù)并不總是創(chuàng)建常量。更多詳情,請參見 使用構(gòu)造函數(shù)一節(jié)。
工廠構(gòu)造函數(shù)
在實(shí)現(xiàn)不會一直新建類的實(shí)例的構(gòu)造函數(shù)時使用factory 關(guān)鍵字。你不能吃,一個工廠構(gòu)造函數(shù)可能會從緩存返回一個實(shí)例,或者它可能返回一個子類型的實(shí)例。
以下示例演示從緩存返回對象的工廠構(gòu)造函數(shù):
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: 工廠構(gòu)造函數(shù)無法訪問 this。
可以像調(diào)用其它構(gòu)造函數(shù)那樣調(diào)用工廠構(gòu)造函數(shù):
var logger = Logger('UI');
logger.log('Button clicked');
方法
方法是為對象提供行為的函數(shù)。
實(shí)例方法
對象的實(shí)例方法可以訪問實(shí)例變量和 this。下面示例中的 distanceTo() 方法是一個實(shí)例方法的示例:
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是提供對象屬性的讀寫訪問的特殊方法。記得每個實(shí)例變量有一個隱式getter,以及如若合適時的setter??梢允褂?code>get和set關(guān)鍵字通過實(shí)現(xiàn)getter和setter來創(chuàng)建額外的屬性:
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// 定義兩個計(jì)算屬性: 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,可以以實(shí)例變量開始,然后通過方法封裝它們,都無需修改客戶端模式。
Note: 不論是否顯式的定義getter遞增 (++)等運(yùn)算符都以預(yù)期的方式運(yùn)行。 為避免任意預(yù)期的副作用,運(yùn)算符僅調(diào)用getter一次,在臨時變量中w 保存它的值。
抽象方法
實(shí)例、getter和setter方法可以是抽象的,定義接口并將其實(shí)現(xiàn)留給其它類。抽象方法僅能存在于 抽象類中。
要讓方法為抽象的,使用分號 (;) 而非方法體:
abstract class Doer {
// 定義實(shí)例變量和方法...
void doSomething(); // 定義一個抽象方法
}
class EffectiveDoer extends Doer {
void doSomething() {
// 提供一個實(shí)現(xiàn),因此這里該方法不是抽象的...
}
}
抽象類
使用 abstract 修飾符來定義一個抽象類 – 一個無法實(shí)例化的類。抽象類對于定義接口非常有用,通常伴有一些實(shí)現(xiàn)。如果希望抽象類為可實(shí)例化的,定義一個 工廠構(gòu)造函數(shù)。
抽象類通常有 抽象方法。這下是一個聲明擁有抽象方法的抽象類的示例:
// 這個類聲明為抽象類,
// 因此無法實(shí)例化。
abstract class AbstractContainer {
// 定義構(gòu)造函數(shù)、字段、方法...
void updateChildren(); // 抽象方法
}
隱式接口
每個類隱式定義包含類的所有實(shí)例成員的接口及它實(shí)現(xiàn)的任意接口。如果希望創(chuàng)建支持類B而不繼承B的應(yīng)用的類A,類 A 應(yīng)實(shí)現(xiàn) B 的 接口。
類A通過在implements從句中通過聲明它們來實(shí)現(xiàn)一個或多個接口,然后中提供這些接口所要求的 API。例如:
// A person. 包含greet()的隱式接口
class Person {
// 在接口中,但僅在本庫中可見
final _name;
// 不在接口中,因?yàn)檫@是一個構(gòu)造函數(shù)
Person(this._name);
// 在接口中
String greet(String who) => 'Hello, $who. I am $_name.';
}
// 一個Person接口的實(shí)現(xiàn)
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()));
}
這是一個指定類實(shí)現(xiàn)多個接口的示例:
class Point implements Comparable, Location {...}
繼承類
使用 extends 來創(chuàng)建子類,并用 super 來引用超類:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
重載成員
子類可重載實(shí)例方法、getters和setters??梢允褂?@override 標(biāo)注來表明你想要重載一個成員:
class SmartTelevision extends Television {
@override
void turnOn() {...}
// ···
}
要精減類型安全的代碼中方法參數(shù)或?qū)嵗兞康念愋停梢允褂?a target="_blank">關(guān)鍵字 covariant。
可重載運(yùn)算符
可以重載下表中所顯示的運(yùn)算符。例如,如果你定義了一類Vector類,可以定義一個+ 方法來對兩個向量進(jìn)行想加。
| < | + | | | [] |
| > | / | ^ | []= |
| <= | ~/ | & | ~ |
| >= | * | << | == |
| – | % | >> | |
Note: 你可能流到到 != 并不是一個可重載運(yùn)算符。表達(dá)式 e1 != e2 只是針對 !(e1 == e2)的語法糖。
以下是一個重載重載 + 和 - 運(yùn)算符的類的示例:
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);
// 未顯示運(yùn)算符 == 和 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));
}
如果重載==,還應(yīng)當(dāng)重載 Object的 hashCode getter。有關(guān)重載 == 和 hashCode的示例,參見 實(shí)現(xiàn)映射的鍵。
更多有關(guān)重載總的信息,參見 繼承類。
noSuchMethod()
要在代碼嘗試使用不存在的方法或?qū)嵗兞繒r進(jìn)行監(jiān)測或回應(yīng),可以重載noSuchMethod():
class A {
// 除非你重載noSuchMethod,使用一個不存在的
// 成員會導(dǎo)致NoSuchMethodError報錯
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: ' +
'${invocation.memberName}');
}
}
你無法調(diào)用一個未實(shí)現(xiàn)的方法,以下情況除外:
- 接收者有一個靜態(tài)類型
dynamic。 - 接收者有一個定義未實(shí)現(xiàn)方法的靜態(tài)類型(抽象方法沒有問題),并且接收者的動態(tài)類型有一個不同于
Object類中的對noSuchMethod()的實(shí)現(xiàn)。
更多信息,參數(shù)非正式的 noSuchMethod 推送規(guī)范。
枚舉類型
枚舉類型,常稱為枚舉或enum, 是一種用于展示固定數(shù)量的常量值的特殊的類。
使用enum
使用enum關(guān)鍵字聲明一個枚舉類型:
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實(shí)現(xiàn)一個enum。
- 不能顯式地實(shí)例化一個 enum。
更多信息,參見Dart語言規(guī)范。
為類添加功能: mixin
mixin是在多級類中利用類的代碼的一種方式。
要使用mixin,使用 with 關(guān)鍵字接一個或多個mixin 名稱。以下示例顯示使用mixin的兩個類:
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
要實(shí)現(xiàn)一個mixin,創(chuàng)建繼承Object和未聲明構(gòu)造函數(shù)的類。除非你希望讓mixin和普通類一樣可用,否則請使用 mixin 關(guān)鍵字來替代 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可以調(diào)用它未定義的方法 – 使用 on 來指定所要求的超類:
mixin MusicalPerformer on Musician {
// ···
}
Version note: 對 mixin 關(guān)鍵字的支持在Dart 2.1中引入。更早發(fā)行版本中的代碼通常使用abstract class來進(jìn)行代替。更多有關(guān)2.1對mixinw 修改的信息,參見 Dart SDK修改記錄 和 2.1 mixin規(guī)范。
類變量和方法
使用 static 關(guān)鍵字來實(shí)現(xiàn)類范圍的變量和方法。
靜態(tài)變量
靜態(tài)變量(類變量)對類范圍的狀態(tài)和常量非常有用:
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
靜態(tài)變量直到使用時才進(jìn)行初始化。
Note: 本文遵循 樣式指南推薦 的推薦,使用 lowerCamelCase (首字母小寫的駝峰命名法)來作為常量名。
靜態(tài)方法
靜態(tài)方法(類方法)不對實(shí)例進(jìn)行操作,因此無法訪問 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: 考慮對常用或廣泛使用的工具和功能使用頂級函數(shù)來代替靜態(tài)方法。
可以使用靜態(tài)方法來作為編譯時常量。例如,你可以傳遞一個靜態(tài)方法作為對常量構(gòu)造函數(shù)的參數(shù)。
泛型
如果你在查看基本數(shù)組類型List的 API 文檔,實(shí)際上看到的類型是 List<E>。<…>標(biāo)記表示 List是一種泛型(或參數(shù)化)類型 – 使用擁有正式類型參數(shù)的類型。按照慣例,大部分類型變量都有單字母名稱,如 E, T, S, K和 V。
為什么使用泛型?
泛型通常是類型安全所需要的,但它們有比允許你的代碼運(yùn)行更多的好處:
- 合理地指定泛型會產(chǎn)生更好的生成代碼。
- 可以使用泛型來減少代碼重復(fù)。
如果你想要列表僅包含字符串,可以聲明它為List<String> (將其讀作“字符串列表”)。這樣你、你的程序員小伙伴和你的工具都會監(jiān)測到為該列表賦非字符串值可能是錯誤的。以下是一個示例:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error
另一個使用泛型的原因是減少代碼重復(fù)量。泛型讓我們可以在多個類型中共享單個接口和實(shí)現(xiàn),而又仍擁有靜態(tài)分析的好處。例如,假設(shè)你創(chuàng)建接口來捕獲一個對象:
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
發(fā)現(xiàn)你想要該接口的字符串版本,所以創(chuàng)建了另一個接口:
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
在以上代碼中,T是占位類型。它是一個可以看作類型的占位符,開發(fā)者可以在稍后定義這個類型。
使用集合字面量
列表、集和映射字面量可以是參數(shù)化的。參數(shù)字面量和我們所看到的其它字面量一樣,只是可以在方括號(或花括號)之前添加 <*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'
};
使用帶有構(gòu)造函數(shù)的參數(shù)化類型
在使用構(gòu)造函數(shù)時要指定一個或多個類型,將類型放在類名后的尖括號 (<...>) 中。例如:
var nameSet = Set<String>.from(names);
以下代碼創(chuàng)建一個鍵為整型值為View類型的映射:
var views = Map<int, View>();
泛型集合及它們所包含的類型
Dart的泛型是實(shí)化(reified)類型,表示它們可以在運(yùn)行時攜帶類型信息。例如,可以測試一個集合的類型:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
Note: 不同的是,Java中的泛型使用的是擦除(erasure)類型,表示泛型參數(shù)在運(yùn)時會被刪除。在 Java 中可以測試一個對象是否為List,但無法測試它是否為 List<String>。
限制參數(shù)化類型
在實(shí)現(xiàn)泛型時,你可能會希望限制它的參數(shù)的類型??梢允褂?extends來做到:
class Foo<T extends SomeBaseClass> {
// 實(shí)現(xiàn)代碼在這里...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
使用 SomeBaseClass 或任意其它子類作為泛型的參數(shù)都是可以的:
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
也可以不指定泛型參數(shù):
var foo = Foo();
print(foo); // 'Foo<SomeBaseClass>'的實(shí)例
指定任意非SomeBaseClass 類型會導(dǎo)致報錯:
var foo = Foo<Object>();
使用泛型方法
一開始Dart對泛型的支持僅限于類。一種新的語法,稱為泛型方法(generic method),允許在方法和函數(shù)中使用類型參數(shù):
first<T>(List<T> ts) {
// 做一些初始化工作或錯誤檢查,然后...
tmp = ts[0];
// 做一些其它檢查或處理...
return tmp;
}
這里對first (<T>) 的泛型參數(shù)允許我們在多處使用類型參數(shù) T :
- 在函數(shù)的返回類型中 (
T) - 在參數(shù)的類型中(
List<T>) - 在局部變量的類型中 (
T tmp).
更多有關(guān)泛型的信息,參見使用泛型方法。
庫和可見性
import 和 library 指令有且于我們創(chuàng)建模塊化和可分享的代碼基。庫不僅提供API,也是一個私有單元:以下劃線(_)開頭的標(biāo)識符僅在庫內(nèi)可見。每個 Dart 應(yīng)用都是庫,即使它不使用 library 指令。
庫可以使用包進(jìn)行分發(fā)。
使用庫
使用 import 來指定一個庫中的命名空間如何在另一個庫的作用域中使用。例如,Dart web應(yīng)用通常使用 dart:html 庫,可以像這樣導(dǎo)入:
import 'dart:html';
import 所要求的唯一參數(shù)是一個指定庫的URI。對于內(nèi)置庫,URI有一個特殊的 dart: 協(xié)議。對于其它庫,可以使用文件系統(tǒng)路徑或package: 協(xié)議。 package: 協(xié)議指定由像pub工具這樣的包管理器所提供的庫。例如:
import 'package:test/test.dart';
Note: URI 表示統(tǒng)一資源標(biāo)識符。 URL(統(tǒng)一資源定位符) 是一種常見的URI。
指定庫的前綴
如果導(dǎo)入兩個帶有沖突標(biāo)識符的庫,可以對其中之一或兩者指定一個前綴。例如,如果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();
僅導(dǎo)入庫的部分內(nèi)容
如果只想要使用庫的部分內(nèi)容,可以選擇性的導(dǎo)入庫,例如:
// 僅導(dǎo)入foo
import 'package:lib1/lib1.dart' show foo;
// 導(dǎo)入除foo以外的所有名稱
import 'package:lib2/lib2.dart' hide foo;
懶加載庫
延時加載(也稱作懶加載) 允許web應(yīng)用按照需求在需要用到庫時加載庫。以下是一些可能會用到延時加載的情況:
- 為減少web應(yīng)用的初始啟動時間。
- 執(zhí)行A/B測試 – 比如嘗試一種算法的可選實(shí)現(xiàn)。
- 加載很少使用到的功能,如可選界面和對話框。
僅有dart2js支持延時加載。Flutter, Dart VM和dartdevc 均不支持延時加載。更多內(nèi)容,參見issue #33118 和 issue #27776。
要對庫進(jìn)行懶加載,必須首先使用 deferred as導(dǎo)入它。
import 'package:greetings/hello.dart' deferred as hello;
在需要該庫時,使用庫的標(biāo)識符調(diào)用 loadLibrary() 。
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
以上代碼中, await 關(guān)鍵字暫停代碼執(zhí)行直到庫被加載。更多有關(guān)async 和 await的內(nèi)容,參見 異步支持。
可以對庫進(jìn)行多次 loadLibrary() 調(diào)用,并不會產(chǎn)生問題。該庫只會加載一次。
使用延時加載時記住如下各點(diǎn):
- 延時加載庫的常量在導(dǎo)入文件中不是常量。記住,這些量直至延時庫加載后才存在。
- 不能在導(dǎo)入文件中使用延時庫中的類型。而是考慮將接口類型移到由延時庫和導(dǎo)入文件所共同導(dǎo)入的庫中。
- Dart在你使用
deferred as *namespace*所定義的命名空間中隱式地插入loadLibrary()。loadLibrary()函數(shù)返回Future。
實(shí)現(xiàn)庫
參見創(chuàng)建庫軟件包 來獲取有關(guān)如何實(shí)現(xiàn)一個庫軟件包的建議,包括:
- 如何組織庫的源碼
- 如何使用
export指令 - 何時使用
part指令 - 何時使用
library指令
異步支持
Dart庫中有大量返回 Future 或 Stream 對象的函數(shù)。這些函數(shù)是異步的,它們在設(shè)置一個可能很耗時的操作(如 I/O)后返回,無需等待操作完成。
async 和 await 關(guān)鍵字支持異步編程,讓你可以編寫看起來類似同步代碼的異步代碼。
處理Future
在需要完成的Future結(jié)果時,有兩個選擇:
- 使用
async和await - 使用在庫導(dǎo)覽中所描述的Future API
使用async 和 await 的是異步代碼,但和同步代碼很像。例如,下面的代碼使用了await 來等待異步函數(shù)的執(zhí)行結(jié)果:
await lookUpVersion();
要使用 await,代碼必須放在一個 async 函數(shù)中,一個標(biāo)記為 async的函數(shù)中:
Future checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
Note: 雖然 async 函數(shù)可能會執(zhí)行些耗時的操作,但它不會等待這些操作。而是 async函數(shù)僅在其遇到第一個await 表達(dá)式 (詳情)時才執(zhí)行.。然后它返回一個Future對象,僅在 await 表達(dá)式完成后才恢復(fù)執(zhí)行。
使用和 try, catch和 finally 來在使用了await的代碼中處理錯誤和清理:
try {
version = await lookUpVersion();
} catch (e) {
// React to inability to look up the version
}
可以在一個async函數(shù)中多次使用 await 。例如,以下代碼對函數(shù)的執(zhí)行結(jié)果等待3次:
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
在 await表達(dá)式 *expression*中, *表達(dá)式*的值通常為Future,如若不是,那么其值自動在Future中進(jìn)行封裝。這個Future對象表明會返回一個對象。await表達(dá)式 的值是那么返回的對象。await表達(dá)式讓執(zhí)行暫停,直至對象可用。
如果你在使用 await時得到了一個編譯時錯誤, 確保 await 出現(xiàn)在async 函數(shù)中。例如,在應(yīng)用的main()函數(shù)中使用 await, main()函數(shù)體必須標(biāo)記為 async:
Future main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}
聲明async函數(shù)
async 函數(shù)是一個通過async修飾符標(biāo)記函數(shù)體的函數(shù)。
對函數(shù)添加 async 關(guān)鍵字會讓其返回一個Future。例如,思考下面這個返回一個字符串的同步函數(shù):
String lookUpVersion() => '1.0.0';
如果你將其修改為異步 函數(shù), 例如因?yàn)槲磥韺?shí)現(xiàn)會非常耗時 – 返回的值就是一個 – Future:
Future<String> lookUpVersion() async => '1.0.0';
注意函數(shù)體不需要使用Future API。 Dart在必要時會創(chuàng)建Future對象。如果函數(shù)不返回有用的值,設(shè)置其返回類型為 Future<void>。
對于使用futures, async和 await交互入門介紹,參數(shù) 異步編程codelab.
處理流(Stream)
在需要從Stream獲取值時,有兩種選擇:
- 使用
async和一個異步for循環(huán) (await for)。 - 使用Stream API,參見 在庫的導(dǎo)覽中所述。
Note: 在使用 await for之前,確保它會讓代碼更清晰并且你確實(shí)需要等待所有stream的結(jié)果。例如,通常對于 UI 事件監(jiān)聽器不應(yīng)使用await for ,因?yàn)?UI框架會發(fā)送無數(shù)的事件流。
異步 for循環(huán)有如下的形式:
await for (varOrType identifier in expression) {
// 在每次流發(fā)射值時執(zhí)行
}
*expression* 的值必須為 Stream類型。執(zhí)行流程如下:
- 等流發(fā)射值時
- 執(zhí)行 for循環(huán)體,將變量設(shè)置為所射的值
- 重復(fù)1 和 2 直到流關(guān)閉
要停止監(jiān)聽流,可以使用 break 或 return 語句,它會跳出for循環(huán)并取消對流的監(jiān)聽。
如果在實(shí)現(xiàn)異步for 循環(huán)時得到了一個編譯時錯誤,確保 await for 出現(xiàn)在一個 async 函數(shù)中。例如,要在應(yīng)用的main() 中使用異步 for循環(huán),main()函數(shù)體必須標(biāo)記為 async:
Future main() async {
// ...
await for (var request in requestServer) {
handleRequest(request);
}
// ...
}
更多有關(guān)異步編程的信息,參見庫導(dǎo)覽的 dart:async 一節(jié)。
生成器
在需要便利地生成一系列值時,考慮使用生成器函數(shù)。Dart內(nèi)置支持兩種生成器函數(shù):
要實(shí)現(xiàn)一個同步生成器函數(shù),將函數(shù)體標(biāo)記為 sync*,并使用 yield 語句來傳送值:
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
要實(shí)現(xiàn)一個異步生成器函數(shù),將函數(shù)體標(biāo)記為 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);
}
}
可調(diào)用類
實(shí)現(xiàn) call() 方法來允許Dart類的實(shí)例可以像函數(shù)一樣調(diào)用。
在下例中,WannabeFunction 類定義一個接收3個字符串并進(jìn)行拼接的call()函數(shù),每個字符串間以空格分隔,最后接感嘆號。點(diǎn)擊 Run 來執(zhí)行代碼。
隔離(Isolate)
大部分電腦,甚至是移動平臺,都有多核 CPU。為利用這些多核,開發(fā)者的傳統(tǒng)做法是使用共享內(nèi)存線程并發(fā)運(yùn)行。但共享狀態(tài)并發(fā)很容易出錯并導(dǎo)致復(fù)雜的代碼。
代替線程,所有的Dart代碼在isolate內(nèi)運(yùn)行。每個isolate有其自己的內(nèi)存堆,確保沒有isolate的狀態(tài)可通過另一個isolate訪問。
更多內(nèi)容,參見:
- Dart異步編程: Isolate和事件循環(huán)
- dart:isolate API手冊 包含 Isolate.spawn() 和 TransferableTypedData
- Flutter網(wǎng)站上的后臺解析 指南
Typedef
在Dart中,函數(shù)像字符串和數(shù)值一樣都是對象。typedef,或函數(shù)類型別名,給函數(shù)類型一個可在聲明字段和返回類型時使用的名稱。typedef在函數(shù)類型賦值給變量時保留類型信息。
思考如下未使用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是一個函數(shù),
// 但什么類型的函數(shù)呢?
assert(coll.compare is Function);
}
在將f 賦值給 compare時類型信息丟失了。 f的類型為 (Object, ``Object) → int (→ 表示返回),而compare 的類型是Function。如果我們修改代碼使用顯式的名稱并保留類型信息,開發(fā)人員和工具都可以使用該信息。
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: 當(dāng)前typedef僅限于函數(shù)類型。我們預(yù)計(jì)會進(jìn)行調(diào)整。
因?yàn)閠ypedef只是別名,它們提供一種檢查任意函數(shù)類型的方式。例如:
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!
}
元數(shù)據(jù)(metadata)
使用metadata來為代碼提供更多信息。metadata標(biāo)以字符 @開始,后接對編譯時常量的引用(如 deprecated) 或?qū)ΤA繕?gòu)造函數(shù)的調(diào)用。
Dart代碼中有兩個可用的標(biāo):@deprecated 和 @override。 對于使用 @override的示例,參見 繼承類。以下是一個使用 @deprecated 標(biāo)注的示例:
class Television {
/// _Deprecated: 使用 [turnOn] 來代替。_
@deprecated
void activate() {
turnOn();
}
/// 打開電視的電源
void turnOn() {...}
}
可以定義自己的metadata標(biāo)。下面是定義接收兩個參數(shù)的@todo標(biāo)注的示例:
library todo;
class Todo {
final String who;
final String what;
const Todo(this.who, this.what);
}
以下是使用該 @todo 標(biāo)注的示例:
import 'todo.dart';
@Todo('seth', 'make this do something')
void doSomething() {
print('do something');
}
metadata 可以出現(xiàn)在庫、類、typedef、類型參數(shù)、構(gòu)造函數(shù)、工廠、函數(shù)、字段、參數(shù)或變量聲明之前,以及放在import或export指令之前??梢栽谶\(yùn)行時使用反射來獲取metadata。
注釋
Dart支持單行注釋、多行注釋和文檔注釋。
單行注釋
單行注釋以 //開頭。 // 和行尾之間的所有內(nèi)容都會被Dart編譯器所忽略
void main() {
// TODO: refactor into an AbstractLlamaGreetingFactory?
print('Welcome to my Llama farm!');
}
多行注釋
多行注釋以 /* 開頭,并以 */結(jié)束。 /* 和 */ 之間的所有內(nèi)容都被Dart編譯器所忽略(除非注釋是文檔注釋,參見下一節(jié))。多行注釋可以嵌套。
void main() {
/*
* This is a lot of work. Consider raising chickens.
Llama larry = Llama();
larry.feed();
larry.exercise();
larry.clean();
*/
}
文檔注釋
文檔注釋是以/// 或 /**開頭的多行或單行注釋。對連續(xù)行使用/// 與多行文檔注釋異曲同工。
在文檔注釋內(nèi)部, Dart編譯器忽略方括號所包裹之外的文件。使用方括號,可以引用類、方法、字段、頂級變量、函數(shù)和參數(shù)。方括號中的名稱在文檔記錄的程序元素詞法作用域中解析。
以下是一個帶有其它類和參數(shù)的文檔注釋的示例:
/// 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的文檔生成工具。 有關(guān)生成的文檔的示例,參見 Dart API文檔。有關(guān)如何架構(gòu)注釋的建議,參見 Dart文檔注釋指南。
總結(jié)
本頁總結(jié)了Dart語言的常用特性。更多功能正在實(shí)現(xiàn)中,但我們預(yù)計(jì)它們不會破壞現(xiàn)有代碼。更多信息,參見Dart語言規(guī)范 和 高效Dart.
要了解Dart核心庫的更多知識,參見 Dart庫導(dǎo)覽.
本文首發(fā)地址:Alan Hou的個人博客