dart簡單入門

- 推薦圖書: Dart程式語言 - 【美】Gilad Bracha
- 建議切一個視窗開啟 DartPad 邊看邊碼,更能加深印象!
寫在前面
dart其實並不算一門非常新的語言,最早版本發行與2011年10月,目標是取代JavaScript成為下一代Web開發語言(很顯然他失敗了)。截止發稿已經發布了2.x版本,與初始版本並無太多改動,保持了相容性。他沒有像同胞哥哥 Golang 在後端佔有一席之地一樣在Web端有所作為,而是不溫不火好幾年,直到 Flutter 的出現才有所改善。
語言本身來說,他是一門 強型別且面向類程式設計 的語言,前端朋友如果長期以JavaScript為主力語言的話可能會有些許不適應,但是相信在短暫的學習之後就會上手,並喊出“真香”。
筆者是實實在在的新手,本文更多也是作為讀書筆記,如果有什麼不足或紕漏,還望各位不吝賜教,定虛心接受。
語言設計理念
萬物皆物件
不同於JavaScript和Java有物件和基本值,在dart的世界裡 任何事物都是物件 ,無一例外。這一點可以讓我們更好地去操作我們的程式碼而不用關心拆裝箱等問題,在一定程度上簡化了構建邏輯,簡化了實現任務。
面向介面程式設計,而非面向實現
關注物件的行為而非內部實現時OOP的核心原則,在dart中這一原則被更好地強調和實現,具體方式有以下幾條:
- 在dart中並不存在宣告的介面,因為 所有的類都可以被當做介面 ,不管其他類是否採用了同樣的底層實現(部分core type除外)
- dart中沒有final方法, 允許幾乎重寫所有方法 (部分內建操作符除外)。
- dart把物件進行抽象封裝, 所有的操作都是通過存取來改變物件狀態的 ,即在其他語言中習以為常的
=
賦值和.
取值在dart中也只是get()
和set()
的語法糖 。
型別時為開發服務
這點我感覺與TypeScript的理念(或是說實現效果)比較相近,畢竟大家覺得強型別語言寫起來舒服的原因就是 清晰的介面 和 便捷的IDE提示 ,往編譯層面來說就是為編譯器提供更好的預判,從而優化效能。dart在型別方面採用了可選型別(sound type),算是在強弱型別之間找到了一個平衡:
- 型別在語法層面時可選的
- 型別對執行時的語義沒有影響。
這意味著你也可以把dart當做一門動態型別語言來編寫,雖然不推薦,但是還是在一定程度上給了開發者很高的自由度,讓其他語言的開發者更快遷移到dart上來。
基本語法
和其他C-style語法相似,你之前學到的知識大概率是能用得上的。如果你寫過Java的話應該會感覺更加親切,因為真的很像。
變數宣告
變數宣告方面其實沒有宣告特別的地方,你可以
給變數增加型別:
var a='1';//不帶型別的變數宣告 String a ='1';//帶型別的變數宣告 const a='1';//常量宣告 final a='1';//在呼叫後被初始化
函式
-
基本的函式可以參考下面,包含了 三目運算子 和 錯誤丟擲 。
maxElement(a){ //你可以在maxElement前面加上返回型別 如 int maxElement(a) //你也可以在a前面標註上型別來宣告a的型別如 maxElement(List<int> a) var currentMax = a.isEmpty ? throw 'Maximal element undefined for empty array' : a[0]; for(var i = 0; i < a.length; i++){ currentMax = max(a[i],currentMax); } return currentMax; }
-
dart還支援了
可選引數
/具名引數
/引數預設值
,當然你也可以使用@require
來標註具名引數中必須傳入的值。void foo1(a,[b=1]){//a為引數,b為可選引數並設定了預設值1 print('${a},${b}');//當僅有變數時也可以簡寫為print('$a,$b') } void foo2(a,{@required b,c=0}){//a為引數,b/c為具名引數,b為必傳,c設定了預設值0 print('${a},${b},${c}'); } void main() { foo1(1, 2);//1,2 foo2(1, b:2);//1,2,0 }
-
支援函式作為引數傳遞,當然也就支援箭頭函式,不同於JavaScript中的箭頭函式和普通函式有this的區別, dart中的箭頭函式就是實實在在的“糖”
void main()=>{};
-
dart同樣支援了閉包,但是和JavaScript的又不大一樣,這裡有個不得不吐的槽:
for (var i = 0; i < 5; i++) { setTimeout(function () { console.log(i);//他會很不和預期地打出五個5 }, 1000 * i); }
這段程式碼應該是面試的常客了,但是類似的程式碼在dart中就顯得很“正常”
import 'dart:async'; void main(){ for (var i = 0; i < 5; i++) { Timer(Duration(seconds: i), () { print(i);//0,1,2,3,4 }); } }
因為在dart中,每次執行for都會給程式碼新建一個 context ,所以在 每次執行的Timer都是一個全新的
i
-
在函式的呼叫上頁支援
..
來級聯操作,它不像.
會返回呼叫後的結果,而是返回被呼叫者本身,這樣的設計給鏈式呼叫多了一種選擇。void main(){ /*1*/"Hello".length.toString();//5 /*2*/"Hello"..length.toString();//Hello /*第2行等價於*/ "Hello".length; "Hello".toString(); }
-
如果函式呼叫
()
也是語法糖?沒錯。他就是.call()
方法的語法糖。所以你可以給物件加上call
方法以使用()
來呼叫方法。class fakeFunc { call() { print('works!'); } } void main() { var a = fakeFunc(); a();//works! }
-
dart同樣支援迭代器語法,支援同步和非同步
naturalsTo(n) sync* {//同步迭代器 var k=0; while(k<n) yield k++; } naturalsTo(n) async* {//非同步迭代器 var k=0; while(k<n) yield await k++; }
-
還有一個不得不提的方法
noSuchMethod
,如果執行時物件沒有對應的方法,將會呼叫這個方法,這使得我們可以由此做一些”Hack“(關於反射和動態程式碼)對於noSuchMethod()的引數只有一個Invocation。關於Invocation的布林屬性所辨認的方法呼叫的句法形式,如下表所示:
x.y | x.y = e | x.y(…) | |
---|---|---|---|
isMethod | false | false | true |
isGetter | true | false | false |
isSetter | false | true | false |
isAccessor | true | true | false |
class A{ @override noSuchMethod(Invocation inv)=>print('fallback!'); } void main(){ var a = A(); //注:更多時候你將會被IDE攔下來 a.x();//'fallback!' }
來寫一個類吧
-
你可以像這樣寫一個類
注意:dart是 不支援函式過載 的
class Point{ var x, y; Point(a,b){ x = a; y = b; } scale(factor){ return new Point(x * factor, y * factor);//在Dart2中你可以無條件省略new關鍵詞 } }
-
你還可以像Java/C++一樣寫個類
class Point{ int x, y; Point(a,b){ this.x = a; this.y = b; } scale(factor){ return new Point(x * factor, y * factor);//在Dart2中你可以無條件省略new關鍵詞 } }
-
當然,dart也有自己的語法糖
class Point{ var x, y; Point(this.x,this.y);//第一個引數將會自動賦給x,第二個引數會自動賦給y scale(factor) =>new Point(x * factor, y * factor);//在Dart2中你可以無條件省略new關鍵詞 operator +(p) => new Poinit(x + p.x,y + p.y);//就像在c++裡一樣,你又可以重寫運算子了! }
-
在dart中還支援了
staic
/final
/const
關鍵詞,還有const構造方法
還有抽象關鍵詞
abstract
,與Java用法相似,在此就不做舉例。class Foo{ static var onlyOne = 1; static const neverChange = 0; final lazyLoad;//推薦大部分變數都設定為final,可以在宣告時初始化,也可以在建構函式初始化(必須二選一)。 get onlyOne(){} set onlyOne(){} Foo(lazyOne):lazyLoad=lazyOne,super(key:lazyOne);//final值也可以通過建構函式後冒號初始化,呼叫父類建構函式super。 const Foo()//所有const關鍵詞賦值中呼叫的只能是常量或字面量,因為所有被const修飾的方法/變數將會在編譯階段被求值。 }
-
dart的類支援了
extends
/with
/implements
,按需取用。- 和其他現代語言一樣,dart支援單繼承多介面,在此基礎上還增加了飽受爭議的mixin(一對多)。
-
mixin
關鍵詞也可以替代class
作為宣告關鍵詞來使用,以專門編寫用於mixin的類,如果此mixin要求物件實現介面則使用on
關鍵詞在最後修飾。 - 可參考 Flutter Dart語法(1):extends 、 implements 、 with的用法與區別
class Bar { var a = 1; m(p, q) {} } mixin Bar2 on Bar { /* * on修飾符要求mixin物件的父類實現了Bar介面 * */ @override void m(q, p) { super.m(q, p); } //... } class Foo1 extends Bar { /* * 1. Dart中的繼承是單繼承 * 2. 建構函式不能繼承 * 3. 子類重寫超類的方法,要用@override * 4. 子類呼叫超類的方法,要用super * 注意:不可以重寫父類中宣告變數的set和get方法(隱式),同時重寫的方法要與父類中方法引數個數相同。 * */ //...other codes } class Foo2 implements Bar { /* * 1. class 就是 interface * 2. 當class被當做interface用時,class中的方法就是介面的方法,需要在子類裡重新實現,在子類實現的時候要加@override * 3. 當class被當做interface用時,class中的成員變數也需要在子類裡重新實現。在成員變數前加@override * 4. 實現介面可以有多個 * */ @override var a = 1; @override m(p, q) {} //...other codes } class Foo3 with Bar { /* * 1. mixins類只能繼承自object * 2. mixins類不能有建構函式 * 3. 一個類可以mixins多個mixins類 * 4. 可以mixins多個類,不破壞Dart的單繼承 * 注意:mixin有點像extends,但是我理解上更像是原型鏈的模型,with後即鏈上物件,採取的是覆蓋原則,即靠後的生效。(甚至可以粗暴地理解為複製程式碼) * */ //...other codes } class Foo4 extends Bar with Bar2 { //通過繼承的方式實現。 //...other codes } class Foo5 extends Foo2 with Bar2 { //通過繼承的方式實現。 //...other codes }
其他語句
- 支援c-style的
if(){}else if(){}else{}
- 支援以
{}
分界的塊狀作用域,不允許塊之間互相遞迴宣告。 - 支援c-style的
for
/while
/do-while
迴圈以及相對應的continue
/break
(還有對應的label也是支援的),還支援了for-in
語法來快捷遍歷集合類 - 支援
switch-case
多條件選擇注意:case的值僅能為常量,且不允許 falls-through (順延至下一個case)
- 支援
assert
語法,優化開發體驗。void main(){ int x = -1; assert(x>0);//在開發模式下報錯 x++; }
異常處理
dart支援最舒適的 throw
/ try{}catch(){}finally{}
語法,不多說!
庫/模組
- dart的模組化比較像C/C++/Java,他是將import的檔案直接注入到 當前檔案全域性作用域 下的。
-
dart引入關鍵詞為
import
,支援使用as
來重新命名,還有deferred as
來延遲載入,也支援使用show
/hide
組合器來限定引入範圍。library lib1; import 'stack.dart' show pop,push; import 'advancedStack.dart' as stack2 hide pop,push; import 'mayUseless.dart' deferred as rarelyUsed;//只有在使用時才會被載入 void main(){ onLoad(loadSuccess)=>loadSuceess?doStuff():makeExcusses(); rarelyUsed.loadLibrary().then(onLoad); }
-
export
用於中轉某個庫需要在當前檔案暴露的方法,但並不是和import配套使用的。同樣支援使用show
/hide
組合器來限定引入範圍。類似於JavaScript裡的
export from
語法export 'stack.dart' hide pop;
-
還有一個對我而言比較陌生的關鍵詞:
part
,有的時候一個庫可能太大,不能方便的儲存在一個檔案當中。Dart允許我們把一個庫拆分成一個或者多個較小的part元件。或者我們想讓某一些庫共享它們的私有物件的時候,我們需要使用part。// A.dart import 'stack.dart' show pop; part 'A_1.dart';
//A_1.dart part of 'A.dart'; void main(){ pop(1); }
這裡我們可以看到,
A_1.dart
是part of 'A.dart'
的檔案,可以理解為,A_1
是A
的一部分。在part test2.dart
中,我們並沒有引入stack.dart
包就直接使用了pop
方法,是因為,在part中,import進來的庫是共享名稱空間的。不是所有的庫都有名稱,但如果使用part來構建庫,那麼庫必須要命名。
library xxx;
每個子part都存放在各自的檔案中。但是它們共享同一作用域,庫的內部名稱空間,以及所有的匯入(import)。
可選型別/泛型
正如之前提到的,dart中的型別是可選的,它僅為開發提供便利,同時也提供了泛型來約束集合內的型別,用法與C++/Java類似。
List<num> list; Map<String,int> map; Map<String,dynamic> map2; Map<dynamic,dynamic> freeMap;//等價於Map freeMap;
你可以使用 is
關鍵字來檢測是否為某個類的例項
void main(){ var v=[1,2,3]; v is Map;//false v is List;//true v is Object;//always true }
還可以使用 as
進行強制型別轉換,這部分與Java的強制型別轉換相似,只能是從子類轉化為父類。
void main(){ 1 as Object;//無意義的轉換,僅作舉例 }
字面量
當然別忘了,他們也都還是物件
- 整數
- 浮點數(遵循IEEE754標準的64-bit浮點數)
- 布林
-
字串(轉義字元用法與JavaScript相似)
void main(){ var man='Mick'; 'It\'s hot today!'; "It's hot today!"; "'It\'s hot today!'$man said."; ''' It! is! hot! today! '''; }
-
列表
[1,2,3]
- Map
{'1':1,'2':2}
注意:雖然和JavaScript中的物件很像但是不能通過
.something
的方式代替['sonething']
- 函式
- Symbol
Async
讓我很喜歡JavaScript的原因之一就是在疏不在堵的非同步呼叫(在Node上更為明顯),這也是很多JavaScript新手的末日。
很高興在dart上能找到幾乎一樣的實現方案: Future
import 'dart:async'; //定義了返回結果值為String型別 Future<String> getData(String category) async { var request = await _httpClient.getUrl(Uri.parse(url)); var response = await request.close(); return await response.transform(utf8.decoder).join(); } void main() async{ var data = await getData('test');//呼叫async函式 //鏈式呼叫,捕獲異常 var future = //調式呼叫 new Future .then(funA(),onError: (e) { handleError(e); }) .then(funB(),onError: (e) { handleError(e); }); }
與es6中的 Promise
用法基本一致,具名引數的加成使得程式碼可讀性更佳,真香!
一些有趣的特性
- 在dart程式中一定存在一個入口函式,和C一樣,我們把他命名為main(),雖然是在面向類程式設計,但是我們依然可以 在檔案的最外層可以直接宣告函式和變數 ,這點和Java倒是不大一樣,但其實幕後工作編譯器都幫我們做好了,我們只管寫就好了!
- dart中的整數長度取決於你的記憶體大小(編譯成目標語言除外
。說你呢JavaScript!) -
final
宣告的值將是採用 懶載入的方式 ,即 只有當get()
被呼叫之前,該變數才會被初始化 。 - 在dart中 只有bool型別的true是true,其他物件都是false ,當然在你寫出來的時候IDE就會拼命給你報錯了。
- dart 不存在內部類 ,類中 不存在私有方法 所有的方法都是可見並大部分都是可以重寫的。私有變數以
_
開頭,如_privateThing
;
一些高階特性
由於本文為入門教程,筆者對於dart也是入門不久,以下高階程式設計部分用的較少,作為補充僅提及,後續可能會更新補充,有興趣的讀者可以自行搜尋學習。
- 反射(Mirror)
- 代理(Proxy)