dart語言基礎

此文章是v1.0+時編寫,年代久遠,小心有毒,謹慎食用!!!

一些重要概念

  • 所有的東西都是對象,所有的對象都是類的實例。即使 數字、函數、null
    也都是對象。所有的對象都繼承自 Object 類。
  • 指定靜態類型表明你的意圖,并使檢查類型檢查成為可能。在 Dart 1.x 指定類型是可選的,然而 Dart 正在朝著完全類型安全的語言發展。
  • 在 Dart 1.x 中,強類型 strong mode 是可選的,但在 Dart 2 中,默認就是 strong mode。
  • Dart 在運行前解析所有的代碼,可以使用些小技巧,例如:通過使用類型或編譯時常量,來捕捉錯誤或使代碼運行的更快。
  • Dart 支持頂級的函數,也支持類或對象的靜態和實例方法。也可以在函數內部嵌套函數或本地函數。
  • 同樣,Dart 支持頂級的變量,也支持類或對象的靜態變量和實例變量(也被稱作字段或屬性)。
  • Dart沒有 public、protected、private 等關鍵字,如果一個標識符以 _開頭則表示私有。
  • 標識符以小寫字母或下劃線_開頭,后面跟著字符和數字的任意組合。
  • 明確區分表達式和語句。
  • Dart tools 會報告兩種類型的問題:警告(warnings)和錯誤(errors)。警告僅標志著你的代碼可能不會工作,但并不會阻止程序執行;錯誤可能是編譯時錯誤,也可能是運行時錯誤。編譯時錯誤會阻止程序執行;運行時錯誤會在程序執行時拋出異常。
  • Dart 1.x 有兩種運行時模式:生產模式和檢查模式。推薦在開發和 debug 時使用檢查模式,部署到生產模式。生產模式時 Dart 程序默認的運行時模式。
  • Dart 2 廢棄了 checked mode 默認 strong mode。
  • Dart 2 不再使用 Dartium,改為 dartdevc(DDC)。
  • 沒有初始化的變量都會被賦予默認值 null。
  • final的值只能被設定一次。const 是一個編譯時的常量,可以通過 const 來創建常量值,var c=const[];,這里 c 還是一個變量,只是被賦值了一個常量值,它還是可以賦其它值。實例變量可以是 final,但不能是 const

關鍵字(Keywords)

abstract continue false new this
as default final null throw
assert deferred finally operator true
async do for part try
async* dynamic get rethrow typedef
await else if return var
break enum implements set void
case export import static while
catch external in super with
class extends is switch yield
const factory library sync yield

內置類型(Built-in types)

  • numbers
  • strings
  • booleans
  • lists (also known as arrays)
  • maps
  • runes (for expressing Unicode characters in a string)
  • symbols

is is!操作符判斷對象是否為指定類型,如num、String等。

as用來類型斷言。

  • num
    • int 取值范圍:-2^53 to 2^53
    • double
// String -> int
var one = int.parse('1');

// String -> double
var onePointOne = double.parse('1.1');

// int -> String
String oneAsString = 1.toString();

// double -> String 注意括號中要有小數點位數,否則報錯
String piAsString = 3.14159.toStringAsFixed(2);
  • String

    • Dart 的String 是 UTF-16 編碼的一個隊列。
    • '...',"..."表示字符串。
    • '''...''',"""..."""表示多行字符串。
    • r'...',r"..."表示“raw”字符串。
  • bool

    • Dart 是強 bool 類型檢查,只有 bool 類型的值是true 才被認為是 true。
  • List

// 使用List的構造函數,也可以添加int參數,表示List固定長度,不能進行添加 刪除操作
var vegetables = new List();

// 或者簡單的用List來賦值
var fruits = ['apples', 'oranges'];

// 添加元素
fruits.add('kiwis');

// 添加多個元素
fruits.addAll(['grapes', 'bananas']);

// 獲取List的長度
assert(fruits.length == 5);

// 獲取第一個元素
fruits.first;

// 獲取元素最后一個元素
fruits.last;

