重要概念
- 可以放在變量中的都是對象,所有對象都是類的實例,包括數字,函數,null都是對象, 所有對象都是繼承自 Object類
- Dart類型是強類型語言,但是Dart也支持類型推斷,如果要明確說明不需要任何類型,請使用特殊類型dynamic。
- Dart支持泛型,比如
List<int>
(包含int的數組),List<dynamic>
(包含任意類型對象的數組) - Dart支持top-level函數(比如main()),以及方法(靜態方法和實例方法). 也可以在函數中創建函數(內嵌函數或本地函數)
- 同樣的,Dart支持top-level變量,以及綁定到類或對象的變量(靜態變量和實例變量),實例變量有時候被稱為 字段或者屬性
- 不像Java,Dart沒有
public
,protected
,private
關鍵字.如果標識符以下劃線(_)開頭,則它就是私有變量 - 標識符可以以字母或下劃線(_)開頭
變量
var name = 'Bob';
變量存儲引用, name變量包含了一個String對象的引用,該對象的值是'Bob'.
name被推斷為String類型,但是可以通過指定來改變其類型,如果對象不局限于單一類型,可以指定為Object或者dynamic
dynamic name = 'Bob';
默認值
未初始化的變量的初始值為null
,即使數字類型的初始值也是null
,因為在Dart中, everything is object!
int lineCount;
assert(lineCount == null);
重點: 生產環境下,代碼會忽略 assert()調用, 開發環境中, 斷言會在條件為false時拋出異常
final 和 const
如果永遠不會修改變量,使用 final 或者 const。而不是使用var或者其他類型。
final 變量只會被設置一次,一個 const 變量是編譯時常量(const是隱式的final),final 修飾的top-level變量和類變量在第一次使用時初始化
Note: 實例變量可以是final,但不能是const, final 實例變量必須在構造函數體開始之前初始化(可以在變量聲明,構造函數參數,或者在構造函數的初始化列表)
final name = 'Bob'; // 沒有類型聲明
final String nickname = 'Bobby';
不能修改final變量的值
name = 'Alice'; // Error: a final variable can only be set once.
const 用于編譯時常量。 如果const在類中使用, 使用static const標識。在聲明該變量的地方,將值設置為編譯時常量,比如數字或者字符串,
const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere
const 關鍵字并不只是用來聲明常量變量,也可以用來創建常量值,以及聲明創建常量值的構造函數。任意變量都可以有一個常量值
var foo = const [];
final bar = const[];
const baz = []; // 等同于 const[]
可以從const聲明的初始化表達式中省略const,就像上面的 baz
。
可以改變 non-final,non-const變量的值,即使該變量有過一個 const 值:
foo = [1,2,3];// 之前是 const[]
但是不能修改const變量的值:
baz = [42];// Error
同樣的,
var foo = const [];
foo這個數組的值也不能再改變,即foo不能添加/移除元素
foo.add(1);
error
Unsupported operation: Cannot add to an unmodifiable list
內置類型
Dart語言特別支持以下類型:
- numbers
- strings
- booleans
- lists (也就是數組)
- sets
- maps
- runes (為了在字符串中表示Unicode字符)
- symbols
可以使用字面量來初始化這些類型。
Number
Dart數字有兩種類型
int
不超過64位的整數,具體取決于平臺。在Dart VM上,值的范圍: 到
。 編譯為JavaScript的Dart使用的是Javascript number,值的范圍是
到
double
64位(雙精度)浮點型數字。由IEEE 754標準規定
int和double都是num的子類。 num類包含基礎運算符,比如+,-,*,/,以及abs(),ceil(),floor(),(在int類里有位運算符,比如<<)
在Dart2.1中,整型會在需要時自動轉為double類型
double z = 1;// 等同于 double z = 1.0;
下面是字符串轉為數字,反之亦然
// String -> int
var one = int.parse('1');
assert(one == 1);
// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);
// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');
// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');
String
Dart字符串是一系列UTF-16代碼單元。 可以使用單引號或雙引號來創建字符串:
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.";
可以使用${expression}將表達式的值放在字符串中,如果表達式是標識符,可以省略{}, 要獲得與對象對應的字符串,可以調用對象的toString()
方法
可以使用相鄰的字符串或者 + 號來連接兩個字符串
// 相鄰的字符串
var s1 = 'String '
'concatenation'
" works even over line breaks.";
assert(s1 ==
'String concatenation works even over '
'line breaks.');
// +號
var s2 = 'The + operator ' + 'works, as well.';
assert(s2 == 'The + operator works, as well.');
創建多行字符串的方法:使用單引號/雙引號的三重引號
var s1 = '''
You can create
multi-line strings like this one.
''';
var s2 = """This is also a
multi-line string.""";
可以添加一個前綴 r來創建一個‘raw’字符串
var s = r'In a raw string, not even \n gets special treatment.';
bool
Dart中的布爾類行為bool,有兩個值:true,false;
Dart是類型安全的,也就意味著不會像OC那樣有非0即真的情況。條件表達式中必須明確傳遞一個布爾值。
// 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);
List
Dart中的數組,有序集合。
var list = [1, 2, 3];
注意: Dart會類型推斷list的類型為 List<int>. 如果之后向其中添加其他非int的對象,編譯器會拋出錯誤
跟其他語言的數組一樣,List的下標索引從0開始,
var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);
list[1] = 1;
assert(list[1] == 1);
要創建編譯時常量的列表,請在列表前添加const:
var constantList = const [1,2,3];
// constantList[1] = 1; 會有錯誤,因為列表是常量 不能再修改了
Dart2.3擴展運算符(...)和空值感知運算符(...?),它提供了一種將多個元素插入到集合的簡潔方法。
比如,可以使用擴展運算符將一個列表中的所有元素插入到另一個列表中
var list = [1,2,3];
var list2 = [0, ...list];
assert(list2.length == 4);
如果擴展運算符右邊的表達式有可能為null,可以使用空值感知運算符來避免異常。
var list;
var list2 = [0, ...?list];
assert(list2.length == 1);
Dart2.3也引入了collection if和collection for來創建集合。
下面是使用collection if的例子,列表包含三個/四個 item:
var nav = [
'Home',
'Furniture',
'Plants',
if (promoActive) 'Outlet'
];
使用collection for來操作列表item,然后將它們添加到另一個列表:
var listOfInts = [1, 2, 3];
var listOfStrings = [
'#0',
for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');
Set
Dart中的無序集合是Set,
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
創建一個空的Set,請使用前面帶有類型參數的{},或者將{}賦給類行為Set的變量
var names = <String>{};
// Set<String> names = {}; // This works, too.
// var names = {}; // Creates a map, not a set.
Set還是Map? Map和Set的字面亮語法類似。由于Map首先出現,所以{}默認是Map類型。如果忘記了{}的類型注釋,Dart會創建一個Map<dynamic, dynamic>類型的對象,也就是說{}默認會是Map類型
add()
,addAll()
方法來為Set添加元素:
var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
使用.length
來獲取Set的元素個數:
var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
assert(elements.length == 5);
要創建一個編譯時常量的Set,在Set前添加const
final constantSet = const {
'fluorine',
'chlorine',
'bromine',
'iodine',
'astatine',
};
// constantSet.add('helium'); // error
類似于List, Set也支持擴展運算符(...)和空值感知運算符(...?);
Map
類似于iOS中的字典。key和value都可以是任意類型的對象,key只能出現一次,value可以出現多次。
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
也可以使用Map的構造函數來創建Map:
var gifts = Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
var nobleGases = Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';
向一個Map中添加鍵值對,類似于JS:
var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds'; // Add a key-value pair
如果查詢一個不存在的key,會返回null:
var gifts = {'first': 'partridge'};
assert(gifts['fifth'] == null);
使用.length
來獲取Map元素的個數:
var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);
創建一個編譯時常量的Map,在Map字面量前使用const
final constantMap = const {
2: 'helium',
10: 'neon',
18: 'argon',
};
// constantMap[2] = 'Helium'; //error
Dart2.3之后,Map也支持...
和...?
Runes
在Dart中,Runes是字符串的UTF-32代碼點。
Unicode為世界上所有書寫系統中的每個字母,數字和符號定義了唯一的數值。由于Dart字符串是 UTF-16編碼的序列,因此在字符串中表示32位的Unicode值需要特殊的語法。
表達Unicode代碼點的常用方法是 \uXXXX
,其中XXXX是4位十六進制值。 例如,心臟字符(?)是 \u2665
。 要指定多于或少于4個十六進制數字,請將值放在大括號中。 例如,笑的表情符號(??)是\u{1f600}
。
String類有幾個屬性可用于提取rune信息。codeUnitAt和codeUnit屬性返回16位的code unit。使用runes屬性獲取字符串的runes
以下示例說明了runes(符文)、16位代碼單元和32位代碼點之間的關系:
main() {
var clapping = '\u{1f44f}';
print(clapping);
print(clapping.codeUnits);
print(clapping.runes.toList());
Runes input = new Runes(
'\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}');
print(new String.fromCharCodes(input));
}
打印信息:
flutter: ??
flutter: [55357, 56399]
flutter: [128079]
flutter: ? ?? ?? ?? ?? ??
Symbol
Symbol對象表示Dart程序中聲明的運算符或標識符。可能永遠也用不到Symbol。。。
要獲取標識符的符號,可以使用symbol字面量,#
后跟標識符:
#radix
#bar
Function
Dart是真面向對象的語言,所以即使是函數也是一個對象,類型為Function。這意味著函數可以分配給變量或者作為參數傳遞給其他函數。
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
或者
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
=> expr是{ return expr; }的縮寫,
函數可以有兩種類型的參數:必需和可選。必需參數放在首位,后面跟著一些可選參數,
可選參數
可選參數可以是位置參數,也可以是命名參數
可選命名參數
當調用一個函數的時候,可以使用paramName: value的形式指定命名參數:
enableFlags(bold: true, hidden: false);
當定義一個函數時,使用{param1, param2, …}的形式來制定命名參數:
/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold, bool hidden}) {...}
Flutter實例創建表達式可能變得復雜,因此Widget構造函數僅使用命名參數。 這使得實例創建表達式更易于閱讀。
你可以在任何Dart代碼(不僅是Flutter)中使用@required
來注釋一個命名參數,來表明該參數是必須的:
const Scrollbar({Key key, @required Widget child})
當構建Scrollbar時,如果child
參數缺失,就會報一個錯誤
可選位置參數
把一組函數參數包括在[]
,來標記這些參數是可選位置參數:
String say(String from, String msg, [String device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
調用該函數-不傳可選參數
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
調用該函數-傳遞可選參數
assert(say('Bob', 'Howdy', 'smoke signal') ==
'Bob says Howdy with a smoke signal');
參數默認值
可以使用=
來為命名參數或者位置參數設置默認值。默認值必須是編譯時常量,如果沒有提供默認值,那默認值就是null。
舉個例子:
/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold = false, bool hidden = false}) {...}
// bold will be true; hidden will be false.
enableFlags(bold: true);
下面示范了如何為位置參數設置默認值:
String say(String from, String msg,
[String device = 'carrier pigeon', String mood]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
if (mood != null) {
result = '$result (in a $mood mood)';
}
return result;
}
assert(say('Bob', 'Howdy') ==
'Bob says Howdy with a carrier pigeon');
也可以傳一個List或者Map作為默認值:
void doStuff(
{List<int> list = const [1, 2, 3],
Map<String, String> gifts = const {
'first': 'paper',
'second': 'cotton',
'third': 'leather'
}}) {
print('list: $list');
print('gifts: $gifts');
}
main()函數
每一個app都有一個頂層的main()函數,作為應用的入口點。該函數返回值為void,并接收一個可選的List<String>
參數。
void main() {
querySelector('#sample_text_id')
..text = 'Click me!'
..onClick.listen(reverseText);
}
// Run the app like this: dart args.dart 1 test
void main(List<String> arguments) {
print(arguments);
assert(arguments.length == 2);
assert(int.parse(arguments[0]) == 1);
assert(arguments[1] == 'test');
}
函數作為first-class對象
函數本身可以作為一個參數傳遞給另一個函數,比如:
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
// Pass printElement as a parameter.
list.forEach(printElement);
也可以把函數賦值給一個變量:
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');
匿名函數
大多數函數是有名字的,比如main()
或者printElement ()
,我們也可以創建一個沒有名字的函數-匿名函數,或者創建lambda以及閉包。你可以把匿名函數賦值給一個變量,方便添加到一個集合中,或者從集合中刪除。
匿名函數看起來類似于命名函數-零個或多個參數,
以下示例定義了一個匿名函數,有一個無類型參數item。
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
詞匯范圍
Dart是一種詞法范圍的語言,這意味著變量的范圍是靜態確定的(只需通過代碼的布局)。
bool topLevel = true;
void main() {
var insideMain = true;
void myFunction() {
var insideFunction = true;
void nestedFunction() {
var insideNestedFunction = true;
assert(topLevel);
assert(insideMain);
assert(insideFunction);
assert(insideNestedFunction);
}
}
}
閉包
閉包是一個函數對象,它可以訪問其詞法范圍中的變量,即使該函數在其原始范圍之外使用也是如此。
函數可以關閉周圍范圍中定義的變量。 在以下示例中,makeAdder()捕獲變量addBy。 無論返回的函數在哪里,它都會記住addBy。
/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(num addBy) {
return (num i) => addBy + i;
}
void main() {
// Create a function that adds 2.
var add2 = makeAdder(2);
// Create a function that adds 4.
var add4 = makeAdder(4);
assert(add2(3) == 5);
assert(add4(3) == 7);
}
函數相等
這里有個例子來測試頂層函數,靜態函數,以及實例函數的相等性:
void foo() {} // A top-level function
class A {
static void bar() {} // A static method
void baz() {} // An instance method
}
void main() {
var x;
// Comparing top-level functions.
x = foo;
assert(foo == x);
// Comparing static methods.
x = A.bar;
assert(A.bar == x);
// Comparing instance methods.
var v = A(); // Instance #1 of A
var w = A(); // Instance #2 of A
var y = w;
x = w.baz;
// These closures refer to the same instance (#2),
// so they're equal.
assert(y.baz == x);
// These closures refer to different instances,
// so they're unequal.
assert(v.baz != w.baz);
}
返回值
所有的函數都有返回值,如果沒有指定返回值,則返回null。
foo() {}
assert(foo() == null);
操作符
描述 | 操作符 |
---|---|
一元后綴 | expr++?? expr--?? ()?? []?? ? .??? ?. |
一元前綴 | -expr?? !expr?? ~expr?? ++expr?? --expr |
乘法 | * ?? /?? %?? ~/ |
加法 | +????? - |
位移 | <<???? >>???? >>> |
按位與 | & |
按位異或 | ^ |
按位或 | | |
關系和類型測試 | >= ??? >?? <=??? < ?? as ?? is?? is! |
相等 | == ????????? != |
邏輯與 | && |
邏輯或 | |
if null | ?? |
條件 | expr1 ? expr2 : expr3 |
級聯 | .. |
賦值 | =??? *= ??? /=??? +=?? -=??? &=??? ^= 等 |
這里有幾個操作符的用法
a++
a + b
a = b
a == b
c ? a : b
a is T
在上述表格中,每個操作符都比其下一行的操作符有更高的優先級。比如,乘法運算符%比相等運算符==有更高的優先級(因此在==之前先執行%)。相等運算符==優先級高于邏輯與運算符&&。該優先級意味著以下兩行代碼以相同的方式執行:
// 使用括號提高可讀性
if ((n % i == 0) && (d % i == 0)) ...
// 比較難讀,但跟上面是相等的
if (n % i == 0 && d % i == 0) ...
算數運算符
Dart支持以下的算數運算符
運算符 | 含義 |
---|---|
+ | 加法 |
- | 相減 |
-expr | 取反 |
* | 相乘 |
/ | 相除,返回的是double |
~/ | 相除,返回的是整數int |
% | 取余 |
比如:
assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // Result is a double
assert(5 ~/ 2 == 2); // Result is an int
assert(5 % 2 == 1); // Remainder
assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');
Dart還支持自增自減運算:
var a, b;
a = 0;
b = ++a; // Increment a before b gets its value.
assert(a == b); // 1 == 1
a = 0;
b = a++; // Increment a AFTER b gets its value.
assert(a != b); // 1 != 0
a = 0;
b = --a; // Decrement a before b gets its value.
assert(a == b); // -1 == -1
a = 0;
b = a--; // Decrement a AFTER b gets its value.
assert(a != b); // -1 != 0
相等和關系運算符
跟其他語言的一樣
類型判斷運算符
as, is, 和 is! 操作符在運行時檢查類型非常方便。
操作符 | 含義 |
---|---|
as | 類型轉換(也用于指定庫前綴) |
is | 如果對象具有指定的類型則返回true |
is! | 如果對象具有指定的類型返回false |
如果obj
實現了由T
指定的接口,obj is T
的結果為true。比如,obj is Object
永遠都是true。
使用as
運算符將對象強制轉換為特定的類型。 通常情況下應該將as
作為is
的簡寫,比如:
if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}
可以使用as
來簡寫:
(emp as Person).firstName = 'Bob';
注意: 該代碼并不是等價的。如果 emp是null或者不是Person類的對象。使用is不會有什么影響,使用as的話會拋出異常。
賦值運算符
// 將value賦值給a
a = value;
// 如果b是null,將value賦值給b;否則,b將保持不變
b ??= value;
邏輯運算符
跟其他語言類似
位運算
與C一樣
final value = 0x22;
final bitmask = 0x0f;
assert((value & bitmask) == 0x02); // AND
assert((value & ~bitmask) == 0x20); // AND NOT
assert((value | bitmask) == 0x2f); // OR
assert((value ^ bitmask) == 0x2d); // XOR
assert((value << 4) == 0x220); // Shift left
assert((value >> 4) == 0x02); // Shift right
條件表達式
Dart有兩個運算符,可以簡明地計算可能需要if-else語句的表達式:
condition ? expr1 : expr2
三目運算符,跟其他語言一樣
expr1 ?? expr2
如果expr1是 non-null,則返回它的值,否則,計算并返回expr2的值。
如果要基于布爾表達式來賦值的話使用三目運算
var visibility = isPublic ? 'public' : 'private';
如果布爾表達式要測試null,請考慮使用??。
String playerName(String name) => name ?? 'Guest';
前面的例子至少可以用其他兩種方式編寫,但不夠簡潔:
// Slightly longer version uses ?: operator.
String playerName(String name) => name != null ? name : 'Guest';
// Very long version uses if-else statement.
String playerName(String name) {
if (name != null) {
return name;
} else {
return 'Guest';
}
級聯表示法(..
)
級聯(..
)允許對同一對象進行一系列操作。 除了函數調用,還可以訪問同一對象上的字段。 這通常可以節省創建臨時變量的步驟,并允許編寫更多流暢的代碼。
querySelector('#confirm') // Get an object.
..text = 'Confirm' // Use its members.
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
上面的代碼等同于:
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
也可以內嵌我們的級聯表達式,比如:
final addressBook = (AddressBookBuilder()
..name = 'jenny'
..email = 'jenny@example.com'
..phone = (PhoneNumberBuilder()
..number = '415-555-0100'
..label = 'home')
.build())
.build();
小心在返回實際對象的函數上構造級聯。 例如,以下代碼會失敗:
var sb = StringBuffer();
sb.write('foo')
..write('bar'); // Error: method 'write' isn't defined for 'void'.
sb.write()
函數返回void,不能在void上構建級聯。
注意:嚴格來說,級聯的“雙點”符號不是運算符。 它只是Dart語法的一部分。
其他運算符
只介紹下?.
:最左邊的操作數可以為null,比如:foo?.bar
,如果foo不為null,則從foo中選擇bar屬性,如果foo為null,則foo?.bar
為null。
控制流語句
if-else,for循環,while/do-while跟其他語言一樣
Dart中的Switch語句使用==來比較整數,字符串或者編譯時常量。比較對象必須是同一個類的實例(而不是其子類),并且該類不能覆蓋==。
每個非空case子句以break語句結束。 結束非空case子句的其他有效方法是continue,throw或return語句。
var command = 'OPEN';
switch (command) {
case 'CLOSED':
executeClosed();
break;
case 'PENDING':
executePending();
break;
case 'APPROVED':
executeApproved();
break;
case 'DENIED':
executeDenied();
break;
case 'OPEN':
executeOpen();
break;
default:
executeUnknown();
}
斷言
跟其他語言的斷言一樣。Flutter中只有在debug模式下才開啟斷言。
異常
與Java相比,Dart的所有異常都是未經檢查的異常。 方法不會聲明它們可能引發的異常,并且不需要捕獲任何異常。
Dart提供了Exception和Error類型,以及許多預定義的子類型。 當然,也可以自定義異常。 Dart程序可以拋出任何非null對象(不僅僅是Exception和Error對象)作為異常。
Throw
拋出異常:
throw FormatException('Expected at least 1 section');
也可以拋出任意對象:
throw 'Out of llamas!';
Catch
捕獲異常會阻止異常傳播(除非重新拋出異常),并有機會處理它:
try {
breedMoreLlamas();
} on OutOfLlamasException {
buyMoreLlamas();
}
要處理可能拋出多種類型異常的代碼,可以指定多個catch子句。 與拋出對象的類型匹配的第一個catch子句處理異常。 如果catch子句未指定類型,則該子句可以處理任何類型的拋出對象:
try {
breedMoreLlamas();
} on OutOfLlamasException {
// A specific exception
buyMoreLlamas();
} on Exception catch (e) {
// Anything else that is an exception
print('Unknown exception: $e');
} catch (e) {
// No specified type, handles all
print('Something really unknown: $e');
}
如前面的代碼所示,可以使用on
或catch
或兩者結合使用。 需要指定異常類型時使用on
, 在需要異常對象時使用catch
。
可以指定兩個參數到catch()
。第一個參數是拋出的異常,第二個是堆棧跟蹤信息(一個StackTrace
對象)。
try {
// ···
} on Exception catch (e) {
print('Exception details:\n $e');
} catch (e, s) {
print('Exception details:\n $e');
print('Stack trace:\n $s');
}
要部分處理異常,同時允許異常傳播,可以使用rethrow
關鍵字。
void misbehave() {
try {
dynamic foo = true;
print(foo++); // Runtime error
} catch (e) {
print('misbehave() partially handled ${e.runtimeType}.');
rethrow; // Allow callers to see the exception.
}
}
void main() {
try {
misbehave();
} catch (e) {
print('main() finished handling ${e.runtimeType}.');
}
}
Finally
為了確保無論異常是否拋出都會執行一些代碼,可以使用finally
語句。如果catch
語句沒有匹配到該異常,則該異常會在執行finally
語句的代碼之后拋出。
try {
breedMoreLlamas();
} finally {
// Always clean up, even if an exception is thrown.
cleanLlamaStalls();
}
finally
語句會在匹配異常的catch
語句之后執行:
try {
breedMoreLlamas();
} catch (e) {
print('Error: $e'); // Handle the exception first.
} finally {
cleanLlamaStalls(); // Then clean up.
}
類
Dart是一種面向對象的語言,具有類和基于mixin的繼承。 每個對象都是一個類的實例,所有類都來自Object。 基于Mixin的繼承意味著雖然每個類(除了Object)只有一個超類,但是類的body可以在多個類層次結構中重用。
類的成員
類具有函數、方法以及實例變量等成員。
使用點語法(.
)來引用實例變量或者方法:
var p = Point(2, 2);
// Set the value of the instance variable y.
p.y = 3;
// Get the value of y.
assert(p.y == 3);
// Invoke distanceTo() on p.
num distance = p.distanceTo(Point(4, 4));
使用?.
來代替.
,可以防止左邊運算對象為null的異常。
// If p is non-null, set its y value to 4.
p?.y = 4;
使用構造函數
可以使用構造函數創建對象。 構造函數名稱可以是ClassName
或ClassName.identifier
。 例如,以下代碼使用Point()
和Point.fromJson()
構造函數創建Point
對象:
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
一些類提供了編譯時構造函數,創建一個編譯時常量。在構造函數名稱前加const
關鍵字:
var p = const ImmutablePoint(2, 2);
構造兩個相同的編譯時常量時,只會產生一個實例:
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // a和b是同一個實例
在常量上下文中,可以在構造函數或字面量之前省略const
:
// Lots of const keywords here.
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
除了第一個const
,其他的都可以省略:
// 只有一個const,它建立了恒定的上下文。
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
如果常量構造函數在常量上下文之外,并且在沒有使用const
,則會創建一個非常量對象:
var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant
assert(!identical(a, b)); // NOT the same instance!
獲取對象類型
在運行時獲取對象的類型,可以使用對象的runtimeType
屬性,返回一個Type對象/
print('The type of a is ${a.runtimeType}');
實例變量
聲明實例變量:
class Point {
num x; // Declare instance variable x, initially null.
num y; // Declare y, initially null.
num z = 0; // Declare z, initially 0.
}
所有未初始化的實例變量默認值都是null
.
所有的實例變量都會生成一個隱式的getter方法。非final
實例變量也會隱式的生成setter方法。
class Point {
num x;
num y;
}
void main() {
var point = Point();
point.x = 4; // Use the setter method for x.
assert(point.x == 4); // Use the getter method for x.
assert(point.y == null); // Values default to null.
}
構造函數
通過創建與其類同名的函數來聲明構造函數。
class Point {
num x, y;
Point(num x, num y) {
// There's a better way to do this, stay tuned.
this.x = x;
this.y = y;
}
}
this
關鍵字代表著當前實例。在名稱沖突時使用this
,一般情況下,Dart會省略this
.
Dart具有語法糖,使其變得簡單:
class Point {
num x, y;
// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}
默認構造函數
如果沒有聲明構造函數,則會提供一個默認的構造函數。默認的構造函數沒有參數,并且會調用其父類的無參數的構造函數。
構造函數不能繼承
子類不能繼承父類的構造函數!
命名構造函數
使用命名構造函數為類實現多個構造函數:
class Point {
num x, y;
Point(this.x, this.y);
// Named constructor
Point.origin() {
x = 0;
y = 0;
}
}
重定向構造函數
有時構造函數的唯一目的是重定向到同一個類中的另一個構造函數。 重定向構造函數的body是空的,構造函數調用出現在冒號(:
)之后。
class Point {
num x, y;
// The main constructor for this class.
Point(this.x, this.y);
// Delegates to the main constructor.
// 重定向到main函數
Point.alongXAxis(num x) : this(x, 0);
}
常量構造函數
如果類生成的對象永遠不會改變,則可以使這些變量為編譯時常量。定義一個const
構造函數來確保所有的實例變量都是final
。
class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
final num x, y;
const ImmutablePoint(this.x, this.y);
}
工廠(Factory)構造函數
當構造函數不需要每次都創建新的實例時,可以使用factory
關鍵字。例如,一個工廠構造函數可能從緩存中返回實例,或者可能返回一個子類的實例。
下面的例子展示了工廠構造函數從緩存中返回實例:
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) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
注意: 工廠構造函數不能訪問
this
.
調用工廠構造函數跟其他的構造函數一樣:
var logger = Logger('UI');
logger.log('Button clicked');
方法
方法是為對象提供行為的函數。(函數是獨立存在的,方法需要依賴對象,這就是函數與方法的區別)。
實例方法
實例方法可以訪問實例變量和this
。下面的distanceTo()
就是一個實例方法:
import 'dart:math';
class Point {
num x, y;
Point(this.x, this.y);
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
getter & setter
每個實例變量都有一個隱式的getter,合適的話還有一個setter。可以通過set
和get
關鍵字實現setter和getter來創建其他的屬性
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// 定義兩個計算屬性: 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);
}
抽象方法
實例方法,setter和getter可以是抽象的,可以定義接口,但將其實現留給其他類。抽象方法只能存在于抽象類。
使用分號(;
)而不是方法體來定義一個抽象方法:
abstract class Doer {
// Define instance variables and methods...
void doSomething(); // 定義抽象方法
}
class EffectiveDoer extends Doer {
void doSomething() {
// Provide an implementation, so the method is not abstract here...
}
}
抽象類
使用abstract
修飾符來定義抽象類(無法實例化的類)。抽象類對于定義接口非常有用,通常還有一些實現。如果希望抽象類看起來是可以實例化的,請定義工廠構造函數。
抽象類一般具有抽象方法:
// 這個類被定義為抽象類,因此它不能實例化
abstract class AbstractContainer {
// 定義構造函數,字段,方法...
void updateChildren(); //抽象方法
}
隱式接口
每個類都隱式定義一個接口,該接口包含該類的所有實例成員及其實現的所有接口。如果要在不繼承class B實現的情況下,創建一個class A來支持class B的API,class A應該implements
B的接口。
// 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()));
}
下面是一個類實現多個接口的例子:
class Point implements Comparable, Location {...}
擴展類
使用extends
創建子類,使用super
引用父類:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
override 成員
子類可以覆蓋實例方法、getter
和setter
。可以使用@override
注釋來表示要覆蓋一個成員:
class SmartTelevision extends Television {
@override
void turnOn() {...}
// ···
}
覆蓋運算符
您可以覆蓋下表中顯示的操作符。例如,如果您定義一個向量類,您可能定義一個+方法來添加兩個向量。
> |
/ |
^ |
[]= |
---|---|---|---|
< |
+ |
| |
[] |
<= |
~/ |
& |
~ |
>= |
* |
<< |
== |
- |
% |
>> |
注意:
!=
是不能覆蓋的,因為e1 != e2
是!(e1 == e2)
的語法糖。
下面是一個覆蓋+
和 -
操作符的例子:
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));
}
如果你覆蓋了==
,你也應該覆蓋對象的hashCode
getter方法。
noSuchMethod()
當代碼試圖調用不存在的方法或者實例變量,可以覆蓋noSuchMethod()
來檢測或響應。
class A {
// 除非覆蓋了noSuchMethod。使用不存在的成員會導致NoSuchMethodError
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: ' +
'${invocation.memberName}');
}
}
枚舉類型
使用
使用enum
關鍵字聲明一個枚舉類型:
enum Color { red, green, blue }
每個枚舉值都有一個index
getter,它返回枚舉值的位置。比如,第一個值的index
為0,第二個值的index
為1.
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
要獲取枚舉中所有的值,可以使用枚舉中的values
常量。
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);
枚舉有以下限制:
- 不能子類化、
mixin
或者implment
一個枚舉 - 不能顯式的實例化枚舉
向類添加feature: mixin
mixin是一種在多個繼承類中重用代碼的一種方式。
若要使用mixin,請使用with
關鍵字后跟一個或多個mixin名稱。下面的例子顯示了兩個使用mixin的類:
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
要實現mixin
,創建一個擴展Object
的類,并且不聲明構造函數,除非希望mixin
像常規類一樣使用。
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');
}
}
}
比如,指定只有某些類型可以使用mixin,這樣你的mixin可以調用它沒有定義的方法-使用on
來指定所需的父類:
mixin MusicalPerformer on Musician {
// ···
}
Dart 2.1版本中引入了對mixin的支持,早期版本中通常使用抽象類
類變量和方法
使用static
關鍵字來實現類范圍的變量和方法。
靜態變量(Static變量)
靜態變量(類變量)對于類范圍內的狀態和常量很有用:
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
靜態變量在使用之前不會初始化
靜態方法
靜態方法(類方法)不能操作實例,因為不能訪問this
:
import 'dart:math';
class Point {
num x, y;
Point(this.x, this.y);
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);
}
}
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);
}
可以使用靜態方法作為編譯時常量,比如,可以傳遞一個靜態方法作為靜態構造函數的參數
注意: 對于通用的或者廣泛使用的功能函數,考慮使用頂層函數,而不是靜態方法
泛型
如果查看List的API文檔,會發現List的實際類型是List<E>
。<...>
表示法將List標記為泛型(或者參數化)類型-具有正式類型參數的類型。按照慣例,大多數類型變量都有單字母名稱,例如E,T,S,K,V
為什么使用泛型
類型安全通常需要泛型,但有更多的好處:
- 正確指定泛型類型會產生更好的代碼
- 使用泛型來減少代碼重復
如果想要數組僅僅包含字符串,可以聲明為List<String>
。這樣可非字符串插入到列表中的就會有錯誤:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error
使用泛型的另外一個理由是減少代碼的重復。泛型允許在多個類型之間分享單個接口和實現。比如:創建一個用于緩存對象的接口:
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
然后發現想要一個特定于字符串的版本,然后可以這樣實現:
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
后來,你可能需要更多的類型...
泛型可以省去所有這些接口的麻煩,創建一個帶有類型參數的接口:
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
在這段代碼中,T
是一個替身類型,一個占位符,
使用集合字面量
List
,Set
,Map
可以參數化:
var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
跟構造函數一起使用參數化類型
要在使用構造函數時指定一個或多個類型,請將類型放在類名后面的尖括號中(<...>
),比如:
var nameSet = Set<String>.from(names);
var views = Map<int, View>();
泛型集合以及其所包含的類型
Dart泛型被具體化,這意味著它們在運行時攜帶類型信息。例如,你可以測試一個集合的類型:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
限制參數化類型
當實現一個泛型時,可能想要限制參數的類型,可以使用extends
:
class Foo<T extends SomeBaseClass> {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
使用SomeBaseClass
或其子類作為泛型參數是OK的:
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
不指定泛型參數也是可以的:
var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'
指定任意非SomeBaseClass
類型會導致錯誤:
var foo = Foo<Object>();
使用泛型方法
T first<T>(List<T> ts) {
// Do some initial work or error checking, then...
T tmp = ts[0];
// Do some additional checking or processing...
return tmp;
}
first(<T>)
上的泛型類型參數允許在幾個地方使用類型參數T:
- 函數的返回類型(
T
) - 參數的類型(
List<T>
) - 局部變量(
T tmp
)
支持異步
Dart庫充滿了返回Future
或者Stream
對象的函數。這些函數是異步的:它們在一個可能非常耗時的操作(比如I/O)之后返回,而不需要等待操作完成。
async
和wait
關鍵字支持異步編程,允許我們編寫看起來類似同步的異步代碼
處理Future
當需要一個完整Future的結果時,有兩種選擇:
- 使用
async
和await
- 使用 Future API
使用async
和await
的代碼是異步的,但看起來是同步的。比如:
await lookUpVersion();
要使用await
,代碼必須在async
函數中:一個標記為async
的函數:
Future checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
注意:雖然
async
函數可能會執行耗時操作,但并不需要等待這些操作。相反,async
函數只執行到第一個await
表達式,然后返回一個Future
對象,僅在await
表達式完成后才恢復執行。
使用try
,catch
,和 finally
在使用await
的代碼中來錯誤:
try {
version = await lookUpVersion();
} catch (e) {
// React to inability to look up the version
}
可以在async
函數中多次使用await
:
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
在await
表達式中,表達式的值通常是Future
,如果不是,該值也會自動包裝在Future中,await
表達式會使執行暫停,知道該對象可用。
如果在使用await
時遇到編譯時錯誤,請確保await
是在async
函數中。
比如在app的main()
函數中使用await
,則必須時main函數標記為aycnc
Future main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}
處理Stream(流)
當需要從流中獲取值的時候,有兩個選項:
- 使用
async
和異步for循環(await for
) - 使用 Stream API
異步for循環:
await for (varOrType identifier in expression) {
// Executes each time the stream emits a value.
}
表達式(expression)的值必須具有Stream
類型。執行過程如下:
- 等流發出一個值
- 執行for循環的主體,將變量設置為流發出的值
- 重復1和2,直到流關閉
停止監聽流,可以使用break
或者return
語句,該語句會中斷for循環并且取消訂閱流
如果實現一個異步for循環時出現編譯時錯誤,確保await for
在異步函數中
Future main() async {
// ...
await for (var request in requestServer) {
handleRequest(request);
}
// ...
}
Generators(生成器)
當想要懶加載一系列值時,可以考慮使用generator函數,dart內置兩種該函數:
- 同步生成器:返回
Iterable
對象 - 異步生成器:返回
Stream
對象
要實現同步生成器函數,將函數主體標記為sync*
,并使用yield
語句來傳遞值:
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
要實現異步生成器函數,將函數標記為async*
,并使用yield
語句來傳遞值:
Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}
如果生成器是遞歸的,可以使用yield*
來提高性能:
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
Isolates(隔離區)
大多數計算機,即使在移動平臺上,也有多核CPU。 為了利用所有這些核心,開發人員傳統上使用并發運行的共享內存線程。 但是,共享狀態并發容易出錯,并且可能導致代碼復雜化。
所有Dart代碼都在隔離區內運行,而不是線程。 每個隔離區都有自己的內存堆,確保不會從任何其他隔離區訪問隔離區的狀態。