在專欄的第 2 篇預習文章“[Dart 語言概覽](https://time.geekbang.org/column/article/104071)”中,我們簡單地認識了 Dart 這門優秀的程序語言。那么,Dart 與其他語言究竟有什么不同呢?在已有其他編程語言經驗的基礎上,我又如何快速上手呢?
今天,我們就從編程語言中最重要的組成部分,也就是基礎語法與類型變量出發,一起來學習 Dart 吧。
## Dart 初體驗
為了簡單地體驗一下 Dart,我們打開瀏覽器,直接在[repl.it](https://repl.it/languages/dart)新建一個 main.dart 文件就可以了(當然,你也可以在電腦安裝 Dart SDK,體驗最新的語法)。
下面是一個基本的 hello world 示例,我聲明了一個帶 int 參數的函數,并通過字符串內嵌表達式的方式把這個參數打印出來:
~~~
printInteger(int a) {
print('Hello world, this is $a.');
}
main() {
var number = 2019;
printInteger(number);
}
~~~
然后,在編輯器中點擊“run”按鈕,命令行就會輸出:
~~~
Hello world, this is 2019.
~~~
和絕大多數編譯型語言一樣,Dart 要求以 main 函數作為執行的入口。
在知道了如何簡單地運行 Dart 代碼后,我們再來看一下 Dart 的基本變量類型。
## Dart 的變量與類型
在 Dart 中,我們可以用 var 或者具體的類型來聲明一個變量。當使用 var 定義變量時,表示類型是交由編譯器推斷決定的,當然你也可以用靜態類型去定義變量,更清楚地跟編譯器表達你的意圖,這樣編輯器和編譯器就能使用這些靜態類型,向你提供代碼補全或編譯警告的提示了。
在默認情況下,未初始化的變量的值都是 null,因此我們不用擔心無法判定一個傳遞過來的、未定義變量到底是 undefined,還是燙燙燙而寫一堆冗長的判斷語句了。
Dart 是類型安全的語言,并且所有類型都是對象類型,都繼承自頂層類型 Object,因此一切變量的值都是類的實例(即對象),甚至數字、布爾值、函數和 null 也都是繼承自 Object 的對象。
Dart 內置了一些基本類型,如 num、bool、String、List 和 Map,在不引入其他庫的情況下可以使用它們去聲明變量。下面,我將逐一和你介紹。
### num、bool 與 String
作為編程語言中最常用的類型,num、bool、String 這三種基本類型被我放到了一起來介紹。
**Dart 的數值類型 num,只有兩種子類**:即 64 位 int 和符合 IEEE 754 標準的 64 位 double。前者代表整數類型,而后者則是浮點數的抽象。在正常情況下,它們的精度與取值范圍就足夠滿足我們的訴求了。
~~~
int x = 1;
int hex = 0xEEADBEEF;
double y = 1.1;
double exponents = 1.13e5;
int roundY = y.round();
~~~
除了常見的基本運算符,比如 +、-、\*、/,以及位運算符外,你還能使用繼承自 num 的 abs()、round() 等方法,來實現求絕對值、取整的功能。
實際上,你打開官方文檔或查看源碼,就會發現這些常見的運算符也是繼承自 num:
:-: 
圖 1 num 中的運算符
如果還有其他高級運算方法的需求 num 無法滿足,你可以試用一下 dart:math 庫。這個庫提供了諸如三角函數、指數、對數、平方根等高級函數。
**為了表示布爾值,Dart 使用了一種名為 bool 的類型**。在 Dart 里,只有兩個對象具有 bool 類型:true 和 false,它們都是編譯時常量。
Dart 是類型安全的,因此我們不能使用**if(nonbooleanValue)**或**assert(nonbooleanValue)**之類的在 JavaScript 可以正常工作的代碼,而應該顯式地檢查值。
如下所示,檢查變量是否為 0,在 Dart 中需要顯示地與 0 做比較:
~~~
// 檢查是否為 0.
var number = 0;
assert(number == 0);
// assert(number); 錯誤
~~~
**Dart 的 String 由 UTF-16 的字符串組成。**和 JavaScript 一樣,構造字符串字面量時既能使用單引號也能使用雙引號,還能在字符串中嵌入變量或表達式:你可以使用**${express}**把一個表達式的值放進字符串。而如果是一個標識符,你可以省略{}。
下面這段代碼就是內嵌表達式的例子。我們把單詞’cat’轉成大寫放入到變量 s1 的聲明中:
~~~
var s = 'cat';
var s1 = 'this is a uppercased string: ${s.toUpperCase()}';
~~~
為了獲得內嵌對象的字符串,Dart 會調用對象的**toString()**方法。而常見字符串的拼接,Dart 則通過內置運算符“+”實現。比如,下面這條語句會如你所愿聲明一個值為’Hello World!'的字符串:
~~~
var s2 = 'Hello' + ' ' + 'World!' ;
~~~
對于多行字符串的構建,你可以通過三個單引號或三個雙引號的方式聲明,這與 Python 是一致的:
~~~
var s3 = """This is a
multi-line string.""";
~~~
### List 與 Map
其他編程語言中常見的數組和字典類型,在 Dart 中的對應實現是 List 和 Map,統稱為集合類型。它們的聲明和使用很簡單,和 JavaScript 中的用法類似。
接下來,我們一起看一段代碼示例。
* 在代碼示例的前半部分,我們聲明并初始化了兩個 List 變量,在第二個變量中添加了一個新的元素后,調用其迭代方法依次打印出其內部元素;
* 在代碼示例的后半部分,我們聲明并初始化了兩個 Map 變量,在第二個變量中添加了兩個鍵值對后,同樣調用其迭代方法依次打印出其內部元素。
~~~
var arr1 = ["Tom", "Andy", "Jack"];
var arr2 = List.of([1,2,3]);
arr2.add(499);
arr2.forEach((v) => print('${v}'));
var map1 = {"name": "Tom", 'sex': 'male'};
var map2 = new Map();
map2['name'] = 'Tom';
map2['sex'] = 'male';
map2.forEach((k,v) => print('${k}: ${v}'));
~~~
容器里的元素也需要有類型,比如上述代碼中 arr2 的類型是**List**,map2 的類型則為**Map**。Dart 會自動根據上下文進行類型推斷,所以你后續往容器內添加的元素也必須遵照這一類型。
如果編譯器自動推斷的類型不符合預期,我們當然可以在聲明時顯式地把類型標記出來,不僅可以讓代碼提示更友好一些,更重要的是可以讓靜態分析器幫忙檢查字面量中的錯誤,解除類型不匹配帶來的安全隱患或是 Bug。
以上述代碼為例,如果往 arr2 集合中添加一個浮點數**arr2.add(1.1)**,盡管語義上合法,但編譯器會提示類型不匹配,從而導致編譯失敗。
和 Java 語言類似,在初始化集合實例對象時,你可以為它的類型添加約束,也可以用于后續判斷集合類型。
下面的這段代碼,在增加了類型約束后,語義是不是更清晰了?
~~~
var arr1 = <String>['Tom', 'Andy', 'Jack'];
var arr2 = new List<int>.of([1,2,3]);
arr2.add(499);
arr2.forEach((v) => print('${v}'));
print(arr2 is List<int>); // true
var map1 = <String, String>{'name': 'Tom','sex': 'male',};
var map2 = new Map<String, String>();
map2['name'] = 'Tom';
map2['sex'] = 'male';
map2.forEach((k,v) => print('${k}: ${v}'));
print(map2 is Map<String, String>); // true
~~~
### 常量定義
如果你想定義不可變的變量,則需要在定義變量前加上 final 或 const 關鍵字:
* const,表示變量在編譯期間即能確定的值;
* final 則不太一樣,用它定義的變量可以在運行時確定值,而一旦確定后就不可再變。
聲明 const 常量與 final 常量的典型例子,如下所示:
~~~
final name = 'Andy';
const count = 3;
var x = 70;
var y = 30;
final z = x / y;
~~~
可以看到,const 適用于定義編譯常量(字面量固定值)的場景,而 final 適用于定義運行時常量的場景。
## 總結
通過上面的介紹,相信你已經對 Dart 的基本語法和類型系統有了一個初步的印象。這些初步的印象,有助于你理解 Dart 語言設計的基本思路,在已有編程語言經驗的基礎上快速上手。
而對于流程控制語法:如**if-else、for**、**while**、**do-while**、**break/continue、switch-case、assert**,由于與其他編程語言類似,在這里我就不做一一介紹了,更多的 Dart 語言特性需要你在后續的使用過程中慢慢學習。在我們使用 Dart 的過程中,[官方文檔](https://api.dartlang.org/stable/2.2.0/index.html)是我們最重要的學習參考資料。
恭喜你!你現在已經邁出了 Dart 語言學習的第一步。接下來,我們簡單回顧一下今天的內容,以便加深記憶與理解:
* 在 Dart 中,所有類型都是對象類型,都繼承自頂層類型 Object,因此一切變量都是對象,數字、布爾值、函數和 null 也概莫能外;
* 未初始化變量的值都是 null;
* 為變量指定類型,這樣編輯器和編譯器都能更好地理解你的意圖。
## 思考題
對于集合類型 List 和 Map,如何讓其內部元素支持多種類型(比如,int、double)呢?又如何在遍歷集合時,判斷究竟是何種類型呢?
- 前言
- 開篇詞
- 預習篇
- 01丨預習篇 · 從0開始搭建Flutter工程環境
- 02丨預習篇 · Dart語言概覽
- Flutter開發起步
- 03丨深入理解跨平臺方案的歷史發展邏輯
- 04丨Flutter區別于其他方案的關鍵技術是什么?
- 05丨從標準模板入手,體會Flutter代碼是如何運行在原生系統上的
- Dart語言基礎
- 06丨基礎語法與類型變量:Dart是如何表示信息的?
- 07丨函數、類與運算符:Dart是如何處理信息的?
- 08丨綜合案例:掌握Dart核心特性
- Flutter基礎
- 09丨Widget,構建Flutter界面的基石
- 10丨Widget中的State到底是什么?
- 11丨提到生命周期,我們是在說什么?
- 12丨經典控件(一):文本、圖片和按鈕在Flutter中怎么用?
- 13丨ListView在Flutter中是什么?
- 14 丨 經典布局:如何定義子控件在父容器中排版位置?
- 15 丨 組合與自繪,我該選用何種方式自定義Widget?
- 16 丨 從夜間模式說起,如何定制不同風格的App主題?
- 17丨依賴管理(一):圖片、配置和字體在Flutter中怎么用?
- 18丨依賴管理(二):第三方組件庫在Flutter中要如何管理?
- 19丨用戶交互事件該如何響應?
- 20丨關于跨組件傳遞數據,你只需要記住這三招
- 21丨路由與導航,Flutter是這樣實現頁面切換的
- Flutter進階
- 22丨如何構造炫酷的動畫效果?
- 23丨單線程模型怎么保證UI運行流暢?
- 24丨HTTP網絡編程與JSON解析
- 25丨本地存儲與數據庫的使用和優化
- 26丨如何在Dart層兼容Android-iOS平臺特定實現?(一)
- 27丨如何在Dart層兼容Android-iOS平臺特定實現?(二)
- 28丨如何在原生應用中混編Flutter工程?
- 29丨混合開發,該用何種方案管理導航棧?
- 30丨為什么需要做狀態管理,怎么做?
- 31丨如何實現原生推送能力?
- 32丨適配國際化,除了多語言我們還需要注意什么
- 33丨如何適配不同分辨率的手機屏幕?
- 34丨如何理解Flutter的編譯模式?
- 35丨HotReload是怎么做到的?
- 36丨如何通過工具鏈優化開發調試效率?
- 37丨如何檢測并優化FlutterApp的整體性能表現?
- 38丨如何通過自動化測試提高交付質量?
- Flutter綜合應用
- 39丨線上出現問題,該如何做好異常捕獲與信息采集?
- 40丨衡量FlutterApp線上質量,我們需要關注這三個指標
- 41丨組件化和平臺化,該如何組織合理穩定的Flutter工程結構?
- 42丨如何構建高效的FlutterApp打包發布環境?
- 43丨如何構建自己的Flutter混合開發框架(一)?
- 44丨如何構建自己的Flutter混合開發框架(二)?
- 結束語
- 結束語丨勿畏難,勿輕略