Dart 2官方文檔中文版(Dart 編程語言導覽)Part 2

構造函數

通過和類相同的名稱創建函數來聲明構造函數(以及命名構造函數中所描述的可選額外標識符)。最常見形式的構造函數,生成構造函數,創建一個類的新實例:

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;

  }

}

記住構造函數并不會繼承,也就表示超類的構造函數不會由子類繼承。如果希望通過超類中定義的命名構造函數創建子類 ,必須在子類中實現這個構造函數。

調用非默認超類構造函數

默認,子類中的構造函數調用超類中的未命名、無參數的構造函數。超類的構造函數在構造函數體的開頭調用。如果還使用了 初始化程序列表,它在超類調用前執行。總之,執行的順序如下:

  1. 初始化程序列表initializer list
  2. 超類的無參構造函數
  3. 主類的無參構造函數

如果超類中沒有未命名的無參構造函數,那么就必須手動調用超類的一個構造函數。在冒號(:)后、剛好在構造函數體前(如有)指定超類構造函數。

在下例中,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。可以使用getset關鍵字通過實現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).

更多有關泛型的信息,參見使用泛型方法。

庫和可見性

importlibrary 指令有且于我們創建模塊化和可分享的代碼基。庫不僅提供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 #33118issue #27776

要對庫進行懶加載,必須首先使用 deferred as導入它。

import  'package:greetings/hello.dart'  deferred as  hello;

在需要該庫時,使用庫的標識符調用 loadLibrary()

Future greet()  async  {

  await hello.loadLibrary();

  hello.printGreeting();

}

以上代碼中, await 關鍵字暫停代碼執行直到庫被加載。更多有關asyncawait的內容,參見 異步支持

可以對庫進行多次 loadLibrary() 調用,并不會產生問題。該庫只會加載一次。

使用延時加載時記住如下各點:

  • 延時加載庫的常量在導入文件中不是常量。記住,這些量直至延時庫加載后才存在。
  • 不能在導入文件中使用延時庫中的類型。而是考慮將接口類型移到由延時庫和導入文件所共同導入的庫中。
  • Dart在你使用deferred as *namespace*所定義的命名空間中隱式地插入 loadLibrary()loadLibrary() 函數返回Future

實現庫

參見創建庫軟件包 來獲取有關如何實現一個庫軟件包的建議,包括:

  • 如何組織庫的源碼
  • 如何使用 export 指令
  • 何時使用part 指令
  • 何時使用 library 指令

異步支持

Dart庫中有大量返回 FutureStream 對象的函數。這些函數是異步的,它們在設置一個可能很耗時的操作(如 I/O)后返回,無需等待操作完成。

asyncawait 關鍵字支持異步編程,讓你可以編寫看起來類似同步代碼的異步代碼。

處理Future

在需要完成的Future結果時,有兩個選擇:

使用asyncawait 的是異步代碼,但和同步代碼很像。例如,下面的代碼使用了await 來等待異步函數的執行結果:

await lookUpVersion();

要使用 await,代碼必須放在一個 async 函數中,一個標記為 async的函數中:

Future checkVersion()  async  {

  var  version  =  await lookUpVersion();

  // Do something with version

}

Note: 雖然 async 函數可能會執行些耗時的操作,但它不會等待這些操作。而是 async函數僅在其遇到第一個await 表達式 (詳情)時才執行.。然后它返回一個Future對象,僅在 await 表達式完成后才恢復執行。

使用和 try, catchfinally 來在使用了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()函數中使用 awaitmain()函數體必須標記為 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, asyncawait交互入門介紹,參數 異步編程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類型。執行流程如下:

  1. 等流發射值時
  2. 執行 for循環體,將變量設置為所射的值
  3. 重復1 和 2 直到流關閉

要停止監聽流,可以使用 breakreturn 語句,它會跳出for循環并取消對流的監聽。

如果在實現異步for 循環時得到了一個編譯時錯誤,確保 await for 出現在一個 async 函數中。例如,要在應用的main() 中使用異步 for循環,main()函數體必須標記為 async

Future main()  async  {

  // ...

  await for  (var  request in  requestServer)  {

    handleRequest(request);

  }

  // ...

}

更多有關異步編程的信息,參見庫導覽的 dart:async 一節。

生成器

在需要便利地生成一系列值時,考慮使用生成器函數。Dart內置支持兩種生成器函數:

  • 同步生成器:返回一個可迭代 Iterable 對象。
  • 異常生成器: 返回一個Stream 對象。

要實現一個同步生成器函數,將函數體標記為 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訪問。

更多內容,參見:

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的個人博客

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容