Flutter要火!Dart你會了嗎?

從Flutter問世,人們對他的關注一直不斷,特別是前不久Flutter 1.0發佈後,人們對他的關注更多了,Flutter要火!那就學習一下了,我呢,身爲一個前端開發工作者,就以一個前端開發者的身份來學習Flutter,由於Flutter是使用的Dart語言,那就從Dart開始吧!

語言特性

  1. Dart所有的東西都是對象, 即使是數字numbers、函數function、null也都是對象,所有的對象都繼承自Object類。
  2. Dart動態類型語言, 儘量給變量定義一個類型,會更安全,沒有顯示定義類型的變量在 debug 模式下會類型會是 dynamic(動態的)。
  3. Dart 在 running 之前解析你的所有代碼,指定數據類型和編譯時的常量,可以提高運行速度。
  4. Dart中的類和接口是統一的,類即接口,你可以繼承一個類,也可以實現一個類(接口),自然也包含了良好的面向對象和併發編程的支持。
  5. Dart 提供了頂級函數(如:main())。
  6. Dart 沒有 public、private、protected 這些關鍵字,變量名以"_"開頭意味着對它的 lib 是私有的。
  7. 沒有初始化的變量都會被賦予默認值 null。
  8. final的值只能被設定一次。const 是一個編譯時的常量,可以通過 const 來創建常量值,var c=const[];,這裏 c 還是一個變量,只是被賦值了一個常量值,它還是可以賦其它值。實例變量可以是 final,但不能是 const。

編程語言並不是孤立存在的,Dart也是這樣,他由語言規範、虛擬機、類庫和工具等組成:

  • SDK:SDK 包含 Dart VM、dart2js、Pub、庫和工具。
  • Dartium:內嵌 Dart VM 的 Chromium ,可以在瀏覽器中直接執行 dart 代碼。
  • Dart2js:將 Dart 代碼編譯爲 JavaScript 的工具。
  • Dart Editor:基於 Eclipse 的全功能 IDE,幷包含以上所有工具。支持代碼補全、代碼導航、快速修正、重構、調試等功能。

關鍵字(56個)

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

變量與常量

變量聲明與初始化

調用的變量name包含對String值爲“張三” 的對象的引用,name推斷變量的類型是String,但可以通過指定它來更改該類型,如果對象不限於單一類型(沒有明確的類型),請使用Object或dynamic關鍵字。
  // 沒有明確類型,編譯的時候根據值明確類型
  var name = ‘Bob’; 
  Object name = '張三';
  dynamic name = '李四';

  // 顯示聲明將被推斷類型, 可以使用String顯示聲明字符串類型
  String name = 'Bob' ;

默認值
未初始化的變量的初始值爲null(包括數字),因此數字、字符串都可以調用各種方法

  //測試 數字類型的初始值是什麼?
  int lineCount;
  // 爲false的時候拋出異常
  assert(lineCount == null);
  print(lineCount); //打印結果爲null,證明數字類型初始化值是null

final and const

如果您從未打算更改一個變量,那麼使用 final 或 const,不是var,也不是一個類型。
一個 final 變量只能被初始化一次; const變量是一個編譯時常量,(Const變量是隱式的final)
final的頂級或類變量在第一次使用時被初始化。
被final修飾的頂級變量或類變量在第一次聲明的時候就需要初始化。
// The final variable 'outSideFinalName' must be initialized.
final String outSideFinalName

被final或者const修飾的變量,變量類型可以省略,建議指定數據類型。

 //可以省略String這個類型聲明
 final name = "Bob";
 final String name1  = "張三";
 
 const name2 = "alex";
 const String name3 = "李四";

被 final 或 const 修飾的變量無法再去修改其值。

 final String outSideFinalName = "Alex";
 // outSideFinalName', a final variable, can only be set once
 // 一個final變量,只能被設置一次。
 outSideFinalName = "Bill";
 
 const String outSideName = 'Bill';
 // 這樣寫,編譯器提示:Constant variables can't be assigned a value
 // const常量不能賦值
 // outSideName = "小白";

flnal 或者 const 不能和 var 同時使用

 // Members can't be declared to be both 'const' and 'var'
 const var String outSideName = 'Bill';
 
 // Members can't be declared to be both 'final' and 'var'
 final var String name = 'Lili';

