Dart 語言學習筆記

基礎語法

註釋

Dart 的單行註釋也是 // ,註釋內容最好是個完整的句子,一般以大寫字母開頭,以句點或其餘標點符號結束:html

// Not if there is nothing before it.
if (_chunks.isEmpty) return false;
複製代碼

/* ... */ 只適合用來注掉某一段暫時不用的代碼,不適合當成註釋使用。
/// 是 Dart 推薦使用的文檔註釋語法,dartdoc 會根據文檔註釋生成 HTML 文檔,使用方括號能夠連接到一個類、方法、字段等位置:git

/// Feeds your llama [Food].
  ///
  /// The typical llama eats one bale of hay per week.
  void feed(Food food) {
    // ...
  }
複製代碼

因爲歷史緣由, Dart 也支持 JavaDoc 樣式的文檔註釋 /** ... */,但這種類型的文檔註釋會產生兩個無實際內容的行,對於使用 * 標誌列表項等場景並不方便,因此推薦使用 /// 文檔註釋。github

變量

能夠賦值給變量的都是對象,包括數字、函數和 null 對象,對象都是類的實例,都繼承自 Object
變量存的是引用。
雖然 Dart 是強類型語言,可是類型聲明是可選的,由於 Dart 能夠推斷出變量到底是什麼類型的。
可使用 var 關鍵字聲明變量而無需指定具體類型,如 var name = 'Bob';var number = 42;
有些操做須要處理任何可能的對象,如 log() 方法須要調用給定對象的 toString()。而在 Dart 中有兩種類型: ObjectdynamicObject 適合表示能夠是任何對象的場景,而 dynamic 適合表示更復雜的類型,如意味着 Dart 的類型系統已經不足以表示的一系列容許的類型,或者值來自 interop 或 其餘超過靜態類型系統範圍的類型,或者你想明確地聲明運行時動態處理的變量:express

void log(Object object) {
  print(object.toString());
}

/// Returns a Boolean representation for [arg], which must
/// be a String or bool.
bool convertToBool(dynamic arg) {
  if (arg is bool) return arg;
  if (arg is String) return arg == 'true';
  throw new ArgumentError('Cannot convert $arg to a bool.');
}
複製代碼

final 和 const

若是變量永遠不會改變,可使用 finalconst 去聲明,final 變量只能賦值一次,const 變量是編譯時常量(也是隱式 final 的),final 的頂級類變量是在其第一次被使用時初始化的。實例變量能夠是 final 的,但不能是 const 的。
const 不單能聲明常量,還能夠建立常量值,或者聲明能夠建立常量值的構造器。編程

內置的類型

num 類型有兩個子類型 intdoubleint 型位數不超過64位,在 Dart VM 上是 -263 到 263 - 1,double 是64位浮點數,遵循 IEEE 754 標準。
String 類型是 UTF-16 編碼,可使用單引號也可使用雙引號,所以能夠靈活的避免字符串限定符。能夠利用 ${expression} 在字符串中使用表達式的值,若是表達式是個標識符,那麼 {} 能夠省略。字符串拼接可使用緊鄰的字符串或者 + 號:api

var s1 = 'String '
    'concatenation'
    " works even over line breaks.";
var s2 = 'The + operator ' + 'works, as well.';
複製代碼

多行的字符串可使用三個單引號或三個雙引號包裹。 可使用前綴 r 建立原始字符串:緩存

// 在原始字符串中,即便是 \n 也再也不是特殊字符了
var s = r"In a raw string, even \n isn't special.";
複製代碼

Dart 的集合 API 包含 lists, sets, 和 maps。List 可使用傳統構造器建立,也可使用 [] 語法建立,Map 可使用傳統構造器建立,也可使用 {} 語法建立:bash

// Use a List constructor.
var vegetables = new List();
// Or simply use a list literal.
var fruits = ['apples', 'oranges'];
// Maps can be built from a constructor.
var searchTerms = new Map();
// Maps often use strings as keys.
var hawaiianBeaches = {
  'Oahu': ['Waikiki', 'Kailua', 'Waimanalo'],
  'Big Island': ['Wailea Bay', 'Pololu Beach'],
  'Kauai': ['Hanalei', 'Poipu']
};
複製代碼

ListSet 都實現了 Iterable,能夠經過迭代器去遍歷,而 Map 能夠經過它的keysvalues 屬性獲取迭代器。markdown

