近期公司準備啓動新項目,經過技術團隊一番調研,決定採用 Flutter 來開發第一版App,故作此文,常來回顧溫習。由於項目採用敏捷開發模式,故本文主要總結和記錄 Dart 常用語法,更多高級和生僻用法將在後面開發過程中不定期更新。
First of all
在我們正式接觸 Dart 語法之前,需要銘記以下內容,這將會對後續 Dart 語法的學習、理解和應用有很大幫助:
- 萬物皆對象, 每個對象都是一個類的實例。在 Dart 中,甚至連數字、方法和
null
都是對象,並且所有的對象都繼承於 Object 類。 - 儘管 Dart 語言是一種強類型語言,但你在類型聲明時仍然可以不指定類型,因爲 Dart 可以自動進行類型推斷。如在代碼
var number = 5;
中,number
變量的類型就被推斷爲int
,當你並不想顯式地聲明類型時,你可以使用特有的類型 dynamic 來標識。 - Dart 語言同樣支持泛型,如
List<int>
、List<dynamic>
(同 Java 中的List<Object>
)。 - Dart 語言支持頂級方法(即不與類綁定的方法,如上的
main
方法),以及綁定類和實例的方法(分別對應靜態方法和實例方法),而且還支持方法嵌套(同 Python 和 JS)。 - 同樣,Dart 還支持頂級變量,以及在類中定義的變量(如靜態變量和實例變量)。
- 和 Java 不同的是,Dart 沒有
public
、protected
以及private
關鍵字。它通過給變量或者方法名加上_
前綴來將其標識爲私有域。
變量
你可以通過 var
或者特定的類型來修飾變量,如果變量的類型不是固定的,也可以通過 dynamic
或者 Object
來修飾。如:
// 類型推斷
var name = 'Bob';
// 指定類型
String name = 'Bob';
// 類型不侷限於string
dynamic name = 'Bob';
默認值
Dart 中聲明時如果未初始化,會被自動初始化爲null
:
int lineCount;
assert(lineCount == null);
注:
assert
只在開發環境有效,在實際的生產環境無效。
final 與 const
final
和 const
標識該變量只能被賦值一次,區別在於 final
是運行時賦值,const
是編譯時賦值。
final String nickname = 'Jack';
// final修飾時, 可省略類型聲明
final name = 'Jack';
const
不僅可以用來聲明變量,還能用來創建常量值,如:
// 此時不能在對列表a進行更新操作
var a = const [1, 2];
// 注意以下 const 修飾的區別
var foo = const [];
const baz = []; // Equivalent to `const []`
...
foo = [1, 2, 3]; // Was const []
baz = [42]; // Error: Constant variables can't be assigned a value.
數據類型
Dart 語言支持的數據類型與 Java 類似,但也有所不同,下面我們來按照“求同存異”的法則來分別認識它們。
數字類型
Dart 中的數字類型只有兩種:Int 和 double。與 Java 不同的是,它並沒有提供 float 類型。例如,我們可以通過以下方式來定義數字:
// 定義整型
var x = 123;
var hex = 0xDEADBEEF;
// 定義雙精度類型
var y = 1.199;
var exponents = 1.42e5;
再來看下常用的數據轉換該如何執行:
// String -> int
var a = int.parse('1');
assert(a == 1);
// String -> double
var b = double.parse('1.1');
assert(b == 1.1);
// int -> String
String c = 1.toString();
assert(c == '1');
// double -> String
String d = 3.14159.toStringAsFixed(2);
assert(d == '3.14');
int
類型可以執行傳統的按位移位( <<
,>>
),AND( &
)和OR( |
)運算符,如:
assert((3 << 1) == 6); // 0011 << 1 == 0110
assert((3 >> 1) == 1); // 0011 >> 1 == 0001
assert((3 | 4) == 7); // 0011 | 0100 == 0111
字符串類型
字符串的聲明可以通過單引號或者雙引號來引入:
var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to use the other delimiter.";
字符串拼接可以通過 +
或者 換行
來實現,如:
var a = '123' + '456'
var b = '123'
'456';
assert(a == b)
print(a);
// Out: 123456
另外,可以通過三重引號來修飾多行字符串(維持特定格式):
var s1 = '''
You can create
multi-line strings like this one.
''';
var s2 = """This is also a
multi-line string.""";
布爾類型
Dart 中的 bool
類型可以通過 if-else
或者是 assert
來檢查,如:
// Check for an empty string.
var fullName = '';
assert(fullName.isEmpty);
// Check for zero.
var hitPoints = 0;
assert(hitPoints <= 0);
// Check for null.
var unicorn;
assert(unicorn == null);
// Check for NaN.
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);
數組類型
在 Dart 語言中,數組即列表,列表即數組。
void main() {
var list = [1, 2, 3];
list.add(4);
list.remove(3);
list[1] = 5;
var last = list[list.length - 1];
print('list is $list');
print('last is $last');
}
另外,你可以通過 …
或者 ...?
(避免空異常) 來批量插入多個元素:
var list1 = [1, 2, 3];
var list2 = [0, ...list1];
var list3;
var list24 = [0, ...?list3];
除此以外,還可以通過 if
或者 for
條件運算符來插入元素:
var nav = [
'Home',
'Furniture',
'Plants',
if (promoActive) 'Outlet'
];
var listOfInts = [1, 2, 3];
var listOfStrings = [
'#0',
for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');
Set 和 Map
Dart中的Set是無序的唯一項的集合。要創建一個空集合,有以下兩種方式:
var names = <String>{};
Set<String> names = {}; // This works, too.
// var names = {}; // Creates a map, not a set.
通過以下方式創建常量 Set:
final constantSet = const {
'fluorine',
'chlorine',
'bromine',
'iodine',
'astatine',
};
// constantSet.add('helium'); // Uncommenting this causes an error.
至於 Map 存儲的是鍵值對數據,key
和 value
可以是任何類型。可以通過以下方式創建一個 Map:
var gifts = Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
// 或者通過以下方式
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
函數
Dart 是一門完全面向對象的語言,即使它的函數也是一個對象,並且有自己的類型—— Function
。這就意味着函數可以被賦值爲一個變量,或者作爲一個參數在其他函數之間傳遞。
對於只包含一個表達式的方法體,你也可以使用 =>
的簡寫形式,如:
void say() {
print('123');
}
// 該寫法與以上有同樣效果
void say2() => print('123');
此外,函數同樣支持類型推斷,如:
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
// 與下面的效果一樣
isNoble(atomicNumber) => _nobleGases[atomicNumber] != null;
可選參數
與 kotlin 語言類似,Dart 的 函數同樣支持可選參數這一功能,主要分爲命名可選參數和位置可選參數,它們不能在一個函數中同時使用。但它們都支持默認參數設置。
-
命名可選參數
命名可選參數通過花括號
{}
來指定可選的命名參數,如:void main() { hello(name: 'Tom'); } void hello({@required String name='Jack', int age=18}) { print('Hello, my name is $name, i\'m $age years old.'); }
我們可以爲命名可選參數設置默認值,在使用的時候如果不指定該可選參數的值,那麼它就會自動使用默認值。另外,我們可以通過
@required
來標註那些不想被忽略的參數。 -
位置可選參數
顧名思義,可選位置參數就允許我們在使用某個函數時可以忽略某個參數,如:
void main() { sayHello('Tom', 19); } void sayHello(String name, int age, [String hobby = null]) { var result = 'Hello, my name is $name, i\'m $age years old'; if (hobby != null) { result += ', my bobby is $hobby.'; } print(result); }
匿名函數
不管什麼語言,好像都能看到匿名函數的身影,我們可以通過以下方式來簡單聲明一個匿名函數:
var loge = (msg) => Logger.print(Logger.Error, msg);
void main() {
loge("there has some errors");
}
Dart 中 list 就提供了匿名函數 —— forEach
,如:
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
常用操作符
Dart 提供了很多功能強大的操作符,這裏列舉幾個常用的:
-
類型判斷
如:
is
相當於 Java 中的instanceof
-
整除
如:
a ~/ b
等價於(a /b) as int
-
非空調用
如:
a?.b
相當於a == null ? null : a.b
-
三目運算
如:
a??b
相當於a == null ? b : a
-
三目賦值運算
如:
a ??= b
相當於a = a == null ? b : a
-
類型轉換
如:
a as int
相當於 Java 中的(int) a
-
級聯操作符
級聯操作符用**
..
** 來表示,主要用來對一個對象進行連續操作,有點類似 kotlin 中的 apply 和 let,如:var button = Button(this); button.text = 'Confirm'; button.color = Color.parse("#f3f355"); button.onClick.listen((v) => toast('Confirmed')); // 等價於下面寫法 Button(this) ..text = 'Confirm' // Use its members. ..color = Color.parse("#f3f355") ..onClick.listen((v) => toast('Confirmed'));
異常處理
拋異常
在 Dart 中可以拋出非空對象(不僅僅是 Exception
或 Error
)作爲異常。拋出異常的方式很簡單,只需要這樣:
throw FormatException('Expected at least 1 section');
throw 'Out of llamas!';
void distanceTo(Point other) => throw UnimplementedError();
捕獲異常
我們可以通過 try-on
來捕獲某個特定異常,如:
try {
breedMoreLlamas();
} on OutOfLlamasException {
buyMoreLlamas();
}
當然,你可以拋出多個類型的 Exception
,但是由第一個 catch
到的分句來處理
如果 catch
分句沒有指定類型,那麼它可以處理任何類型的異常:
try {
throw 'This a Exception!';
} on Exception catch(e) {
print('Unknown exception: $e');
} catch(e) {
print('Unknown type: $e');
}
無論是否發生異常,如果想要執行某段代碼,可以通過 finally
實現:
try {
throw 'This a Exception!';
} catch(e) { // 如果去掉 catch 語句,那麼異常將會在finally代碼塊之後傳遞。
print('Catch Exception: $e');
} finally {
print('Close');
}
類和對象
Dart 中,所有的對象都是類的實例,並且所有的類都是 Object
的子類。
類的定義和構造函數
類的定義用 class
關鍵字,如果未顯式定義構造函數,會默認一個空的構造函數,這一點與 Java 不謀而合。另外,Dart 還提供 命名構造函數這一功能,格式爲:Class.costructorName(var params)
。這種用法在某些場景下很實用,比如:
class Person{
String name;
int age;
bool sex;
String hobby;
Person(this.name, this.age, this.sex, this.hobby);
/// 命名構造函數
Person.fromJson(Map json){
print("Person constructor...");
this.name = json['name'];
this.age = json['age'];
this.sex = json['sex'];
this.hobby = json['hobby'];
}
}
通過命名構造函數,我就可以直接通過多種方式來生成當前類的實例,這裏通過 Json 格式數據生成 Person
對象。另外,如果構造函數只是簡單的參數傳遞,也可以直接在構造函數後加 :
來對參數進行賦值,多個參數可通過 ,
相連:
class Point {
num x;
num y;
num z;
Point(this.x, this.y, z) { //第一個值傳遞給this.x,第二個值傳遞給this.y
this.z = z;
}
Point.fromeList(var list): //命名構造函數,格式爲Class.name(var param)
x = list[0], y = list[1], z = list[2]{//使用冒號初始化變量
}
//當然,上面句你也可以簡寫爲:
//Point.fromeList(var list): this(list[0], list[1], list[2]);
String toString() => 'x:$x y:$y z:$z';
}
如果你要創建一個不可變的對象,你可以定義編譯時常量對象
需要在構造函數前加 const
:
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y); // 常量構造函數
static final ImmutablePoint origin = const ImmutablePoint(0, 0); // 創建一個常量對象不能用new,要用const
}
屬性訪問器
即我們常說的 Setter/Getter
,主要是用來讀寫一個屬性的方法。每個字段都對應一個隱式的 Getter
和 Setter
,但是調用的時候是 obj.name
,而不是 obj.name()
。當然,你可以通過 get
和 set
關鍵字擴展功能
來實現自己的 Setter/Getter
, 如果字段爲 final
或者 const
的話,那麼它只能有一個 getter
方法。如:
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// Define two calculated properties: right and bottom.
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}
void main() {
var rect = Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
工廠構造函數
並不是任何時候我們都需要創建一個新的對象,如:可以返回一個已經緩存好的對象或者該類的子類對象。我們以返回該類的一個緩存對象爲例:
class Logger {
final String name;
bool mute = false;
// _cache is library-private, thanks to
// the _ in front of its name.
static final Map<String, Logger> _cache =
<String, Logger>{};
factory Logger(String name) {
return _cache.putIfAbsent(
name, () => Logger._internal(name));
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
注意:factory 構造函數不能通過
this
關鍵字來獲取類的成員。
抽象類
Dart 中並沒有 interface
關鍵字,只有 abstract
來修飾"抽象類",但是,這裏的抽象類既可以被繼承(extends),也可以被實現(implements)。如:
abstract class Person { // 可以不用 abstract 修飾,如果加上 abstract 的話 Person 不能被實例化
String greet(who);
}
class Student implements Person {
String name;
Student(this.name);
String greet(who) => 'Student: I am $name!';
}
class Teacher implements Person {
String name;
Teacher(this.name);
String greet(who) => 'Teacher: I am $name!';
}
void main() {
Person student = new Student('Wang');
Person teacher = new Teacher('Lee');
print( student.greet('Chen'));
print(teacher.greet('Chen'));
}
同樣地,Dart 中的類是單繼承,多實現。這裏使用的 implements
後,子類 Student
無法去訪問父類 Persion
中的變量,但是抽象方法必須顯式實現。相反,如果使用 extends
繼承,子類就可以直接使用父類的非私有變量,倘若父類不是抽象類,那麼子類同樣不需要顯式實現裏面方法。如:
// A person. The implicit interface contains greet().
class Person {
// In the interface, but visible only in this library.
final _name;
// Not in the interface, since this is a constructor.
Person(this._name);
// In the interface.
String greet(String who) => 'Hello, $who. I am $_name.';
}
// An implementation of the Person interface.
class Impostor implements Person {
get _name => '';
String greet(String who) => 'Hi $who. Do you know who I am?';
}
String greetBob(Person person) => person.greet('Bob');
void main() {
print(greetBob(Person('Kathy')));
print(greetBob(Impostor()));
}
運算符重載
以下的運算符支持重載功能:
< |
+ |
| |
[] |
---|---|---|---|
> |
/ |
^ |
[]= |
<= |
~/ |
& |
~ |
>= |
* |
<< |
== |
– |
% |
>> |
這一點與 kotlin 語言中的運算符重載功能類似。我們以下面這個表示平面向量的類爲例,爲它增加兩個向量相加和相減功能:
class Vector {
final int x, y;
Vector(this.x, this.y);
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
// Operator == and hashCode not shown. For details, see note below.
// ···
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
枚舉
枚舉的使用比較簡單,和 Java 中的用法類似,聲明的元素 index
值從 0
開始計算。
enum Color { red, green, blue }
......
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
Mixin 混合模式
Mixins 是一種在多個類的層級中複用類中代碼的一種方式。在類名後添加 with
關鍵字,並在 with
後緊跟類的標識符列表,通過逗號分隔,如:
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
如果想要聲明一個混合模式,可以通過 mixin
來代替 class
定義一個不含構造方法的類:
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}
當然,我們也可以指定某個類來使用定義的混合模式,通過 on
關鍵字:
mixin MusicalPerformer on Musician {
// ···
}
靜態變量和函數
定義靜態變量和函數的方法與 Java 類似:
class Point {
num x, y;
Point(this.x, this.y);
static const originalPoint = Point(0, 0);
static num distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}
// usage
void main() {
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(2.8 < distance && distance < 2.9);
print(distance);
}
異步操作
Dart 是單線程執行的,如果進行 I/O 操作或進行耗時的操作時,程序可能會發生阻塞。Dart 庫中的很多函數都返回 Future
或者 Stream
對象,這些方法都是異步執行的,它們可以在建立一個耗時操作之後返回,而無需等待耗時操作執行完成。
我們可以通過兩種方式來實現異步操作:
- 通過
async
和await
實現 - 藉助於 Future API 來實現
async 和 await
通過 async
和 await
關鍵字,我們可以輕鬆實現異步操作。要使用 await
,必須要將函數用 async
修飾,同時函數會返回一個 Future
對象。簡單用法如下:
Future checkVersion() async {
try {
version = await lookUpVersion();
} catch (e) {
// React to inability to look up the version
}
}
生成器
-
同步生成器 — sync*
值得注意的是,同步生成器會返回
Iterable
對象:Iterable<int> naturalsTo(int n) sync* { int k = 0; while (k < n) yield k++; }
-
異步生成器 — async*
異步生成器返回
Stream
對象:Stream<int> asynchronousNaturalsTo(int n) async* { int k = 0; while (k < n) yield k++; }
-
yield*
通過
yield*
在遞歸中提升性能:Iterable<int> naturalsDownFrom(int n) sync* { if (n > 0) { yield n; yield* naturalsDownFrom(n - 1); } }
將類對象作爲函數調用
Dart 允許我們將一個類的對象作爲函數來調用,只需要實現 call()
函數即可:
class WannabeFunction {
call(String a, String b, String c) => '$a $b $c!';
}
main() {
var wf = new WannabeFunction();
var out = wf("Hi","there,","gang");
print('$out');
}
未完待續
此文只是 Dart 征程的一個起點,亦是 Flutter 中的一塊基石,後續還有更多進階用法和細節 API 等着我們去挖掘,keep learning!