Dart語法詳解(三)——進階篇
- 2020 年 3 月 27 日
- 筆記
前言
在上一篇文章,我們詳細的介紹了Dart語法的一些基本語法,這一這篇文章,我們繼續介紹Dart的語法的相關知識。
異常
不管是Java語言還是Dart語言,都有異常,以及異常的捕獲,但是不同的是dart中的異常都是非檢查異常,方法可以不聲明可能拋出的異常,也不要求捕獲任何異常。
Dart提供了Exception和Error類型以及一些子類型來定義異常。不過,還可以自定義異常,只要拋出非空對象作為異常即可,不要求必須是Exception和Error對象,但是一般來說都是拋出Exception和Error類型。
接下來我們詳細介紹一下。
Exception類型

Error類型

異常拋出
異常的拋出和Java還是很相像的。
//拋出Exception對象 throw new FormatException('格式異常'); //拋出Error對象 throw new NullThrownError(); //拋出任意非null對象 // throw '這是一個異常';
異常捕獲
try { throw new NullThrownError(); // throw new OutOfMemoryError(); } on OutOfMemoryError { //on 指定異常類型 print('沒有記憶體了'); // rethrow; //把捕獲的異常給 重新拋出 } on Error { //捕獲Error類型 print('Unknown error catched'); } on Exception catch (e) { //捕獲Exception類型 print('Unknown exception catched'); } catch (e, s) { //catch() 可以帶有一個或者兩個參數, 第一個參數為拋出的異常對象, 第二個為StackTrace對象堆棧資訊 print(e); print(s); } }
類
類的概念和Java當中類似。 但是也有很大的區別。
- 類的構造
//java中寫法 class P { double x; double y; P(int x, int y) { this.x = x; this.y = y; } } //dart建議寫法 class P { num x; num y; Point(this.x, this.y);
- 重定向構造函數
class P { num x; num y; Point(this.x, this.y); //重定向構造函數,使用冒號調用其他構造函數 P.alongXAxis(num x) : this(x, 0); }
- 類的Get和Set方法
class Rectangle { num left; num top; num width; num height; Rectangle(this.left, this.top, this.width, this.height); num get right => left + width; set right(num value) => left = value - width; num get bottom => top + height; set bottom(num value) => top = value - height; }
- mixins
Mixins是Dart裡面的一個全新概念,簡單來說,用來複用多個類之間的程式碼,減少耦合,換句話來說,可以從中擴展方法(或變數)而不擴展類。
不用不知道,一用起來才發現,真香!
下面具體舉例說明一下:
例如,我們有這麼一個職業關係圖:

image.png
從圖中可以梳理出以下關係:
- 工程師類目,有軟體工程師和建築工程師等,他們共同點都是工程師。
- 教師類目有美術教師,IT教師等,他們共同點都是教師。
如果我們用Java來實現的花
//工作者 abstract class Worker { public abstract void doWork();//工作者需要工作 } //工程師 class Engineer extends Worker { @Override public void doWork() { System.out.println("工程師在工作"); } } //教師 class Teacher extends Worker { @Override public void doWork() { System.out.println("教師在教學"); } } //軟體工程師 class SoftwareEngineer extends Engineer { } //建築工程師 class BuildingEngineer extends Engineer { } //美術教師 class ArtTeacher extends Teacher { } //IT教師 class ITTeacher extends Teacher { }
如果用Dart來實現
//工作者 abstract class Worker { void doWork();//工作者需要工作 } //工程師 class Engineer extends Worker { void doWork() { print('工程師在工作'); } } //教師 class Teacher extends Worker { void doWork() { print('教師在教學'); } } //軟體工程師 class SoftwareEngineer extends Engineer { } //建築工程師 class BuildingEngineer extends Engineer { } //美術教師 class ArtTeacher extends Teacher { } //IT教師 class ITTeacher extends Teacher { }
從以上來看,似乎Java和Dart沒有什麼特別大的區別,因為Dart也是單繼承。
下面,我們再把場景豐富一下。

image.png
通過圖形或表格可以看出,軟體工程師和IT教師都具備修電腦的能力,建築工程師和美術教師都具備手繪的能力,但是這些能力都是他們特有的,不是工程師或者教師具備的能力,所以不能在他們的父類中實現。
如果使用Java,那麼我們自然就想到了使用interface。
interface CanFixComputer { void fixComputer(); } interface CanDesignSoftware { void designSoftware(); } //軟體工程師 class SoftwareEngineer extends Engineer implements CanFixComputer, CanDesignSoftware { @Override public void fixComputer() { System.out.println("修電腦"); } @Override public void designSoftware() { System.out.println("設計軟體"); } } //IT教師 class ITTeacher extends Teacher implements CanFixComputer { @Override public void fixComputer() { System.out.println("修電腦"); } }
但是,我們知道Dart當中沒有interface的概念,但並不意味著這門語言沒有介面,事實上,Dart任何一個類都是介面,你可以實現任何一個類,只需要重寫那個類裡面的所有具體方法。
如果要用Dart來實現的化,只需要將interface修改成abstract class。
但是我們發現,fixComputer這個介面被繼承了兩次,並且兩次的實現都是一樣的,這裡就出現了程式碼重複和冗餘的問題。怎麼辦呢?在java中我們有介面的default實現來解決這個問題(這是一個java8出現的不得已的方案。)
但是,有了mixins之後,這件事就變得不那麼難了。
abstract class CanFixComputer { void fixComputer() { print('修電腦'); } } abstract class CanDesignSoftware { void designSoftware() { print('設計軟體'); } } //軟體工程師 class SoftwareEngineer extends Engineer with CanFixComputer, CanDesignSoftware { } //IT教師 class ITTeacher extends Teacher with CanFixComputer { } main() { ITTeacher itTeacher = new ITTeacher(); itTeacher.doWork(); itTeacher.fixComputer(); SoftwareEngineer softwareEngineer = new SoftwareEngineer(); softwareEngineer.doWork(); softwareEngineer.fixComputer(); softwareEngineer.designSoftware(); }
通過以上程式碼,我們可以看到這裡不用implements,更不是extends,而是with。
每個具有某項特性的類不再需要具體去實現同樣的功能,介面是沒法實現功能的,而通過繼承的方式雖然能實現功能,但已經有父類,同時不是一個父類,又不能多繼承,所以這個時候,Dart的Mixin機制就比Java的介面會高效,開發上層的只需要關心當前需要什麼特性,而開發功能模組的關心具體要實現什麼功能。
關於順序的理解
既然是with,那應該也會有順序的區別。
class First { void doPrint() { print('First'); } } class Second { void doPrint() { print('Second'); } } class Father { void doPrint() { print('Father'); } } class Son1 extends Father with First,Second { void doPrint() { print('Son1'); } } class Son2 extends Father with First implements Second { void doPrint() { print('Son2'); } } main() { Son1 son1 = new Son1(); son1.doPrint(); Son2 son2 = new Son2(); son2.doPrint(); }
輸出的內容是:
Son1 Son2
通過這裡,我們可以看到 可以看到,無論是extends、implements還是mixin,優先順序最高的是在具體類中的方法。
下一個例子:
class First { void doPrint() { print('First'); } } class Second { void doPrint() { print('Second'); } } class Father { void doPrint() { print('Father'); } } class Son1 extends Father with First,Second { } class Son2 extends Father with First implements Second { } main() { Son1 son1 = new Son1(); son1.doPrint(); Son2 son2 = new Son2(); son2.doPrint(); }
輸出的結果:
Second First
其實在Son2中implements只是說要實現他的doPrint()方法,這個時候其實具體實現是First中Mixin了具體實現。 而Mixin的具體順序也是可以從程式碼倒過來看的,最後mixin的優先順序是最高的。
泛型
在Dart當中,有很多的容器對象,在創建對象時都可以定義泛型類型,這一點和Java是一樣的。 例如:
var list = List<String>(); list.add('aaa'); list.add('bbb'); list.add('ccc'); print(list); var map = Map<int, String>(); map[1] = 'aaaa'; map[2] = 'bbbb'; map[3] = 'cccc'; print(map);
和Java的區別
Java中的泛型資訊是編譯時的,泛型資訊在運行時是不存在的。
Dart的泛型類型是固化的,在運行時也有可以判斷的具體類型。
非同步
Future
說到非同步就不得不說到Future。
Future與JavaScript中的Promise非常相似,表示一個非同步操作的最終完成(或失敗)及其結果值的表示。簡單來說,它就是用於處理非同步操作的,非同步處理成功了就執行成功的操作,非同步處理失敗了就捕獲錯誤或者停止後續操作。一個Future只會對應一個結果,要麼成功,要麼失敗。
因為Flutter返回的都是一個Fluter對象,自然就可以採用鏈式方法。
- Future.then 任務執行完後的子任務
- Future.delayed 延遲執行
- Future.catchError 如果非同步任務發生錯誤,我們可以在catchError中捕獲錯誤。
- Future.whenComplete 完成的時候調用。
- Future.wait 等待多個非同步任務都執行結束後才進行一些操作。
Async/await
如果業務邏輯中有大量非同步依賴的情況,將會出現上面這種在回調裡面套回調的情況,過多的嵌套會導致的程式碼可讀性下降以及出錯率提高,並且非常難維護,這個問題被形象的稱為回調地獄(Callback Hell)。
這個問題在JS當中十分的突出,Dart幾乎是同樣的問題,然後把相關方法平移過來。所以功能和用法也幾乎是相同的。
- async用來表示函數是非同步的,定義的函數會返回一個Future對象,可以使用then方法添加回調函數。
- await 後面是一個Future,表示等待該非同步任務完成,非同步完成後才會往下走;await必須出現在 async 函數內部。
只有async方法才能使用await關鍵字調用方法 如果調用別的async方法必須使用await關鍵字。
Stream
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: (){ });
輸出
I/flutter (17666): hello 1 I/flutter (17666): Error I/flutter (17666): hello 3
Isolates-隔離
所有Dart程式碼都在隔離區內運行,而不是執行緒。每個隔離區都有自己的記憶體堆,確保不會從任何其他隔離區訪問隔離區的狀態。
最後
通過這三篇文章,我們基本上把Dart語言的所涉及,所涵蓋的內容都講述了一遍。
這些還是偏理論多一些,語法還是在多實踐,多寫,多練的過程當中來找到其中的真諦。
接下來,我們就開始詳細的展開Flutter的介紹了! Flutter已經是Top20的軟體庫,通過接下來的一系列的文章,希望我和大家一起來學習Flutter,一起進步,一起有所收穫,掌握未來技術主流的主動權!
有什麼好的建議,意見,想法歡迎給我留言!
參考文檔
https://kevinwu.cn/p/ae2ce64/ https://www.jianshu.com/p/06604077d843 《Flutter實戰》