Dart語言指南(二)

類 Classes

Dart是一種面向對象的語言 包含類和基於 mixin 的繼承兩部分。每一個對象是一個類的實例, 而且 Object.是全部類的父類。 基於 mixin 的繼承指的是每一個類(除了 Object )都只有一個父類,類體還能夠在多個類繼承中被重用。html

要建立一個對象,你可使用 new 關鍵詞並在其後跟上一個構造函數 .構造函數能夠寫成 ClassName或者ClassName.identifier. 好比:java

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);

對象擁有由函數和數據組成的成員(方法 和 實例變量).當你調用一個方法時, 是基於一個對象調用它的: 這個方法可以訪問該對象的全部方法和數據.git

使用一個點(.) 引用實例變量或方法:程序員

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時產生的異常:github

// If p is non-null, set its y value to 4.
p?.y = 4;

一些類提供常量構造函數。 要使用常量構造函數建立編譯時常數,請使用const 代替 new:web

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!

在運行時獲取一個對象的類型, 你可使用Object類的 runtimeType 屬性, 該屬性返回一個 Type 對象.express

print('The type of a is ${a.runtimeType}');

如下部分討論如何實現類.編程

變量實例

聲明實例變量:json

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.

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). 構造函數的最多見形式是生成構造函數,建立一個類的新實例:

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語法糖讓其變得容易:

class Point {
  num x;
  num y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}

默認構造函數

若是您沒有聲明構造函數,則會爲您提供默認構造函數。 默認構造函數沒有參數,並調用父類中的無參數構造函數。.

構造函數不能繼承

子類不會從他們的超類繼承構造函數.聲明沒有構造函數的子類只有默認(無參數,無名稱)構造函數.

命名構造函數

使用命名構造函數爲類實現多個構造函數或提供額外的聲明:

class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // Named constructor
  Point.fromJson(Map json) {
    x = json['x'];
    y = json['y'];
  }
}

請記住,構造函數不是繼承的,這意味着父類的命名構造函數不會被子類繼承。 若是要使用父類中定義的命名構造函數建立子類,則必須在子類中實現該構造函數.

調用父類的非默認構造函數

默認狀況下,子類中的構造函數調用超類的未命名的無參數構造函數。 超類的構造函數在構造函數體的起始處被調用。 若是一個 初始化器列表 也被使用,它將在超類被調用以前執行。 總而言之,執行順序以下:

  1. 初始化程序列表
  2. 父類的無參構造
  3. 主類的無參構造

若是超類沒有未命名的無參數構造函數,則必須手動調用超類中的一個構造函數。 在冒號 (:)以後,在構造函數體(若是有的話)以前指定超類構造函數.

下面的例子中,Employee類的構造函數調用了其父類Person的命名構造函數. 點擊運行按鈕 ( red-run.png ) 執行代碼.

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(findDefaultData());
}

Note: 在構造函數初始化列表中使用super() 時,將其放在最後. 更多信息查看 Dart usage guide.

Warning: 父類的構造函數的參數無權訪問 this. 好比, 參數能訪問靜態方法不能訪問實例方法.

Initializer list

除了調用超類構造函數以外,還能夠在構造函數體運行以前初始化實例變量,用逗號分隔初始化器.

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字段。 單擊運行按鈕( red-run.png) 執行代碼.

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;
  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 關鍵字. 例如, 一個工廠構造函數可能從緩存中返回實例或返回一個子類的實例.

下面的例子演示工廠構造函數如何從緩存中返回對象:

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方法能夠是抽象的,定義一個接口,但將其實現交給其餘類。 要使方法變成抽象方法,請使用分號 (;) 而不是方法體:

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 .

抽象類一般具備 抽象方法. 這是一個聲明一個抽象類的例子,它有一個抽象方法:

// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
  // ...Define constructors, fields, methods...

  void updateChildren(); // Abstract method.
}

如下類不是抽象的,所以即便它定義了一個抽象方法也能夠實例化:

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()));
}

下面是一個指定一個類實現多個接口的例子:

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 關鍵字來實現類範圍的變量和方法.

靜態變量

靜態變量(類變量)對於類範圍的狀態和常量頗有用:

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).

使用泛型的另外一個緣由是減小代碼重複.泛型讓您在多個類型之間共享一個接口和實現, 同時仍然利用檢查模式和靜態分析預警。 例如,假設您建立一個用於緩存對象的接口:

abstract class ObjectCache {
  Object getByKey(String key);
  setByKey(String key, Object value);
}

你發現你想要這個接口的字符串特定版本,因此你建立另外一個接口:

abstract class StringCache {
  String getByKey(String key);
  setByKey(String key, String value);
}

接着你想要這個接口的數字特別版本… 因而你有了個主意.

通用類型能夠節省您建立全部這些接口的麻煩。 相反,您能夠建立一個接受類型參數的單一接口:

abstract class Cache<T> {
  T getByKey(String key);
  setByKey(String key, T value);
}

在此代碼中,T是佔位類型。 這是一個佔位符,您能夠將其視爲開發人員稍後將定義的類型.

使用集合字面量

List 和 map 字面量能被參數化.參數化字面量就像你以前見過的字面量同樣,除了你在括號以前使用的 <type> (對於list集合) 或 <keyTypevalueType> (對於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集合:

var views = new Map<int, View>();

泛型集合及其包含的類型

Dart泛型類型被 修改, 意味着會附帶類型信息. 例如,您能夠測試集合的類型,即便在生產模式下:

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的通用支持僅限於類。 一種較新的語法(稱爲泛型方法)容許在方法和函數上使用類型參數:

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 庫, 能夠這樣引入:

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類,那麼你可能會有這樣的代碼:

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.

引入庫的一部分

若是您只想使用庫的一部分,則能夠有選擇地導入庫. 例如:

// 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表明的值時,您有兩個選擇:

一樣,當您須要從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.
  }
}

你可使用 trycatch, 和 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:

Future<String> lookUpVersion() async => '1.0.0';

請注意,該函數的主體不須要使用Future API。 若有必要,Dart建立Future對象.

結合Futures使用await表達式

await表達式具備如下形式:

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() 函數中使用 awaitmain() 的正文必須標記爲 async:

main() async {
  checkVersion();
  print('In main: version is ${await lookUpVersion()}');
}

結合 streams 使用異步循環

異步for循環具備如下形式:

await for (variable declaration in expression) {
  // Executes each time the stream emits a value.
}

表達式 的值必須具備Stream類型。 執行狀況以下:

  1. 等待直到Stream發出一個值.
  2. 執行for循環的主體,將變量設置爲該發射值.
  3. 重複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語言異步支持:階段1Dart語言異步支持:階段2, 和 Dart語言規範.

可調用類

爲了讓你的Dart類像方法同樣被調用,請實現 call() 方法.

在下例中, WannabeFunction 類定義了 call() 方法,該函數須要三個字符串並鏈接它們, 用空格分開,並附加感嘆號. 單擊運行按鈕 ( red-run.png ) 執行代碼.

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:

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是簡單的別名,它們提供了檢查任何函數類型的方法.例如:

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註解的示例,它須要兩個參數

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,類型參數,構造函數,工廠,函數,字段,參數或變量聲明以前,以及 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編譯器忽略全部文本,除非它包含在括號中。 使用括號,能夠參考類,方法,字段,頂級變量,函數和參數。 括號中的名稱在已記錄的程序元素的詞法範圍內獲得解決.

如下是有關引用其餘類和參數的文檔註釋的示例:

/// 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圖書館之旅.

相關文章
相關標籤/搜索