常量如果是類級別的,請使用 static const

 // 常量如果是類級別的,請使用 static const
 static const String name3 = 'Tom';
  // 這樣寫保存
 // Only static fields can be declared as const
 // 只有靜態字段可以聲明爲const
 //const String name3 = 'Tom';

常量的運算

 const speed = 100; //速度(km/h)
 const double distance = 2.5 * speed; // 距離 = 時間 * 速度

 final speed2 = 100; //速度(km/h)
 final double distance2 = 2.5 * speed2; // 距離 = 時間 * 速度

const關鍵字不只是聲明常數變量,您也可以使用它來創建常量值,以及聲明創建常量值的構造函數,任何變量都可以有一個常量值。

 // 注意: [] 創建的是一個空的list集合
 // const []創建一個空的、不可變的列表(EIL)。
 var varList = const []; // varList 當前是一個EIL
 final finalList = const []; // finalList一直是EIL
 const constList = const []; // constList 是一個編譯時常量的EIL

 // 可以更改非final,非const變量的值
 // 即使它曾經具有const值
 varList = ["haha"];

 // 不能更改final變量或const變量的值
 // 這樣寫,編譯器提示:a final variable, can only be set once
 // finalList = ["haha"];
 // 這樣寫,編譯器提示:Constant variables can't be assigned a value  
 // constList = ["haha"];

在常量表達式中,該運算符的操作數必須爲'bool'、'num'、'String'或'null', const常量必須用conat類型的值初始化。

const String outSideName = 'Bill';
final String outSideFinalName = 'Alex';
const String outSideName2 = 'Tom';

const aConstList = const ['1', '2', '3'];

// In constant expressions, operands of this operator must be of type 'bool', 'num', 'String' or 'null'
// 在常量表達式中,該運算符的操作數必須爲'bool'、'num'、'String'或'null'。
const validConstString = '$outSideName $outSideName2 $aConstList';

// Const variables must be initialized with a constant value
// const常量必須用conat類型的值初始化
const validConstString = '$outSideName $outSideName2 $outSideFinalName';

var outSideVarName='Cathy';
// Const variables must be initialized with a constant value.
// const常量必須用conat類型的值初始化
const validConstString = '$outSideName $outSideName2 $outSideVarName';

// 正確寫法
const String outSideConstName = 'Joy';
const validConstString = '$outSideName $outSideName2 $outSideConstName';

數據類型

num

  • num 是數字類型的父類,有兩個子類 int 和 double。
  • int 根據平臺的不同,整數值不大於64位。在Dart VM上,值可以從-263到263 - 1,編譯成JavaScript的Dart使用JavaScript代碼,允許值從-253到253 - 1。
  • double 64位(雙精度)浮點數,如IEEE 754標準所規定。
 int a = 1;
 print(a);
 
 double b = 1.12;
 print(b);
 
 // String -> int
 int one = int.parse('1');
 // 輸出3
 print(one + 2);
 
 // String -> double
 var onePointOne = double.parse('1.1');
 // 輸出3.1
 print(onePointOne + 2);

 // int -> String
 String oneAsString = 1.toString();
 // The argument type 'int' can't be assigned to the parameter type 'String'
 //print(oneAsString + 2);
 // 輸出 1 + 2
 print('$oneAsString + 2');
 // 輸出 1 2
 print('$oneAsString 2');

 // double -> String 注意括號中要有小數點位數,否則報錯
 String piAsString = 3.14159.toStringAsFixed(2);
 // 截取兩位小數, 輸出3.14
 print(piAsString);
 
 String aString = 1.12618.toStringAsFixed(2);
 // 檢查是否四捨五入,輸出1.13,發現會做四捨五入
 print(aString);

String

  • Dart裏面的String是一系列 UTF-16 代碼單元。
  • 您可以使用單引號或雙引號來創建一個字符串。
  • 單引號或者雙引號裏面嵌套使用引號。
  • 用 或{} 來計算字符串中變量的值,需要注意的是如果是表達式需要${表達式}
 String singleString = 'abcdddd';
 String doubleString = "abcsdfafd";
 
 String sdString = '$singleString a "bcsd" ${singleString}';
 String dsString = "abc 'aaa' $sdString";
 print(sdString);
 print(dsString);


 String singleString = 'aaa';
 String doubleString = "bbb";
 // 單引號嵌套雙引號
 String sdString = '$singleString a "bbb" ${doubleString}';
 // 輸出 aaa a "bbb" bbb
 print(sdString);
 
 // 雙引號嵌套單引號
 String dsString = "${singleString.toUpperCase()} abc 'aaa' $doubleString.toUpperCase()";
 // 輸出 AAA abc 'aaa' bbb.toUpperCase(), 
 可以看出 ”$doubleString.toUpperCase()“ 沒有加“{}“,導致輸出結果是”bbb.toUpperCase()“
 print(dsString);

