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

構(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í)行的順序如下:

  1. 初始化程序列表initializer list
  2. 超類的無參構(gòu)造函數(shù)
  3. 主類的無參構(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í)行代碼。

點(diǎn)擊查看

因?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í)行代碼。

點(diǎn)擊查看

重定向構(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)泛型的信息,參見使用泛型方法。

庫和可見性

importlibrary 指令有且于我們創(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 #33118issue #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)asyncawait的內(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庫中有大量返回 FutureStream 對象的函數(shù)。這些函數(shù)是異步的,它們在設(shè)置一個可能很耗時的操作(如 I/O)后返回,無需等待操作完成。

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

處理Future

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

使用asyncawait 的是異步代碼,但和同步代碼很像。例如,下面的代碼使用了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, catchfinally 來在使用了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, asyncawait交互入門介紹,參數(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í)行流程如下:

  1. 等流發(fā)射值時
  2. 執(zhí)行 for循環(huán)體,將變量設(shè)置為所射的值
  3. 重復(fù)1 和 2 直到流關(guān)閉

要停止監(jiān)聽流,可以使用 breakreturn 語句,它會跳出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ù):

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

要實(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í)行代碼。

點(diǎn)擊查看

隔離(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)容,參見:

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容