Flutter學習之Dart語法特性

1、前言

第一天把Flutter環境搭建了,並簡單實現第運行第一個Flutter項目,感受很不錯,一些基本操做和原生體驗差很少。用Flutter框架寫過App項目的開發者都知道,Flutter是一個使用Dart語言開發的跨平臺移動UI框架,經過自建繪製引擎,能高性能、高保真地進行Android和iOS開發。可能不少開發者都會像我同樣,會有個疑問?爲何Flutter會選擇Dart呢?我特地查了下網上資料,總結如下下面六點:html

  • Dart在release下是AOT(Ahead Of Time, 運行前,好比普通的靜態編譯)編譯的,編譯成快速、可預測的本地代碼,使Flutter幾乎均可以使用Dart編寫,在Debug下,Dart是JIT(jUST In Time 運行時編譯,邊運行邊編譯,java虛擬機就用到)編譯,開發週期快,就好像Flutter亞熱級的熱重載。
  • Dart能夠更輕鬆地建立以60fps運行的流暢動畫和轉場,60fps是一個什麼概念呢?我相信王者榮耀你們都知道,王者榮耀的遊戲時幀率評測報告是在40fps-50fps中,越高的fps會提供更快速的操控流暢度,體驗感更溫馨。
  • Dart能夠在沒有鎖的狀況下進行對象分配和垃圾回收。像JavaScript同樣,Dart避免了搶佔式調度和共享內存,大多數支持併發執行線程的計算機語言(Java、Kotlin、Swift)都使用搶佔式來切換線程,搶佔就會產生競態條件,競態條件頗有可能致使嚴重錯誤,如應用程序崩潰致使數據丟失等等,通常解決競態條件使用來保護,可是鎖自己可能致使卡頓,也有可能產生死鎖,而Dart針對這個問題,採起了isolate方法來解決,Dart中的線程稱爲isolate,不共享內存。那麼Dart是單線程的,意味根本不容許搶佔。單線程有助於開發者確保關鍵功能(動畫和轉場)完成而無需搶佔。 Flutter應用程序被編譯爲本地代碼,所以它們不須要再領域之間創建緩慢的橋樑,因此啓動速度快得多。
  • Dart使Flutter不須要單獨的聲明式佈局語言,如JSX或XML,或者單獨的可視化界面構建器。其實針對這點我以爲Android的XML界面構建器是很直觀閱讀的。
  • Dart容易學習,具備靜態和動態語言用戶都熟悉的特性。
  • 易於移植,Dart可編譯成ARM和X86代碼,這樣Dart移動應用程序能夠在iOS、Android和其餘地方運行。

那知道Flutter爲何選擇Dart以後,做爲開發Flutter項目來說,把Dar學好就頗有必要了,下面就從基本語法開始對Dart入門。java

2、Dart環境

1.本地環境

我這邊本地是用IntelliJ IDEA來編寫Dart項目,我這邊簡單說一下環境配置流程:web

  1. 下載Flutter sdk 以前下載過,在第一天配置Android Stdio環境有說。
  2. Dart插件的安裝,Windows下選擇File--Setting--Plugins搜索Dart下載,而Mac下選擇屏幕左上角IntelliJ IDEA--Perferences--Plugins,以下圖:

Dart下載
下載完重啓便可。

  1. 配置Dart SDK位置,Windows下選擇File--Setting--Languages & Frameworks--Dart--Dart SDK Path配置,而Mac下選擇屏幕左上角IntelliJ IDEA--Perferences--Languages & Frameworks,以下圖:

配置Dart sdk
4.在 File-- New-- Project-- Dart,選擇 Dart語言,以下圖:

選擇dart語言

  1. 建立項目名字,設置項目路徑,以下圖:

6. 最後點擊 Finish按鈕,會看到項目已經被建立了,以下圖:

Dart運行結果示意

2.網頁環境

除了上面經過IntelliJ IDEA本地配置環境外,若是以爲上面配置Dart環境太麻煩就走網頁環境,就是下面這個連接直接在線編寫Dart小語法練習項目dartpad.dartlang.org/,下面例子是循環輸出5個hello,示意圖以下:算法

線上學習Dart
看仍是很方便快捷的。

3、Dart一些概念

  • 在Dart中,一切都是對象,全部的對象都是繼承Object,也就是全部可以使用變量引用的都是對象,每一個對象都是一個了類的實例。在Dart中甚至數字、方法和null都是對象。
  • 沒有賦初值的變量都會有默認值null
  • 標識符能夠以字母或者_下劃線開頭,後面能夠是其餘字符和數字的組合。
  • Dart支持頂級方法,如main方法,同時還支持在類中定義函數(靜態函數和實例函數),還能夠在方法中定義方法,Dart支持頂層變量,也支持類變量或對象變量。
  • Dart沒有publicprotectedprivate關鍵字。若是某個變量如下劃線_開頭,表明這個變量是在庫中是私有的。
  • Dart中的類和接口都是統一的,類便是接口,你能夠繼承一個類,也能夠實現一個類,天然也包含了良好的面向對象和併發編程的支持。
  • final的值只能被設定一次。const是一個編譯時的常量,能夠經過const來建立常量值,var n = const[],這裏n仍是一個變量,只是被賦值了一個常量值,它仍是能夠符其餘值。實例變量能夠是final,但不能是const。
  • Dart是強類型語言,但能夠用var或者dynamic來聲明一個變量,Dart會自動推斷其數據類型,dynamic相似C#
  • 使用靜態類型能夠更清晰表面你的意圖,而且可讓靜態分析工具來分析你的代碼。
  • Dart在運行以前會先解析你的代碼。你能夠經過使用類型或者編譯時常量來幫助Dart去捕獲異常以及讓代碼運行的更高效。
  • Dart工具能夠指出兩種問題:警告和錯誤。警告只是說你的代碼可能有問題,可是並不會阻止你的代碼執行。錯誤能夠是編譯時錯誤也能夠是運行時錯誤。遇到編譯時錯時,代碼將沒法執行;運行時錯誤將會在運行代碼的時候致使一個異常。

4、Dart語法

1.關鍵字

下表是Dart語言的關鍵字express

const null class new this
as default final continue 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
false extends is switch yield
abstract factory library sync* yield*

2.一個最基本的Dart程序

//定義打印數字方法
printNumber(num Number){
  print('The number is $Number');
}

//執行程序入口
void main(){
  //定義初始化一個變量
  var number = 6.76;
  //調用打印數字方法
  printNumber(number);
}
複製代碼

上面是一個簡單基本的Dart程序,雖然簡單可是用到了Dart不少特性:編程

  • //這是註釋符號,還能夠用/*...*/這是不少語言的註釋符號
  • num這是數值型,Stringintbool是另外其餘幾種類型。注意:數值型num包含整形int和浮點型double
  • 6.76是一個數字常量,數字常量是編譯時常量。
  • print()打印內容的方法
  • "..."或者是'...'表示字符串常量
  • $variableName或者是${expression}是字符串插值:在字符串常量中引用變量或者表達式
  • var一種不指定類型聲明變量的方式
  • main()是Dart程序的入口方法,每一個程序都須要一個這樣得分方法

3.Variables變量

var name = 'knight'; 上面是聲明變量並賦值的示例,變量是一個引用,上面的名字爲name的變量引用一個內容爲"knight"的String對象。json

4.Default value(默認值)

沒有初始化的變量自動獲取一個默認值爲null。類型爲數字的變量若是沒有初始化那麼默認的值也是null,由於數字類型也是對象,上面直接上代碼:api

//定義打印數字方法
printNumber(num Number){
  print("The number is $Number");
}

//執行程序入口
void main(){
  //定義初始化一個變量
  var number;

  //調用打印數字方法
  printNumber(number);
}
複製代碼

上面打印的結果是The number is null數組

5.Optional types(可選的類型)

聲明變量的時候,能夠選擇加上具體類型,以下面:瀏覽器

//定義初始化一個變量
  double number = 6.666;
複製代碼

添加類型能夠更加清晰表達你的意圖。IDE編譯器等工具備可使用類型來更好的幫助,提供提示代碼補全,提早發現bug等功能。

6.Final and const

若是之後不打算修改一個變量,使用final或者const。一個final變量只能賦值一次;一個const變量是編譯時常量。注意:const變量同時也是final變量,實例變量能夠爲final但不能是const。直接上例子:

//定義初始化一個變量
  final double number = 6.666;
  number = 6.667;
  //調用打印數字方法
  printNumber(number);
複製代碼

上面例子用final修飾number並賦值,但number = 6.67的時候,想從新給number再賦值的時候,編譯錯報錯:number,a final variable,can only be set once.,意思和上面所說的同樣就是final變量只能賦值一次!下面改成定義爲const來修飾number:

//定義初始化一個變量
  const double number = 6.666;
  number = 6.667;
  //調用打印數字方法
  printNumber(number);
複製代碼

一樣number = 6.667編譯器會報錯Constant variables can not be assigned a value意思是常量值不能賦值,上面也說了,由於const變量同時也是final變量。若是const變量在類中,請定義爲static const。能夠直接定義const和旗初始值,也能夠定義一個const變量使用其餘const變量的值來初始化其值,以下面:

//定義初始化一個變量
  const double number = 6.66;
  const double number1 = 2 * number;
複製代碼

上面例子的number1就是用了number來將本身初始化值,const關鍵字不只僅只用來定義常量。有能夠用來建立不變的值,還能定義構造函數爲const類型 ,這中類型的構造函數建立的對象是不可改變的,任何變量均可以有一個不變的值。

7.Built-in types(內置的類型)

在Dart有幾種內置的數據類型:數值型-Number、布爾型-boolean、鍵值對-Map、字符串-String、列表-List、其餘類型-Runes、Symbols

7.1數值型-Number

Dart中提供了兩種類型:

數值類型

//執行程序入口
void main(){


  //整形,其取值一般位於-2的53次方到2的53之間。
  num x = 777;
  //浮點數 64位
  x = 777.7;

  int y = 777;
  y = 777.7;       //這一行編譯器會報錯,由於將int型的數據轉爲double型

  double n = 77,7;
  d = 77;          //這個地方會報錯,由於將double型的數據轉爲int型

  int x1 = 7;
  int x2 = 77;
  int x3 = 777;
  int x4 = 7777;

  print('${x1.bitLength}'); //佔了3個bit 至關於00000000 00000111
  print('${x2.bitLength}'); //佔了7個bit 至關於00000000 01001101
  print('${x3.bitLength}'); //佔了10個bit 至關於00000011 00001001
  print('${x4.bitLength}'); //佔了13個bit 至關於00011110 01100001


}
複製代碼

上面例子能夠看到三個點:

  • 使用num聲明的變量,能夠隨意的轉換類型,若是使用int或者double明確的聲明,那就不能轉換了
  • 判斷一個int值須要多少位時,可使用bitLength

8.數值型的操做

運算符:+、-、*、/、~/、% 經常使用屬性:isNaN、isEven、isOdd 經常使用方法:abs()、round()、floor()、ceil()、toInt()、toDouble()

//執行程序入口
void main(){


  int i =7;
  double d = 10.1;

  print(i / d);               //0.6930693069306931
  print(i ~/ d);              //0 這個操做是取整 就是得出商

  print(i.isOdd);             // 判斷是奇數
  print(i.isEven);            // 判斷是偶數


  //String -> int
  var x1 = int.parse("7");
  print(x1 == 7);              //輸出true

  //Sting -> double
  var x2 = double.parse("7.7");
  print(x2 == 7.7);             //輸出true

  //int -> String
  var x3 = 7.toString();
  print(x3 == '7');             //輸出true

  //double -> String
  var x4 = 7.1234.toStringAsFixed(2);
  print(x4 == '7.12');          //輸出true

  //求絕對值
  var x5 = (-7).abs();
  print(x5 == 7);

  //四捨五入1
  var x6 = (7.7).round();
  print(x6);                   //輸出8

  //四捨五入2
  var x7 = (7.3).round();
  print(x7);                   //輸出7

  //求小於它的最大整數
  var x8 = (7.7).floor();
  print(x8);                   //輸出7

  //求大於它的最小整數
  var x9 = (7.7).ceil();
  print(x9);                   //輸出8

  double num1 = 7.77;
  print(num1);                //結果是7.77
  double num2 = 7;
  print(num2);                //結果是7.0
  int num3 = 7;
  print(num3.toDouble());     //int 轉 double 結果是7.0
  double num4 = 7.77;
  print(num4.toInt());        //double 轉 int 結果是7
  
}
複製代碼

上面列出了一些平時遇到最多的操做,如求餘,求整,類型轉換等。

9.Strings(字符串)

Dart字符串是UTF-16編碼的字符序列,可使用單引號或者雙引號來建立字符串:

//執行程序入口
void main(){

  String m_str1 = '單引號字符串';
  String m_str2 = "雙引號字符串";

  print(m_str1);        //輸出:單引號字符串
  print(m_str2);        //輸出:雙引號字符串

}
複製代碼

String中單、雙引號互相嵌套狀況

//執行程序入口
void main(){

  String m_str1 = '單引號中的"雙引號"字符串';
  String m_str2 = "雙引號中的'單引號'字符串";

  print(m_str1);        //輸出:單引號中的"雙引號"字符串
  print(m_str2);        //輸出:雙引號中的'單引號'字符串

  //單引號裏面有單引號,必須在前面加反斜槓
  String m_str3 = '單引號中的\'單引號\'';
  String m_str4 = "雙引號裏面有雙引號,\"雙引號\"";
  print(m_str3);        //輸出:單引號中的'單引號'
  print(m_str4);        //輸出:雙引號裏面有雙引號,"雙引號"

}
複製代碼

單引號嵌套單引號之間不容許出現空串(不是空格),雙引號嵌套雙引號之間不容許出現空串:

//String m_str5 = '單引號''''單引號'; //報錯
  String m_str6 = '單引號'' ''單引號';
  print(m_str6);        //輸出: 單引號 單引號

  String m_str7 = '單引號''*''單引號';
  print(m_str7);        //輸出: 單引號*單引號

  //String m_str8 = "雙引號""""雙引號"; //報錯

  String m_str9 = "雙引號"" ""雙引號";
  print(m_str9);        //輸出: 雙引號 雙引號

  String m_str10 = "雙引號""*""雙引號";
  print(m_str10);       //輸出: 雙引號*雙引號
複製代碼

單雙引號混合嵌套空串是能夠的,以下:

String m_str11 = '單引號""""單引號';
  print(m_str11);       //輸出: 單引號""""單引號

  String m_str12 = '單引號"" ""單引號';
  print(m_str12);       //輸出: 單引號"" ""單引號

  String m_str13 = '單引號""*"""單引號';
  print(m_str13);       //輸出: 單引號""*"""單引號

  String m_str14 = "雙引號''''雙引號";
  print(m_str14);       //輸出: 雙引號''''雙引號

  String m_str15 = "雙引號'' ''雙引號";
  print(m_str15);       //輸出: 雙引號'' ''雙引號

  String m_str16 = "雙引號''*''雙引號";
  print(m_str16);       //輸出: 雙引號''*''雙引號
複製代碼

字符串拼接方式,以下:

//使用空格拼接,多個空格也是能夠地
  String m_str1 = '單引號字符串' '拼接'     '---';
  print(m_str1);       //輸出:單引號字符串拼接---

  //使用換行符和空格
  String m_str2 = '單引號字符串'
    '換行''加空格' '';
  print(m_str2);       //輸出: 單引號字符串換行加空格

  //單雙引號 空格拼接
  String m_str3 = "單雙引號字符串加空格" '拼接'      "----";
  print(m_str3);      //輸出: 雙引號字符串加空格拼接----

  //單雙引號 換行 空格
  String m_str4 = "單雙引號字符串"
    '換行' '加空格' '***';
  print(m_str4);      //輸出: 單雙引號字符串換行加空格***

  //使用三個單引號建立多行字符串
  String m_str5 = '''
     三個單引號+
     拼接
     ''';
  print(m_str5);      /*輸出    三個單引號+
                                拼接
                                */

  //使用三個雙引號建立多行字符串
  String m_str6 = """
    三個雙引號+
    拼接
    """;
  print(m_str6);      /*輸出    三個雙引號+
                                拼接
                                */

  String m_str7 = "正常拼接"+",用加號了來拼接";
  print(m_str7);      //輸出: 正常拼接,用加號了來拼接
複製代碼

經過提供一個r前綴能夠建立"原始raw"字符串,在字符串加字符,或者在\前面再加一個\,能夠避免\的轉義做用,以下:

String m_str1 = r"sdsdsds";
  print(m_str1);  //輸出 sdsdsds

  print("換行:\n"); //輸出:換行

  print(r"換行:\n"); //輸出:換行:\n

  print("換行:\\n"); //輸出:換行:\n
複製代碼

${表達式的使用},相似JS中ES6的表達式使用,使用$能夠獲取字符串的內容,用${表達式}能夠將表達式的值放入字符串中,使用${表達式}也可使用字符串拼接。下面也是直接上例子:

bool flag = true;
  String m_str1 = "字符串";
  print("看看這個值:${m_str1} ""看看這個值flag:${flag}"); //輸出:字符串:字符串 看看這個值flag:true

  //使用$+字符串
  String name = "knight";
  print("$name" + "CTO");     //輸出:knightCTO;

  //使用字符串拼接,運用了String類中的toUpperCase函數,把字母變成大寫 
  String m_str = "Android";
  assert('${m_str.toUpperCase()} is very good' ==       
        'ANDROID is very good');
  
複製代碼

==操做符判斷兩個對象的內容是否同樣,若是了;兩個字符串包含同樣的字符編碼序列,則他們是相等的。在生產模式 assert() 語句被忽略了。在檢查模式assert(condition) 會執行,若是條件不爲 true 則會拋出一個異常。

10.Boolean(布爾值)