bool

  • Dart 是強 bool 類型檢查,只有bool 類型的值是true 才被認爲是true。
  • 只有兩個對象具有bool類型:true和false,它們都是編譯時常量。
  • Dart的類型安全意味着您不能使用 if(nonbooleanValue) 或 assert(nonbooleanValue) 等代碼, 相反Dart使用的是顯式的檢查值。
  • assert 是語言內置的斷言函數,僅在檢查模式下有效
  • 在開發過程中, 除非條件爲真,否則會引發異常。(斷言失敗則程序立刻終止)。
  // 檢查是否爲空字符串
  var fullName = '';
  assert(fullName.isEmpty);

  // 檢查0
  var hitPoints = 0;
  assert(hitPoints <= 0);

  // 檢查是否爲null
  var unicorn;
  assert(unicorn == null);

  // 檢查是否爲NaN
  var iMeantToDoThis = 0 / 0;
  assert(iMeantToDoThis.isNaN);

List集合
在Dart中,數組是List對象,因此大多數人只是將它們稱爲List。
Dart list文字看起來像JavaScript數組文字

 //創建一個int類型的list
 List list = [10, 7, 23];
 // 輸出[10, 7, 23]
 print(list);
 
 // 使用List的構造函數,也可以添加int參數,表示List固定長度,不能進行添加 刪除操作
 var fruits = new List();
 
 // 添加元素
 fruits.add('apples');
 
 // 添加多個元素
 fruits.addAll(['oranges', 'bananas']);
 
 List subFruits = ['apples', 'oranges', 'banans'];
 // 添加多個元素
 fruits.addAll(subFruits);
 
 // 輸出: [apples, oranges, bananas, apples, oranges, banans]
 print(fruits);
 
 // 獲取List的長度
 print(fruits.length);
 
 // 獲取第一個元素
 print(fruits.first);
 
 // 獲取元素最後一個元素
 print(fruits.last);
 
 // 利用索引獲取元素
 print(fruits[0]);
 
 // 查找某個元素的索引號
 print(fruits.indexOf('apples'));
 
 // 刪除指定位置的元素,返回刪除的元素
 print(fruits.removeAt(0));

 // 刪除指定元素,成功返回true,失敗返回false
 // 如果集合裏面有多個“apples”, 只會刪除集合中第一個改元素
 fruits.remove('apples');

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

 // 刪除指定範圍(索引)元素,含頭不含尾
 fruits.removeRange(start,end);

 // 刪除指定條件的元素(這裏是元素長度大於6)
 fruits.removeWhere((item) => item.length >6);

 // 刪除所有的元素
 fruits.clear();
注意事項:
可以直接打印list包括list的元素,list也是一個對象。但是java必須遍歷才能打印list,直接打印是地址值。
和java一樣list裏面的元素必須保持類型一致,不一致就會報錯。
和java一樣list的角標從0開始。
如果集合裏面有多個相同的元素“X”, 只會刪除集合中第一個改元素

Map集合

一般來說,map是將鍵和值相關聯的對象。鍵和值都可以是任何類型的對象。
每個鍵只出現一次,但您可以多次使用相同的值。Dart支持map由map文字和map類型提供。

  • 初始化Map方式一: 直接聲明,用{}表示,裏面寫key和value,每組鍵值對中間用逗號隔開。
 // Two keys in a map literal can't be equal.
 // Map companys = {'Alibaba': '阿里巴巴', 'Tencent': '騰訊', 'baidu': '百度', 'Alibaba': '釘釘', 'Tenect': 'qq-music'};
 
 Map companys = {'Alibaba': '阿里巴巴', 'Tencent': '騰訊', 'baidu': '百度'};
 // 輸出:{Alibaba: 阿里巴巴, Tencent: 騰訊, baidu: 百度}
 print(companys);
  • 創建Map方式二:先聲明,再去賦值。
 Map schoolsMap = new Map();
 schoolsMap['first'] = '清華';
 schoolsMap['second'] = '北大';
 schoolsMap['third'] = '復旦';
 // 打印結果 {first: 清華, second: 北大, third: 復旦}
 print(schoolsMap);

 var fruits = new Map();
 fruits["first"] = "apple";
 fruits["second"] = "banana";
 fruits["fifth"] = "orange";
 //換成雙引號,換成var 打印結果 {first: apple, second: banana, fifth: orange}
 print(fruits);

