類
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<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 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<void>。
處理流
當您需要從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。
要了解有關Dart核心庫的更多信息,請參閱 Dart Libraries之旅。