Dart中以bool表明布爾值,只有兩個對象是布爾類型的,那就是truefalse所建立的對象,這兩個對象都是編譯時常量。當Dart須要一個布爾值,只有true對象才被認爲是true,全部其餘值都是false。看下下面的例子:

String name ="knight";
  //報錯 由於name不是bool類型
  if(name){
    print(name);

  }
複製代碼

上面代碼會拋出異常,提示name不是布爾值,Dart使用的是先式的檢查值,以下圖:

// 檢查是否爲空字符串
  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);
複製代碼

assert是語言內置的斷言的函數,僅在檢查模式有效,在開發過程當中,除非條件爲真,不然會引起異常。(斷言失敗則程序馬上終止)

11.Lists列表

array是編程語言中最多見的集合類型,我相信身爲開發者,都用過。在Dart中數組就是List對象,因此通常稱爲lists,下面直接上Dart list的示例:

//建立一個int類型的list 並賦值爲0,1,2,3,4
  List list =  [0,1,2,3,4];

  //使用構建的方式建立list
  List list1 = new List();

  //建立一個常量的List,不能夠改變的List
  List list2 = const[0,1,2,3];

  //增長泛型
  List list3 = new List<String>();

  //建立固定的長度的數組列表,不能移除或者增長
  List list4 = new List(5);

  //建立包含全部如下元素的可改變的長度列表
  List list5 = new List.from([0,1,2,3]);

  //建立在固定範圍內改變長度的列表
  List list6 = new List()..length = 10;

  //建立包含全部元素的固定長度列表
  List list7 = new List.unmodifiable([0,1,2]);

  //用生成器給全部元素賦初始值
  List list8 = new List<int>.generate(5, (int i){
    return i + i;

  });

複製代碼

List經常使用的一些api:

//在列表中存放不一樣類型的對象
  List list = [1,2,3,false,"Kinght"];
  print(list);          //輸出:[1, 2, 3, false, Kinght]

  //在列表中添加元素
  list.add(7);
  print(list);          //輸出:[1, 2, 3, false, Kinght, 7]

  //修改列表下標爲1的值
  list[1] = "paul";
  print(list);          //輸出:[1, paul, 3, false, Kinght, 7]

  //移除列表的指定值得的元素
  list.remove("paul");
  print(list);          //輸出:[1, 3, false, Kinght, 7]

  //移除列表指定下標下的元素
  list.removeAt(0);
  print(list);          //輸出:[3, false, Kinght, 7]

  //獲取列表的長度
  print(list.length);   //輸出:4

  //向列表中的指定位置添加元素 在第0的位置上插入Android
  list.insert(0, "Android");
  print(list);          //輸出:[Android, 3, false, Kinght, 7]

  //判斷數組中是否有某元素
  print(list.indexOf("Android")); //這裏存在,輸出對應的下標,若是沒有則輸出-1

  //排序
  List list1 = [3,1,2,6,7];
  // 根據語法提示: List.sort([(int, int) → int compare]) → void
  list1.sort((a,b) => a.compareTo(b));
  print(list1);           //輸出:[1, 2, 3, 6, 7]
複製代碼

簡單總結:

  1. 下標索引從0開始。
  2. 能夠直接打印List的元素,List也是一個對象。

12.Maps

一般來說,Map是一個鍵值對相關的對象,鍵和值能夠是任何類型的對象。每一個鍵只出現一次,而一個值則能夠出現屢次。上面直接上Map集合的建立方式:

//1.經過構建器來建立Map
  Map map1 = new Map();
  //添加值 賦值
  map1["one"] = 'Android';
  map1["two"] = 'IOS';
  map1["three"] = 'Flutter';
  print(map1);              //輸出:{one: Android, two: IOS, three: Flutter}

  //2.經過複製的形式
  Map map2 = Map.of(map1);
  print(map2);              //輸出:{one: Android, two: IOS, three: Flutter}

  //3.跟上面形式同樣 Object.fromEntries() 函數傳入一個鍵值對的列表,並返回一個帶有這些鍵值對的新對象。
  // 這個迭代參數應該是一個可以實現@iterator方法的的對象,返回一個迭代器對象。它
  // 生成一個具備兩個元素的相似數組的對象,第一個元素是將用做屬性鍵的值,第二個元素是與該屬性鍵關聯的值。
  Map map3 = Map.fromEntries(map1.entries);
  print(map3);

  //4.直接聲明,直接賦值key爲String類型的map
  Map map4 = {'one':'Android',
    'two':'IOS',
    'three':'Flutter'};
  print(map4);              //輸出:{one: Android, two: IOS, three: Flutter}

  //5.建立一個空的Map
  Map map5 = Map.identity();
  print(map5);              //輸出:{}


  //6.建立不可變的Map
  Map map6 = const {'one':'Android','two':'IOS','three':'flutter'};
  print(map6);              //輸出:{one: Android, two: IOS, three: flutter}

  //7.在目標的map6建立(複製)新的不可修改map7
  Map map7 = Map.unmodifiable(map6);
  print(map7);              //輸出:{one: Android, two: IOS, three: flutter}

  //8.建立key爲int值得map
  Map map8 = {1:'Android',
    2:'IOS',
    3:'Flutter'};
  print(map8);              //輸出:{1: Android, 2: IOS, 3: Flutter}

  //9.根據list所提供的key value來建立map
  List<String> keys = ['one','two'];
  List<String> values = ['Android','IOS'];
  Map map9 = Map.fromIterables(keys, values);
  print(map9);               //輸出:{one: Android, two: IOS}
  
   //經過構建器來建立Map
   Map map10 = new Map();
   //添加值 賦值 賦值不一樣類型的Map
   map10["one"] = 'Android';
   map10["two"] = 'IOS';
   map10["three"] = 'Flutter';
   map10[4] = 'RN';
   print(map10);              //輸出:{one: Android, two: IOS, three: Flutter, 4: RN}
複製代碼

Map經常使用的一些api:

//建立Map key是int類型,value是String類型
   var  map1 = new Map<int,String>();

   //對Map第一個位置賦值,中括號是key
   map1[0] = 'Android';
   //對Map第二個位置賦值
   map1[1] = 'IOS';
   //對Map第三個值賦值
   map1[2] = 'flutter';
   //對Map賦空值
   map1[3] = null;
   //由於Map中的鍵值是惟一的,當第二次輸入的key若是存在,Value會覆蓋以前
   map1[2] = 'RN';
   print(map1);                //{0: Android, 1: IOS, 2: RN, 3: null}

   //獲取Map的長度
   print(map1.length);         //輸出:4

   //判斷Map是否爲空
   print(map1.isNotEmpty);     //輸出結果:true

   //判斷Map是否不爲空
   print(map1.isEmpty);        //輸出結果:false

   //檢索Map是否含有某個Key
   print(map1.containsKey(1)); //輸出:true

   //檢索Map是否包含某個Value
   print(map1.containsValue('Android'));  //輸出:true

   //刪除某個鍵值對
   map1.remove(0);
   print(map1);                //輸出:{1: IOS, 2: RN, 3: null}

   //獲取全部的key
   print(map1.keys);           //輸出:(1, 2, 3)

   //獲取全部的values
   print(map1.values);         //輸出:(IOS, RN, null)

   //循環打印
   /* key:1, value:IOS key:2, value:RN key:3, value:null */
     map1.forEach((key,value) {
     print("key:${key}, value:${value}");
   });

複製代碼

簡單總結:

  1. 當Map的Key沒有指定類型時,Key類型不一致也不會報錯。
  2. Map裏面的key不能相同。可是value能夠相同,value能夠爲空字符串或者爲null。
  3. 建立Map有兩種方式:經過構造器(new)和直接賦值。

13.Runes

在Dart中,runes表明字符串的UTF-32 code points。Unicode爲每個字符、標點符號、表情符號等都定義了一個惟一的數值。什麼意思呢?也就是在書寫系統中,每個字母,數字都是有惟一的數值。因爲在Dart字符串是UTF-16 code units字符序列,因此在字符串表達32-bit Unicode值就須要新的語法,一般用\uXXXX的方式表示Unicode code point,這裏的XXXX是4個16進制的數。如:心形符號💗是\u2665。對於非4個數值的狀況,把編號放到大括號便可。例如,笑臉(😁)是\u{1f600}String類有一些屬性能夠提取rune信息。codeUnitAtcodeUnit屬性返回16-bit code units。使用runes屬性來獲取字符串的runes信息。下面示例演示了runes16-bit code units、和32-bit code points之間的關係:

//執行程序入口
void main(){

  var clapp = '\u{1f44f}';
  print(clapp);                  //輸出:👏

  print(clapp.codeUnits);        //輸出: [55357, 56399]
  print(clapp.runes.toList());   //輸出: [128079]

  //使用String. fromCharCodes方法顯示字符圖形
  Runes input = new Runes(
      '\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}');
  print(new String.fromCharCodes(input));   //輸出:♥ 😅 😎 👻 🖖 👍

}
複製代碼

14.Symbols