Map API

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

// Map的賦值,中括號中是Key,這裏可不是數組
aMap[1] = '小米';

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

// map裏面的value可以相同
aMap[2] = 'alibaba';

// map裏面value可以爲空字符串
aMap[3] = '';

// map裏面的value可以爲null
aMap[4] = null;

print(aMap);

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

//刪除某個鍵值對
aMap.remove(1); 

print(aMap);  
注意事項:
map的key類型不一致也不會報錯。
添加元素的時候,會按照你添加元素的順序逐個加入到map裏面,哪怕你的key,比如分別是 1,2,4,看起來有間隔,事實上添加到map的時候是{1:value,2:value,4:value} 這種形式。
map裏面的key不能相同。但是value可以相同,value可以爲空字符串或者爲null。

運算符

一元后置操作符 expr++ expr-- () [] . ?.
一元前置操作符expr !expr ~expr ++expr --expr
乘除 / % ~/
加減 + -
位移 << >>
按位與 &
按位異或 ^
邏輯與 &&

關係和類型判斷 >= > <= < as is is!
等 == !=
如果爲空 ??
條件表達式 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');
 }

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最終都會執行到
    }
  }

函數 Function

以下是一個實現函數的例子:

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

main()函數

每個應用程序都必須有一個頂層main()函數,它可以作爲應用程序的入口點。該main()函數返回void並具有List<String>參數的可選參數。

void main() {
   querySelector('#sample_text_id')
       ..text = 'Click me!'
       ..onClick.listen(reverseText);
}

級聯符號:允許您在同一個對象上進行一系列操作。除了函數調用之外,還可以訪問同一對象上的字段。這通常會爲您節省創建臨時變量的步驟,並允許您編寫更流暢的代碼。

querySelector('#confirm') // Get an object.
    ..text = 'Confirm' // Use its members.
    ..classes.add('important')
    ..onClick.listen((e) => window.alert('Confirmed!'));

上述例子相對於:

 var button = querySelector('#confirm');
 button.text = 'Confirm';
 button.classes.add('important');
 button.onClick.listen((e) => window.alert('Confirmed!'));

級聯符號也可以嵌套使用。 例如:

  final addressBook = (AddressBookBuilder()
   ..name = 'jenny'
   ..email = '[email protected]'
   ..phone = (PhoneNumberBuilder()
         ..number = '415-555-0100'
         ..label = 'home')
       .build())
   .build();

當返回值是void時不能構建級聯。 例如,以下代碼失敗:

var sb = StringBuffer();
sb.write('foo') // 返回void
  write('bar'); // 這裏會報錯
注意: 嚴格地說,級聯的..符號不是操作符。它只是Dart語法的一部分。

可選參數

可選的命名參數, 定義函數時,使用{param1, param2, …},用於指定命名參數。例如:

 //設置[bold]和[hidden]標誌
 void enableFlags({bool bold, bool hidden}) {
      // ... 
 }  
 
 enableFlags(bold: true, hidden: false);

可選的位置參數,用[]它們標記爲可選的位置參數:

  String say(String from, String msg, [String device]) {
      var result = '$from says $msg';
      if (device != null) {
         result = '$result with a $device';
      }
      return result;
  }

下面是一個不帶可選參數調用這個函數的例子:

say('Bob', 'Howdy'); //結果是: Bob says Howdy

下面是用第三個參數調用這個函數的例子:

say('Bob', 'Howdy', 'smoke signal'); //結果是:Bob says Howdy with a smoke signal

默認參數

函數可以使用=爲命名參數和位置參數定義默認值。默認值必須是編譯時常量。如果沒有提供默認值,則默認值爲null。
下面是爲命名參數設置默認值的示例:

  // 設置 bold 和 hidden 標記的默認值都爲false
  void enableFlags2({bool bold = false, bool hidden = false}) {
       // ...
  }

  // 調用的時候:bold will be true; hidden will be false.
  enableFlags2(bold: true);