// 利用索引獲取元素
assert(fruits[0] == 'apples');

// 查找某個元素的索引號
assert(fruits.indexOf('apples') == 0);

// 刪除指定位置的元素,返回刪除的元素
fruits.removeAt(index);

// 刪除指定元素,成功返回true,失敗返回false
fruits.remove('apples');

// 刪除最后一個元素,返回刪除的元素
fruits.removeLast();

// 刪除指定范圍元素,含頭不含尾,成功返回null
fruits.removeRange(start,end);

// 刪除指定條件的元素,成功返回null
fruits.removeWhere((item) => item.length >6);

// 刪除所有的元素
fruits.clear();
assert(fruits.length == 0);

// sort()對元素進行排序,傳入一個函數作為參數,return <0表示由小到大, >0表示由大到小
fruits.sort((a, b) => a.compareTo(b));
  • Map
// Map的聲明
var hawaiianBeaches = {
    'oahu' : ['waikiki', 'kailua', 'waimanalo'],
    'big island' : ['wailea bay', 'pololu beach'],
    'kauai' : ['hanalei', 'poipu']
};
var searchTerms = new Map();

// 指定鍵值對的參數類型
var nobleGases = new Map<int, String>();

// Map的賦值,中括號中是Key,這里可不是數組
nobleGase[54] = 'dart';

//Map中的鍵值對是唯一的
//同Set不同,第二次輸入的Key如果存在,Value會覆蓋之前的數據
nobleGases[54] = 'xenon';
assert(nobleGases[54] == 'xenon');

// 檢索Map是否含有某Key
assert(nobleGases.containsKey(54));

//刪除某個鍵值對
nobleGases.remove(54);
assert(!nobleGases.containsKey(54));
  • Runes
    Dart 中 runes 是UTF-32字符集的string 對象。
string = 'Dart';
string.codeUnitAt(0); // 68 返回指定位置index的16位UTF-16編碼單元
string.codeUnits;     // [68, 97, 114, 116] 返回一個只讀的List,包含字符串中所有的UTF-16編碼單元。 
  • Symbol

symbol字面量是編譯時常量,在標識符前面加#。如果是動態確定,則使用Symbol構造函數,通過new來實例化。

函數(Functions)

所有的函數都會有返回值。如果沒有指定函數返回值,則默認的返回值是null
。沒有返回值的函數,系統會在最后添加隱式的return 語句。

函數可以有兩種類型的參數:

  • 必須的——必須的參數放在參數列表的前面。
  • 可選的——可選的參數跟在必須的參數后面。
可選的參數

可以通過名字位置指定可選參數,但是兩個不能同時存在。

  • 命名可選參數使用{},默認值使用冒號:,調用時需指明參數名,與順序無關。
  • 位置可選參數使用[],默認值使用等號=,調用時參數按順序賦值。

操作符(Operators)

表中,操作符的優先級依次降低。

描述 操作符
一元后置操作符 expr++ expr-- () [] . ?.
一元前置操作符 -expr !expr ~expr ++expr --expr
乘除 * / % ~/
加減 + -
位移 << >>
按位與 &
按位異或 ^
按位或
關系和類型判斷 >= > <= < as is is!
== !=
邏輯與 &&
邏輯或
若為null ??
條件表達式 expr1 ? expr2 : expr3
級聯 ..
賦值 = *= /= ~/= %= += -= <<= >>= &= ^= = ??=

流程控制語句(Control flow statements)

  • if...else
  • for
  • while do-whild
  • break continue
  • switch...case
  • assert(僅在checked模式有效)

異常(Exceptions)

throw
  • 拋出固定類型的異常:
throw new FormatException('Expected at least 1 section');
  • 拋出任意類型的異常:
throw 'Out of llamas!';
  • 因為拋出異常屬于表達式,可以將throw語句放在=>語句中,或者其它可以出現表達式的地方:
distanceTo(Point other) =>
    throw new UnimplementedError();
catch