函數

Dart 是真正的面嚮對象語言,因此即便是函數也是對象,類型是 Function。函數做爲一類對象(first-class objects),能夠做爲參數傳遞給其餘函數。
若是函數體只包含一個表達式語句,可使用 => 簡寫,如:閉包

bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}
複製代碼

能夠簡寫成:

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
複製代碼

這種箭頭一般稱做胖箭頭(fat arrow)。
區分 expressionstatement 有時仍是挺重要的,不包含 if 等控制流語句的程序元素均可以稱之爲表達式,包括操做符表達式,return 子句,throw 子句,print 等函數的調用。

可選參數

可選參數有兩種,命名可選參數和有序可選參數,可選參數必須放在全部必須參數的後面。命名可選參數的列表使用 {param1, param2, …} 聲明,使用 paramName: value 調用。有序可選參數的列表使用 [param1, param2, …] 聲明:

void enableFlags(String styleName, {bool bold, bool hidden}) {
}
...
enableFlags("NORMAL", hidden: false);
複製代碼
String say(String from, String msg, [String device]) {
}
...
say('Bob', 'Howdy');
say('Bob', 'Howdy', 'smoke signal');
}
複製代碼

可選參數能夠經過 = 指定默認參數值,這個默認參數值必須是編譯時常量,若是不指定默認參數值,那麼默認參數值爲 null,因此不要顯示地設置默認值爲 null:

void enableFlags({bool bold = false, bool hidden = false}) {
}
...
enableFlags(bold: true);
複製代碼

main() 函數

每一個 app 都必須有一個頂層 main() 函數做爲 app 的入口, main() 函數的參數列表是可選的 List<String> ,返回值是 void

匿名函數

沒有名字的函數有時會稱爲 匿名函數(anonymous function)lambda 表達式(lambda)閉包(closure),匿名函數的聲明與普通函數相似:

([[Type] param1[, …]]) { 
  codeBlock; 
}; 
複製代碼

如:

var functionList = [];
  functionList.add((int element) {
    print("element:" + element.toString());
  });
複製代碼

做用域

Dart 是一個 lexically scoped 語言,即 詞法做用域/靜態做用域 語言。也就是說,變量的做用域是根據它所處代碼的佈局位置靜態決定的,你能夠根據大括號去判斷變量的做用域。

詞法閉包

閉包(closure)就是一個函數對象能夠訪問它詞法做用域內的變量,即便這個函數在原始範圍外被使用。例如,下面的例子,makeAdder() 會捕獲 addBy. 不管他返回的函數在哪,他都會記住 addBy:

/// 返回一個匿名函數,匿名函數的返回值是
/// 匿名函數的參數加上 [addBy].
Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

void main() {
  // 建立一個返回值是參數值加2的函數
  var add2 = makeAdder(2);

  // 建立一個返回值是參數值加4的函數
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}
複製代碼

返回值

全部函數都有返回值,若是不指定返回值,默認會隱式添加 return null; 到函數體中。

操做符

5 / 2 的值是浮點數 2.5,而5 ~/ 2 的值纔是整型的 2。
判斷對象類型用 isis! 操做符,使用 as 操做符進行類型轉換:

if (emp is Person) {
  emp.firstName = 'Bob';
}
// 能夠簡寫成下面的語句,可是若是emp 不是 Person 類型的
// 那麼下面的語句會拋出異常
(emp as Person).firstName = 'Bob';
複製代碼

使用 ??= 操做符能夠給一個值爲 null 的變量賦值。使用 ?. 能夠避免左邊表達式的值爲空致使的異常,如 foo?.barfoo 不爲空時返回 bar 屬性,在 foo 爲空時返回 null

級聯符號(..)

級聯符號 .. 可讓你連續操做相同的對象,不單能夠連續地調用函數,還能夠連續地訪問方法,這樣作能夠避免建立臨時變量,從而寫出更流暢的代碼,流式編程更符合現代編程習慣和編程風格:

final addressBook = (new AddressBookBuilder()
      ..name = 'jenny'
      ..email = 'jenny@example.com'
      ..phone = (new PhoneNumberBuilder()
            ..number = '415-555-0100'
            ..label = 'home')
          .build())
    .build();
複製代碼

嚴格意義上說,級聯符號不是操做符,而是 Dart 語法的一部分。

