構造函數
通過和類相同的名稱創建函數來聲明構造函數(以及命名構造函數中所描述的可選額外標識符)。最常見形式的構造函數,生成構造函數,創建一個類的新實例:
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<String, num> 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<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: 工廠構造函數無法訪問 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<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實現一個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<E>
。<…>標記表示 List是一種泛型(或參數化)類型 – 使用擁有正式類型參數的類型。按照慣例,大部分類型變量都有單字母名稱,如 E, T, S, K和 V。
為什么使用泛型?
泛型通常是類型安全所需要的,但它們有比允許你的代碼運行更多的好處:
- 合理地指定泛型會產生更好的生成代碼。
- 可以使用泛型來減少代碼重復。
如果你想要列表僅包含字符串,可以聲明它為List<String>
(將其讀作“字符串列表”)。這樣你、你的程序員小伙伴和你的工具都會監測到為該列表賦非字符串值可能是錯誤的。以下是一個示例:
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 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的泛型是實化(reified)類型,表示它們可以在運行時攜帶類型信息。例如,可以測試一個集合的類型:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
Note: 不同的是,Java中的泛型使用的是擦除(erasure)類型,表示泛型參數在運時會被刪除。在 Java 中可以測試一個對象是否為List,但無法測試它是否為 List<String>
。
限制參數化類型
在實現泛型時,你可能會希望限制它的參數的類型。可以使用 extends
來做到:
class Foo<T extends SomeBaseClass> {
// 實現代碼在這里...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
使用 SomeBaseClass
或任意其它子類作為泛型的參數都是可以的:
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
也可以不指定泛型參數:
var foo = Foo();
print(foo); // 'Foo<SomeBaseClass>'的實例
指定任意非SomeBaseClass
類型會導致報錯:
var foo = Foo<Object>();
使用泛型方法
一開始Dart對泛型的支持僅限于類。一種新的語法,稱為泛型方法(generic method),允許在方法和函數中使用類型參數:
first<T>(List<T> ts) {
// 做一些初始化工作或錯誤檢查,然后...
tmp = ts[0];
// 做一些其它檢查或處理...
return tmp;
}
這里對first
(<T>
) 的泛型參數允許我們在多處使用類型參數 T
:
- 在函數的返回類型中 (
T
) - 在參數的類型中(
List<T>
) - 在局部變量的類型中 (
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<String> lookUpVersion() async => '1.0.0';
注意函數體不需要使用Future API。 Dart在必要時會創建Future對象。如果函數不返回有用的值,設置其返回類型為 Future<void>
。
對于使用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內置支持兩種生成器函數:
要實現一個同步生成器函數,將函數體標記為 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);
}
}
可調用類
實現 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<T> = int Function(T a, T b);
int sort(int a, int b) => a - b;
void main() {
assert(sort is Compare<int>); // 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的個人博客