一個Symbolobject表明Dart程序聲明的操做符或者標識符。你也許歷來不會用到Symbol,可是該功能對於經過名字來引用標識符的狀況是很是有價值的,特別是混淆後的代碼,標識符的名字被混淆了,可是Symbol的名字不會改變,下面列下例子:

//使用 Symbol 字面量來獲取標識符的 symbol 對象,也就是在標識符 前面添加一個 # 符號:
  //#radix
  //#bar

  print(#n == new Symbol('n'));  //輸出:true

  var name = 'knight';
  Symbol symbol = #name;
  print(symbol);                //輸出:Symbol("name")
  print(#name);                 //輸出:Symbol("name")
複製代碼

15.function方法

Dart是一個真正的面嚮對象語言,方法也是對象而且具備一種類型,Function。這意味着,方法能夠賦值給變量,也能夠當作其餘方法的參數。也能夠把Dart類的實例當作方法來調用,下面是定義方法的示例:

//定義一個方法 判斷列表對應下標是否爲null
bool isNoble(int atomicNumber) {
  return list[atomicNumber] != null;
}
複製代碼

雖然在Effective Dart推薦在公開的APIs上使用靜態類型,固然也能夠選擇忽略類型定義:

//定義一個方法 判斷列表對應下標是否爲null 忽略類型定義
isNoble(int atomicNumber) {
  return list[atomicNumber] != null;
}
複製代碼

對於只有一個表達式的方法,能夠選擇使用縮寫語法來定義:

//定義一個方法 判斷列表對應下標是否爲null 縮寫寫法
bool isNoble(int atomicNumber) => list[atomicNumber] != null;
複製代碼

=> expr是語法{return expr;}形式的縮寫。=>形式也稱爲胖箭頭語法。注意:在箭頭(=>)和冒號(;)之間只能使用一個表達式,不能使用語句。方法能夠有兩種類型的參數:必需和可選的。必需的參數在參數列表前面,後面是可選參數。

15.1.Optional parameters(可選參數)

什麼是可選參數麼?定義一個函數時,形參能夠定義爲可選參數,當調用這個方法時,能夠不傳這個可選參數。可選參數能夠是命名參數或者基於位置的參數,可是這兩種參數不能同時當作可選參數

15.1.1.Optional named parameters(可選命名參數)

調用方法的時候,可使用這種形式paramName:value來指定命名參數。例如:

enableFlags(bold: true, hidden: false);
複製代碼

定義方法時,使用{param1,param2,...}的形式來指定可選命名參數:

enableFlags({bool bold, bool hidden}) {
  // ...
}
複製代碼
15.1.2.Optional positional parameters(可選位置參數)

把一些方法的參數放到[]中就變成可選位置參數了,例子以下:

//定義一個方法 [String device]是可選位置參數 也就是調用這個方法能夠不傳這個參數
String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

//不使用可選參數調用方法
assert(say('Bob', 'Howdy') == 'Bob says Howdy');

//使用可選參數調用方法
assert(say('Bob', 'Howdy', 'smoke signal') ==
'Bob says Howdy with a smoke signal');
複製代碼
15.1.3.Default parameter value(默認參數值)

在定義方法的時候,可使用=來定義可選參數的默認值。默認值只能是編譯時常量。若是沒有提供默認值,則默認值爲null。下面是設置可選參數默認值的示例:

//定義一個返回類型爲空的方法 方法中hidden默認值爲false
void enableFlags({bool bold = false,bool hidden = false}){

}

//調用方法 沒有傳hidden的值,那默認值就是false
enableFlags(bold:true);
複製代碼

下面的示例顯示瞭如何設置位置參數的默認值:

//定義一個方法 這個方法位置可選位置參數device的默認參數是carrier pigon
//也就是當調用這個方法,沒有傳這個參數時,這個參數會取默認值
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;
}

//調用上面的方法
assert(say('Bob', 'Howdy') ==
'Bob says Howdy with a carrier pigeon');
複製代碼

還可使用list或者map做爲默認值,下面的示例定義一個方法doStuff(),並分別爲listgifts參數指定默認值:

//執行程序入口
void main(){

  //調用判斷對應小標的值是否爲空
 // print(isNoble(1)); //輸出:true


  doStuff();    //輸出:list: [1, 2, 3]
                //輸出:gifts: {first: paper, second: cotton, third: leather}


}

//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');
}
複製代碼

15.2.The main() function(入口函數)

每一個應用都須要有個頂級的main()入口方法才能執行。main()方法的返回值爲void而且有個可選的List<String>參數,下面是一個web應用的main()方法:

void main() {
  querySelector("#sample_text_id")
    ..text = "Click me!"
    ..onClick.listen(reverseText);
}
複製代碼

在上面代碼中..y語法是級聯調用。使用級聯調用,能夠在一個對象上執行多個操做。下面是一個命令行應用的main()方法,而且使用方法參數做爲輸入參數:

// Run the app like this: dart args.dart 1 test
void main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');
}
複製代碼

15.3.Functions as first-class objects(一等方法對象)

能夠把方法當作參數調用另一個方法。如:

printElement(element) {
  print(element);
}

var list = [1, 2, 3];

// 遍歷集合
list.forEach(printElement);
複製代碼

方法也能夠賦值給一個變量:

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

15.4.Anonymous functions(匿名方法)

大部分方法都帶有名字,例如main()或者printElement()。也能夠建立沒有名字的方法,稱爲匿名方法,有時候也被稱爲lambda或者clourse閉包。能夠把匿名方法賦值給一個變量,而後可使用這個方法,好比添加集合或者從集合中刪除。匿名函數和命名函數相似,在括號之間能夠定義一些參數·,參數使用逗號分割,也能夠是可選參數。後面大括號中的代碼爲函數體:

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

下面的代碼定義了一個參數爲i的匿名函數。list中的每一個元素都會調用這個函數來打印出來,同時來計算了每一個元素在list中的索引位置。

var list = ['apples', 'oranges', 'grapes', 'bananas', 'plums'];
list.forEach((i) {
  print(list.indexOf(i).toString() + ': ' + i);
});
//輸出:

0: apples
1: oranges
2: grapes
3: bananas
4: plums
複製代碼

上面說到若是方法只包含一個語句,可使用胖箭頭語法縮寫,把上面的代碼改爲下面一樣的意思:

list.forEach((i) => print(list.indexOf(i).toString() + ': ' + i));
複製代碼

15.5.靜態做用域

Dart是靜態做用域語言,變量的做用域在寫代碼的時候就缺的過了。基本上大括號裏面定義的變量就只能在大括號裏面訪問,和java做用域相似。

var topLevel = true;