下一個示例顯示如何爲位置參數設置默認值:

 String say(String from, String msg,
    [String device = 'carrier pigeon', String mood]) {
        var result = '$from says $msg';
        if (device != null) {
            result = '$result with a $device';
        }
        if (mood != null) {
            result = '$result (in a $mood mood)';
        }
        return result;
 }
 //調用方式:
 say('Bob', 'Howdy'); //結果爲:Bob says Howdy with a carrier pigeon;

您還可以將list或map作爲默認值傳遞。下面的示例定義一個函數doStuff(),該函數指定列表參數的默認list和gifts參數的默認map。

 // 使用list 或者map設置默認值
 void doStuff(
     {List<int> list = const [1, 2, 3],
     Map<String, String> gifts = const {'first': 'paper', 
     'second': 'cotton', 'third': 'leather'
    }}) {
     print('list:  $list');
     print('gifts: $gifts');
 }

作爲一個類對象的功能
您可以將一個函數作爲參數傳遞給另一個函數。

  void printElement(int element) {
     print(element);
  }

  var list = [1, 2, 3];

  // 把 printElement函數作爲一個參數傳遞進來
  list.forEach(printElement);

您也可以將一個函數分配給一個變量。

  var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
  assert(loudify('hello') == '!!! HELLO !!!');

匿名函數

大多數函數都能被命名爲匿名函數,如 main() 或 printElement()。您還可以創建一個名爲匿名函數的無名函數,有時也可以創建lambda或閉包。您可以爲變量分配一個匿名函數,例如,您可以從集合中添加或刪除它。
一個匿名函數看起來類似於一個命名函數 - 0或更多的參數,在括號之間用逗號和可選類型標註分隔。
下面的代碼塊包含函數的主體:

  ([[Type] param1[, …]]) { 
     codeBlock; 
  }; 

下面的示例定義了一個具有無類型參數的匿名函數item,該函數被list中的每個item調用,輸出一個字符串,該字符串包含指定索引處的值。

 var list = ['apples', 'bananas', 'oranges'];
 list.forEach((item) {
    print('${list.indexOf(item)}: $item');
 });

如果函數只包含一條語句,可以使用箭頭符號=>來縮短它, 比如上面的例2可以簡寫成:
list.forEach((item) => print('${list.indexOf(item)}: $item'));

返回值

所有函數都返回一個值,如果沒有指定返回值,則語句return null,隱式地附加到函數體。

  foo() {}
  assert(foo() == null);

類(Classes)

對象

  • Dart 是一種面向對象的語言,並且支持基於mixin的繼承方式。
  • Dart 語言中所有的對象都是某一個類的實例,所有的類有同一個基類--Object。
  • 基於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);

使用.(dot)來調用實例的變量或者方法。

  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引發異常。

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

使用const替代new來創建編譯時的常量構造函數。

 var p = const ImmutablePoint(2, 2);

使用runtimeType方法,在運行中獲取對象的類型。該方法將返回Type 類型的變量。


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

實例化變量(Instance variables)

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


 class Point {
    num x; // Declare instance variable x, initially null.
    num y; // Declare y, initially null.
    num z = 0; // Declare z, initially 0.
 }

類定義中所有的變量, Dart語言都會隱式的定義 setter 方法,針對非空的變量會額外增加 getter 方法。

 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)

聲明一個和類名相同的函數,來作爲類的構造函數。

  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關鍵字指向了當前類的實例, 上面的代碼可以簡化爲:

 class Point {
    num x;
    num y;

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

構造函數不能繼承(Constructors aren’t inherited)

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

命名的構造函數(Named constructors)

使用命名構造函數從另一類或現有的數據中快速實現構造函數。

class Point {
   num x;
   num y;

   Point(this.x, this.y);

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

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

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

默認情況下,子類只能調用父類的無名,無參數的構造函數; 父類的無名構造函數會在子類的構造函數前調用; 如果initializer list 也同時定義了,則會先執行initializer list 中的內容,然後在執行父類的無名無參數構造函數,最後調用子類自己的無名無參數構造函數。即下面的順序:
initializer list(初始化列表)
super class’s no-arg constructor(父類無參數構造函數)
main class’s no-arg constructor (主類無參數構造函數)

如果父類不顯示提供無名無參數構造函數的構造函數,在子類中必須手打調用父類的一個構造函數。這種情況下,調用父類的構造函數的代碼放在子類構造函數名後,子類構造函數體前,中間使用:(colon) 分割。

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

注意:上述代碼,初始化程序無法訪問 this 關鍵字。

靜態構造函數

如果你的類產生的對象永遠不會改變,你可以讓這些對象成爲編譯時常量。爲此,需要定義一個 const 構造函數並確保所有的實例變量都是 final 的。

class ImmutablePoint {
    final num x;
    final num y;
    const ImmutablePoint(this.x, this.y);
    static final ImmutablePoint origin = const ImmutablePoint(0, 0);
}

重定向構造函數

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

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 關鍵詞修飾的構造函數時,這個構造函數不必創建類的新實例。例如,工廠構造函數可能從緩存返回實例,或者它可能返回子類型的實例。 下面的示例演示一個工廠構造函數從緩存返回的對象:

class Logger {
   final String name;
   bool mute = false;

   // _cache 是一個私有庫,幸好名字前有個 _ 。 
   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);
       }
    }
    
 }