將可能出現異常的代碼放置到try語句中,可以通過 on語句來指定需要捕獲的異常類型,使用catch來處理異常。

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

可以向catch()傳遞1個或2個參數。第一個參數表示:捕獲的異常的具體信息,第二個參數表示:異常的堆棧跟蹤(stack trace)。

rethrow

rethrow語句用來處理一個異常,同時希望這個異常能夠被其它調用的部分使用。

final foo = '';

void misbehave() {
  try {
    foo = "1";
  } catch (e) {
    print('2');
    rethrow;// 如果不重新拋出異常,main函數中的catch語句執行不到
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('3');
  }
}
finally

Dart 的finally用來執行那些無論異常是否發生都執行的操作。

final foo = '';

void misbehave() {
  try {
    foo = "1";
  } catch (e) {
    print('2');
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('3');
  } finally {
    print('4'); // 即使沒有rethrow最終都會執行到
  }
}

類(Classes)

Dart 是一種面向對象的語言,并且支持基于mixin的繼承方式:一個類可以繼承自多個父類。

使用new語句來構造一個類。構造函數的名字可能是ClassName,也可以是ClassName.identifier。例如:

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);
  • 使用.來調用實例的變量或者方法。
  • 使用 ?. 來避免左邊操作數為null引發異常。
  • 使用const替代new來創建編譯時的常量構造函數。
  • 兩個使用const構建的同一個構造函數,實例相等。
  • 獲取對象的運行時類型使用:o.runtimeType
實例化變量

在類定義中,所有沒有初始化的變量都會被初始化為null。

所有實例變量會生成一個隱式的getter方法,不是finalconst的實例變量也會生成一個隱式的setter方法。

構造函數
class Point {
  num x;
  num y;

  Point(num x, num y) {
    // 這不是最好的方式.
    this.x = x; // this關鍵字指向當前類的實例
    this.y = y;
  }
}
class Point {
  num x;
  num y;

  // 推薦方式
  Point(this.x, this.y);
}
默認構造函數

沒有聲明構造函數,dart會提供默認構造函數。默認構造函數沒有參數,并且調用父類的無參數構造函數。

構造函數不能被繼承

子類不會繼承父類的構造函數。如果不顯式提供子類的構造函數,系統就提供默認的構造函數。

命名構造函數
class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // 命名構造函數
  Point.fromJson(Map json) {
    x = json['x'];
    y = json['y'];
  }
}

使用命名構造函數可以實現一個類多個構造函數。構造函數不能被繼承,父類中的命名構造函數不能被子類繼承。如果想要子類也擁有一個父類一樣名字的構造函數,必須在子類實現這個構造函數。

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

默認情況下,子類調用父類的無參數的非命名構造函數。父類的構造函數會在子類的構造函數體之前(大括號前)調用。如果也使用了初始化列表 ,則會先執行初始化列表 中的內容,即下面的順序:

  1. 初始化列表
  2. 父類無參數構造函數
  3. 主類無參數構造函數

如果父類不顯式提供無參的非命名構造函數,在子類中必須手動調用父類的一個構造函數。在子類構造函數名后,大括號{前,使用super.調用父類的構造函數,中間使用:分割。

父類構造函數參數不能訪問this,例如,參數可以調用靜態方法但不能調用實例方法。

class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // 父類沒有無參數的非命名構造函數,必須手動調用一個構造函數 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()); // 一個函數調用作為參數
}

當在構造函數初始化列表中使用super()時,要把它放在最后。

View(Style style, List children)
    : _children = children,
      super(style) {}
初始化列表

除了調用父類的構造函數,也可以通過初始化列表 在子類的構造函數體前(大括號前)來初始化實例的變量值,使用逗號,分隔。如下所示:

一個初始化器的右邊不能訪問this

class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // 在構造函數體前 初始化列表 設置實例變量
  Point.fromJson(Map jsonMap)
      : x = jsonMap['x'],
        y = jsonMap['y'] {
    print('In Point.fromJson(): ($x, $y)');
  }
}
重定向構造函數

