Dart 學習

  語言特性

  • Dart全部的東西都是對象, 即便是數字numbers、函數function、null也都是對象,全部的對象都繼承自Object類。javascript

  • Dart動態類型語言, 儘可能給變量定義一個類型,會更安全,沒有顯示定義類型的變量在 debug 模式下會類型會是 dynamic(動態的)。php

  • Dart 在 running 以前解析你的全部代碼,指定數據類型和編譯時的常量,能夠提升運行速度。html

  • Dart中的類和接口是統一的,類即接口,你能夠繼承一個類,也能夠實現一個類(接口),天然也包含了良好的面向對象和併發編程的支持。java

  • Dart 提供了頂級函數(如:main())。算法

  • Dart 沒有 public、private、protected 這些關鍵字,變量名以"_"開頭意味着對它的 lib 是私有的。express

  • 沒有初始化的變量都會被賦予默認值 null。編程

  • final的值只能被設定一次。const 是一個編譯時的常量,能夠經過 const 來建立常量值,var c=const[];,這裏 c 仍是一個變量,只是被賦值了一個常量值,它仍是能夠賦其它值。實例變量能夠是 final,但不能是 const。json

  • 編程語言並非孤立存在的,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

變量與常量

  1. 變量聲明與初始化
  • 調用的變量name包含對String值爲「張三」 的對象的引用,name推斷變量的類型是String,但能夠經過指定它來更改該類型,若是對象不限於單一類型(沒有明確的類型),請使用Object或dynamic關鍵字。
// 沒有明確類型,編譯的時候根據值明確類型 var name = ‘Bob’; Object name = '張三'; dynamic name = '李四'; // 顯示聲明將被推斷類型, 可使用String顯示聲明字符串類型 String name = 'Bob' ; 
  1. 默認值
  • 未初始化的變量的初始值爲null(包括數字),所以數字、字符串均可以調用各類方法
//測試 數字類型的初始值是什麼? int lineCount; // 爲false的時候拋出異常 assert(lineCount == null); print(lineCount); //打印結果爲null,證實數字類型初始化值是null 
  1. 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'; 

數據類型

  1. 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); 
  2. 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); 
  3. 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); 
  1. 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(); 
  • 注意事項:

    1. 能夠直接打印list包括list的元素,list也是一個對象。可是java必須遍歷才能打印list,直接打印是地址值。

    2. 和java同樣list裏面的元素必須保持類型一致,不一致就會報錯。

    3. 和java同樣list的角標從0開始。

    4. 若是集合裏面有多個相同的元素「X」, 只會刪除集合中第一個改元素

  1. 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); 
  • 注意事項

    1. map的key類型不一致也不會報錯。

    2. 添加元素的時候,會按照你添加元素的順序逐個加入到map裏面,哪怕你的key,好比分別是 1,2,4,看起來有間隔,事實上添加到map的時候是{1:value,2:value,4:value} 這種形式。

    3. 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)

  1. throw
  • 拋出固定類型的異常
throw new FormatException('Expected at least 1 section'); 
  • 拋出任意類型的異常

    throw 'Out of llamas!'; 
  • 由於拋出異常屬於表達式,能夠將throw語句放在=>語句中,或者其它能夠出現表達式的地方

    distanceTo(Point other) =>
         throw new UnimplementedError(); 
  1. 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'); } 
  1. rethrow
  • rethrow語句用來處理一個異常,同時但願這個異常可以被其它調用的部分使用。
final foo = ''; void misbehave() { try { foo = "1"; } catch (e) { print('2'); rethrow;// 若是不從新拋出異常,main函數中的catch語句執行不到 } } void main() { try { misbehave(); } catch (e) { print('3'); } } 
  1. 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; } 
  1. 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 = 'jenny@example.com' ..phone = (PhoneNumberBuilder() ..number = '415-555-0100' ..label = 'home') .build()) .build(); 
    • 當返回值是void時不能構建級聯。 例如,如下代碼失敗:
    var sb = StringBuffer(); sb.write('foo') // 返回void ..write('bar'); // 這裏會報錯 
    • 注意: 嚴格地說,級聯的..符號不是操做符。它只是Dart語法的一部分。
  1. 可選參數

    • 可選的命名參數, 定義函數時,使用{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 
  2. 默認參數

    • 函數可使用=爲命名參數和位置參數定義默認值。默認值必須是編譯時常量。若是沒有提供默認值,則默認值爲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'); } 
  3. 做爲一個類對象的功能

  • 您能夠將一個函數做爲參數傳遞給另外一個函數。
void printElement(int element) { print(element); } var list = [1, 2, 3]; // 把 printElement函數做爲一個參數傳遞進來 list.forEach(printElement); 
  • 您也能夠將一個函數分配給一個變量。
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!'; assert(loudify('hello') == '!!! HELLO !!!'); 
  1. 匿名函數

    • 大多數函數都能被命名爲匿名函數,如 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')); 
  2. 返回值

  • 全部函數都返回一個值,若是沒有指定返回值,則語句return null,隱式地附加到函數體。
foo() {}
  assert(foo() == null); 

類(Classes)

  1. 對象
  • 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}'); ``` 
  1. 實例化變量(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. } 
  1. 構造函數(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); } 
  1. 構造函數不能繼承(Constructors aren’t inherited)
  • Dart 語言中,子類不會繼承父類的命名構造函數。若是不顯式提供子類的構造函數,系統就提供默認的構造函數。
  1. 命名的構造函數(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']; } } 
  • 構造函數不能被繼承,父類中的命名構造函數不能被子類繼承。若是想要子類也擁有一個父類同樣名字的構造函數,必須在子類是實現這個構造函數。
  1. 調用父類的非默認構造函數
  • 默認狀況下,子類只能調用父類的無名,無參數的構造函數; 父類的無名構造函數會在子類的構造函數前調用; 若是initializer list 也同時定義了,則會先執行initializer list 中的內容,而後在執行父類的無名無參數構造函數,最後調用子類本身的無名無參數構造函數。即下面的順序:

    1. initializer list(初始化列表)
    2. super class’s no-arg constructor(父類無參數構造函數)
    3. 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'; } 
  1. 初始化列表

    • 除了調用父類的構造函數,也能夠經過初始化列表在子類的構造函數體前(大括號前)來初始化實例的變量值,使用逗號,分隔。以下所示:
    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 關鍵字。

  2. 靜態構造函數

    • 若是你的類產生的對象永遠不會改變,你可讓這些對象成爲編譯時常量。爲此,須要定義一個 const 構造函數並確保全部的實例變量都是 final 的。
    class ImmutablePoint { final num x; final num y; const ImmutablePoint(this.x, this.y); static final ImmutablePoint origin = const ImmutablePoint(0, 0); } 
  3. 重定向構造函數

    • 有時候構造函數的目的只是重定向到該類的另外一個構造函數。重定向構造函數沒有函數體,使用冒號:分隔。
    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); } 
  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); } 
  1. 工廠構造函數
  • 當實現一個使用 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。

