開發FlutterApp之前我們肯定要先了解Dart這門語言及語言的特性、語法等。最近看了大量的
Dart語言相關內容,本章會來簡述。 目錄
- 概念及優點
- 變數
- 函式
- 閉包
- 非同步支援
概念及優點:
- Dart: Google及全球的其他開發者,使用 Dart 開發了一系列高質量、 關鍵的 iOS、Android 和 web 應用。 Dart 非常適合移動和 web 應用的開發。
1.高效
Dart 語法清晰簡潔,工具簡單而強大。 輸入檢測可幫助您儘早識別細微錯誤。 Dart 擁有久經考驗的 核心庫(core libraries) 和一個已經擁有數以千計的 packages 生態系統
2.快速
Dart 提供提前編譯優化,以在移動裝置和 web 上實現可預測的高效能和快速啟動。
3.可移植
Dart 可以編譯成 ARM 和 x86 程式碼,因此 Dart 移動應用程式可以在 iOS,Android 及 更高版本上實現本地執行。 對於 web 應用程式,Dart 可以轉換為 JavaScript。
4.易學
Dart 是物件導向的程式語言,語法風格對於許多現有的開發人員來說都很熟悉。瞭解Java、JS語言 ,使用 Dart 也就很簡單,也有Swift的一些特性。
5.響應式
Dart 可以便捷的進行響應式程式設計。由於快速物件分配和垃圾收集器的實現, 對於管理短期物件(比如 UI 小部件), Dart 更加高效。 Dart 可以通過 Future 和 Stream 的特性和API實現非同步程式設計。
變數
- var 自動推斷型別(這點與OC、Java不同),接收任何型別的的變數,但是一旦賦值,型別就不能改變,即本來是字串,之後就只能是字串(這點與JS不同)。
var a = "字串";
//主意:如果這樣就會報錯,型別在第一次指定後就不能改變
a = 1;
複製程式碼
原因:Dart是強型別語言,任何變數都有各自的型別,編譯時會根據首次賦值資料的型別來推斷其型別,編譯結束後其型別不能更改。JS是純粹的弱型別指令碼語言,var只是變數的宣告。
- dynamic
dynamic與var一樣都是關鍵詞,宣告的變數可以賦值任意型別物件。宣告的變數可以在後期改變賦值型別。即本來是字串,之後可以賦值為number等其他型別。
dynamic a = "字串";
//不會報錯
a = 1;
複製程式碼
- Object
Object與dynamic一樣也是宣告的變數可以賦值任意型別物件,宣告的變數可以在後期改變賦值型別。
Object b = "hello world";
//不會報錯
b = 10;
複製程式碼
不同之處,dynamic宣告的物件編譯器會提供所有可能的組合,至少不會報錯(但有可能執行時會因為找不到之前預製的組合,造成崩潰), 而Object宣告的物件只能使用Object的屬性與方法, 否則編譯器會報錯。
dynamic a = "";
Object b = "";
//編譯器不報錯,不警告。
print(a.length);
//編譯器會警告報錯(Object沒有length的getter方法):The getter 'length' is not defined for the class 'Object'
print(b.length);
複製程式碼
注意:dynamic可以理解為id型別,任何型別都可以轉換成id(dynamic)型別,可以用id(dynamic)去接,編譯器不會報錯,但是在執行時可能會產生錯誤出現崩潰現象。
- final
final 為執行時常量。
final修飾的常量必須在宣告的時候就進行初始化,而且在初始化之後值不可變
final a = "名字";
//會報錯
a = "性別";
複製程式碼
- const
const 為編譯時常量。 const不僅僅可以宣告常數變數,也可以宣告常量值以及宣告建立常量值的建構函式,任何變數都可以有一個常量值;
final aList = const[];
const bList = const[];
var cList = const[];
這裡的aList和bList就是兩個空的、不可變的列表集合,而cList則是空的、可變的列表集合;
需要注意的是:cList可以重新賦值,可以改變,而aList和bList不可以重新賦值;
複製程式碼
- 函式 Dart是物件導向的語言,所以即使是函式也是物件,並且有一個型別Function。這意味著函式可以賦值給變數或作為引數傳遞給其他函式,這是函數語言程式設計的典型特徵。
1.函式宣告
返回型別 方法體 (引數1, 引數2, ...){
方法體...
return 返回值
}
String getPerson(String name, int age){
return name + '${age}';
}
//如果返回型別不指定時,此時預設為dynamic。
複製程式碼
2.箭頭函式
對於只包含一個表示式的函式,可以使用簡寫語法。
getPerson(name, age) => name+ ', $age' ;
bool isNoble (int atomicNumber)=> _nobleGases [ atomicNumber ] != null ;
複製程式碼
3.函式作為變數(方法物件)、入參
//函式作為變數
var method1 = (str){
print(str)
};
method1("kakalala");
//函式作為引數
void execute(var callbackMethod){
callbackMethod();
}
//兩種
execute(() => print("xxx"));
execute(method1("kakalala"));
複製程式碼
4.可選引數(可選位置引數、可選命名引數)
- 可選位置引數:[param1, param2, ...],可以設定預設引數 包裝一組函式引數,用[]標記為可選的位置引數,並放在引數列表的最後面:
getPerson(String name, [int age = 99, String gender = "御姐"]){
print ("name = $name, age = $age, gender = $gender");
}
//getPerson() ;這種不傳參是會報錯的。
getPerson(null) ;
getPerson('不知火') ;
getPerson('不知火', 100);
getPerson('不知火', null, "蘿莉");
複製程式碼
控制檯輸出
flutter: name = null, age = 99, gender = 御姐
flutter: name = 不知火, age = 99, gender = 御姐
flutter: name = 不知火, age = 100, gender = 御姐
flutter: name = 不知火, age = null, gender = 蘿莉
複製程式碼
注意:name引數是必須傳入的,否則會報錯。後邊的可選位置引數如果不傳會是null,傳null還是會返回null。可選位置引數可以設定預設引數。
- 可選命名引數:{param1, param2, ...} 在傳入的時候,需要指定下對應的引數名,放在引數列表的最後面,用於指定命名引數。可以設定預設引數。
getPerson(String name, {int age = 100, String gender = "狼狗"}){
print("name = $name, age = $age, gender = $gender");
}
//getPerson() ;這種不傳參是會報錯的。
getPerson(null) ;
getPerson('燼天玉藻前') ;
getPerson('燼天玉藻前', age: 99 );
getPerson('燼天玉藻前', gender: "御姐" );
getPerson('燼天玉藻前', age: 99, gender: "奶狗");
複製程式碼
控制檯輸出:
flutter: name = null, age = 100, gender = 狼狗
flutter: name = 燼天玉藻前, age = 100, gender = 狼狗
flutter: name = 燼天玉藻前, age = 99, gender = 狼狗
flutter: name = 燼天玉藻前, age = 100, gender = 御姐
flutter: name = 燼天玉藻前, age = 99, gender = 奶狗
複製程式碼
注意:固定引數必須傳入(那怕傳個null),可選命名引數可以設定預設引數。
- 預設引數值
預設引數值即我們在方法的引數列表上面使用 “=” 號給入一個常量值,如果沒有傳入該值的時候,就使用我們給入的常量值。
注意,不能同時使用可選的位置引數和可選的命名引數
//這種是不可以的,錯誤事例。
getPerson(String name, {int age = 100, String gender = "狼狗"}, [int age2 = 1002, String gender2 = "狼狗2"]){
}
複製程式碼
閉包
閉包是一個方法(物件),閉包定義在其它方法內部,能夠訪問外部方法的區域性變數,並持有其狀態。
void main() {
// 建立一個函式add1,返回加2
Function add1 = addNum(2);
// 建立一個函式add2,返回加4
Function add2 = addNum(4);
// 2 + 3 = 5
print(add1(3));
// 4 + 3 = 7
print(add2(3));
}
// 返回一個函式物件,功能是返回累加的數字
Function addNum(int addBy){
return (int i) => addBy + I;
}
複製程式碼
控制檯輸出:
flutter: 5
flutter: 7
複製程式碼
非同步支援
Dart程式碼執行在一個單執行緒,如果Dart程式碼阻塞了---例如,程式計算很長時間,或者等待I/O,整個程式就會凍結。
Dart非同步函式:Future、Stream,設定好耗時操作後返回,不會阻塞執行緒。
async和await關鍵詞支援了非同步程式設計,允許寫出和同步程式碼很像的非同步程式碼。
Future
Future與JS中的Promise和Swift的RXSwift非常相似,其語法也是鏈式函式呼叫,該函式非同步操作執行後,最終返回成功(執行成功的操作)、失敗(捕獲錯誤或者停止後續操作),失敗和成功是對立的只會出現一種。
注意:Future 的所有API的返回值都是一個Future物件,所以可以進行鏈式呼叫。
- Future建構函式
Future(FutureOr computation()) computation 的返回值可以是普通值或者是Future物件,但是都是Future物件接收
Future<num> future1 = Future(() {
print('async call1');
return 123;
});
//直接呼叫
future1.then((data) {
//執行成功會走到這裡
print(data);
}, onError: (e) {
print("onError: \$e");
}).catchError((e) {
//執行失敗會走到這裡
print(e);
}).whenComplete(() {
//無論成功或失敗都會走到這裡
});
Future<Future> future2 = Future((){
print('async call2');
return future1;
});
//巢狀呼叫
future2.then((value) => value).then((value) => {
print('222---'+value.toString())
});
複製程式碼
控制檯列印
Reloaded 1 of 499 libraries in 154ms.
flutter: async call1
flutter: 123
flutter: async call2
flutter: 222---123
複製程式碼
注意:computation函式體中的程式碼是被非同步執行的,與JS中Promise建構函式的回撥執行時機不一樣,如需要被同步執行,則使用如下這個命名建構函式:
Future.sync(FutureOr computation())
//該段程式碼放到上邊程式碼之後執行
Future<num> future3 = Future.sync((){
print('sync call');
return 333;
});
future3.then((value) => {
print('sync'+'$value')
});
複製程式碼
控制檯輸出
flutter: sync call
flutter: sync333
flutter: async call1
flutter: 123
flutter: async call2
flutter: 222---123
複製程式碼
由此可見,future3(sync)方法會先執行,之後在執行之前的future1、future2.可見正常的future中的computation函式體中的程式碼是被非同步執行的。
- Future.then then中接收非同步結果
Future.delayed(new Duration(seconds: 2),(){
return "延遲2s執行";
}).then((data){
print(data);
});
複製程式碼
- Future.catchError
捕獲錯誤
Future.delayed(new Duration(seconds: 2),(){
//return "延遲2s執行";
throw AssertionError("Error");
}).then((data){
//執行成功會走到這裡
print("success");
}).catchError((e){
//執行失敗會走到這裡
print(e);
});
複製程式碼
在非同步任務中丟擲了一個異常,then的回撥函式將不會被執行, catchError回撥函式將被呼叫;並不是只有 catchError回撥才能捕獲錯誤,then方法還有一個可選引數onError(之前介紹結構體時已經提到),我們也可以它來捕獲異常:
Future.delayed(new Duration(seconds: 2), () {
//return "延遲2s執行";
throw AssertionError("Error");
}).then((data) {
print("success");
}, onError: (e) {
print(e);
});
複製程式碼
- Future.whenComplete
不管成功失敗都要處理事件的場景,會呼叫此方法,比如在網路請求前彈出載入對話方塊,在請求結束後關閉對話方塊。這種場景,有兩種方法,第一種是分別在then或catch中關閉一下對話方塊,第二種就是使用Future的whenComplete回撥:
Future.delayed(new Duration(seconds: 2),(){
//return "延遲2s執行";
throw AssertionError("Error");
}).then((data){
//執行成功會走到這裡
print(data);
}).catchError((e){
//執行失敗會走到這裡
print(e);
}).whenComplete((){
//無論成功或失敗都會走到這裡
});
複製程式碼
- Future.wait
需要等待多個非同步任務都執行結束後再統一進行一些操作(比如我們有一個介面,需要先分別從兩個網路介面獲取資料,獲取成功後,我們需要將兩個介面資料進行特定的處理後再顯示到UI介面上) Future.wait就是做這件事的(類似RXswift的zip函式),它接受一個Future陣列引數,只有陣列中所有Future都執行成功後,才會觸發then的成功回撥,只要有一個Future執行失敗,就會觸發錯誤回撥。
Future<List<Future>> future4 = Future.wait([
// 2秒後返回結果
Future.delayed(new Duration(seconds: 2), () {
return "延遲2s執行";
}),
// 4秒後返回結果
Future.delayed(new Duration(seconds: 4), () {
return " 延遲4s執行";
})
])
future4.then((value) => {
print(value[0]+ value[1]);
}).catchError((e){
print(e);
});
複製程式碼
控制檯輸出
//等待4s輸出
flutter: 延遲2s執行 延遲4s執行
複製程式碼
- 回撥地獄(Callback Hell) 程式碼中有大量非同步邏輯,並且出現大量非同步任務依賴其它非同步任務的結果時,必然會出現回撥中套回撥情況。我們需要使用async/await和Future.then來解決這種問題。 使用場景:大量依賴的業務邏輯,登入流程邏輯等 先來看下回撥地獄例子:
//先分別定義各個非同步任務
Future<String> future1(String str1){
...
//第一個任務
};
Future<String> future2(String str2){
...
//第二個任務
};
Future future3(String info){
...
// 第三個任務
};
future1("str1").then((str2){
//1返回資料,作為2的引數
future2(str2).then((info){
//2的返回資料,作為3的入參
future3(info).then((){
//獲取3的返回資料
...
});
});
})
複製程式碼
Future消除Callback Hell 使用Future的鏈式機制,依次向下就避免了巢狀。跟JS的Promise完全一樣。不足之處是還是有一層回撥。
future1("str1").then((str2){
return future2(str2);
}).then((info){
return future3(info);
}).then((e){
//執行3接下來的操作
}).catchError((e){
//錯誤處理
print(e);
});
複製程式碼
async/await消除callback hell 上邊的方式雖然避免了巢狀,但是在每個方法還是有一層回撥。我們可以使用async/await來實現像同步程式碼那樣來執行非同步任務而不使用回撥的方式。
task() async {
try{
String str2 = await future1("str1");
String info = await future2(str2);
await future3(info);
//執行接下來的操作
} catch(e){
//錯誤處理
print(e);
}
}
複製程式碼
- async/await async用來表示函式是非同步的,定義的函式會返回一個Future物件,可以使用then方法新增回撥函式
await 後面是一個Future,表示等待該非同步任務完成,非同步完成後才會往下走;await必須出現在 async 函式內部。
async/await將一個非同步流用同步的程式碼表現出來。
async/await只是一個語法糖,JS編譯器或Dart直譯器最終都會將其轉化為一個JS的Promise和Dart的Future的呼叫鏈。
Stream
如果說Future是可以接收單個非同步事件返回單個事件的成功失敗,那麼Stream就可以接收多個非同步事件,並返回多個事件的成功失敗,供使用者使用。 使用場景:多次讀取資料的非同步任務場景,如網路內容下載、檔案讀寫等 該例子藉助了其他地方的例子。
Stream.fromFutures([
// 1秒後返回結果
Future.delayed(new Duration(seconds: 1), () {
return "hello 1";
}),
// 丟擲一個異常
Future.delayed(new Duration(seconds: 2),(){
throw AssertionError("Error");
}),
// 3秒後返回結果
Future.delayed(new Duration(seconds: 3), () {
return "hello 3";
})
]).listen((data){
print(data);
}, onError: (e){
print(e.message);
},onDone: (){
});
複製程式碼
控制檯輸出
flutter: hello 1
flutter: Error
flutter: hello 3
複製程式碼
Future.wait是函式中存在多個延時操作,則以延時最長操作完成後統一返回,其他的延時操作等待最長的延時操作完成。
async/await:處理多個非同步操作,前後有依賴邏輯的,使用非同步實現同步。
Stream:統一監聽該Stream中的多個非同步延時操作返回,相當於之前的多個Future非同步處理統一監聽。
- 其中Future和Stream只做了常用的方法和函式的介紹,更詳細的會在之後依次給大家做下總結。
到這裡大概把Dart中經常使用的語法和屬性方法介紹了一遍,有錯誤或者理解不到位的地方,可以提出,共同進步。
Dart相比Java和JavaScript還是有許多優點有優勢的,Dart既能進行服務端指令碼、APP開發、web開發,但是生態目前不足,不過Flutter目前火熱,相信生態之後會越來越好。