Dart語言簡介

1.3 Dart語言簡介

在跨平臺開領(lǐng)域被 JS 一統(tǒng)天下的今天,Dart 語言的出現(xiàn)無疑是一股清流。作為后來者,Dart語言有著不少 Java、Kotlin 和 JS 的影子,所以對于 Android 原生開發(fā)者、前端開發(fā)者而言無疑是非常友好。

官方也提供了包括 iOS 、React Native 等開發(fā)者遷移到 Flutter 上的文檔,所以請不要擔(dān)心,Dart 語言不會是你掌握 Flutter 的門檻,甚至作為開發(fā)者,就算你不懂 Dart 也可以看著代碼摸索。

注意:由于本書并非專門介紹Dart語言的書籍,所以本章主要會介紹一下在Flutter開發(fā)中常用的語法特性,如果想更多了解Dart,讀者可以去Dart官網(wǎng)學(xué)習(xí),現(xiàn)在互聯(lián)網(wǎng)上Dart相關(guān)資料已經(jīng)很多了。另外Dart 2.0已經(jīng)正式發(fā)布,所以本書所有示例均采用Dart 2.0語法。

1.3.1 語言特性

  • Dart所有的東西都是對象, 即使是數(shù)字numbers、函數(shù)function、null也都是對象,所有的對象都繼承自O(shè)bject類。

  • Dart動態(tài)類型語言, 盡量給變量定義一個類型,會更安全,沒有顯示定義類型的變量在 debug 模式下會類型會是 dynamic(動態(tài)的)。

  • Dart 在 running 之前解析你的所有代碼,指定數(shù)據(jù)類型和編譯時的常量,可以提高運(yùn)行速度。

  • Dart中的類和接口是統(tǒng)一的,類即接口,你可以繼承一個類,也可以實(shí)現(xiàn)一個類(接口),自然也包含了良好的面向?qū)ο蠛筒l(fā)編程的支持。

  • Dart 提供了頂級函數(shù)(如:main())。

  • Dart 沒有 public、private、protected 這些關(guān)鍵字,變量名以"_"開頭意味著對它的 lib 是私有的。

  • 沒有初始化的變量都會被賦予默認(rèn)值 null。

  • final的值只能被設(shè)定一次。const 是一個編譯時的常量,可以通過 const 來創(chuàng)建常量值,var c=const[];,這里 c 還是一個變量,只是被賦值了一個常量值,它還是可以賦其它值。實(shí)例變量可以是 final,但不能是 const。

  • 編程語言并不是孤立存在的,Dart也是這樣,他由語言規(guī)范、虛擬機(jī)、類庫和工具等組成:

  • SDK:SDK 包含 Dart VM、dart2js、Pub、庫和工具。

  • Dartium:內(nèi)嵌 Dart VM 的 Chromium ,可以在瀏覽器中直接執(zhí)行 dart 代碼。

  • Dart2js:將 Dart 代碼編譯為 JavaScript 的工具。

  • Dart Editor:基于 Eclipse 的全功能 IDE,并包含以上所有工具。支持代碼補(bǔ)全、代碼導(dǎo)航、快速修正、重構(gòu)、調(diào)試等功能。

1.3.2 數(shù)據(jù)類型

  1. num
  • num 是數(shù)字類型的父類,有兩個子類 int 和 double。

  • int 根據(jù)平臺的不同,整數(shù)值不大于64位。在Dart VM上,值可以從-263到263 - 1,編譯成JavaScript的Dart使用JavaScript代碼,允許值從-253到253 - 1。

  • double 64位(雙精度)浮點(diǎn)數(shù),如IEEE 754標(biāo)準(zhǔn)所規(guī)定


    int a = 1;

    print(a);

    double b = 1.12;

    print(b);

    // String -> int

    int one = int.parse('1');

    // 輸出3

    print(one + 2);

    // String -> double

    var onePointOne = double.parse('1.1');

    // 輸出3.1

    print(onePointOne + 2);

    // int -> String

    String oneAsString = 1.toString();

    // The argument type 'int' can't be assigned to the parameter type 'String'

    //print(oneAsString + 2);

    // 輸出 1 + 2

    print('$oneAsString + 2');

    // double -> String 注意括號中要有小數(shù)點(diǎn)位數(shù),否則報(bào)錯

    String piAsString = 3.14159.toStringAsFixed(2);

    // 截取兩位小數(shù), 輸出3.14

  1. String
  • Dart里面的String是一系列 UTF-16 代碼單元。

  • 您可以使用單引號或雙引號來創(chuàng)建一個字符串。

  • 單引號或者雙引號里面嵌套使用引號。

  • 用 或{} 來計(jì)算字符串中變量的值,需要注意的是如果是表達(dá)式需要${表達(dá)式}

  1. bool
  • Dart 是強(qiáng) bool 類型檢查,只有bool 類型的值是true 才被認(rèn)為是true。

  • 只有兩個對象具有bool類型:true和false,它們都是編譯時常量。

  • Dart的類型安全意味著您不能使用 if(nonbooleanValue)assert(nonbooleanValue) 等代碼, 相反Dart使用的是顯式的檢查值。

  • assert 是語言內(nèi)置的斷言函數(shù),僅在檢查模式下有效