注意:工廠構造函數不能用 this。

方法

方法就是爲對象提供行爲的函數。

實例方法

對象的實例方法可以訪問實例變量和 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);
   }
 }

setters 和 Getters

是一種提供對方法屬性讀和寫的特殊方法。每個實例變量都有一個隱式的 getter 方法,合適的話可能還會有 setter 方法。你可以通過實現 getters 和 setters 來創建附加屬性,也就是直接使用 get 和 set 關鍵詞:

class Rectangle {
   num left;
   num top;
   num width;
   num height;

   Rectangle(this.left, this.top, this.width, this.height);

   // 定義兩個計算屬性: 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 ,你可以直接使用實例變量,並且在不改變客戶代碼的情況下把他們包裝成方法。
注: 不論是否顯式地定義了一個 getter,類似增量(++)的操作符,都能以預期的方式工作。爲了避免產生任何向着不期望的方向的影響,操作符一旦調用 getter ,就會把他的值存在臨時變量裏。

抽象方法

Instance , getter 和 setter 方法可以是抽象的,也就是定義一個接口,但是把實現交給其他的類。要創建一個抽象方法,使用分號(;)代替方法體:

 abstract class Doer {
    // ...定義實例變量和方法...
    void doSomething(); // 定義一個抽象方法。
 }

 class EffectiveDoer extends Doer {
     void doSomething() {
        // ...提供一個實現,所以這裏的方法不是抽象的...
     }
 }

枚舉類型

枚舉類型,通常被稱爲 enumerations 或 enums ,是一種用來代表一個固定數量的常量的特殊類。
聲明一個枚舉類型需要使用關鍵字 enum :
 enum Color {
    red,
    green,
    blue
 }

在枚舉中每個值都有一個 index getter 方法,它返回一個在枚舉聲明中從 0 開始的位置。例如,第一個值索引值爲 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 語句 中使用枚舉。如果 e 在 switch (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'
      }
    

枚舉類型有以下限制

  • 你不能在子類中混合或實現一個枚舉。
  • 你不能顯式實例化一個枚舉。

爲類添加特徵:mixins

mixins 是一種多類層次結構的類的代碼重用。
要使用 mixins ,在 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');
     }
   }
 }

類的變量和方法

使用 static 關鍵字來實現類變量和類方法。
只有當靜態變量被使用時才被初始化。

靜態變量, 靜態變量(類變量)對於類狀態和常數是有用的:

  class Color {
     static const red = const Color('red'); // 一個恆定的靜態變量
     final String name;      // 一個實例變量。 
     const Color(this.name); // 一個恆定的構造函數。
  }

  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);
    assert(distance < 2.9 && distance > 2.8);
  }
注:考慮到使用高階層的方法而不是靜態方法,是爲了常用或者廣泛使用的工具和功能。
你可以將靜態方法作爲編譯時常量。例如,你可以把靜態方法作爲一個參數傳遞給靜態構造函數。

抽象類

使用 abstract 修飾符來定義一個抽象類,該類不能被實例化。抽象類在定義接口的時候非常有用,實際上抽象中也包含一些實現。如果你想讓你的抽象類被實例化,請定義一個 工廠構造函數 。
抽象類通常包含 抽象方法。下面是聲明一個含有抽象方法的抽象類的例子:
 // 這個類是抽象類,因此不能被實例化。
 abstract class AbstractContainer {
   // ...定義構造函數,域,方法...

   void updateChildren(); // 抽象方法。
 }