有時候構造函數的目的只是重定向到該類的另一個構造函數。重定向構造函數沒有函數體,使用冒號:分隔。

class Point {
  num x;
  num y;

  // 主構造函數
  Point(this.x, this.y) {
    print("Point($x, $y)");
  }

  // 重定向構造函數,指向主構造函數,函數體為空
  Point.alongXAxis(num x) : this(x, 0);
}

void main() {
  var p1 = new Point(1, 2);
  var p2 = new Point.alongXAxis(4);
}
常量構造函數

如果類的對象不會發生變化,可以構造一個編譯時的常量構造函數。定義格式如下:

  • 定義所有的實例變量是final
  • 使用const聲明構造函數。
class ImmutablePoint {
  final num x;
  final num y;
  const ImmutablePoint(this.x, this.y);
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);
}
工廠構造函數

當實例化了一個構造函數后,不想每次都創建該類的一個新的實例的時候使用factory關鍵字,定義工廠構造函數,從緩存中返回一個實例,或返回一個子類型的實例。

工廠構造函數不能訪問this

class Logger {
  final String name;
  bool mute = false;
  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);
    }
  }
}

main() {
  var p1 = new Logger("1");
  p1.log("2");

  var p2 = new Logger('22');
  p2.log('3');
  var p3 = new Logger('1');// 相同對象直接訪問緩存
}

方法
實例方法

實例方法可以訪問實例變量和this

Getters and setters

get()set()方法是Dart 語言提供的專門用來讀取和寫入對象的屬性的方法。每一個類的實例變量都有一個隱式的getter和可能的setter(如果字段為finalconst,只有getter)。

++之類的操作符,無論是否明確定義了getter,都按預期的方式工作。為了避免意想不到的副作用,操作符調用getter一次,將其值保存在臨時變量中。

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);
}
抽象方法

抽象方法,只有函數聲明,沒有函數體,以分號;結尾,作為接口在其他類中實現。調用抽象方法會導致運行時錯誤。如果在不是抽象類中定義抽象方法會引發warning,但不會阻止實例化。

abstract class Doer {
  ...

  void doSomething(); // 定義抽象方法
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // ...Provide an implementation, so the method is not abstract here...
  }
}
可重寫的運算符

下面是可重寫的運算符,在運算符前面使用operator關鍵字。

如果重寫==,需要重寫ObjecthashCodegetter。

< + { []
> / ^ []=
<= ~/ & ~
>= * << ==
% >>
抽象類

使用abstract關鍵字定義一個抽象類,抽象類不能實例化。抽象類通常用來定義接口。

假如需要將抽象類實例化,需要定義一個factory constructor。

抽象類通常會包含一些抽象的方法。

abstract class AbstractContainer { // 抽象類
  // ....

  void updateChildren(); // 抽象方法
}
隱式接口

每一個類都隱式的定義一個接口,這個接口包含了這個類的所有實例成員和它實現的所有接口。

一個類可以實現一個或多個(用,隔開)接口,通過implements關鍵字。

class Person {
  final _name;
  Person(this._name);
  String greet(who) => 'hello,$who,i am $_name';
}

class Imposter implements Person {
  final _name = '';
  String greet(who) => 'hi $who.do you know who i am.';
}

greetBob(Person p) => p.greet('bob');
main(List<String> args) {
  print(greetBob(new Person('lili')));
  print(greetBob(new Imposter()));
}

繼承

使用extends來創造子類,使用super來指向父類。

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ...
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ...
}

子類可以重載實例方法,getter和setter。使用@override注解重載方法。

class A {
  @override
  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) {
    // ...
  }
}
枚舉類型

枚舉類型是一種特殊的類,通常用來表示一組固定數字的常量值。

使用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關鍵字獲取枚舉的所有值,返回一個列表。

print(Color.values); // [Color.red, Color.green, Color.blue]

在switch語句中使用枚舉,必須在case語句中判斷所有的枚舉,否則會獲得警告。

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: //必須完成所有判斷,否則會引發warning
    print(aColor);  // 'Color.blue'
}