在開發(fā)過程中, 除非條件為真,否則會引發(fā)異常。(斷言失敗則程序立刻終止)。
  1. List集合

    • 在Dart中,數(shù)組是List對象,因此大多數(shù)人只是將它們稱為List。

    • Dart list文字看起來像JavaScript數(shù)組文字


    //創(chuàng)建一個int類型的list

    List list = [10, 7, 23];

    // 輸出[10, 7, 23]

    print(list);

    // 使用List的構(gòu)造函數(shù),也可以添加int參數(shù),表示List固定長度,不能進(jìn)行添加 刪除操作

    var fruits = new List();

  1. Map集合
  • 一般來說,map是將鍵和值相關(guān)聯(lián)的對象。鍵和值都可以是任何類型的對象。
每個鍵只出現(xiàn)一次,但您可以多次使用相同的值。Dart支持map由map文字和map類型提供。
  • 初始化Map方式一: 直接聲明,用{}表示,里面寫key和value,每組鍵值對中間用逗號隔開。

1.3.3 變量聲明

  1. var

var 可以定義變量,如 var tag = "666" ,這和 JS 、 Kotlin 等語言類似,同時 Dart 也算半個動態(tài)類型語言,同時支持閉包。var可以接收任何類型的變量,但最大的不同是Dart中var變量一旦賦值,類型便會確定,則不能再改變其類型,如:


  var t;

  t = "hi world";

  // 下面代碼在dart中會報(bào)錯,因?yàn)樽兞縯的類型已經(jīng)確定為String,

  // 類型一旦確定后則不能再更改其類型。

  t = 1000;

`Dart` 屬于是**強(qiáng)類型語言** ,但可以用 `var`  來聲明變量,`Dart` 會**自推導(dǎo)出數(shù)據(jù)類型**,所以 `var` 實(shí)際上是編譯期的“語法糖”。
  1. dynamicObject

    Object 是Dart所有對象的根基類,也就是說所有類型都是Object的子類(包括Function和Null),所以任何類型的數(shù)據(jù)都可以賦值給Object聲明的對象.

    dynamicvar一樣都是關(guān)鍵詞,聲明的變量可以賦值任意對象。

    dynamicObject相同之處在于,他們聲明的變量可以在后期改變賦值類型。


    dynamic t;

    Object x;

    t = "hi world";

    x = 'Hello Object';

    //下面代碼沒有問題

    t = 1000;

    x = 1000;

dynamicObject不同的是,dynamic聲明的對象編譯器會提供所有可能的組合,

Object聲明的對象只能使用Object的屬性與方法, 否則編譯器會報(bào)錯。如:


    dynamic a;

    Object b;

    main() {

        a = "";

        b = "";

        printLengths();

    } 

    printLengths() {

        // no warning

        print(a.length);

        // warning:

        // The getter 'length' is not defined for the class 'Object'

        print(b.length);

    }

變量a不會報(bào)錯, 變量b編譯器會報(bào)錯

**`dynamic` 表示動態(tài)類型**, 被編譯后,實(shí)際是一個 `object` 類型,在編譯期間不進(jìn)行任何的類型檢查,而是在運(yùn)行期進(jìn)行類型檢查。

`dynamic`的這個特點(diǎn)使得我們在使用它時需要格外注意,這很容易引入一個運(yùn)行時錯誤.
  1. finalconst

如果您從未打算更改一個變量,那么使用 finalconst,不是var,也不是一個類型。 一個 final 變量只能被設(shè)置一次,兩者區(qū)別在于:const 變量是一個編譯時常量,final變量在第一次使用時被初始化。被final或者const修飾的變量,變量類型可以省略,如:


  //可以省略String這個類型聲明

  final str = "hi world";

  //final String str = "hi world";

  const str1 = "hi world";

  //const String str1 = "hi world";