循環

若是循環(迭代)的對象是 Iterable 對象,能夠利用它的 forEach() 方法對集合中的元素進行操做,forEach() 方法的參數是一個函數,它會按序應用集合中的每一個元素給這個函數:

void printElement(int element) {
  print(element);
}
var list = [1, 2, 3];
list.forEach(printElement);
複製代碼

ListSet 這樣的 Iterable 類還支持 for-in 循環:

var collection = [0, 1, 2];
for (var x in collection) {
  print(x); // 0 1 2
}
複製代碼

異常處理

拋出的異常能夠是任意對象,但最好是 ErrorException 對象:

throw new FormatException('Expected at least 1 section');
...
throw 'Out of llamas!';
複製代碼

捕獲異常可使用 oncatch 或者同時使用:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // 捕獲 OutOfLlamasException 類型的異常,不須要使用異常對象
  buyMoreLlamas();
} on Exception catch (e) {
  // 捕獲 Exception 異常,打印異常對象
  print('Unknown exception: $e');
} catch (e) {
  // 捕獲並處理任何類型的 thrown 對象
  print('Something really unknown: $e');
}
複製代碼

catch() 能夠有兩個參數,第一個是拋出的異常對象,第二個是堆棧跟蹤對象。若是捕獲異常並處理後還容許該異常繼續傳播,可使用 rethrow 關鍵字:

final foo = '';

void misbehave() {
  try {
    foo = "You can't change a final variable's value.";
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // Allow callers to see the exception.
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}
複製代碼

finally 子句會在 catch 子句執行完執行。若是沒有 catch 子句,那麼會在finally 子句運行完纔開始傳播異常。

類與繼承

Dart 的繼承是基於 mixin 的繼承,也就是說,雖然每一個類(除了 Object 類)有且只有一個超類,但類體仍然能夠在多個類層級上重用。
構造器的名字能夠是 ClassName(無名構造器),也能夠是 ClassName.identifier(命名構造器),實例化時的 new 關鍵字能夠省略。
全部的實例變量都會自動生成隱式的 getter 方法,非 final 的實例變量也會自動生成隱式的 setter 方法,使用 getset 關鍵詞能夠建立額外的屬性和其 getter/setter 方法的實現。
Dart 有個語法糖能夠方便地將構造器參數賦值給實例變量(在構造器體執行以前):

class Point {
  num x, y;
  Point(this.x, this.y);
}
複製代碼

若是不聲明構造器,會隱式包含一個無名無參構造器,並調用父類的無名無參構造器。
子類不能繼承父類的構造器,因此,若是若是子類想利用父類的命名構造器建立實例,就必須實現父類的命名構造器。若是父類沒有無名無參構造器,那麼你就必須手動調用父類的一個構造器:

class Person {
  String firstName;
  Person.fromJson(Map data) {
    print('in Person');
  }
}
class Employee extends Person {
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}
複製代碼

初始化的順序爲: 初始化列表 → 父類無參構造器 → 主類無參構造器
能夠在構造器函數體執行前初始化實例變量,初始化列表使用逗號隔開,初始化列表特別適合初始化 final 字段:

class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}
複製代碼

重定向到同類其餘構造器:

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 = new Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}
複製代碼

可使用 abstract 修飾符聲明一個不能直接實例化的抽象類,抽象類能夠包含抽象方法,抽象方法沒有方法體,以分號結尾。
每一個類都隱式地定義了一個接口,該接口包含該類的全部實例成員及其實現的全部接口。能夠經過 implements 子句實現一或多個接口,以逗號隔開。
使用 extends 建立子類,使用 super 引用父類,使用 @override 註解有意地重載成員。

在面向對象的語言中,多繼承是個很是糟糕的設計,由於多繼承會產生一系列問題,如:
多繼承會使類之間的關係更加複雜。若是一個類有多個超類,超類又可能有多個超類,那麼類之間的關係將會變得尤其複雜,你很難理清一個類的超類到底有哪些,繼承關係又是怎樣的。
多繼承可能會出現菱形繼承的狀況,這種狀況下會出現二義性,雖然 C++ 使用虛繼承的方式能夠暫時避免這種二義性,但會使程序可讀性和可調試性變差。
因此現代的面嚮對象語言都不會容許多繼承這種狀況存在,其實繼承的目的無非是爲了重用並拓展類的功能,而經過繼承來重用類體並非一個好的方式,首先繼承會「白箱複用」,會將超類細節暴露給子類,無論子類願不肯意。若是超類更改,其全部子類也不得不改變。並且繼承在運行時的行爲不易改變。
因此儘量少用繼承,多用組合。繼承是 is-a 的關係,而組合是 has-a 的關係。