main() {
  var insideMain = true;

  myFunction() {
    var insideFunction = true;

    nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}
複製代碼

注意nestedFunction能夠訪問全部的變量,包含頂級變量。

15.6.Lexical closures(詞法閉包)

一個閉包是一個方法對象,無論該對象在何處被調用,該對象均可以訪問其做用域內的變量,方法能夠封閉定義到其做用域內的變量。方法能夠封閉定義到其做用域內的變量。下面示例中,makeAdder()捕獲到了變量addBy。無論在哪裏執行makeAdder()所返回的函數,均可以使用addBy參數:

Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

main() {
  // Create a function that adds 2.
  var add2 = makeAdder(2);

  // Create a function that adds 4.
  var add4 = makeAdder(4);

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

15.7.Testing functions for equality(測試函數是否相等)

下面是測試頂級方法、靜態函數和實例函數相等的示例:

foo() {}               // 頂級方法

class A {
  static void bar() {} // 靜態方法
  void baz() {}        // 實例方法
}

void main() {
  var x;

  // 比較頂級函數
  x = foo;
  print(foo == x);    //輸出:true

  // 比較靜態方法
  x = A.bar;
  print(A.bar == x);  //輸出:true

  // 比較實例方法
  var v = new A(); // 實例1
  var w = new A(); // 實例2
  var y = w;
  x = w.baz;

  //這些閉包引用相同的實例
  //全部相等
  print(y.baz == x);   //輸出:true

  // 閉包引用不一樣的實例,因此不相等`
  print(v.baz != w.baz);   //輸出:true
}
複製代碼

15.8.Return values (返回值)

全部的函數都返回一個值。若是沒有指定返回值,則默認把語句return null;做爲函數的最後☝一個語句執行。

16.Operators 操做符

操做符
上面列了操做符和一些例子,在操做符表格所列的操做符都是按照優先級順序從左到右,從上倒下的方式來排列,上面和左邊的操做符優先級要高於下面和右邊的。例如 %操做符優先高於 ==,而等號高於 &&,下面的代碼結果是同樣的:

void main() {
   var n = 2;
   var i = 2;
   var d = 7;
   if((n % i == 0) && (d % i == 0)){
     print('符合條件');
   }else{
     print('不符合條件');       //進入這裏
   }

   if(n % i == 0 && d % i == 0){
     print('符合條件');
   }else{
     print('不符合條件');       //進入這裏
   }
}
複製代碼

16.1.算術操做符

算術操做符

16.2.相等相關的操做符

相等相關的操做符

16.3.類型斷定操做符

類型斷定操做符
只有 obj實現了 T的接口, obj is T纔是true。例如 obj is Object老是true。使用 as操做符吧對象轉換爲特定的類型。通常狀況下,能夠把它當作 is斷定類型而後調用所斷定對象的函數縮寫形式,以下面的示例:

if (emp is Person) { // Type check
  emp.firstName = 'Bob';
}
複製代碼

使用as操做符能夠簡化上面的代碼:

(emp as Person).firstName = 'Bob';
複製代碼

注意:上面的這兩個代碼效果是有區別的。若是emp是null或者不是person類型,則第一個示例使用is則不會執行條件裏面的代碼,而第二個狀況使用as則會拋出一個異常。

16.4.賦值操做符

使用=操做符賦值。可是還有一個??=操做符來指定值爲null的變量值。

a = value;   // 給 a 變量賦值
b ??= value; // 若是 b 是 null,則賦值給 b;
             // 若是不是 null,則 b 的值保持不變
複製代碼

還有複合賦值符+=等能夠賦值:

複合賦值操做符
下面的代碼使用賦值操做符符合複合賦值操做符:

var a = 2;           // Assign using =
a *= 3;              // Assign and multiply: a = a * 3
assert(a == 6);
複製代碼

下面是複合賦值操做符工做原理解釋:

複合賦值操做符解釋

16.5.邏輯操做符

邏輯操做符

16.6.位和移位操做符

在Dart中能夠單獨操做數字的某一位,下面操做符一樣應用於整數:

位與移位操做符

16.7.條件表達式

Dart有兩個特殊的操做符能夠用來替代if-else語句: condition ? expr1 : expr2 若是condition是true,執行expr1(並返回執行的結果);不然執行expr2並返回結果。 expr1 ?? expr2若是expr1是non-null,返回其值;不然執行expr2並返回其結果。若是是基於布爾表達式的值來賦值,考慮使用?:

var finalStatus = m.isFinal ? 'final' : 'not final';
複製代碼

若是是基於布爾表達式是測試值是否爲null,考慮使用??

String toString() => msg ?? super.toString();

//上面代碼能夠用下面代碼來表示,意思效果是同樣的,代碼易懂可是不簡潔
String toString() => msg == null ? super.toString() : msg;

String toString() {
  if (msg == null) {
    return super.toString();
  } else {
    return msg;
  }
}
複製代碼

16.8.級聯操做符

級聯操做符(..)能夠在同一對象上連續調用多個函數以及訪問成員變量。使用級聯操做符能夠避免建立臨時變量,而且寫出來的代碼看起來更加流暢,如:

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

第一個方法quertSelector返回一個selector對象。後面的級聯操做符都是調用這個對象的成員,並忽略每一個操做所返回的值。上面代碼和下面的代碼功能同樣:

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

級聯調用也能夠嵌套:

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

在方法上使用級聯操做符須要很是當心,例以下面代碼是不合法的:

var sb = new StringBuffer();
sb.write('foo')..write('bar');
複製代碼

sb.write函數返回一個void,沒法再void上使用級聯操做符。注意級聯語法不是操做符,只是語法!

17.Control flow statements(流程控制語句)

Dart中的控制流程語句和java語言很像,能夠說是差很少的:

17.1.if else

if (isRaining()) {//條件語句
  you.bringRainCoat();//內容體
} else if (isSnowing()) {//條件語句
  you.wearJacket();//內容體
} else {
  car.putTopDown();//內容體
}
複製代碼

17.2.for循環

可使用標準的for循環:

var message = new StringBuffer("Dart is fun");
for (var i = 0; i < 5; i++) {
  message.write('!');
}

//使用foreach循環 list 和 Set均可以用這種方式
List numbers = [1,2,3,4,5,6,7,8,910];
numbers.foreach((number)=> print(number));

//使用for in循環,通常List和Set都是用這種方式
List numbers = [1,2,3,4,5,6,7,8,910];
for(var number in numbers){
     print(number);
}
複製代碼

17.3.While and do-while

while循環在執行循環以前先判斷條件是否知足:

//判斷條件
while (!isDone()) {
  //內容
  doSomething();
}

//例子
var i = 0;
while(i > 5){
    i++;
}
複製代碼

do-while循環是先執行循環代碼再判斷條件:

do {
  printLine();//內容體
} while (!atEndOfPage());//條件判斷
//例子
var i = 0;
do{
    i++;
}while(i > 7);
複製代碼

17.4.Break and continue

使用break來終止循環:

while (true) {
  if (shutDownRequested()) break;
  processIncomingRequests();
}
複製代碼

使用continue來開始下一次循環:

for (int i = 0; i < candidates.length; i++) {
  var candidate = candidates[i];
  if (candidate.yearsExperience < 5) {
    continue;
  }
  candidate.interview();
}
複製代碼

上面代碼在實現Iterable接口對象(List和Map)可使用下面寫法:

candidates.where((c) => c.yearsExperience >= 5)
          .forEach((c) => c.interview());
複製代碼

17.5.Switch and case

Dart中的Switch語句使用==比較integer、String、或者編譯時常量。比較的兌現必須都是同一個類的實例(而且不是其之類),calss必須沒有覆寫==操做符,每一個非空的case語句都必須有一個break語句。另外還能夠經過continuethrowreturn來介紹非空case語句。當沒有case語句匹配時,可使用default語句來匹配這種默認狀況。

var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClosed();
    break;
  case 'PENDING':
    executePending();
    break;
  case 'APPROVED':
    executeApproved();
    break;
  case 'DENIED':
    executeDenied();
    break;
  case 'OPEN':
    executeOpen();
    break;
  default:
    executeUnknown();
}
複製代碼

下面的示例代碼再case省略了break語句,編譯的時候會出現一個錯誤:

void main() {

  var number = 1;
  var i = 0;
  switch(number){
    case 1;
    i++;
    case 2:
      i--;
      break;

  }

}
複製代碼

在Dart中的空case語句中能夠不要break語句:

void main() {

  var number = 1;
  var i = 0;
  switch(number){
    case 1;
    i++;
    case 2:
      i--;
      break;

  }

}
複製代碼

若是須要實現這種繼續到下一個case語句中繼續執行,則可使用continue語句跳轉對應的標籤處繼續執行:

void main() {

  var number = 1;
  var i = 0;
  switch(number){
    case 1;
    i++;
    case 2:
      i--;
      break;

  }

}
複製代碼

每一個case語句能夠有局部變量,局部變量只有在這個語句內可見。

17.6.Assert(斷言)

若是條件表達式結果不知足須要,則可使用assert語句倆打斷代碼的執行。示例以下:

// 確保變量是非空值 
assert(text != null);
// 確保值是小於100
assert(number < 100);
// 確保這是一個 https 地址
assert(urlString.startsWith('https'));
複製代碼

assert上面也說過了,assert方法參數能夠爲任何布爾值的表達式或者方法。若是返回的值爲true,斷言執行經過,執行結束。若是返回值爲false,斷言執行失敗,會拋出異常,斷言只有在檢查模式運行有效,若是生產模式運行,則斷言不會執行。

18.Exceptions(異常)

代碼中能夠出現異常和捕獲異常。異常表示一些未知的錯誤狀況。若是異常沒有捕獲,則異常會拋出,致使拋出異常的代碼終止執行。和java不一樣的是,全部的Dart異常是非檢查異常。方法不必定聲明·來看他們所拋出的異常,而且你不要求捕獲異常,Dart提供了ExceptionError類型,以及一些子類型。還能夠本身定義異常類型。可是,Dart代碼能夠拋出任何非null對象爲異常,不只僅是實現了ExceptionError對象。

18.1.Throw

下面是拋出或者扔出一個異常的示例:

thow new FormatException('Expected at least 1 section');
複製代碼

還能夠拋出任意對象:

throw 'Out of llamas!';
複製代碼

因爲拋出異常時一個表達式,因此能夠在=>語句中使用,也能夠在其餘能使用表達式的地方拋出異常。

distanceTo(Point other) =>
    throw new UnimplementedError();
複製代碼

18.2.Catch

捕獲異常能夠避免異常繼續傳遞(從新拋出rethrow)異常除外。捕獲異常給你一個處理該異常的機會:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}
複製代碼

對於能夠拋出多種類型異常的代碼,你能夠指定多個捕獲語句。每一個語句分別對應一個異常類型,若是捕獲語句沒有指定異常類型,則該能夠捕獲任何異常類型:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');
}
複製代碼

如以前代碼所示,可使用on或者catch來聲明捕獲語句,也能夠同時使用。使用on來指定異常類型,使用catch來捕獲異常對象。函數catch()能夠帶有一個或者兩個參數,第一個參數爲拋出的異常對象,第二個爲堆棧信息。

...
} on Exception catch (e) {
  print('Exception details:\n $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}
複製代碼

使用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}.');
  }
}
複製代碼

18.3.Finally

不管是否拋出異常,要確保某些代碼都要執行,可使用finally語句來實現。若是沒有catch語句來捕獲異常,則在執行完finally語句後,異常被拋出了:

try {
  breedMoreLlamas();
} finally {
  // 即便拋出異常也會執行
  cleanLlamaStalls();
}
複製代碼

