Flutter Dart語法學習

Miaoz??發表於2020-07-22

Dart

開發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目前火熱,相信生態之後會越來越好。

相關文章