Mixin 的概念源於 LISP 社區,在那裏是指被設計用來與各個超類一塊兒工做的類。對 Mixin 介紹比較詳細的是 Mixins in Strongtalk 這篇論文。
一個類會隱式地定義一個 mixin, mixin 被類體定義,與類和它的超類構成函數關係,類實際上就是 mixin 的應用,是將其隱式定義的 mixin 應用於其超類的結果。Mixin應用函數應用 相似,從數學上來講,一個 mixin M 能夠當作是超類到子類的函數,也就是說,將 M 做用於超類 S,結果是新的 S 子類,在研究資料裏一般寫做 M ▷ S。所以對於 mixin 的組合來講: (M_1 * M_2) ▷ S = M_1 ▷ (M_2 ▷ S)。 一個類隱式定義的 mixin 一般只會在給定超類定義處應用一次,因此爲了讓 mixin 可以應用不一樣的超類,咱們須要獨立於任何特定超類來聲明 mixin,或者將類隱式定義的 mixin 剝離出來並在原始聲明以外重用它。

Mixin 能夠經過普通類定義的方式隱式定義,但要想提取 mixin 就必需要保證類不能聲明構造器,這也避免了因爲在繼承鏈上傳遞構造器參數引發的各類併發症。

abstract class Collection<E> {
  Collection<E> newInstance();
  Collection<E> map((f) {
    var result = newInstance();
    forEach((E e) { result.add(f(e)); });
    return result;
  });
  void forEach(void f(E element)) {
    // ...
  }
  void add(E element) {
    // ...
  }
}

abstract class DOMElementList<E> = DOMList with Collection<E>; abstract class DOMElementSet<E> = DOMSet with Collection<E>; 複製代碼

在這裏,Collection 就是一個隱式聲明瞭 mixin 的普通類,它沒有構造器。而 DOMElementListDOMElementSet 類就是 mixin 的應用,它們被特殊的類聲明格式定義,也就是經過 with 子句聲明 mixin 到 父類的應用。這兩個類也是抽象的,由於他們沒有實現 Collection 類的抽象方法 newInstance()
也就是說,DOMElementList 就是 Collection mixin ▷ DOMList,而 DOMElementSet 就是 Collection mixin ▷ DOMSet
這樣的好處是,Collection 類的代碼能夠在多個類層次上共享。咱們這裏列舉了兩個類層次,一個是以 DOMList 爲根的,一個是以 DOMSet 爲根的。咱們不須要複製粘貼Collection 類的代碼,而 Collection 的任何更改都會傳播到這兩個類層次中,這樣也極大地解放了代碼的維護。
上面的例子只是簡單說明了一種形式的 mixin 應用,應用的結果是一個有名字的類。而另外一種形式,with 子句後跟上以逗號分隔的標識符列表,全部的標識符必須表示一個類,這種形式下,多個 mixin 會被組合並應用到 extends 子句的超類中,從而產生一個匿名超類,即:

class DOMElementList<E> extends DOMList with Collection<E> {
   DOMElementList<E> newInstance() => new DOMElementList<E>();
}

class DOMElementSet<E> extends DOMSet with Collection<E> {
  DOMElementSet<E> newInstance() => new DOMElementSet<E>();
}
複製代碼

這裏的 DOMElementList 再也不是 Collection mixin ▷ DOMList 的應用,而是一個新類,它的超類是這個應用。
若是 DOMList 有非平凡構造器,那麼構造將會是這樣:

class DOMElementList<E> extends DOMList with Collection<E> {
  DOMElementList<E> newInstance() => new DOMElementList<E>(0);
  DOMElementList(size): super(size);
}
複製代碼

每一個 mixin 都有屬於本身的被獨立調用的構造器,超類也是如此。因爲 mixin 的構造器不能被聲明,對它的調用能夠用 ; 語法省略,在底層實現時,這個調用會被放在初始化列表的開始處。
爲了讓多個 mixin 應用到一個類是而又不引入多箇中間聲明,可使用這種方便的語法糖:

class Person {
  String name;
  Person(this.name);
}

class Maestro extends Person with Musical, Aggressive, Demented {
  Maestro(name):super(name);
}
複製代碼

在這裏,Maestro 的超類就是這樣的 mixin 應用:
Demented mixin ▷ Aggressive mixin ▷ Musical mixin ▷ Person
mixin 與普通繼承同樣,隱私性取決於其聲明的位置。mixin 的應用的靜態元素一樣不能被繼承使用,Dart 中靜態元素是不能被繼承的。
那 mixin 應用後的實例類型究竟是什麼類型呢?毫無疑問,它必須是命名 mixin 類的子類型,也必須是原來類的子類型。原來的類有它本身的超類,爲了確保指定的 mixin 應用 兼容原來的類,Dart 會給 with 子句的類額外的要求。
若是類 A 定義時的 with 子句是 mixin M,而 M 又是繼承的 類 K,那麼類 A 就必須支持類 K 的接口。

class S {
  twice(int x) => 2 * x;
}

abstract class I {
   twice(x);
}

abstract class J {
   thrice(x);
}
class K extends S implements I, J {
  int thrice(x) => 3* x;
}

class B {
  twice(x) => x + x;
}
class A = B with K; 複製代碼

A 必須支持 K 的超類 S 的隱式接口,以確保 A 確實是 M 的子類型。K 必須實現 twice() 以知足 I 的須要,同時必須實現 thrice() 以知足 J 的須要,顯然 K 已經知足了這些須要。
如今咱們聲明瞭 A,咱們從 K 的 mixin 中 拿到了 thrice() 的實現,但這個 mixin 沒有提供 twice() 的實現,幸運的是,B 有這個實現,因此總的來講,A 確實知足了 I,J 和 S 的須要。
相反,對於這樣的類 D:

class D {
   double(x) => x+x;
}

class E = D with K; 複製代碼

會收到一個警告,由於 E 沒有 twice() 方法,所以不知足 I 和 S,也就不能使用預期的 K 了。
若是一個類有泛型參數,那麼它的 mixin 也必須有相同類型的參數。
在 1.13 版本以後,Mixin 能夠繼承普通類而不要求必定是 Object,Mixin 可使用 super.method()

操做符重載

能夠經過 operator 關鍵字重載包括 +>== 在內的操做符,但要注意,重載 == 操做符的同時也要重載 hashCodegetter 方法:

class Person {
  final String firstName, lastName;

  Person(this.firstName, this.lastName);

  // Override hashCode using strategy from Effective Java,
  // Chapter 11.
  @override
  int get hashCode {
    int result = 17;
    result = 37 * result + firstName.hashCode;
    result = 37 * result + lastName.hashCode;
    return result;
  }

  // You should generally implement operator == if you
  // override hashCode.
  @override
  bool operator ==(dynamic other) {
    if (other is! Person) return false;
    Person person = other;
    return (person.firstName == firstName &&
        person.lastName == lastName);
  }
}
複製代碼

枚舉類型

使用 enum 關鍵詞定義枚舉類型,枚舉類型的值有一個 index getter,枚舉值的列表能夠經過它的 values 常量獲取,枚舉支持 switch 語句,你不能繼承,mix in 或者實現枚舉類,不能顯式實例化枚舉:

enum Color { red, green, blue }
assert(Color.red.index == 0);
List<Color> colors = Color.values;
複製代碼

library 和 可見性

每一個 Dart app 都是一個 library,即便它沒使用 library 指令,library 不只提供 API,仍是個隱私單元,如下劃線(_)開頭的標識符只在當前 library 內可見。
使用 import 指令能夠指定在另外一個 library 範圍內如何使用另外一個 library 的命名空間。對於內置的 library,URI 是特定的 dart: scheme,而其餘的 URI 能夠是文件系統路徑或者 package: scheme:

import 'dart:html';
import 'package:test/test.dart';
複製代碼

若是兩個 library 的標識符衝突,可使用前綴加以區分:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// Uses Element from lib1.
Element element1 = new Element();

// Uses Element from lib2.
lib2.Element element2 = new lib2.Element();
複製代碼

若是隻想引入 library 的部分功能,須要使用 showhide 指定:

// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
複製代碼

若是想要懶加載某個 library,先在 import 時經過 deferred as 指定 library 的標識符,而後在須要使用它的時候調用標識符的 loadLibrary():