注意 Dart 下的數(shù)值,在作為字符串使用時,是需要顯式指定的。比如:int i = 0; print("aaaa" + i); 這樣并不支持,需要 print("aaaa" + i.toString()); 這樣使用,這和 Java 與 JS 存在差異,所以在使用動態(tài)類型時,需要注意不要把 number 類型當(dāng)做 String 使用。

注意 Dart 中數(shù)組等于列表,所以 var list = [];List list = new List() 可以簡單看做一樣。

1.3.4 函數(shù)

Dart是一種真正的面向?qū)ο蟮恼Z言,所以即使是函數(shù)也是對象,并且有一個類型Function。這意味著函數(shù)可以賦值給變量或作為參數(shù)傳遞給其他函數(shù),這是函數(shù)式編程的典型特征。

每個應(yīng)用程序都必須有一個頂層main()函數(shù),它可以作為應(yīng)用程序的入口點(diǎn)。該main()函數(shù)返回void并具有List<String>參數(shù)的可選參數(shù)。

  1. 函數(shù)聲明

  bool isNoble(int atomicNumber) {

    return _nobleGases[atomicNumber] != null;

  }

Dart函數(shù)聲明如果沒有顯式聲明返回值類型時會默認(rèn)當(dāng)做dynamic處理,注意,函數(shù)返回值沒有類型推斷:


typedef bool CALLBACK();



//不指定返回類型,此時默認(rèn)為dynamic,不是bool

isNoble(int atomicNumber) {

  return _nobleGases[atomicNumber] != null;

}



void test(CALLBACK cb){

    print(cb());

}

//報(bào)錯,isNoble不是bool類型

test(isNoble);

  1. 對于只包含一個表達(dá)式的函數(shù),可以使用簡寫語法

  bool isNoble (int atomicNumber)=> _nobleGases [ atomicNumber ] != null ; 

  1. 函數(shù)作為變量

  var say = (str){

    print(str);

  };

  say("hi world");

  1. 函數(shù)作為參數(shù)傳遞

  void execute(var callback) {

      callback();

  }

  execute(() => print("xxx"))

  1. 可選參數(shù)

可選的命名參數(shù)

定義函數(shù)時,使用{param1, param2, …},放在參數(shù)列表的最后面,用于指定命名參數(shù)。例如:


  //設(shè)置[bold]和[hidden]標(biāo)志

  void enableFlags({bool bold, bool hidden}) {

      // ...

  }

調(diào)用函數(shù)時,可以使用指定命名參數(shù)。例如:paramName: value


  enableFlags(bold: true, hidden: false);

可選命名參數(shù)在Flutter中使用非常多。

可選的位置參數(shù)

包裝一組函數(shù)參數(shù),用[]標(biāo)記為可選的位置參數(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ù)調(diào)用這個函數(shù)的例子:


  say('Bob', 'Howdy'); //結(jié)果是: Bob says Howdy

下面是用第三個參數(shù)調(diào)用這個函數(shù)的例子:


  say('Bob', 'Howdy', 'smoke signal'); //結(jié)果是:Bob says Howdy with a smoke signal

注意,不能同時使用可選的位置參數(shù)和可選的命名參數(shù)

  1. 默認(rèn)參數(shù)
  • 函數(shù)可以使用=為命名參數(shù)和位置參數(shù)定義默認(rèn)值。默認(rèn)值必須是編譯時常量。如果沒有提供默認(rèn)值,則默認(rèn)值為null。

  • 下面是為命名參數(shù)設(shè)置默認(rèn)值的示例:


      // 設(shè)置 bold 和 hidden 標(biāo)記的默認(rèn)值都為false

    void enableFlags2({bool bold = false, bool hidden = false}) {

      // ...

    }

    // 調(diào)用的時候:bold will be true; hidden will be false.

    enableFlags2(bold: true);

*  下一個示例顯示如何為位置參數(shù)設(shè)置默認(rèn)值:

    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;

    }

    //調(diào)用方式:

    say('Bob', 'Howdy'); //結(jié)果為:Bob says Howdy with a carrier pigeon;

  1. 匿名函數(shù)
  • 大多數(shù)函數(shù)都能被命名為匿名函數(shù),如 main() 或 printElement()。您還可以創(chuàng)建一個名為匿名函數(shù)的無名函數(shù),有時也可以創(chuàng)建lambda或閉包。您可以為變量分配一個匿名函數(shù),例如,您可以從集合中添加或刪除它。

  • 一個匿名函數(shù)看起來類似于一個命名函數(shù) - 0或更多的參數(shù),在括號之間用逗號和可選類型標(biāo)注分隔。

  • 下面的代碼塊包含函數(shù)的主體:


        ([[Type] param1[, …]]) {

            codeBlock;

        };

    /// 下面的示例定義了一個具有無類型參數(shù)的匿名函數(shù)item,該函數(shù)被list中的每個item調(diào)用,輸出一個字符串,該字符串包含指定索引處的值。

        var list = ['apples', 'bananas', 'oranges'];

        list.forEach((item) {

          print('${list.indexOf(item)}: $item');

        });