枚舉類型有以下限制:

  • 不能繼承,mixin,或實現一個枚舉。
  • 不能顯式的實例化一個枚舉。
minxins

mixins是一種在多個類層次結構中,重用一個類的代碼的方法。使用with關鍵字后跟多個mixin名。

class Musician extends Performer with Musical {
  // ...
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}
class變量和方法

使用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');
}
  • 靜態方法

靜態方法不能被實例調用,不能訪問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);
  print(distance); // 2.8284271247461903
}

對于通用或廣泛使用的工具和功能,應該使用頂級函數,而不是靜態方法。

可以使用靜態方法作為編譯時常量。例如,可以給一個常量構造函數,傳遞一個靜態方法作為參數。

泛型(Generics)

使用<...> 的方式來定義泛型。

為什么使用泛型
  • 雖然Dart 語言中類型是可選的,但是明確的指明使用的是泛型,會讓代碼更好理解。
 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);
 }
用于集合類型(Using collection literals)

泛型用于ListMap 類型參數化。

  • List: <type>
  • Map: <keyType, valueType>
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);
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<Stirng>可能有很多不是string的條目在里面。解決辦法是檢查每一項,或者包裹在一個異常處理程序中。

限制參數化類型

當實現一個泛型時,如果需要限制它參數的類型,可以使用extends關鍵字。

// T 必須是SomeBaseClass或它的后代
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>();
}

庫和可見性

使用importlibrary 指令可以方便的創建一個模塊或分享代碼。一個Dart 庫不僅能夠提供相應的API,還可以包含一些以_開頭的私有變量僅在庫內部可見。

每一個Dart 應用都是一個庫,即使它沒有使用library指令。庫可以方便是使用各種類型的包。

使用庫(Libraries and visibility)

使用import指定怎樣的命名空間,從一個庫引用另一個庫。

import唯一要求的參數是指定庫的URI。

  • dart內置庫,URI組合dart:
  • 其他庫,使用文件路徑或package:組合。package:組合式通過包管理工具提供的。
import 'dart:html';
import 'package:mylib/mylib.dart';
指定一個庫的前綴

如果導入的庫擁有相互沖突的名字,使用as為其中一個或幾個指定不一樣的前綴。

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.
導入庫的一部分

如果只需要使用庫的一部分內容,使用showhide有選擇的導入。

// 僅導入foo.
import 'package:lib1/lib1.dart' show foo;

// 除了foo都導入
import 'package:lib2/lib2.dart' hide foo;
延遲加載庫

延遲加載,也叫懶加載,允許應用程序按需加載庫。使用延遲加載的場景:

  • 減少程序初始化啟動時間。
  • 執行A/B測試——嘗試替換一個算法的實現。
  • 加載很少用的功能,比如可選的屏幕和對話框。

要延遲加載一個庫,首先必須使用deferred as導入它。

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

當需要使用庫的時候,使用庫名調用loadLibrary()

greet() async {
  // 使用await關鍵字暫停執行,直到庫加載
  await hello.loadLibrary();
  hello.printGreeting();
}

可以在代碼中多次調用loadLibrary()方法。但是實際上它只會被執行一次。

使用延遲加載的注意事項:

  • 延遲加載的內容只有在加載后才存在。
  • Dart 隱式的將deferred as改為了deferred as namespace。loadLibrary()返回值是Future。

異步支持(Asynchrony support)

使用async函數和await表達式實現異步操作。

當需要使用一個從Future返回的值時,有兩個選擇:

  • 使用asyncawait。
  • 使用Future API。

當需要從一個Stream獲取值時,有兩個選擇:

  • 使用async和異步的循環(await for)。
  • 使用Stream API。

代碼使用了asyncawait就是異步的,雖然看起來像同步代碼。

await lookUpVersion()

要使用await,代碼必須在函數后面標記為async

checkVersion() async {
  var version = await lookUpVersion();
  if (version == expectedVersion) {
    // Do something.
  } else {
    // Do something else.
  }
}

