跟隨《Flutter實戰(zhàn)·第二版》學習,建議直接看原書
變量聲明
- var
Dart中的var變量一旦賦值,類型便會確定,不能再改變其類型
var t = "hi world";
// 下面代碼在dart中會報錯,因為變量t的類型已經(jīng)確定為String,
// 類型一旦確定后則不能再更改其類型。
t = 1000;
Dart 本身是一個強類型語言,任何變量都是有確定類型的,在 Dart 中,當用var聲明一個變量后,Dart 在編譯時會根據(jù)第一次賦值數(shù)據(jù)的類型來推斷其類型,編譯結束后其類型就已經(jīng)被確定
- dynamic和Object
Object 是 Dart 所有對象的根基類,也就是說在 Dart 中所有類型都是Object的子類(包括Function和Null),所以任何類型的數(shù)據(jù)都可以賦值給Object聲明的對象。 dynamic與Object聲明的變量都可以賦值任意對象,且后期可以改變賦值的類型,這和 var 是不同的
dynamic t;
Object x;
t = "hi world";
x = 'Hello Object';
//下面代碼沒有問題
t = 1000;
x = 1000;
dynamic與Object不同的是dynamic聲明的對象編譯器會提供所有可能的組合,而Object聲明的對象只能使用 Object 的屬性與方法, 否則編譯器會報錯
dynamic a;
Object b = "";
main() {
a = "";
printLengths();
}
printLengths() {
// 正常
print(a.length);
// 報錯 The getter 'length' is not defined for the class 'Object'
print(b.length);
}
dynamic 的這個特點使得我們在使用它時需要格外注意,這很容易引入一個運行時錯誤,比如下面代碼在編譯時不會報錯,而在運行時會報錯:
print(a.xx); // a是字符串,沒有"xx"屬性,編譯時不會報錯,運行時會報錯
- final和const
如果您從未打算更改一個變量,那么使用 final 或 const,不是var,也不是一個類型。 一個 final 變量只能被設置一次,兩者區(qū)別在于:const 變量是一個編譯時常量(編譯時直接替換為常量值),final變量在第一次使用時被初始化。被final或者const修飾的變量,變量類型可以省略
//可以省略String這個類型聲明
final str = "hi world";
//final String str = "hi world";
const str1 = "hi world";
//const String str1 = "hi world";
- 空安全(null-safety)
Dart 中一切都是對象,這意味著如果我們定義一個數(shù)字,在初始化它之前如果我們使用了它,假如沒有某種檢查機制,則不會報錯
test() {
int I;
print(i*8);
}
在 Dart 引入空安全之前,上面代碼在執(zhí)行前不會報錯,但會觸發(fā)一個運行時錯誤,原因是 i 的值為 null 。但現(xiàn)在有了空安全,則定義變量時我們可以指定變量是可空還是不可空。
int i = 8; //默認為不可空,必須在定義時初始化。
int? j; // 定義為可空類型,對于可空變量,我們在使用前必須判空。
// 如果我們預期變量不能為空,但在定義時不能確定其初始值,則可以加上late關鍵字,
// 表示會稍后初始化,但是在正式使用它之前必須得保證初始化過了,否則會報錯
late int k;
k=9;
如果一個變量我們定義為可空類型,在某些情況下即使我們給它賦值過了,但是預處理器仍然有可能識別不出,這時我們就要顯式(通過在變量后面加一個”!“符號)告訴預處理器它已經(jīng)不是null了,比如:
class Test{
int? I;
Function? fun;
say(){
if(i!=null) {
print(i! * 8); //因為已經(jīng)判過空,所以能走到這 i 必不為null,如果沒有顯式申明,則 IDE 會報錯
}
if(fun!=null){
fun!(); // 同上
}
}
}
上面中如果函數(shù)變量可空時,調用的時候可以用語法糖:
fun?.call() // fun 不為空時則會被調用
函數(shù)
Dart是一種真正的面向對象的語言,所以即使是函數(shù)也是對象,并且有一個類型Function。這意味著函數(shù)可以賦值給變量或作為參數(shù)傳遞給其他函數(shù),這是函數(shù)式編程的典型特征
1. 函數(shù)聲明
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
Dart函數(shù)聲明如果沒有顯式聲明返回值類型時會默認當做dynamic處理,注意,函數(shù)返回值沒有類型推斷:
typedef bool CALLBACK();
//不指定返回類型,此時默認為dynamic,不是bool
isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
void test(CALLBACK cb){
print(cb());
}
//報錯,isNoble不是bool類型
test(isNoble);
- 對于只包含一個表達式的函數(shù),可以使用簡寫語法:
bool isNoble (int atomicNumber)=> true ;
- 函數(shù)作為變量
var say = (str) {
print(str);
};
say("hi world");
函數(shù)作為參數(shù)傳遞
void execute(var callback) {
callback();
}
execute( () => print("xxx"))可選的位置參數(shù)
包裝一組函數(shù)參數(shù),用[]標記為可選的位置參數(shù),并放在參數(shù)列表的最后面:
String say(String from, String msg, [String device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
下面是一個不帶可選參數(shù)調用這個函數(shù)的例子:
say('Bob', 'Howdy'); //結果是: Bob says Howdy
下面是用第三個參數(shù)調用這個函數(shù)的例子:
say('Bob', 'Howdy', 'smoke signal'); //結果是:Bob says Howdy with a smoke signal
- 可選的命名參數(shù)
定義函數(shù)時,使用{param1, param2, …},放在參數(shù)列表的最后面,用于指定命名參數(shù)。例如:
//設置[bold]和[hidden]標志
void enableFlags({bool bold, bool hidden}) {
// ...
}
調用函數(shù)時,可以使用指定命名參數(shù)。例如:paramName: value
enableFlags(bold: true, hidden: false);
可選命名參數(shù)在Flutter中使用非常多。注意,不能同時使用可選的位置參數(shù)和可選的命名參數(shù)。
mixin
Dart是不支持多繼承的,但是它支持mixin,簡單來講mixin可以"組合"多個類
定義一個 Person 類,實現(xiàn)吃飯、說話、走路和寫代碼功能,同時定義一個 Dog 類,實現(xiàn)吃飯、和走路功能:
class Person {
say() {
print('say');
}
}
mixin Eat {
eat() {
print('eat');
}
}
mixin Walk {
walk() {
print('walk');
}
}
mixin Code {
code() {
print('key');
}
}
class Dog with Eat, Walk{}
class Man extends Person with Eat, Walk, Code{}
定義了幾個 mixin,然后通過 with 關鍵字將它們組合成不同的類。
有一點需要注意:如果多個mixin 中有同名方法,with 時,會默認使用最后面的 mixin 的,mixin 方法中可以通過 super 關鍵字調用之前 mixin 或類中的方法
異步支持
Dart類庫有非常多的返回Future或者Stream對象的函數(shù)。 這些函數(shù)被稱為異步函數(shù):它們只會在設置好一些耗時操作之后返回,比如像 IO操作。而不是等到這個操作完成。
async和await關鍵詞支持了異步編程,允許您寫出和同步代碼很像的異步代碼
Future
Future與JavaScript中的Promise非常相似,表示一個異步操作的最終完成(或失?。┘捌浣Y果值的表示。簡單來說,它就是用于處理異步操作的,異步處理成功了就執(zhí)行成功的操作,異步處理失敗了就捕獲錯誤或者停止后續(xù)操作。一個Future只會對應一個結果,要么成功,要么失敗。
Future 的所有API的返回值仍然是一個Future對象,所以可以很方便的進行鏈式調用
Future.then
為了方便示例,在本例中我們使用Future.delayed 創(chuàng)建了一個延時任務(實際場景會是一個真正的耗時任務,比如一次網(wǎng)絡請求),即2秒后返回結果字符串"hi world!",然后我們在then中接收異步結果并打印結果
Future.delayed(Duration(seconds: 2),(){
return "hi world!";
}).then((data){
print(data);
});
Future.catchError
如果異步任務發(fā)生錯誤,我們可以在catchError中捕獲錯誤,我們將上面示例改為:
Future.delayed(Duration(seconds: 2),(){
//return "hi world!";
throw AssertionError("Error");
}).then((data){
//執(zhí)行成功會走到這里
print("success");
}).catchError((e){
//執(zhí)行失敗會走到這里
print(e);
});
在本示例中,我們在異步任務中拋出了一個異常,then的回調函數(shù)將不會被執(zhí)行,取而代之的是 catchError回調函數(shù)將被調用;但是,并不是只有 catchError回調才能捕獲錯誤,then方法還有一個可選參數(shù)onError,我們也可以用它來捕獲異常
Future.delayed(Duration(seconds: 2), () {
//return "hi world!";
throw AssertionError("Error");
}).then((data) {
print("success");
}, onError: (e) {
print(e);
});
Future.whenComplete
有些時候,我們會遇到無論異步任務執(zhí)行成功或失敗都需要做一些事的場景,比如在網(wǎng)絡請求前彈出加載對話框,在請求結束后關閉對話框。這種場景,有兩種方法,第一種是分別在then或catch中關閉一下對話框,第二種就是使用Future的whenComplete回調,我們將上面示例改一下
Future.delayed(Duration(seconds: 2),(){
//return "hi world!";
throw AssertionError("Error");
}).then((data){
//執(zhí)行成功會走到這里
print(data);
}).catchError((e){
//執(zhí)行失敗會走到這里
print(e);
}).whenComplete((){
//無論成功或失敗都會走到這里
});
Future.wait
有些時候,我們需要等待多個異步任務都執(zhí)行結束后才進行一些操作,比如我們有一個界面,需要先分別從兩個網(wǎng)絡接口獲取數(shù)據(jù),獲取成功后,我們需要將兩個接口數(shù)據(jù)進行特定的處理后再顯示到UI界面上,應該怎么做?答案是Future.wait,它接受一個Future數(shù)組參數(shù),只有數(shù)組中所有Future都執(zhí)行成功后,才會觸發(fā)then的成功回調,只要有一個Future執(zhí)行失敗,就會觸發(fā)錯誤回調。下面,我們通過模擬Future.delayed 來模擬兩個數(shù)據(jù)獲取的異步任務,等兩個異步任務都執(zhí)行成功時,將兩個異步任務的結果拼接打印出來,代碼如下:
Future.wait([
// 2秒后返回結果
Future.delayed(Duration(seconds: 2), () {
return "hello";
}),
// 4秒后返回結果
Future.delayed(Duration(seconds: 4), () {
return " world";
})
]).then((results){
print(results[0]+results[1]);
}).catchError((e){
print(e);
});
執(zhí)行上面代碼,4秒后你會在控制臺中看到“hello world”。

Async/await
Dart中的async/await 和JavaScript中的async/await功能和用法是一模一樣的,如果你已經(jīng)了解JavaScript中的async/await的用法,可以直接跳過本節(jié)
回掉地獄(Callback Hell)
如果代碼中有大量異步邏輯,并且出現(xiàn)大量異步任務依賴其它異步任務的結果時,必然會出現(xiàn)Future.then回調中套回調情況。舉個例子,比如現(xiàn)在有個需求場景是用戶先登錄,登錄成功后會獲得用戶ID,然后通過用戶ID,再去請求用戶個人信息,獲取到用戶個人信息后,為了使用方便,我們需要將其緩存在本地文件系統(tǒng),代碼如下:
//先分別定義各個異步任務
Future<String> login(String userName, String pwd){
...
//用戶登錄
};
Future<String> getUserInfo(String id){
...
//獲取用戶信息
};
Future saveUserInfo(String userInfo){
...
// 保存用戶信息
};
接下來,執(zhí)行整個任務流:
login("alice","******").then((id){
//登錄成功后通過,id獲取用戶信息
getUserInfo(id).then((userInfo){
//獲取用戶信息后保存
saveUserInfo(userInfo).then((){
//保存用戶信息,接下來執(zhí)行其它操作
...
});
});
})

可以感受一下,如果業(yè)務邏輯中有大量異步依賴的情況,將會出現(xiàn)上面這種在回調里面套回調的情況,過多的嵌套會導致的代碼可讀性下降以及出錯率提高,并且非常難維護,這個問題被形象的稱為回調地獄(Callback Hell)。回調地獄問題在之前 JavaScript 中非常突出,也是 JavaScript 被吐槽最多的點,但隨著 ECMAScript 標準發(fā)布后,這個問題得到了非常好的解決,而解決回調地獄的兩大神器正是 ECMAScript6 引入了Promise,以及ECMAScript7 中引入的async/await。 而在 Dart 中幾乎是完全平移了 JavaScript 中的這兩者:Future相當于Promise,而async/await連名字都沒改。
看看通過Future和async/await如何消除上面示例中的嵌套問題
使用Future消除Callback Hell
login("alice","******").then((id){
return getUserInfo(id);
}).then((userInfo){
return saveUserInfo(userInfo);
}).then((e){
//執(zhí)行接下來的操作
}).catchError((e){
//錯誤處理
print(e);
});
正如上文所述, “Future 的所有API的返回值仍然是一個Future對象,所以可以很方便的進行鏈式調用” ,如果在then 中返回的是一個Future的話,該future會執(zhí)行,執(zhí)行結束后會觸發(fā)后面的then回調,這樣依次向下,就避免了層層嵌套。
使用Async/await消除Callback Hell
通過Future回調中再返回Future的方式雖然能避免層層嵌套,但是還是有一層回調,有沒有一種方式能夠讓我們可以像寫同步代碼那樣來執(zhí)行異步任務而不使用回調的方式?答案是肯定的,這就要使用async/await了
task() async {
try{
String id = await login("alice","******");
String userInfo = await getUserInfo(id);
await saveUserInfo(userInfo);
//執(zhí)行接下來的操作
} catch(e){
//錯誤處理
print(e);
}
}
- async用來表示函數(shù)是異步的,定義的函數(shù)會返回一個Future對象,可以使用 then 方法添加回調函數(shù)。
- await 后面是一個Future,表示等待該異步任務完成,異步完成后才會往下走;await必須出現(xiàn)在 async 函數(shù)內部
可以看到,我們通過async/await將一個異步流用同步的代碼表示出來了
其實,無論是在 JavaScript 還是 Dart 中,async/await 都只是一個語法糖,編譯器或解釋器最終都會將其轉化為一個 Promise(Future)的調用鏈
Stream
Stream 也是用于接收異步事件數(shù)據(jù),和 Future 不同的是,它可以接收多個異步操作的結果(成功或失?。?。 也就是說,在執(zhí)行異步任務時,可以通過多次觸發(fā)成功或失敗事件來傳遞結果數(shù)據(jù)或錯誤異常。 Stream 常用于會多次讀取數(shù)據(jù)的異步任務場景,如網(wǎng)絡內容下載、文件讀寫等
Stream.fromFutures([
//1秒后返回結果
Future.delayed(Duration(seconds: 1),(){
return 'hello 1';
}),
//拋出一個異常
Future.delayed(Duration(seconds: 2), (){
throw AssertionError('Error');
}),
//3秒后返回結果
Future.delayed(Duration(seconds: 3), (){
return 'hello 3';
})
]).listen((data) {
print(data);
}, onError: (e){
print(e.message);
}, onDone: (){
});
上面的代碼會依次輸出:
flutter: hello 1
flutter: Error
flutter: hello 3
綜合起來看,Dart 既能進行服務端腳本、APP 開發(fā)、Web 開發(fā),這就有優(yōu)勢了