定義的finally語句在任何匹配的catch語句以後執行:

try {
  breedMoreLlamas();
} catch(e) {
  print('Error: $e');  // 優先處理異常
} finally {
  cleanLlamaStalls();  // 而後再執行
}
複製代碼

19.Classes

Dart是一個面向對象編程語言,同時支持基於mixin的繼承機制。每一個對象都是一個類的實例,全部的類都繼承於object。基於Mixin的繼承意味着每一個類(Object除外)都只有一個超類,一個類的代碼能夠在其餘多個類繼承中重複使用·。使用new關鍵字和構造函數來建立新的對象。構造函數名字能夠爲ClassName或者ClassName.identifier。例如:

var jsonData = JSON.decode('{"x":1, "y":2}');

// 建立Point類型的對象
var p1 = new Point(2, 2);

// 根據json來建立Point對象
var p2 = new Point.fromJson(jsonData);
複製代碼

對象的成員包括方法和數據(函數和示例變量)。當你調用一個函數的時候,你是在一個對象上調用:函數須要訪問對象的方法和數據,使用(.)來引用對象的變量或者方法:

var p = new Point(2, 2);

// 對實例對象的變量y賦值
p.y = 3;

// 從成員變量獲得值
assert(p.y == 3);

// 從p複製實例對象
num distance = p.distanceTo(new Point(4, 4));
複製代碼

使用?.來替代,能夠避免當左邊對象爲null時候拋出異常:

//若是P不是空對象,那麼對其變量y賦值
p?.y = 4;
複製代碼

有些類提供了常量構造函數。使用常量構造函數能夠建立編譯時常量,要使用常量構造函數只須要用const替代new便可:

var p = const ImmutablePoint(2, 2);
複製代碼

兩個同樣的編譯時常量實際上是同一個對象:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

print(identical(a, b)); // 它們是相同的實例,輸出true
複製代碼

可使用Object的runtimeType屬性來判斷實例的類型,該屬性返回一個Type對象。

print('The type of a is ${a.runtimeType}');
複製代碼

19.1.Instance variables

下面是如何定義實例變量的示例:

class Point{
  num x;//聲明實例變量x,初始化爲空
  num y;//聲明實例變量y,溫馨化爲空
  num z = 0;//聲明實例變量z,初始化爲0
}
複製代碼

全部沒有初始化時變量值都是null。每一個實例變量都會自動生成一個getter方法。Non-final實例變量還會自定生成一個setter方法:

class Point{
  num x;//聲明實例變量x,初始化爲空
  num y;//聲明實例變量y,溫馨化爲空
  num z = 0;//聲明實例變量z,初始化爲0
}
void main() {
  var point = new Point();
  point.x = 4;              //使用setter方法對x變量賦值
  print(point.x == 4);      //輸出true 使用getter獲取x變量的值
  print(point.y == null);   //輸出true

}
複製代碼

若是在實例變量定義的時候初始化該變量(不是在構造函數或者其餘方法中初始化),改值是在實例對象的時候初始化的,也就是在構造函數和初始化參數列表執行以前。

19.2.Constructors

定義一個和類名字同樣的方法就定義一個構造函數還能夠帶有其餘可選的標識符。常見的構造函數生一個對象的新實例:

class Point {
  num x;
  num y;

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

this關鍵字指當前的實例。只有當名字衝突的時候才使用this。因爲構造函數參數賦值給實例變量的場景太常見了,Dart提供一個語法糖來簡化這個操做:

class Point {
  num x;
  num y;

  // 設置x和y的語法糖
  // 在構造函數主體運行以前
  Point(this.x, this.y);
}
複製代碼
19.2.1.Default copnstructors(默認構造函數)

若是沒有定義構造函數,則會有個默認構造函數。默認構造函數沒有參數,而且會調用超類的沒有參數的構造函數

19.2.2.Constructors aren’t inherited(構造函數不會繼承)

子類不會繼承超類的構造函數。子類若是沒有定義構造函數,則只有一個默認構造函數。

19.2.3.Named constructors(命名構造函數)

使用命名構造函數能夠爲一個類實現多個構造函數,或者使用命名構造函數來更清晰本身的意圖:

class Point {
  num x;
  num y;

  Point(this.x, this.y);

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

構造函數不能繼承,因此超類的命名構造函數也不會被繼承,若是子類也有超類同樣命名構造函數,就必須在子類中本身實現該構造函數。

19.2.4.Invoking a non-default superclass constructor(調用超類構造函數)

默認狀況下,子類的構造函數會自動調用超類的無名無參數的默認構造函數。超類的構造函數在子類構造函數體開始執行的位置調用。若是提供了一個 initializer list(初始化參數列表) ,則初始化參數列表在超類構造函數執行以前執行。 下面是構造函數執行順序:

  1. initializer list(初始化參數列表)
  2. superclass’s no-arg constructor(超類的無名構造函數)
  3. main class’s no-arg constructor(主類的無名構造函數)

若是超類沒有無名無參構造函數,則須要手動去調用超類的其餘構造函數。在構造函數參數後使用冒號:能夠調用超類構造函數,下面中,Employee類的構造函數調用超類Person的命名構造函數:

//定義Person類
class Person {
  String firstName;

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

class Employee extends Person {
  // Person 沒有默認構造函數
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // 打印輸出
  // in Person
  // in Employee

}
複製代碼

因爲超類構造函數的參數在構造函數執行以前執行,因此擦拭能夠是一個表達式或者一個方法調用:

class Employee extends Person {
  // ...
  Employee() : super.fromJson(findDefaultData());
}
複製代碼

若是在構造函數的初始化列表中使用 super(),須要把它放到最後。調用超類構造函數的參數沒法訪問 this。 例如,參數能夠爲靜態函數可是不能是實例函數。

19.3.Initializer list(初始化列表)

在構造函數體執行以前除了能夠調用超類構造函數以外,還能夠 初始化實例參數。 使用逗號分隔初始化表達式:

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。初始化列表很是適合用來設置 final 變量的值。 下面示例代碼中初始化列表設置了三個 final 變量的值:

import 'dart:math';

export 'src/DartProject_base.dart';

// TODO: Export any libraries intended for clients of this package.


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

 Point(x, y)
     : x = x,
       y = y,
       distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
 var p = new Point(2, 3);
 print(p.distanceFromOrigin);//輸出:3.605551275463989
}
複製代碼

19.4.Redirecting constructors(重定向構造函數)

有時候一個構造函數會調動類中的其餘構造函數。一個重定向構造函數是沒有代碼的,在構造函數聲明後,使用冒號調用其餘構造函數。

class Point {
  num x;
  num y;

  // 主構造函數
  Point(this.x, this.y);

  // 調用主構造函數
  Point.alongXAxis(num x) : this(x, 0);
}
複製代碼

19.5.Constant constructors(常量構造函數)

若是你的類提供一個狀態不變的對象,你能夠把這些對象定義爲編譯時常量。要實現這個功能,須要定義一個const構造函數, 而且聲明全部類的變量爲final

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

19.6.Factory constructors(工廠方法構造函數)

若是一個構造函數並不老是返回一個新的對象,則使用factory來定義這個構造函數。例如,一個工廠構造函數可能從緩存中獲取一個實例並返回,或者返回一個子類型的實例。例子:

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);
    }
  }
}
複製代碼

使用new關鍵字來調用工廠構造函數

var logger = new Logger('UI');
logger.log('Button clicked');
複製代碼

19.7.函數

函數是類中定義的方法,是類對象的行爲。

19.7.1.Instance methods(實例函數)

對象的實例函數能夠訪問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);
  }
}
複製代碼

Getterssetters是用來設置和訪問對象屬性的特殊函數。每一個實例變量都隱含的具備一個getter, 若是變量不是final的則還有一個setter。能夠經過實行gettersetter來建立新的屬性, 使用getset關鍵字定義gettersetter

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

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

  // 定義兩個計算屬性:右和下。
  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);
}
複製代碼

gettersetter的好處是,能夠開始使用實例變量,後來能夠把實例變量用函數包裹起來,而調用代碼的地方不須要修改。

19.7.2.Abstract methods(抽象函數)

實例函數、 getter、和setter函數能夠爲抽象函數,抽象函數是隻定義函數接口可是沒有實現的函數,由子類來實現該函數。若是用分號來替代函數體則這個函數就是抽象函數。

abstract class Doer {
  // ...定義實例變量和方法...

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

class EffectiveDoer extends Doer {
  void doSomething() {
    // ...提供實現,所以此處的方法不是抽象的...
  }
}
複製代碼

調用一個沒實現的抽象函數會致使運行時異常。

19.8.Overridable operators(可覆寫的操做符)

下表中的操做符能夠被覆寫。 例如,若是你定義了一個 Vector 類, 你能夠定義一個 + 函數來實現兩個向量相加。

可覆寫的操做符
下面舉了覆寫 +-操做符的示例:

class Vector {
  final int x;
  final int y;
  const Vector(this.x, this.y);