1.3.5 異步支持

Dart類庫有非常多的返回Future或者Stream對象的函數(shù)。 這些函數(shù)被稱為異步函數(shù):它們只會在設(shè)置好一些耗時操作之后返回,比如像 IO操作。而不是等到這個操作完成。

asyncawait關(guān)鍵詞支持了異步編程,允許您寫出和同步代碼很像的異步代碼。

Future

Future與JavaScript中的Promise非常相似,表示一個異步操作的最終完成(或失?。┘捌浣Y(jié)果值的表示。簡單來說,它就是用于處理異步操作的,異步處理成功了就執(zhí)行成功的操作,異步處理失敗了就捕獲錯誤或者停止后續(xù)操作。一個Future只會對應(yīng)一個結(jié)果,要么成功,要么失敗。

由于本身功能較多,這里我們只介紹其常用的API及特性。還有,請記住,Future 的所有API的返回值仍然是一個Future對象,所以可以很方便的進(jìn)行鏈?zhǔn)秸{(diào)用。

Future.then

為了方便示例,在本例中我們使用Future.delayed 創(chuàng)建了一個延時任務(wù)(實(shí)際場景會是一個真正的耗時任務(wù),比如一次網(wǎng)絡(luò)請求),即2秒后返回結(jié)果字符串"hi world!",然后我們在then中接收異步結(jié)果并打印結(jié)果,代碼如下:


Future.delayed(new Duration(seconds: 2),(){

  return "hi world!";

}).then((data){

  print(data);

});

Future.catchError

如果異步任務(wù)發(fā)生錯誤,我們可以在catchError中捕獲錯誤,我們將上面示例改為:


Future.delayed(new Duration(seconds: 2),(){

  //return "hi world!";

  throw AssertionError("Error"); 

}).then((data){

  //執(zhí)行成功會走到這里 

  print("success");

}).catchError((e){

  //執(zhí)行失敗會走到這里 

  print(e);

});

在本示例中,我們在異步任務(wù)中拋出了一個異常,then的回調(diào)函數(shù)將不會被執(zhí)行,取而代之的是 catchError回調(diào)函數(shù)將被調(diào)用;但是,并不是只有 catchError回調(diào)才能捕獲錯誤,then方法還有一個可選參數(shù)onError,我們也可以它來捕獲異常:


Future.delayed(new Duration(seconds: 2), () {

  //return "hi world!";

  throw AssertionError("Error");

}).then((data) {

  print("success");

}, onError: (e) {

  print(e);

});

Future.whenComplete

有些時候,我們會遇到無論異步任務(wù)執(zhí)行成功或失敗都需要做一些事的場景,比如在網(wǎng)絡(luò)請求前彈出加載對話框,在請求結(jié)束后關(guān)閉對話框。這種場景,有兩種方法,第一種是分別在thencatch中關(guān)閉一下對話框,第二種就是使用FuturewhenComplete回調(diào),我們將上面示例改一下:


Future.delayed(new Duration(seconds: 2),(){

  //return "hi world!";

  throw AssertionError("Error");

}).then((data){

  //執(zhí)行成功會走到這里

  print(data);

}).catchError((e){

  //執(zhí)行失敗會走到這里 

  print(e);

}).whenComplete((){

  //無論成功或失敗都會走到這里

});

Future.wait

有些時候,我們需要等待多個異步任務(wù)都執(zhí)行結(jié)束后才進(jìn)行一些操作,比如我們有一個界面,需要先分別從兩個網(wǎng)絡(luò)接口獲取數(shù)據(jù),獲取成功后,我們需要將兩個接口數(shù)據(jù)進(jìn)行特定的處理后再顯示到UI界面上,應(yīng)該怎么做?答案是Future.wait,它接受一個Future數(shù)組參數(shù),只有數(shù)組中所有Future都執(zhí)行成功后,才會觸發(fā)then的成功回調(diào),只要有一個Future執(zhí)行失敗,就會觸發(fā)錯誤回調(diào)。下面,我們通過模擬Future.delayed 來模擬兩個數(shù)據(jù)獲取的異步任務(wù),等兩個異步任務(wù)都執(zhí)行成功時,將兩個異步任務(wù)的結(jié)果拼接打印出來,代碼如下:


Future.wait([

  // 2秒后返回結(jié)果 

  Future.delayed(new Duration(seconds: 2), () {

    return "hello";

  }),

  // 4秒后返回結(jié)果 

  Future.delayed(new 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é)。

回調(diào)地獄(Callback Hell)

如果代碼中有大量異步邏輯,并且出現(xiàn)大量異步任務(wù)依賴其它異步任務(wù)的結(jié)果時,必然會出現(xiàn)Future.then回調(diào)中套回調(diào)情況。舉個例子,比如現(xiàn)在有個需求場景是用戶先登錄,登錄成功后會獲得用戶ID,然后通過用戶ID,再去請求用戶個人信息,獲取到用戶個人信息后,為了使用方便,我們需要將其緩存在本地文件系統(tǒng),代碼如下:


//先分別定義各個異步任務(wù)

Future<String> login(String userName, String pwd){

  ...

    //用戶登錄

};

Future<String> getUserInfo(String id){

  ...

    //獲取用戶信息

};

Future saveUserInfo(String userInfo){

  ...

  // 保存用戶信息

};

接下來,執(zhí)行整個任務(wù)流:


login("alice","******").then((id){

//登錄成功后通過,id獲取用戶信息   

getUserInfo(id).then((userInfo){

    //獲取用戶信息后保存

    saveUserInfo(userInfo).then((){

      //保存用戶信息,接下來執(zhí)行其它操作

        ...

    });

  });

})

可以感受一下,如果業(yè)務(wù)邏輯中有大量異步依賴的情況,將會出現(xiàn)上面這種在回調(diào)里面套回調(diào)的情況,過多的嵌套會導(dǎo)致的代碼可讀性下降以及出錯率提高,并且非常難維護(hù),這個問題被形象的稱為回調(diào)地獄(Callback Hell)?;卣{(diào)地獄問題在之前JavaScript中非常突出,也是JavaScript被吐槽最多的點(diǎn),但隨著ECMAScript6和ECMAScript7標(biāo)準(zhǔn)發(fā)布后,這個問題得到了非常好的解決,而解決回調(diào)地獄的兩大神器正是ECMAScript6引入了Promise,以及ECMAScript7中引入的async/await。 而在Dart中幾乎是完全平移了JavaScript中的這兩者:Future相當(dāng)于Promise,而async/await連名字都沒改。接下來我們看看通過Futureasync/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對象,所以可以很方便的進(jìn)行鏈?zhǔn)秸{(diào)用” ,如果在then中返回的是一個Future的話,該future會執(zhí)行,執(zhí)行結(jié)束后會觸發(fā)后面的then回調(diào),這樣依次向下,就避免了層層嵌套。

使用async/await消除callback hell

通過Future回調(diào)中再返回Future的方式雖然能避免層層嵌套,但是還是有一層回調(diào),有沒有一種方式能夠讓我們可以像寫同步代碼那樣來執(zhí)行異步任務(wù)而不使用回調(diào)的方式?答案是肯定的,這就要使用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方法添加回調(diào)函數(shù)。

  • await 后面是一個Future,表示等待該異步任務(wù)完成,異步完成后才會往下走;await必須出現(xiàn)在 async 函數(shù)內(nèi)部。

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

其實(shí),無論是在JavaScript還是Dart中,async/await都只是一個語法糖,編譯器或解釋器最終都會將其轉(zhuǎn)化為一個Promise(Future)的調(diào)用鏈。

Stream

Stream 也是用于接收異步事件數(shù)據(jù),和Future 不同的是,它可以接收多個異步操作的結(jié)果(成功或失敗)。 也就是說,在執(zhí)行異步任務(wù)時,可以通過多次觸發(fā)成功或失敗事件來傳遞結(jié)果數(shù)據(jù)或錯誤異常。 Stream 常用于會多次讀取數(shù)據(jù)的異步任務(wù)場景,如網(wǎng)絡(luò)內(nèi)容下載、文件讀寫等。舉個例子:


Stream.fromFutures([

  // 1秒后返回結(jié)果

  Future.delayed(new Duration(seconds: 1), () {

    return "hello 1";

  }),

  // 拋出一個異常

  Future.delayed(new Duration(seconds: 2),(){

    throw AssertionError("Error");

  }),

  // 3秒后返回結(jié)果

  Future.delayed(new Duration(seconds: 3), () {

    return "hello 3";

  })

]).listen((data){

  print(data);

}, onError: (e){

  print(e.message);

},onDone: (){

});

上面的代碼依次會輸出:


I/flutter (17666): hello 1

I/flutter (17666): Error

I/flutter (17666): hello 3

代碼很簡單,就不贅述了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容