本篇主要針對 Flutter 裡 Dart 的一些語法糖實現進行解析,讓你明顯簡單宣告的關鍵字背後,Dart 究竟做了什麼?
如下圖所示,起因是昨天在群裡看到一個很基礎的問題,問: “這段程式碼為什麼不能對 user
進行判空?” 。
其實這個問題很簡單:
- 1、在 Dart 的 Sound Null Safety 下宣告瞭非空的物件是不需要判空;(你想判斷也行,會有警告⚠️)
- 2、使用了
late
關鍵字宣告的物件,如果在沒有初始化的時候直接訪問,就會報錯;
所以這個問題其實很簡單,只需要改成 User? user
就可以簡單解決,但是為什麼本來不可以為空的物件,加了 late
就可以不馬上初始化呢?
late
首先如下圖所示,我們寫一段簡單的程式碼,通過 late
宣告瞭一個 playerAnimation
物件,然後在執行程式碼之後,通過 dump_kernel.dart
對編譯後的 app.dill
進行提取。
如下圖所示,通過提取編譯後的程式碼,可以看到 playerAnimation
其實被轉變成了 Animation?
的可空物件,而當 playerAnimation
被呼叫時,通過 get playerAnimation()
進行判斷,如果此時 playerAnimation == null
, 直接就丟擲 LateError
錯誤。
所以當我們訪問 late
宣告的物件是,如果物件還沒有初始化,就會返回一個異常。
typedef
介紹完 late
接下介紹下 typedef
, typedef
在 Dart 2.13 開始可以用於新的型別別名功能 ,比如:
// Type alias for functions (existing)
typedef ValueChanged<T> = void Function(T value);
// Type alias for classes (new!)
typedef StringList = List<String>;
// Rename classes in a non-breaking way (new!)
@Deprecated("Use NewClassName instead")
typedef OldClassName<T> = NewClassName<T>;
複製程式碼
那麼 typedef
是如何工作的?如下圖所示,可以看到 _getDeviceInfo
方法在編譯後,其實直接就被替換為 List<String>
,所以實際上 StringList
是不參與到編譯後的程式碼執行,所以也不會對程式碼的執行效率有什麼影響。
再舉個例子,如下圖所示,可以看到通過 SelectItemChanged
宣告的 selectItemChanged
,在編譯後其實直接就是 final field (dynamic) →? void selectItemChanged;
。
接著我們通過 Dart 的 tear-off
來看另外一個現象,如下圖所示,可以看到我們從一個任意物件中 x
中提取了 toString
方法,通過閉包,就可以像呼叫常規例項一樣呼叫 x
。
如果在一個物件上呼叫函式並省略了括號, Dart 稱之為
”tear-off”
:一個和函式使用同樣引數的閉包,當呼叫閉包的時候會執行其中的函式,比如:names.forEach(print);
等同於names.forEach((name){print(name);});
那麼編譯後的 getToString
方法會是怎麼樣的?
如下圖所示,可以看到 getToString
方法在編譯後成了一個 static
的靜態方法,並且 ToStringFn
也沒有實際參與執行,也是被替換成了對應的 ()-> core:String
。
所以對於編譯後的程式碼,typedef
並不會對效能和執行結果產生影響。
extension
在 Dart 裡,通過 extension
可以很便捷地為物件進行擴充,那 extension
關鍵字是如何在原物件基礎上實現擴充呢?
如下圖所示,我們宣告瞭一個 Cat
的列舉,並且對 Cat
進行了擴充,從而為列舉的每個值賦值,並且加了 talk
方法。
如下圖所示,編譯後 Cat
裡的列舉值對應變成了一個 static final
的固定地址,並且 CatExtension
裡的 talk
和 value
也被指向了新的位置。
找到對應的實現處發現,CatExtension
裡的 name
和 talk
都變了所在檔案下的 static method
,並且 talk
方法是先定義了 method
實現,之後再通過 tearoff
的 get
實現去呼叫,基本上所有在 extension
裡定義的方法都會有對應的 method
和 tearoff
。
如下圖所示,在 Cat
的使用處,編譯後可以看到 cat.talk()
其實就是執行了 main::CatExtension|talk
。
async / await
最後聊聊 async / await
,我們都知道這是 Dart 裡 Future
的語法糖,那這個語法糖在編譯後是如何執行的呢?
可以看到,loadmore
方法在編譯後被新增了很多的程式碼,其中定義了一個 _Future<void> async_future
並在最後返回,同時我們需要執行的程式碼被包裝到 async_op
裡去執行,而這裡有一個很關鍵的地方就是,async_op
對執行的內容進行了 try catch
的操作,並通過 _completeOnAsyncError
返回。
這也是為什麼我們在外部對一個 Future
進行 try catch
不能捕獲異常的原因,所以如下圖所示,對於 Future
需要通過 .onError((error, stackTrace) => null)
的方式來對異常進行捕獲處理。
明白了這些關鍵字背後的實現後,相信可以更好地幫助你在 Flutter 的日常開發中更優雅地組織你的程式碼,從而避免很多不必須要的問題。
當然,如果用不上,拿去面試“裝X”其實也挺不錯的不是麼?