方法

  • 方法就是爲對象提供行爲的函數。
  1. 實例方法

    • 對象的實例方法能夠訪問實例變量和 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); } } 
  2. 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 ,就會把他的值存在臨時變量裏。

  1. 抽象方法

    • Instance , getter 和 setter 方法能夠是抽象的,也就是定義一個接口,可是把實現交給其餘的類。要建立一個抽象方法,使用分號(;)代替方法體:
    abstract class Doer { // ...定義實例變量和方法... void doSomething(); // 定義一個抽象方法。 } class EffectiveDoer extends Doer { void doSomething() { // ...提供一個實現,因此這裏的方法不是抽象的... } } 
  2. 枚舉類型

  • 枚舉類型,一般被稱爲 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' } ``` ***枚舉類型有如下限制*** * 你不能在子類中混合或實現一個枚舉。 * 你不能顯式實例化一個枚舉。 
  1. 爲類添加特徵: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'); } } } 
  1. 類的變量和方法

    • 使用 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) { // ... } } 

庫和可見性

  1. import,part,library指令能夠幫助建立一個模塊化的,可共享的代碼庫。庫不只提供了API,還提供隱私單元:如下劃線(_)開頭的標識符只對內部庫可見。每一個Dartapp就是一個庫,即便它不使用庫指令。

  2. 庫能夠分佈式使用包。見 Pub Package and Asset Manager 中有關pub(SDK中的一個包管理器)。

  3. 使用庫

  • 使用 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'; 
  1. 指定庫前綴
  • 若是導入兩個庫是有衝突的標識符,那麼你能夠指定一個或兩個庫的前綴。例如,若是 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裏的元素 ``` 
  1. 導入部分庫

    • 若是想使用的庫一部分,你能夠選擇性導入庫。例如:
    // 只導入foo庫 import 'package:lib1/lib1.dart' show foo; //導入全部除了foo import 'package:lib2/lib2.dart' hide foo; 
  2. 延遲加載庫

    • 延遲(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。
  1. 庫的實現

    • 用 library 來來命名庫,用part來指定庫中的其餘文件。 注意:沒必要在應用程序中(具備頂級main()函數的文件)使用library,但這樣作可讓你在多個文件中執行應用程序。
  1. 聲明庫

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

      // 聲明庫,名ballgame library ballgame; // 導入html庫 import 'dart:html'; // ...代碼從這裏開始... 
  1. 關聯文件與庫

    • 添加實現文件,把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... 
  1. 從新導出庫(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 } ``` 

異步的支持

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

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

    • 使用 async 和 await
    • 使用 Future API
  3. 一樣的,當你須要從 Stream 獲取值的時候,你有兩個選擇。

    • 使用 async 和一個異步的 for 循環 (await for)
    • 使用 Stream API
  4. 使用 async 和 await 的代碼是異步的,不過它看起來很像同步的代碼。好比這裏有一段使用 await 等待一個異步函數結果的代碼:

await lookUpVersion()

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

    checkVersion() async { var version = await lookUpVersion(); if (version == expectedVersion) { // Do something. } else { // Do something else. } } 
  2. 你可使用 try, catch, 和 finally 來處理錯誤並精簡使用了 await 的代碼。

    try { server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044); } catch (e) { // React to inability to bind to the port... } 
  3. 聲明異步函數

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

    checkVersion() async { // ... } lookUpVersion() async => /* ... */; 
  • 在函數中添加關鍵字 async 使得它返回一個 Future,好比,考慮一下這個同步函數,它將返回一個字符串。

  • String lookUpVersionSync() => '1.0.0';

  • 若是你想更改它成爲異步方法-由於在之後的實現中將會很是耗時-它的返回值是一個 Future 。

  • Future<String> lookUpVersion() async => '1.0.0';

  • 請注意函數體不須要使用 Future API,若是必要的話 Dart 將會本身建立 Future 對象

  1. 使用帶 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()}'); } ``` 
  1. 結合 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); } ... } 
相關文章
相關標籤/搜索