0x01 瞭解 Dart
(1)概述
- Dart 是一門標準的計算機程式語言,由 Google 於 2011 年推出,專注於改善構建客戶端應用程式的體驗
- Dart 屬於應用層程式語言,一般執行在 Dart VM 上,特定情況會編譯成 Native Code 執行在硬體上
- Dart 相比 Java 更加易於理解;相比 JavaScript 更加規範和工程化
- Dart 特性:
- 語法便捷
- 物件導向程式設計,資料型別均由
Object
派生 - 強型別,可以型別推斷
- 單程序非同步事件模型
- 可以實現多執行緒,透過獨特的隔離區(Isolate)
- 支援泛型和運算子過載
- 可以簡單實現高效程式碼,透過採用強大的 Future 和 Stream 模型
- 支援 混入(Mixin) 特性,可以更好的實現方法複用
- 支援多平臺
- Dart 應用方向:
- 移動端開發:透過基於 Dart + CPP + Skia 開發的 Flutter 框架實現
- 服務端開發:即 DartVM 命令列程式
- Web 端開發:透過 dart2js 可以將 Dart 編譯為 JavaScript
(2)環境配置
以 Windows 為例
-
安裝 Chocolatey,訪問連結,選中 Individual,複製命令到以管理員身份執行的 PowerShell 中執行
使用命令
choco -v
驗證是否安裝成功 -
使用命令
choco install dart-sdk
安裝 Dart SDK新版本更新後,可以使用命令
choco upgrade dart-sdk
更新 -
如果使用 VSCode 進行開發,可以安裝外掛 Dart 等
-
建立 main.dart 檔案,在其中編寫簡單的 Hello World 程式
void main() { print("Hello World!"); }
main()
是 Dart 程式的入口方法
0x02 基本語法
(1)變數與常量
-
使用
var
或dynamic
宣告一個變數,並可以賦予不同型別的值(類似 JavaScript),使用final
宣告只能賦值一次的變數void main() { var a = 10; print(a); // 10 // a = "Hello"; // 報錯:A value of type 'String' can't be assigned to a variable of type 'int'. var b; b = 10; print(b); // 10 b = "Hello"; print(b); // Hello dynamic c; print(c); // null final d = 10; print(d); // d = 20; // 報錯:The final variable 'd' can only be set once. }
- 宣告時賦值則固定了變數的型別,之後重新賦值時必須為該型別的值
- 未賦值則預設為
null
-
使用
const
宣告一個常量,且使用const
宣告的必須是編譯器常量void main() { const a = 10; print(a); }
const
是final
的超集,const
宣告的常量是在非執行時就能獲取的值int getNumber() { return 10; } void main() { final a = getNumber(); const b = getNumber(); // 報錯:Methods can't be invoked in constant expressions. print(a); print(b); }
(2)內建型別
a. 數字型別
-
Dart 支援兩種數字型別:
-
int
:整數型,範圍為 \(-2^{53}\) 到 \(2^{53}\) 之間,如:var number = 10; var hex = 0xffffff; var bigInt = 1234567890123456789;
-
double
:雙精度浮點數,符合 IEEE 754 標準var float = 3.14; var exponents = 1.42e5;
-
-
int
和double
都是num
的子類 -
num
類還包括:- 基本運算子(如
+
、-
等) - 數學方法(如
abs()
、floor()
等) - 位運算子(如
>>
、&
等)
- 基本運算子(如
b. 字元型別
-
Dart 字串採用 UTF-16 編碼,可以透過單引號
''
或雙引號""
來建立字串var string = "SRIGT";
-
字串中透過
$
使用變數、透過${}
使用表示式var string = "SRIGT"; var output = "My name is $string, I am ${2024 - 2000} years old."; // My name is SRIGT, I am 24 years old.
-
可以透過運算子
+
或將多個字串放在一起,實現字串的連線void main() { var string1 = 'Hello' ' ' 'World'; var string2 = 'Hello' + ' ' + 'World'; assert(string1 == string2); }
-
可以透過三個單引號或三個雙引號實現多行字串
void main() { var string1 = "Hello\n" + "World"; var string2 = """Hello World"""; assert(string1 == string2); }
-
可以透過字首
r
實現一個原始 raw 字串var string = r"Hello\nWorld";
字串與數字型別轉換
-
字串與整型
void main() { // 字串轉換為整型 var number = int.parse("123"); assert(number == 123); // 整型轉換為字串 var string = number.toString(); assert(string == "123"); }
-
字串與浮點型
void main() { // 字串轉換為浮點型 var number = double.parse("1.23"); assert(number == 1.23); // 浮點型轉換為字串 var string = number.toStringAsFixed(2); assert(string == "1.23"); }
c. 布林型別
-
Dart 的布林型別包括
true
和false
-
特別的,Dart 中只有
true
為真,其他均為假if (1) print("true"); // 報錯:Conditions must have a static type of 'bool'. if ("") print("true"); // 報錯:Conditions must have a static type of 'bool'. if (null) print("true"); // 報錯:Conditions must have a static type of 'bool'.
相應的,需要明確比較內容獲取布林值
void main() { assert(1 >= 0); assert("".isEmpty); assert(null == null); }
d. 列表(陣列)型別
-
Dart 中的列表型別 List 和其他程式語言的陣列型別相似
void main() { var list = [1, 2, 3]; assert(list[0] == 1); assert(list.length == 3); }
-
透過在列表字面量前新增
const
可以定義以不變的列表常量var list = const [1, 2, 3];
e. 對映型別
-
Dart 中的對映型別 Map 是一個鍵值對相關的物件,鍵值必須唯一
var teachers = { "Alex": "Math", "Bob": "English", "Charlie": "History" }; var students = new Map(); students["David"] = 101; students["Edward"] = 202; students["Fred"] = 501;
f. Runes 型別
-
Dart 中 Runes 用於在字串中表示 Unicode 字元,採用 UTF-32 編碼
void main() { var runes1 = "\u2665"; // ♥ var runes2 = new Runes("\u2665"); assert(runes1 == String.fromCharCodes(runes2)); }
-
對於
\u
的數字大於 4 位則需要使用大括號{}
var runes = "\u{1f600}"; // 😀
g. Symbol 型別
-
Dart 中的 Symbol 物件代表 Dart 程式中宣告的運算子或識別符號
void main() { var symbol1 = #value; var symbol2 = Symbol("value"); assert(symbol1 == symbol2); }
-
常用於反射,混淆後的程式碼中,Symbol 的名字不會被混淆
(3)運算子
描述 | 運算子 |
---|---|
一元后綴 | ++ -- () [] . ?. |
一元字首 | - ! ~ ++ -- |
乘法 | * / % ~/ |
加法 | + - |
移動位運算 | << >> |
與位運算 | & |
異或位運算 | ^ |
或位運算 | ` |
關係和型別測試 | <= >= < > as is is! |
等式 | == != |
邏輯與 | && |
邏輯或 | ` |
三目運算子 | ?: |
級聯 | .. |
賦值 | = *= /= ~/= %= += -= <<= >>= &= ^= ` |
(4)流程控制
-
條件控制
if
、else if
、else
switch
、case
、default
條件語句中必須明確地使表示式的計算結果為布林值
-
迴圈控制
for
while
do
、while
break
、continue
-
斷言:
assert
(5)異常處理
Dart 中採用 try
測試異常,throw
丟擲異常,catch
捕獲異常,finally
執行清理
void main() {
try {
throw Exception('MyError');
} catch (e) {
print(e); // Exception: MyError
} finally {
print('finally'); // finally
}
}
(6)方法
a. 定義方法
-
Dart 推薦在公開的 APIs 上使用靜態型別定義,也支援忽略型別定義(即使用
dynamic
)bool isOpen() { return true; } isClose() { return false; }
-
Dart 支援胖箭頭語法(類似 JavaScript 的箭頭函式)
int increase(int i) => i + 1;
b. 必要引數和可選引數
-
在方法的引數列表中,預設引數都是必要的,且必要引數在前,可選引數在後
-
可選引數包括:
-
可選命名引數,使用
{}
,如:getValue({a, b}) { return a ?? b; } void main() { print(getValue(a: 1)); // 1 print(getValue(b: 2)); // 2 print(getValue(a: 1, b: 2)); // 1 }
-
可選位置引數,使用
[]
getValue([a, b]) { return "$a ${b ?? ''}"; } void main() { print(getValue(1)); // 1 print(getValue(2)); // 2 print(getValue(1, 2)); // 1 2 }
-
c. 引數預設值
當可選引數是指定的靜態型別時,必須提供預設值,其他引數可以提供預設值
String getTrafficLight({bool red = false, bool green = false}) => green
? "green"
: red
? "red"
: "unknown";
void main() {
print(getTrafficLight()); // unknown
print(getTrafficLight(red: true)); // red
print(getTrafficLight(green: true)); // green
}
d. 匿名方法
-
匿名方法指沒有方法名的方法,又稱 lambda 表示式
-
匿名方法也可以採用胖箭頭語法
void main() { var words = const ["hello", "world"]; words.forEach((item) => print(item.toUpperCase())); }
e. 函式閉包
閉包是一個方法物件,可以隨時隨地訪問自己作用域內的變數
Function closure() {
int number = 0;
return () => number++;
}
void main() {
var counter = closure();
print(counter()); // 0
print(counter()); // 1
print(counter()); // 2
}
0x03 物件導向
(1)定義類
-
Dart 的類是一種開發者自定義的型別,類中包含資料、資料的操作方法等
class Person { var name; Person(this.name); void sayHello() { print("Hello $name."); } } void main() { var student = Person("Alex"); print(student.name); // Alex print(student.runtimeType); // Person student.sayHello(); // Hello Alex. }
-
Dart 的類可以有無數個建構函式,可以過載類中的運算子(詳細檢視(5)構造方法)
-
Dart 的類也是介面,介面是透過抽象類來實現的(詳細檢視(9)介面)
(2)訪問控制
-
Dart 中不存在
public
、private
關鍵字 -
類的成員預設都是公開的(
public
) -
對於需要私有化的成員, 需要在其名稱前加下劃線
class Person { var _name; Person(this._name); void sayHello() { print("Hello $_name."); } } void main() { var student = Person("Alex"); student.sayHello(); // Hello Alex. }
(3)setter 與 getter
用於設定和訪問類私有成員
class Person {
var _name;
Person();
// setter
set name(String name) => _name = name;
// getter
String get name => _name;
}
void main() {
var student = Person();
student.name = "Alex";
print(student.name);
}
(4)靜態成員
類靜態成員屬於類本身,不屬於例項物件
class Person {
var name;
Person(this.name);
static String type = "Human";
}
void main() {
var student = Person("Alex");
// print(student.type); // 報錯:The static getter 'type' can't be accessed through an instance.
print(Person.type); // Human
Person.type = "Student";
print(Person.type); // Student
}
(5)構造方法
- 匿名構造方法只能有一個,語法為:
類名稱()
- 命名構造方法語法為:
類名稱.命名()
class Person {
var attr;
// 匿名建構函式
Person(String name) {
this.attr = {'name': name};
}
// 命名建構函式
Person.withId(int id) {
this.attr = {'id': id};
}
}
void main() {
var p1 = new Person('Bob');
var p2 = new Person.withId(1);
print(p1.attr); // {name: Bob}
print(p2.attr); // {id: 1}
print(p1.runtimeType); // Person
print(p2.runtimeType); // Person
}
-
常量構造方法語法為:
const 類名稱()
- 常用於生成不可改變的物件,此時類中的所有例項屬性必須是
final
class Person { final String name; final int age; Person(this.name, this.age); } void main() { final student = Person('Bob', 20); print(student.name); // Bob print(student.age); // 20 }
- 常用於生成不可改變的物件,此時類中的所有例項屬性必須是
-
工廠構造方法語法為:
factory 類名稱()
- 基於工廠構造方法建立時,不一定需要建立一個類的新例項
class Point { num x, y; Point(this.x, this.y); factory Point.fromJson(Map<String, dynamic> json) => Point(json['x'], json['y']); } void main() { Point point = Point.fromJson({'x': 1, 'y': 2}); print("${point.x}, ${point.y}"); }
(6)模擬函式
-
模擬函式指將類的物件作為方法使用
-
類的模擬函式透過特殊方法
call()
實現class Person { var name; Person(this.name); call() { print("Hello, my name is $name."); } } void main() { var student = Person("Alex"); student(); // Hello, my name is Alex. }
(7)繼承
- 宣告類時,透過
extends
指定被繼承的父類 - 透過
super
繼承父類的構造方法 - 透過
@override
覆寫父類的某個方法
class Person {
var name;
Person(this.name);
void sayHello() {
print("Hello, my name is $name.");
}
}
class Student extends Person {
var school;
Student(String name, this.school) : super(name);
@override
void sayHello() {
print("Hello, my name is $name, and I go to $school.");
}
}
void main() {
var student = Student("Alex", "UCLA");
student.sayHello(); // Hello, my name is Alex, and I go to UCLA.
}
上述繼承屬於單繼承,多繼承需要透過(10)混入實現
(8)抽象類
- 使用
abstract
宣告抽象類,透過implements
實現抽象類 - 抽象類不能例項化、不能有物件
abstract class Person {
void sayHello();
}
class Chinese implements Person {
@override
void sayHello() {
print("你好");
}
}
class American implements Person {
@override
void sayHello() {
print("Hello");
}
}
void main() {
var p;
p = new Chinese();
p.sayHello(); // 你好
p = new American();
p.sayHello(); // Hello
}
(9)介面
介面要求子類實現介面中的所有屬性或方法
class Person {
var name;
Person(this.name);
void sayHello() {}
}
class Student implements Person {
var name;
var school;
Student(this.name, this.school);
void sayHello() {
print("Hello, my name is $name, and I go to $school.");
}
}
void main() {
var student = Student("Alex", "UCLA");
student.sayHello(); // Hello, my name is Alex, and I go to UCLA.
}
(10)混入(Mixin)
-
混入用於實現類的多繼承
-
透過
with
實現混入,有以下兩種方法:-
class C extends A with B
class A { getA() => print("a"); } mixin B { getB() => print("b"); } class C extends A with B { getC() => print("c"); } void main() { C().getA(); C().getB(); C().getC(); }
-
class A with B, C
mixin A { getA() => print("a"); } mixin B { getB() => print("b"); } class C with A, B { getC() => print("c"); } void main() { C().getA(); C().getB(); C().getC(); }
-
(11)列舉
透過 enum
宣告一個列舉
enum Day { monday, tuesday, wednesday, thursday, friday, saturday, sunday }
void main() {
Day today = Day.monday;
print('Today is $today.');
}
(12)泛型
泛型用於在例項化物件後指定類成員的靜態型別
class Attribute<T> {
var value;
Attribute(this.value);
void getType() {
print(value.runtimeType);
}
}
void main() {
Attribute<String> name = Attribute("");
Attribute<int> age = Attribute(0);
name.getType(); // String
age.getType(); // int
}
0x04 非同步程式設計
(1)async / await
Dart 中的非同步透過 async
和 await
實現,與 JavaScript 的 async
和 await
類似
void request() async {
await Future.delayed(Duration(seconds: 1)); // 等待 1s
print("Message from request");
}
void main() {
print("Start");
request();
print("End");
}
(2)Future
-
Dart 中的非同步回撥問題透過 Future 實現,與 JavaScript 的 Promise 類似
Future<String> request() async { await Future.delayed(Duration(seconds: 1)); // 等待 1s // throw "Error"; return "Message from request"; } void main() { request().then((value) => print(value), onError: (e) => print(e)); }
-
當同時執行多個請求時,透過
wait
實現等待所有請求結果都返回後,再執行後續操作,類似 JavaScript 的 Promise.all(當佇列中某個非同步任務丟擲異常時,會中斷執行並直接進入異常處理階段)Future<String> request1() async { await Future.delayed(Duration(seconds: 1)); // 等待 1s return "Message from request1"; } Future<String> request2() async { await Future.delayed(Duration(seconds: 2)); // 等待 2s throw "Error2"; // return "Message from request2"; } Future<String> request3() async { await Future.delayed(Duration(seconds: 3)); // 等待 3s return "Message from request3"; } void main() { Future.wait([request1(), request2(), request3()]).then((value) { print(value); }).catchError((error) { print(error); // Error2 }); }
-
Future.then 返回的依然是 Future 型別,可以採用鏈式呼叫的方法
Future<int> request1() async { await Future.delayed(Duration(seconds: 1)); // 等待 1s return 1; } Future<int> request2(int value) async { await Future.delayed(Duration(seconds: 2)); // 等待 2s return value + 2; } void main() { request1() .then((value) => request2(value)) .then((value) => print(value)) .catchError((error) => print(error)); }
(3)Stream
-
Stream 是一系列非同步事件的序列,類似於一個非同步的 Iterable
-
舉例:讀取文字檔案
import 'dart:io'; import 'dart:convert'; void main() { Stream<List<int>> stream = new File("./text.txt").openRead(); stream.transform(utf8.decoder).listen((data) => print(data)); }
-
詳細內容參考官方文件:
- 使用 stream
- 建立 stream
0x05 外部依賴
\(包 > 庫\)
(1)自定義庫
-
新建 module.dart
class Person { String name; int age; Person(this.name, this.age); void printInfo() { print('Name: $name - Age: $age'); } }
-
在 main.dart 中匯入並使用
Person
類import 'module.dart'; void main() { var student = Person("SRIGT", 18); student.printInfo(); }
對於重複命名的變數或方法,可以使用
as
對匯入的庫設定別名解決
(2)核心庫(內建庫)
- 核心庫統一採用字首
dart:庫名稱
,如import 'dart:core';
- 常用核心庫包括:
- core:小而關鍵的內建功能集
- async:非同步程式設計
- math:數學
- convert:具有用於 JSON 和 UTF-8的轉換器,以及對建立其他轉換器的支援
- io:處理檔案、目錄、程序、套接字、 WebSocket 以及 HTTP 客戶端和伺服器
- html:編寫瀏覽器程式,操作 DOM 中的物件和元素,並訪問 HTML5 API
- 該庫正在被 web 庫取代,web 庫具有可以相容 Wasm 等新特性
- ……
(3)第三方庫引用
-
Dart 採用 Pub 包管理系統,官網連結
-
引用第三方庫步驟如下:
-
建立 pubspec.yaml
name: dart_app dependencies: js: ^0.6.0 intl: ^0.17.0
也可以使用命令
dart pub add
新增包資訊到 pubspec.yaml -
使用命令
dart pub get
獲取包 -
使用字首
package:
匯入包中的庫,如import 'package:js/js.dart' as js;
-
使用命令
dart pub upgrade
升級包
-
-End-