  /// 覆寫 + (a + b).
  Vector operator +(Vector v) {
    return new Vector(x + v.x, y + v.y);
  }

  /// 覆寫 - (a - b).
  Vector operator -(Vector v) {
    return new Vector(x - v.x, y - v.y);
  }
}

main() {
  final v = new Vector(2, 3);
  final w = new Vector(2, 2);

  // v == (2, 3)
  assert(v.x == 2 && v.y == 3);

  // v + w == (4, 5)
  assert((v + w).x == 4 && (v + w).y == 5);

  // v - w == (0, 1)
  assert((v - w).x == 0 && (v - w).y == 1);
}
複製代碼

19.9.Abstract classer(抽象類)

使用abstract修飾符定義一個抽象類,一個不能被實例化的類。抽象類一般用來定義接口,以及部分實現,若是抽象類是可實例化的,則定義一個工廠構造函數 抽象類一般具備抽象函數,下面是定義具備抽象函數的抽象類:

// 這個類是抽象類,不能實例化
abstract class AbstractContainer {
  // ...定義構造函數, 變量, 方法...

  void updateChildren(); // 抽象方法.
}
複製代碼

下面的類不是抽象的,可是定義了一個抽象函數,這樣的列是能夠被實例化:

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

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

  //Abstract method causes a warning but doesn't prevent instantiation.
抽象方法致使警告,但不阻止實例化。
  void doSomething();
}
複製代碼

19.10.Implicit interfaces(隱式接口)

每一個類都隱式的定義了一個包含全部實例成員的接口,而且這個類實現了這個接口。若是你想建立類A來支持類B的api,而不想繼承 B 的實現, 則類A應該實現B的接口:

// A 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 {
  // ...
}
複製代碼

19.11.Extending a class(擴展類)

使用extends定義子類,supper引用超類:

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

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

子類能夠覆寫實例函數,gettersetter。下面是覆寫Object類的noSuchMethod()函數的例子, 若是調用了對象上不存在的函數,則就會觸發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) {
    // ...
  }
}
複製代碼

若是要知道編譯時的具體類型,能夠實現這些類來避免警告,和使用@proxy效果同樣:

class A implements SomeClass, SomeOtherClass {
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}
複製代碼

19.12.Enumerated types(枚舉類型)

枚舉類型一般稱爲enumerations或者enums是一種特殊的類,用來表現一個固定數目的常量。使用enum關鍵字來定義枚舉類型:

enum Color {
  red,
  green,
  blue
}
複製代碼

枚舉類型中的每一個值都有一個indexgetter函數,該函數返回該值在枚舉類型定義中的位置(從0開始),例如,第一個枚舉值的位置爲0,第二個爲1:

print(Color.red.index == 0); //輸出:true
print(Color.green.index == 1);//輸出:true
print(Color.blue.index == 2);//輸出:true
複製代碼

枚舉values常量能夠返回全部的枚舉值

List<Color> colors = Color.values;
print(colors[2] == Color.blue);//輸出:true
複製代碼

能夠在switch語句中使用枚舉。若是在switch(e)中的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: // 若是沒有這個,你會看到一個警告
    print(aColor);  // 'Color.blue'
}
複製代碼

枚舉類型具備如下限制:

  1. 沒法繼承枚舉類型,沒法使用mixin、沒法實現一個枚舉類型
  2. 沒法顯示的初始化一個枚舉類型

19.13.Adding features to a class:mixins(爲類添加新的功能)

Mixins是一種多類繼承中重用一個類代碼的方法,使用with關鍵字後面爲一個或者多個mixin名字來使用mixin,上例子如何使用mixin:

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

class Maestro extends Person with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}
複製代碼

定義一個類繼承Object,該類沒有構造函數,不能調用super,則該類就是一個mixin。下面例子:

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');
    }
  }
}
複製代碼

從Dart1.13開始,Mixins能夠繼承其餘類,再也不限制爲繼承Object,Mixins能夠調用super()

19.14.Class variables and methods(類變量和函數)

使用static關鍵字來實現級別的變量和函數。

19.14.1.Static variables(靜態變量)

靜態變量對於類別的狀態是很是有用的:

class Color {
  static const red =
      const Color('red'); // 靜態構造變量.
  final String name;      // 靜態實例變量.
  const Color(this.name); // 構造方法.
}

main() {
  print(Color.red.name == 'red'); //輸出:true
}
複製代碼

靜態變量在第一次使用的時候才被初始化。

19.14.2.Static methods(靜態函數)

靜態函數再也不實例是執行,因此沒法訪問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);
}
複製代碼

對於通用的或者常用的靜態函數,考慮使用頂級方法而不是靜態函數,靜態函數還能夠當作編譯時常量使用,如:把靜態函數當作常量構造函數的參數來使用。

20.泛型

在查看List類型的API文檔,能夠看到實際的類型定義爲List<E>。這個<..>聲明list是一個泛型(或者參數化)類型。一般狀況下,使用一個字母來表明類型參數,例如E,T,S,K和V等。

20.1.Why use generics?(爲什麼使用泛型)

在Dart中類型是可選的,你能夠選擇不用泛型。有不少狀況都要使用類型代表本身的意圖,無論是使用泛型仍是具體類型。如,若是本身但願List只包含字符串對象。則能夠定義爲List<String>表明("list of string")。這樣開發工具或者本身的同事能夠幫助檢查本身的代碼是否把非字符串類型對象給放到這個list中,以下面:

main() {
   List Tech = new List<String>();
   Tech.addAll(['Android','IOS','Flutter']);
   Tech.add(42);//運行時報錯
}
複製代碼

另外使用泛型的緣由是減小重複代碼。泛型能夠在多種類型之間定義同一個實現,同時還能夠繼續使用檢查模式和靜態分析工具提供的代碼分析功能。例如:建立一個保存緩存對象的接口:

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);
}
複製代碼

在上面的代碼中,T是一個備用類型,這是類型佔位符,本身調用該接口的時候會指定具體類型。

20.2.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'
};
複製代碼

20.3.Using parameterized types with constructors(構造函數中使用泛型)

在調用構造函數的時候,在類名字後面使用尖括號(<...>)來指定泛型類型。例如:

var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = new Set<String>.from(names);
複製代碼

下面代碼建立了一個keyinteger,valueView類型的map:

var views = new Map<int, View>();
複製代碼

20.4.Generic collections and the types they contain(泛型集合和包含的類型)

Dart的泛型類型是固化的,在運行時有也能夠判斷具體的類型,例如在運行時也能夠檢測集合裏面的對象類型:

var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // 輸出:true
複製代碼

is表達式只是判斷集合的類型,而不是集合裏面具體對象的類型。在成產模式,List<String>變量能夠包含非字符串類型對象。對於這種狀況,能夠選擇分包判斷每一個對象的類型或者處理類型轉換異常,java中的泛型信息是編譯時的,泛型信息在運行時是不純在。在java中能夠測試一個對象是否爲List,可是沒法測試一個對象是否爲List<String>

20.5.Restricting the parameterrized type(限制泛型類型)

當使用泛型類型的時候,可能想限制泛型的具體類型,使用extends能夠實現這個功能:

// T必須是SomebaseClass或其後代之一。
class Foo<T extends SomeBaseClass> {...}

class Extender extends SomeBaseClass {...}

void main() {
  // 能夠在<>中使用somebaseclass或它的任何子類。
  var someBaseClassFoo = new Foo<SomeBaseClass>();
  var extenderFoo = new Foo<Extender>();

  //也可使用no<>
  var foo = new Foo();



   //指定任何非SomeBaseClass類型將致使警告,在Debug檢查模式,運行時錯誤。
  // var objectFoo = new Foo<Object>();
}
複製代碼

20.6.Using generic method(使用泛型函數)

一開始,泛型只能在Dart類中使用。新的語法也支持在函數和方法上使用泛型。

T first<T>(List<T> ts) {
  // ...作一些初始化工做或者錯誤檢查...
  T tmp ?= ts[0];
  // ...作一些額外的檢查或者處理...
  return tmp;
}
複製代碼

這裏first(<T>)泛型能夠在以下地方使用參數T

  • 函數的返回值類型(T)
  • 參數的類型(List<T>)
  • 局部變量的類型(T tmp)

在Dart1.21開始使用泛型函數,若是須要使用泛型函數,須要設置SDK版本爲1.21或者以上。

21.Libraries and visibility(庫和可見性)

使用importlibrary指令能夠幫助建立模塊化的可分享代碼。庫不只僅提供API,仍是一個私有單元:如下劃線(_)開頭的標識符只有在庫內部可見。每一個Dart app都是一個庫,即便沒有使用library命令也是一個庫。這裏的庫和Android所說的庫有類似的地方,簡單來說就是用人家寫好庫中的API。例如:拍照庫,網絡請求庫等。

21.1.Using libraries(使用庫)

使用import來指定一個庫如何使用另一個庫,例如:Dart web應用一般使用dart:html庫,而後能夠這樣導入庫:

import 'dart:html';
複製代碼