可以使用try,catch和finally配合await處理錯誤。

try {
  server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044);
} catch (e) {
  // 無法綁定到端口時......
}
聲明異步函數

異步函數是一個被async標記的函數。

雖然異步的函數中可能執行耗時的操作,但是函數本身在調用后將會立即返回,即使函數體一條語句也沒執行。

checkVersion() async {
  // ...
}

lookUpVersion() async => /* ... */;

給函數添加async關鍵字將使函數返回一個Future類型。

// 修改前是同步的
String lookUpVersionSync() => '1.0.0';

// 修改后 是異步的 函數體不需要使用Future API
// dart會在必要的時候創建Future對象
Future<String> lookUpVersion() async => '1.0.0';
在 Future 中使用 await 表達式
await expression

可以在異步函數中多次使用await

var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);

await表達式中,表達式通常是一個Future。如果表達式不是Future 類型,它將自動被包裝為Future類型。await expression的返回值是一個對象。await表達式使執行暫停,直到對象可用。

如果await不工作,確保await處于async函數中。即使是在main函數中,也要標記為async

main() async {
  checkVersion();
                          // 這里使用了 await
  print('In main: version is ${await lookUpVersion()}');
}
在Stream中使用異步循環
 // expression的值必須是Stram類型
await for (variable declaration in expression) {
  // Executes each time the stream emits a value.
}

異步循環的執行流程如下:

  1. 等待 stream 發出數據。
  2. 執行循環體,并將變量的值設置為發出的數據。
  3. 重復1.,2.直到stream 對象被關閉。

結束監聽stram 可以使用breakreturen 語句,跳出for循環,取消訂閱stream。

如果異步循環不工作,確保是在一個async函數中,如果使用異步循環在main()函數中,也要確保main()函數被標記為async。

main() async {
  ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  ...
}

可調用類(Callable classes)

Dart 語言中為了能夠讓類像函數一樣能夠被調用,可以實現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'); // Hi there, gang!
  print(wf.runtimeType); // WannabeFunction
  print(out.runtimeType); // String
  print(wf is Function); // true
}

隔離(Isolates)

Dart 代碼都運行在獨立的隔離空間中,每個隔離空間都有自己的內存堆棧,確保每個隔離空間的狀態不會被其它的隔離空間訪問。

類型定義(Typedefs)

typedef關鍵字,用來聲明一種類型,當一個函數類型分配給一個變量時,保留類型信息。

// 聲明一種 Compare類型
typedef int Compare(int a, int b);

int sort(int a, int b) => a - b;

main() {
  assert(sort is Compare); // True!
}

當前 typedef 僅用于函數類型

元數據(Metadata)

元數據是以@開始的修飾符。在@ 后面接著編譯時的常量或調用一個常量構造函數。

所有dart代碼中可用的三個注解:

  • @deprecated 被棄用的
  • @override 重載
  • @proxy 代理
定義自己的元數據

通過library來定義一個庫,在庫中定義一個相同名字的class,然后在類中定義const 構造方法。

// 定義
library todo;

class todo {
  final String who;
  final String what;

  const todo(this.who, this.what);
}

// 使用
import 'todo.dart';

@todo('seth', 'make this do something')
void doSomething() {
  print('do something');
}

元數據可以修飾library(庫), class(類), typedef(類型定義), type parameter(類型參數), constructor(構造函數), factory(工廠函數), function(函數), field(作用域), parameter(參數), 或者 variable declaration(變量聲明)。

可以使用reflection反射,在運行時檢查元數據。

注釋(Comments)

  • 單行注釋:/
  • 多行注釋:/*...*/
  • 文檔注釋:/**...*//// 使用[]引用參考生成API文檔鏈接
class Llama {
  String name;// 這是單行注釋
  void feed(Food food) {
    /*
    這是多行注釋
    /
  }

/**
這里是文檔注釋
*/
  /// Exercises your llama with an [activity] for
  /// [timeLimit] minutes.
  void exercise(Activity activity, int timeLimit) {
    // ...
  }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容