下面的類不是抽象類,因此它可以被實例化,即使定義了一個抽象方法:

 class SpecializedContainer extends AbstractContainer {
    // ...定義更多構造函數,域,方法...

    void updateChildren() {
      // ...實現 updateChildren()...
    }

   // 抽象方法造成一個警告,但是不會阻止實例化。
   void doSomething();
 }

類-隱式接口

每個類隱式的定義了一個接口,含有類的所有實例和它實現的所有接口。如果你想創建一個支持類 B 的 API 的類 A,但又不想繼承類 B ,那麼,類 A 應該實現類 B 的接口。

一個類實現一個或更多接口通過用 implements 子句聲明,然後提供 API 接口要求。例如:

 // 一個 person ,包含 greet() 的隱式接口。
 class Person {
     // 在這個接口中,只有庫中可見。
     final _name;

     // 不在接口中,因爲這是個構造函數。
     Person(this._name);

     // 在這個接口中。
     String greet(who) => 'Hello, $who. I am $_name.';
 }

 //  Person 接口的一個實現。
 class Imposter implements Person {
     // 我們不得不定義它,但不用它。
     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 創建一個子類,同時 supper 將指向父類:

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

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

子類可以重載實例方法, getters 方法, setters 方法。下面是個關於重寫 Object 類的方法 noSuchMethod() 的例子,當代碼企圖用不存在的方法或實例變量時,這個方法會被調用。

  class A {
    // 如果你不重寫 noSuchMethod 方法, 就用一個不存在的成員,會導致NoSuchMethodError 錯誤。
    void noSuchMethod(Invocation mirror) {
        print('You tried to use a non-existent member:' + 
            '${mirror.memberName}');
     }
  }

你可以使用 @override 註釋來表明你重寫了一個成員。

 class A {
    @override
    void noSuchMethod(Invocation mirror) {
       // ...
    }
 }

如果你用 noSuchMethod() 實現每一個可能的 getter 方法,setter 方法和類的方法,那麼你可以使用 @proxy 標註來避免警告。

 @proxy
 class A {
    void noSuchMethod(Invocation mirror) {
        // ...
    }
 }

庫和可見性

import,part,library指令可以幫助創建一個模塊化的,可共享的代碼庫。庫不僅提供了API,還提供隱私單元:以下劃線(_)開頭的標識符只對內部庫可見。每個Dartapp就是一個庫,即使它不使用庫指令。
庫可以分佈式使用包。見 Pub Package and Asset Manager 中有關pub(SDK中的一個包管理器)。
使用庫

使用 import 來指定如何從一個庫命名空間用於其他庫的範圍。
例如,Dart Web應用一般採用這個庫 dart:html,可以這樣導入:

  import 'dart:html';

唯一需要 import 的參數是一個指向庫的 URI。對於內置庫,URI中具有特殊dart:scheme。對於其他庫,你可以使用文件系統路徑或package:scheme。包 package:scheme specifies libraries ,如pub工具提供的軟件包管理器庫。例如:

  import 'dart:io';
  import 'package:mylib/mylib.dart';
  import 'package:utils/utils.dart';

指定庫前綴

如果導入兩個庫是有衝突的標識符,那麼你可以指定一個或兩個庫的前綴。例如,如果 library1 和 library2 都有一個元素類,那麼你可能有這樣的代碼:

  import 'package:lib1/lib1.dart';
  import 'package:lib2/lib2.dart' as lib2;
  // ...
  var element1 = new Element(); // 使用lib1裏的元素
  var element2 =
  new lib2.Element();  // 使用lib2裏的元素

導入部分庫

如果想使用的庫一部分,你可以選擇性導入庫。例如:

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

 //導入所有除了foo
 import 'package:lib2/lib2.dart' hide foo;

延遲加載庫

延遲(deferred)加載(也稱爲延遲(lazy)加載)允許應用程序按需加載庫。下面是當你可能會使用延遲加載某些情況:

  • 爲了減少應用程序的初始啓動時間;
  • 執行A / B測試-嘗試的算法的替代實施方式中;
  • 加載很少使用的功能,例如可選的屏幕和對話框。

爲了延遲加載一個庫,你必須使用 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 來來命名庫,用part來指定庫中的其他文件。 注意:不必在應用程序中(具有頂級main()函數的文件)使用library,但這樣做可以讓你在多個文件中執行應用程序。

聲明庫

利用library identifier(庫標識符)指定當前庫的名稱:

// 聲明庫,名ballgame
  library ballgame;

  // 導入html庫
  import 'dart:html';

  // ...代碼從這裏開始... 

關聯文件與庫

添加實現文件,把part fileUri放在有庫的文件,其中fileURI是實現文件的路徑。然後在實現文件中,添加部分標識符(part of identifier),其中標識符是庫的名稱。下面的示例使用的一部分,在三個文件來實現部分庫。

  • 第一個文件,ballgame.dart,聲明球賽庫,導入其他需要的庫,並指定ball.dart和util.dart是此庫的部分:
  library ballgame;

  import 'dart:html';
  // ...其他導入在這裏...

  part 'ball.dart';
  part 'util.dart';

  // ...代碼從這裏開始...
  • 第二個文件ball.dart,實現了球賽庫的一部分:
part of ballgame;

 // ...代碼從這裏開始...
  • 第三個文件,util.dart,實現了球賽庫的其餘部分:
part of ballgame;

 // ...Code goes here...

重新導出庫(Re-exporting libraries)

  • 可以通過重新導出部分庫或者全部庫來組合或重新打包庫。例如,你可能有實現爲一組較小的庫集成爲一個較大庫。或者你可以創建一個庫,提供了從另一個庫方法的子集。

     // In french.dart:
     library french;
    
     hello() => print('Bonjour!');
     goodbye() => print('Au Revoir!');
    
     // In togo.dart:
     library togo;
    
     import 'french.dart';
     export 'french.dart' show hello;
    
     // In another .dart file:
     import 'togo.dart';
    
     void main() {
         hello();   //print bonjour
         goodbye(); //FAIL
     }
    

異步的支持

Dart 添加了一些新的語言特性用於支持異步編程。最通常使用的特性是 async 方法和 await 表達式。Dart 庫大多方法返回 Future 和 Stream 對象。這些方法是異步的:它們在設置一個可能的耗時操作(比如 I/O 操作)之後返回,而無需等待操作完成

當你需要使用 Future 來表示一個值時,你有兩個選擇。

  • 使用 async 和 await
  • 使用 Future API

同樣的,當你需要從 Stream 獲取值的時候,你有兩個選擇。

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

使用 async 和 await 的代碼是異步的,不過它看起來很像同步的代碼。比如這裏有一段使用 await 等待一個異步函數結果的代碼:

await lookUpVersion()

要使用 await,代碼必須用 await 標記

 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) {
    // React to inability to bind to the port...
 }

