第一章: 起步 1.4 Dart語言簡介

跟隨《Flutter實戰(zhàn)·第二版》學習鼻听,建議直接看原書

變量聲明
  1. var
    Dart中的var變量一旦賦值焚刺,類型便會確定祟印,不能再改變其類型
var t = "hi world";
// 下面代碼在dart中會報錯温学,因為變量t的類型已經確定為String,
// 類型一旦確定后則不能再更改其類型钙畔。
t = 1000;

Dart 本身是一個強類型語言茫陆,任何變量都是有確定類型的,在 Dart 中擎析,當用var聲明一個變量后簿盅,Dart 在編譯時會根據第一次賦值數據的類型來推斷其類型,編譯結束后其類型就已經被確定

  1. dynamic和Object
    Object 是 Dart 所有對象的根基類揍魂,也就是說在 Dart 中所有類型都是Object的子類(包括Function和Null)桨醋,所以任何類型的數據都可以賦值給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"屬性遂鹊,編譯時不會報錯,運行時會報錯
  1. 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";
  1. 空安全(null-safety)

Dart 中一切都是對象,這意味著如果我們定義一個數字裆装,在初始化它之前如果我們使用了它踱承,假如沒有某種檢查機制倡缠,則不會報錯

test() {
  int I; 
  print(i*8);
}

在 Dart 引入空安全之前,上面代碼在執(zhí)行前不會報錯茎活,但會觸發(fā)一個運行時錯誤昙沦,原因是 i 的值為 null 。但現在有了空安全载荔,則定義變量時我們可以指定變量是可空還是不可空盾饮。

int i = 8; //默認為不可空,必須在定義時初始化懒熙。
int? j; // 定義為可空類型丘损,對于可空變量,我們在使用前必須判空工扎。

// 如果我們預期變量不能為空徘钥,但在定義時不能確定其初始值,則可以加上late關鍵字定庵,
// 表示會稍后初始化吏饿,但是在正式使用它之前必須得保證初始化過了,否則會報錯
late int k;
k=9;

如果一個變量我們定義為可空類型蔬浙,在某些情況下即使我們給它賦值過了猪落,但是預處理器仍然有可能識別不出,這時我們就要顯式(通過在變量后面加一個”!“符號)告訴預處理器它已經不是null了畴博,比如:

class Test{
  int? I;
  Function? fun;
  say(){
    if(i!=null) {
      print(i! * 8); //因為已經判過空笨忌,所以能走到這 i 必不為null,如果沒有顯式申明俱病,則 IDE 會報錯
    }
    if(fun!=null){
      fun!(); // 同上
    }
  }
}

上面中如果函數變量可空時官疲,調用的時候可以用語法糖:

fun?.call() // fun 不為空時則會被調用
函數

Dart是一種真正的面向對象的語言,所以即使是函數也是對象亮隙,并且有一個類型Function途凫。這意味著函數可以賦值給變量或作為參數傳遞給其他函數,這是函數式編程的典型特征

1. 函數聲明
bool isNoble(int atomicNumber) {
    return _nobleGases[atomicNumber] != null;
}

Dart函數聲明如果沒有顯式聲明返回值類型時會默認當做dynamic處理溢吻,注意维费,函數返回值沒有類型推斷:

typedef bool CALLBACK();

//不指定返回類型,此時默認為dynamic促王,不是bool
isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

void test(CALLBACK cb){
   print(cb()); 
}
//報錯犀盟,isNoble不是bool類型
test(isNoble);
  1. 對于只包含一個表達式的函數,可以使用簡寫語法:
bool isNoble (int atomicNumber)=> true ;
  1. 函數作為變量
var say = (str) {
    print(str);
};
say("hi world");
  1. 函數作為參數傳遞
    void execute(var callback) {
    callback();
    }
    execute( () => print("xxx"))

  2. 可選的位置參數

包裝一組函數參數蝇狼,用[]標記為可選的位置參數阅畴,并放在參數列表的最后面:

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
  1. 可選的命名參數
    定義函數時,使用{param1, param2, …}迅耘,放在參數列表的最后面贱枣,用于指定命名參數监署。例如:
//設置[bold]和[hidden]標志
void enableFlags({bool bold, bool hidden}) {
    // ... 
}

調用函數時,可以使用指定命名參數冯事。例如:paramName: value

enableFlags(bold: true, hidden: false);

可選命名參數在Flutter中使用非常多焦匈。注意,不能同時使用可選的位置參數和可選的命名參數昵仅。

mixin

Dart是不支持多繼承的,但是它支持mixin累魔,簡單來講mixin可以"組合"多個類

定義一個 Person 類摔笤,實現吃飯、說話垦写、走路和寫代碼功能吕世,同時定義一個 Dog 類,實現吃飯梯投、和走路功能:

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對象的函數么鹤。 這些函數被稱為異步函數:它們只會在設置好一些耗時操作之后返回终娃,比如像 IO操作。而不是等到這個操作完成蒸甜。

async和await關鍵詞支持了異步編程棠耕,允許您寫出和同步代碼很像的異步代碼

Future

Future與JavaScript中的Promise非常相似,表示一個異步操作的最終完成(或失斈隆)及其結果值的表示窍荧。簡單來說,它就是用于處理異步操作的恨憎,異步處理成功了就執(zhí)行成功的操作蕊退,異步處理失敗了就捕獲錯誤或者停止后續(xù)操作。一個Future只會對應一個結果框咙,要么成功咕痛,要么失敗。