import 'package:greetings/hello.dart' deferred as hello;
...
Future greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}
複製代碼

此時 await 關鍵字會暫停程序執行,直到 library 加載成功,能夠屢次調用 loadLibrary(),由於 library 只會加載一次。

異步

Dart 爲異步提供了 FutureStream 兩個類,Future 就像未來某個時刻提供結果的承諾,Stream 是獲取值序列(例如事件序列)的一種方式。
不少時候沒有必要直接使用 Future 或 Stream API,而是使用 asyncawait 關鍵字進行異步編程,這樣更容易理解,並且代碼看起來也更像同步代碼。
await 必須在異步函數中使用,用來等待異步函數返回的結果,如:

Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}
複製代碼

雖然異步函數能夠執行耗時操做,但它不用等待耗時操做完成,這個異步函數會執行直到遇到第一個 await 表達式,而後返回 Future 對象,在 await 表達式執行完後再繼續執行。
能夠在一個異步函數裏 await 屢次,也能夠 try/catch await 的代碼。
await expression 語句中,expression 的值一般是個 Future,若是不是,那麼它會被自動包裝成 Future。await expression 會暫停其以後代碼的執行,直到返回的對象可用。
若是異步函數不須要返回有用值,能夠那麼返回類型應該是 Future<void>
能夠用 await for 異步等待 stream 中的全部結果,但不能是 UI 事件流這樣的 stream,由於這樣的 stream 是無限的。

生成器函數

Dart 內置了兩種生成器函數,同步的生成器函數返回 Iterable 對象,異步的生成器函數返回 Stream 對象,使用 sync*/async* 指定同步異步,使用 yield 交付值,還能夠經過 yield* 遞歸生成:

Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}
Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}
複製代碼

註解

自定義註解就像聲明普通類同樣:

library todo;

class Todo {
  final String who;
  final String what;

  const Todo(this.who, this.what);
}
複製代碼

Effective Dart

代碼風格

  • 類、枚舉、註解、typedef 要使用大駝峯命名法。
  • library 和 源文件的命名(由於有些文件系統並非大小寫敏感的)、import 前綴(即 as 子句)要所有小寫並用下劃線分隔。
  • 常量名等要用小駝峯命名法。
  • 除了兩個字母的首字母縮寫詞,其餘的首字母縮寫詞和縮寫詞應該和普通單詞同樣進行標識符大小寫。如:
    HttpConnectionInfo
    uiHandler
    IOStream
    HttpRequest
    Id
    DB
    複製代碼
  • 每行儘可能不超過80個字符。

使用方式

  • 引入本身庫 lib 目錄下的 library時,要使用相對路徑。
  • 一行放不下的字符串不要使用加號拼接,直接相鄰兩行書寫便可。對於表達式的值和其餘字符串組合的字符串,也不要使用加號拼接,直接使用 ${expression} 插值便可。
  • 使用 .length 判斷集合是否爲空可能會很是慢,因此使用 .isEmpty.isNotEmpty 判斷集合狀況是最好的選擇,更快也更直觀。
  • 拷貝集合有兩種方式:
    var copy1 = iterable.toList();
    var copy2 = new List.from(iterable);
    複製代碼
    第一種能夠保存原始對象的類型參數,而第二種的運行時類型是 dynamic 的,不過能夠經過 new List<int>.from(numbers) 這樣的方式指定。
  • 過濾集合中的某類元素時應該使用 whereType():
    var objects = [1, "a", 2, "b", 3];
    var ints = objects.whereType<int>();
    複製代碼
  • 轉換 iterable 或 stream 的類型時,儘可能不要用 cast(),由於該方法會返回 lazy 集合而且每次操做都要檢查元素類型,若是集合元素和元素操做特別多那麼 lazy 驗證和打包的開銷會特別大,因此 stuff.toList().cast<int>() 徹底能夠用 new List<int>.from(stuff) 替代,stuff.map((n) => 1 / n).cast<double>() 徹底能夠用 stuff.map<double>((n) => 1 / n) 替代。
  • 善於使用 => 簡化簡單的代碼邏輯,如:
    num get x => center.x;
    set x(num value) => center = new Point(value, center.y);
    bool isReady(num time) => minTime == null || minTime <= time;
    複製代碼

參考

相關文章
相關標籤/搜索