本篇主要針對 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”其實也挺不錯的不是么?
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/289898.html
標籤:其他