聲明異步函數

一個異步函數是一個由 async 修飾符標記的函數。雖然一個異步函數可能在操作上比較耗時,但是它可以立即返回-在任何方法體執行之前。

 checkVersion() async {
    // ...
 }

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

在函數中添加關鍵字 async 使得它返回一個 Future,比如,考慮一下這個同步函數,它將返回一個字符串。
String lookUpVersionSync() => '1.0.0';
如果你想更改它成爲異步方法-因爲在以後的實現中將會非常耗時-它的返回值是一個 Future 。
Future<String> lookUpVersion() async => '1.0.0';
請注意函數體不需要使用 Future API,如果必要的話 Dart 將會自己創建 Future 對象

使用帶 future 的 await 表達式

一個 await表達式具有以下形式

await expression

在異步方法中你可以使用 await 多次。比如,下列代碼爲了得到函數的結果一共等待了三次。

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

在 await 表達式中, 表達式 的值通常是一個 Future 對象;如果不是,那麼這個值會自動轉爲 Future。這個 Future 對象表明了表達式應該返回一個對象。await 表達式 的值就是返回的一個對象。在對象可用之前,await 表達式將會一直處於暫停狀態。
如果 await 沒有起作用,請確認它是一個異步方法。比如,在你的 main() 函數裏面使用await,main() 的函數體必須被 async 標記:

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

結合 streams 使用異步循環
一個異步循環具有以下形式:

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

表達式 的值必須有Stream 類型(流類型)。執行過程如下:

在 stream 發出一個值之前等待
執行 for 循環的主體,把變量設置爲發出的值。
重複 1 和 2,直到 Stream 關閉
  • 如果要停止監聽 stream ,你可以使用 break 或者 return 語句,跳出循環並取消來自 stream 的訂閱 。
  • 如果一個異步 for 循環沒有正常運行,請確認它是一個異步方法。 比如,在應用的 main() 方法中使用異步的 for 循環時,main() 的方法體必須被 async 標記。
  main() async {    
     await for (var request in requestServer) {
         handleRequest(request);
     }
  }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章