Future 的所有API的返回值仍然是一個Future對象喇嘱,所以可以很方便的進行鏈式調用

Future.then

為了方便示例茉贡,在本例中我們使用Future.delayed 創(chuà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的回調函數將不會被執(zhí)行,取而代之的是 catchError回調函數將被調用衣厘;但是如蚜,并不是只有 catchError回調才能捕獲錯誤,then方法還有一個可選參數onError影暴,我們也可以用它來捕獲異常

Future.delayed(Duration(seconds: 2), () {
    //return "hi world!";
    throw AssertionError("Error");
}).then((data) {
    print("success");
}, onError: (e) {
    print(e);
});
Future.whenComplete

有些時候错邦,我們會遇到無論異步任務執(zhí)行成功或失敗都需要做一些事的場景,比如在網絡請求前彈出加載對話框型宙,在請求結束后關閉對話框撬呢。這種場景,有兩種方法妆兑,第一種是分別在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í)行結束后才進行一些操作芯勘,比如我們有一個界面,需要先分別從兩個網絡接口獲取數據谱姓,獲取成功后借尿,我們需要將兩個接口數據進行特定的處理后再顯示到UI界面上,應該怎么做屉来?答案是Future.wait路翻,它接受一個Future數組參數,只有數組中所有Future都執(zhí)行成功后茄靠,才會觸發(fā)then的成功回調茂契,只要有一個Future執(zhí)行失敗,就會觸發(fā)錯誤回調慨绳。下面掉冶,我們通過模擬Future.delayed 來模擬兩個數據獲取的異步任務,等兩個異步任務都執(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”战秋。


image.png
Async/await

Dart中的async/await 和JavaScript中的async/await功能和用法是一模一樣的璧亚,如果你已經了解JavaScript中的async/await的用法,可以直接跳過本節(jié)

回掉地獄(Callback Hell)

如果代碼中有大量異步邏輯脂信,并且出現大量異步任務依賴其它異步任務的結果時癣蟋,必然會出現Future.then回調中套回調情況透硝。舉個例子,比如現在有個需求場景是用戶先登錄疯搅,登錄成功后會獲得用戶ID濒生,然后通過用戶ID,再去請求用戶個人信息幔欧,獲取到用戶個人信息后罪治,為了使用方便,我們需要將其緩存在本地文件系統礁蔗,代碼如下:

//先分別定義各個異步任務
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í)行其它操作
        ...
    });
  });
})
image.png

可以感受一下,如果業(yè)務邏輯中有大量異步依賴的情況歧胁,將會出現上面這種在回調里面套回調的情況滋饲,過多的嵌套會導致的代碼可讀性下降以及出錯率提高,并且非常難維護喊巍,這個問題被形象的稱為回調地獄(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用來表示函數是異步的球化,定義的函數會返回一個Future對象,可以使用 then 方法添加回調函數瓦糟。
  • await 后面是一個Future筒愚,表示等待該異步任務完成,異步完成后才會往下走菩浙;await必須出現在 async 函數內部

可以看到巢掺,我們通過async/await將一個異步流用同步的代碼表示出來了

其實,無論是在 JavaScript 還是 Dart 中劲蜻,async/await 都只是一個語法糖陆淀,編譯器或解釋器最終都會將其轉化為一個 Promise(Future)的調用鏈

Stream

Stream 也是用于接收異步事件數據,和 Future 不同的是先嬉,它可以接收多個異步操作的結果(成功或失斣弧)。 也就是說疫蔓,在執(zhí)行異步任務時含懊,可以通過多次觸發(fā)成功或失敗事件來傳遞結果數據或錯誤異常。 Stream 常用于會多次讀取數據的異步任務場景衅胀,如網絡內容下載岔乔、文件讀寫等

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)勢了

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末掸掏,一起剝皮案震驚了整個濱河市茁影,隨后出現的幾起案子,更是在濱河造成了極大的恐慌阅束,老刑警劉巖呼胚,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異息裸,居然都是意外死亡蝇更,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進店門呼盆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來年扩,“玉大人,你說我怎么就攤上這事访圃〕茫” “怎么了?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長况脆。 經常有香客問我饭宾,道長,這世上最難降的妖魔是什么格了? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任看铆,我火速辦了婚禮,結果婚禮上盛末,老公的妹妹穿的比我還像新娘弹惦。我一直安慰自己,他們只是感情好悄但,可當我...
    茶點故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布棠隐。 她就那樣靜靜地躺著,像睡著了一般檐嚣。 火紅的嫁衣襯著肌膚如雪助泽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天嚎京,我揣著相機與錄音报咳,去河邊找鬼。 笑死挖藏,一個胖子當著我的面吹牛,可吹牛的內容都是我干的厢漩。 我是一名探鬼主播膜眠,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼溜嗜!你這毒婦竟也來了宵膨?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤炸宵,失蹤者是張志新(化名)和其女友劉穎辟躏,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體土全,經...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡捎琐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了裹匙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瑞凑。...
    茶點故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖概页,靈堂內的尸體忽然破棺而出籽御,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布技掏,位于F島的核電站铃将,受9級特大地震影響,放射性物質發(fā)生泄漏哑梳。R本人自食惡果不足惜劲阎,卻給世界環(huán)境...
    茶點故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涧衙。 院中可真熱鬧哪工,春花似錦、人聲如沸弧哎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撤嫩。三九已至偎捎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間序攘,已是汗流浹背茴她。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留程奠,地道東北人丈牢。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像瞄沙,于是被迫代替她去往敵國和親己沛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,781評論 2 361