import必須參數爲庫的URL。對於內置的庫,URI使用特殊的dart:scheme。對於其餘的庫,可使用文件系統路徑或者package:schemepackage:scheme指定的庫經過包管理器來提供,如pub工具,如:

import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';
複製代碼

21.2.Specifying a library prefix(指定庫前綴)

若是導入的庫具備衝突的標識符,這個常常遇到,則可使用庫的前綴來區分。例如:若是library1library2都有一個名字爲Element的類,能夠這樣使用:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// ...
Element element1 = new Element();           // 使用 lib1 中的 Element .
lib2.Element element2 = new lib2.Element(); // 使用 lib2 中的 Element.
複製代碼

21.3.Importing only part of a library(導入庫的一部分)

若是隻使用庫的一部分功能,能夠選擇須要導入的內容,例如:

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

// 導入除foo之外的全部名稱。
import 'package:lib2/lib2.dart' hide foo;
複製代碼

21.4.Lazily loading a library(延遲載入庫)

Deferred loading可讓應用在須要的時候再加載庫。這我就想到了App的開啓時間,下面是一些使用延遲加載庫的場景:

  • 減小APP的啓動時間
  • 執行A/B測試,例如嘗試各類算法的不一樣實現
  • 加載不多使用的功能,例如可選的屏幕和對話框

延遲加載一個庫,須要先使用deferred as來導入:

import 'package:deferred/hello.dart' deferred as hello;
複製代碼

當須要使用的時候,使用庫標識符調用loadLibrary()函數來加載庫:

greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}
複製代碼

在上面代碼,使用await關鍵字暫停代碼執行一直到庫加載完成。在一個庫上能夠屢次調用loadLibrary()函數。可是該庫只是載入一次。在使用延遲加載庫的時候,要注意:

  • 延遲加載庫的常量在導入的時候是不可用的。只有當庫加載完畢的時候,庫中常量纔可使用。
  • 在導入文件的時候沒法使用延遲庫中的類型。若是須要使用類型,則考慮把接口類型移動到另一個庫中, 讓兩個庫都分別導入這個接口庫。
  • Dart隱含的把loadLibrary()函數導入到使用deferred as 命名空間loadLibrary()方法返回一個Future

22.Asynchrony support(異步支持)

Dart有一些語言特性來支持異步編程。最多見的特性是async方法和await表達式。Dart庫中有不少返回Future或者Stream對象的方法。這些方法是異步:這些函數在設置完基本的操做後就返回了,而無需等待執行完成。例如讀取一個文件,在打開文件後就返回了。有兩種方式可使用Future對象中的數據:

  • 使用asyncawait
  • 使用Future API

一樣,從Stream中獲取數據也有兩種方式:

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

使用asyncawait的代碼是異步的,可是看起來有點像同步代碼。如:下面是使用await來等待異步方法返回的示例:

await lookUpVersion() 複製代碼

要使用await,其方法必須帶有async關鍵字:

checkVersion() async {
  var version = await lookUpVersion();
  if (version == expectedVersion) {
    // 主體內容.
  } else {
    // 主體內容.
  }
}
複製代碼

可使用try,catchfinally來處理使用await的異常:

try {
  server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044);
} catch (e) {
  // 對沒法綁定到端口做出反應...
}
複製代碼

22.1.Declaring async functions(聲明異步方法)

一個async方法是函數體被標記爲async的方法。雖然異步方法的執行可能須要必定時間,可是異步方法馬上返回-在方法體還沒執行以前就返回了。

checkVersion() async {
  // ...
}

lookUpVersion() async => /* ... */;
複製代碼

在一個方法上添加async關鍵字,則這個方法返回值爲Future。例如,下面是一個返回字符串的同步方法:

String lookUpVersionSync() => '1.0.0';
複製代碼

若是使用async關鍵字,則該方法返回一個Future,而且認爲該函數是一個耗時操做。

Future<String> lookUpVersion() async => '1.0.0';
複製代碼

注意,方法的函數體並並不須要使用FutureAPI。Dart自動在須要的時候建立Future對象。

22.2.Using await expressions with Futures(使用await表達式)

await表達式具備以下的形式:

await expression
複製代碼

在一個異步方法內可使用屢次await表達水。例如,下面的示例使用三次await表達式來執行相關的功能:

var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
複製代碼

await expression中,expression的返回值一般是一個Future;若是返回的值不是Future,則Dart會自動把該值放到Future中返回。Future對象表明返回一個對象的承諾。await expression執行的結果爲這個返回的對象。await expression會阻塞主,直到須要的對象返回爲止。若是await沒法正常使用,請確保是在一個async方法中。例如要在main()方法中使用await,則main()方法的函數體必須標記爲async:

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

22.3.Using asynchronous for loops with Stream(在循環中使用異步)

異步for循環具備如下形式:

await for (variable declaration in expression) {
  // 每次流發出時會執行.
}
複製代碼

上面的expression返回的值必須是Stream類型。執行流程以下:

  1. 等待直到stream返回一個數據
  2. 使用stream返回的參數執行for循環代碼
  3. 重複執行1和2直到stream數據返回完畢

使用break或者return語句能夠中止接收stream數據,這樣就挑出了for循環而且從stream上取消註冊了。若是**異步for循環不能正常工做,確保是在一個async方法中使用。**如:要想在main()方法中使用異步for循環,則須要把main()方法的函數體標記爲async

main() async {
  ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  ...
}
複製代碼

23.Callable classes(可調用的類)

若是Dart類實現call()函數則能夠當作方法來調用,下面例子中,wannabeFunction類定義了一個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!
}
複製代碼

24.Isolates

現代的瀏覽器和移動瀏覽器運行在多核CPU系統上。要充分利用這些CPU,開發者通常使用共享內存數據來爆炸多線程的正確運行。然而,多線程共享數據一般會致使不少潛在的問題,並致使代碼運行出錯,結果不是預期。全部的Dart代碼在isolates中運行而不是線程,每一個isolate都有本身的堆內存,而且確保每一個isolate的狀態都不能被其餘isolate訪問。

25.Typedefs

在Dart語言中,方法也是對象。使用typedef或者function-type-alias來爲方法類型命名,而後可以使用命名的方法,當把方法類型賦值給一個變量的時候,typedef保留類型信息。下面代碼沒有使用typedef:

class SortedCollection {
  Function compare;

  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}

 // Initial, broken implementation.
 int sort(Object a, Object b) => 0;

main() {
  SortedCollection coll = new SortedCollection(sort);

  // 咱們只知道 compare 是一個 Function 類型,
  // 可是不知道具體是何種 Function 類型?
  assert(coll.compare is Function);
}
複製代碼

當把f賦值給compare的時候,類型信息丟失了,f的類型是(object,object)->int固然該類型是一個Function。若是使用顯式的名字並保留類型信息,開發者和工具可使用這些信息:

typedef int Compare(Object a, Object b);

class SortedCollection {
  Compare compare;

  SortedCollection(this.compare);
}

 // Initial, broken implementation.
 int sort(Object a, Object b) => 0;

main() {
  SortedCollection coll = new SortedCollection(sort);
  assert(coll.compare is Function);
  assert(coll.compare is Compare);
}
複製代碼

26.Metadata(元數據)

使用元數據給代碼添加額外信息,元數據註解是以@字符開頭,後面是一個編譯時常量或者調用一個常量構造函數。有三個註解全部的Dart代碼均可使用:@deprecated@override@proxy,下面直接上@deprecated的示例:

class Television {
  /// 已經棄用,請改用[打開]
  @deprecated
  void activate() {
    turnOn();
  }

  /// 打開電視.
  void turnOn() {
    print('on!');
  }
}
複製代碼

能夠定義本身的元數據註解。下面的示例定義一個帶有兩個參數的@todo註解:

library todo;

class todo {
  final String who;
  final String what;

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

複製代碼

使用@todo註解的示例:

import 'todo.dart';

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

元數據能夠在librarytypedeftype parameterconstructorfactoryfunctionfieldparameter、或者variable聲明以前使用,也能夠在import或者export指令以前使用,使用反射能夠再運行時獲取元數據信息。

5、總結

Dart語法和Java語法很像,很容易上手,理解很簡單,用一天就把語法整理了一遍,我爲何要學習Dart語法呢?一開始解釋很清楚了,無非就是把根基打穩。學什麼必定要帶有目的性去學,下面直接上一張圖:

帶有目的性去學

學習資源:

  1. 起步-Dart
  2. 爲java開發人員準備的Dart教程

6、額外知識

Flutter有四種運行模式:Debug、Release、Profile和test。

  1. Debug:Debug模式能夠在真機和模擬器上同時運行:會打開全部的斷言,包括debugging信息、debugger aids(好比observatory)和服務擴展。
  2. Release:Release模式只能在真機上運行,不能在模擬器上運行:會關閉全部斷言和debugging信息,關閉全部debugger工具。
  3. Profile:Profile模式只能在真機上運行,不能在模擬器上運行:基本和Release模式一致。
  4. test: headless test模式只能在桌面上運行:基本和Debug模式一致。

Flutter四種運行方式

相關文章
相關標籤/搜索