一個簡單的 Dart 程序
// 定義一個函數。
printInteger(int aNumber) {
print('The number is $aNumber.'); // 打印輸出到控制臺。
}
// Dart 程序從 main() 函數開始執行。
main() {
var number = 42; // 聲明并初始化一個變量。
printInteger(number); // 調用一個函數。
}
// This is a comment.
// 注釋。
以雙斜杠開頭的一行語句稱為單行注釋。Dart 同樣支持多行注釋和文檔注釋。
int
表示一種數據類型。Dart 中一些其他的內置類型包括 String
、List
和 bool
。
42
表示一個數字字面量。數字字面量是一種編譯時常量。
print()
一種便利的將信息輸出顯示的方式。
'...' (或 "...")
表示字符串字面量。
{expression})
表示字符串插值:字符串字面量中包含的變量或表達式。
main()
一個特殊且 必須的 頂級函數,Dart 應用程序總是會從該函數開始執行。
var
用于定義變量,通過這種方式定義變量不需要指定變量類型。
重要概念
在學習 Dart 語言時, 應該牢記以下幾點:
- 所有變量引用的都是 對象,每個對象都是一個 類 的實例。數字、函數以及
null
都是對象。所有的類都繼承于 Object 類。
盡管 Dart 是強類型語言,但是在聲明變量時指定類型是可選的,因為 Dart 可以進行類型推斷。在上述代碼中,變量
number
的類型被推斷為int
類型。如果想顯式地聲明一個不確定的類型,可以使用特殊類型dynamic
。Dart 支持泛型,比如
List<int>
(表示一組由 int 對象組成的列表)或List<dynamic>
(表示一組由任何類型對象組成的列表)。Dart 支持頂級函數(例如
main
方法),同時還支持定義屬于類或對象的函數(即 靜態 和 實例方法)。你還可以在函數中定義函數(嵌套 或 局部函數)。Dart 支持頂級 變量,以及定義屬于類或對象的變量(靜態和實例變量)。實例變量有時稱之為域或屬性。
Dart 沒有類似于 Java 那樣的
public
、protected
和private
成員訪問限定符。如果一個標識符以下劃線 (_) 開頭則表示該標識符在庫內是私有的。可以查閱 庫和可見性 獲取更多相關信息。標識符 可以以字母或者下劃線 (_) 開頭,其后可跟字符和數字的組合。
Dart 中 表達式 和 語句 是有區別的,表達式有值而語句沒有。比如條件表達式
expression condition ? expr1 : expr2
中含有值expr1
或expr2
。與 if-else 分支語句相比,if-else
分支語句則沒有值。一個語句通常包含一個或多個表達式,但是一個表達式不能只包含一個語句。Dart 工具可以顯示 警告 和 錯誤 兩種類型的問題。警告表明代碼可能有問題但不會阻止其運行。錯誤分為編譯時錯誤和運行時錯誤;編譯時錯誤代碼無法運行;運行時錯誤會在代碼運行時導致異常。
變量
下面的示例代碼將創建一個變量并將其初始化:
var name = 'Bob';
變量僅存儲對象的引用。這里名為 name
的變量存儲了一個 String
類型對象的引用,“Bob” 則是該對象的值。
name
變量的類型被推斷為 String
,但是你可以為其指定類型。如果一個對象的引用不局限于單一的類型,可以根據設計指南將其指定為 Object
或 dynamic
類型。
dynamic name = 'Bob';
除此之外你也可以指定類型:
String name = 'Bob';
默認值
在 Dart 中,未初始化的變量擁有一個默認的初始化值: null。即便數字也是如此,因為在 Dart 中一切皆為對象,數字也不例外。
int lineCount;
assert(lineCount == null);
Final 和 Const
如果你不想更改一個變量,可以使用關鍵字 final 或者 const 修飾變量,這兩個關鍵字可以替代 var 關鍵字或者加在一個具體的類型前。一個 final 變量只可以被賦值一次;一個 const 變量是一個編譯時常量(const 變量同時也是 final 的)。頂層的 final 變量或者類的 final 變量在其第一次使用的時候被初始化。
下面的示例中我們創建并設置兩個 final 變量:
final name = 'Bob'; // 此時 final 替代 var 用于修飾變量名
final String nickname = 'Bobby';// 將 final 加在具體的類型前
你不能修改一個 final 變量的值:
name = 'Alice'; // 錯誤:final 變量只能被設置一次。
使用關鍵字 const 修飾變量表示該變量為 編譯時常量。如果使用 const 修飾類中的變量,則必須加上 static 關鍵字,即 static const(注意:順序不能顛倒(譯者注))。在聲明 const 變量時可以直接為其賦值,也可以使用其它的 const 變量為其賦值:
const bar = 1000000; // 直接賦值
const double atm = 1.01325 * bar; // 利用其它 const 變量賦值
const 關鍵字不僅僅可以用來定義常量,還可以用來創建 常量值,該常量值可以賦予給任何變量。你也可以將構造函數聲明為 const 的,這種類型的構造函數創建的對象是不可改變的。
var foo = const [];
final bar = const [];
const baz = []; // 相當于 `const []`
如果使用初始化表達式為常量賦值可以省略掉關鍵字 const
,比如上面的常量 baz
的賦值就省略掉了 const
。
沒有使用 final 或 const 修飾的變量的值是可以被更改的,即使這些變量之前引用過 const 的值。
foo = [1, 2, 3]; // foo 的值之前為 const []
常量的值不可以被修改:
baz = [42]; // 錯誤:常量不可以被賦值。
內置類型
Dart 語言支持下列的類型:
numbers
strings
booleans
lists (也被稱為 arrays)
sets
maps
runes (用于在字符串中表示 Unicode 字符)
symbols
可以直接使用字面量來初始化上述類型。例如 'This is a string'
是一個字符串字面量,true
是一個布爾字面量。
由于 Dart 中每個變量引用都指向一個對象(一個 類 的實例),你通常也可以使用 構造器 來初始化變量。一些內置的類型有它們自己的構造器。例如你可以使用 Map()
來創建一個 map 對象。
Numbers
Dart 支持兩種 Number 類型
int
整數值長度不超過64位具體取值范圍依賴于不同的平臺。在 DartVM 上其取值位于 -263 至 263 - 1 之間。編譯成 JavaScript 的 Dart 使用 JavaScript 數字,其允許的取值范圍在 -253 至 253 - 1 之間。
double
64位的雙精度浮點數字,且符合 IEEE 754 標準。
int
和 double
都是 num
的子類。num 中定義了一些基本的運算符比如 +、-、*、/ 等,還定義了 abs()
、ceil()
和 floor()
等方法(位運算符,比如 ? 定義在 int 中)。如果 num 及其子類不滿足你的要求,可以查看 dart:math 庫中的 API。
整數是不帶小數點的數字。下面是一些定義整數字面量的例子:
var x = 1;
var hex = 0xDEADBEEF;
如果一個數字包含了小數點,那么它就是浮點型的。下面是一些定義浮點數字面量的例子:
var y = 1.1;
var exponents = 1.42e5;
從 Dart 2.1 開始,整型字面量將會在必要的時候自動轉換成浮點數字面量:
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');
整型支持傳統的位移操作,比如移位(<<、>>)、按位與(&)、按位或( ),例如:
assert((3 << 1) == 6); // 0011 << 1 == 0110
assert((3 >> 1) == 1); // 0011 >> 1 == 0001
assert((3 | 4) == 7); // 0011 | 0100 == 0111
數字字面量為編譯時常量。很多算術表達式只要其操作數是常量,則表達式結果也是編譯時常量。
const msPerSecond = 1000;
const secondsUntilRetry = 5;
const msUntilRetry = secondsUntilRetry * msPerSecond;
Strings
Dart 字符串是 UTF-16 編碼的字符序列。可以使用單引號或者雙引號來創建字符串:
var s1 = '使用單引號創建字符串字面量。';
var s2 = "雙引號也可以用于創建字符串字面量。";
var s3 = '使用單引號創建字符串時可以使用斜杠來轉義那些與單引號沖突的字符串:\'。';
var s4 = "而在雙引號中則不需要使用轉義與單引號沖突的字符串:'";
可以在字符串中以 ${表達式} 的形式使用表達式,如果表達式是一個標識符,可以省略掉 {}。如果表達式的結果為一個對象,則 Dart 會調用該對象的 toString 方法來獲取一個字符串。
var s = '字符串插值';
assert('Dart 有$s,使用起來非常方便。' == 'Dart 有字符串插值,使用起來非常方便。');
assert('使用${s.substring(3,5)}表達式也非常方便' == '使用插值表達式也非常方便。');
可以使用 + 運算符將兩個字符串連接為一個,也可以將多個字符串挨著放一起變為一個:
var s1 = '可以拼接'
'字符串'
"即便它們不在同一行。";
assert(s1 == '可以拼接字符串即便它們不在同一行。');
var s2 = '使用加號 + 運算符' + '也可以達到相同的效果。';
assert(s2 == '使用加號 + 運算符也可以達到相同的效果。');
可以使用三個單引號或者三個雙引號創建多行字符串:
var s1 = '''
你可以像這樣創建多行字符串。
''';
var s2 = """這也是一個多行字符串。""";
在字符串前加上 r 作為前綴創建 “raw” 字符串(即不會被做任何處理(比如轉義)的字符串):
var s = r'在 raw 字符串中,轉義字符串 \n 會直接輸出 “\n” 而不是轉義為換行。';
字符串字面量是一個編譯時常量,只要是編譯時常量都可以作為字符串字面量的插值表達式:
// 可以將下面三個常量作為字符串插值拼接到字符串字面量中。
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';
// 而下面三個常量則不能作為字符串插值拼接到字符串字面量。
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = [1, 2, 3];
const validConstString = '$aConstNum $aConstBool $aConstString';
// const invalidConstString = '$aNum $aBool $aString $aConstList';
Booleans
Dart 使用 bool 關鍵字表示布爾類型,布爾類型只有兩個對象 true 和 false,兩者都是編譯時常量。
Dart 的類型安全不允許你使用類似 if (nonbooleanValue) 或者 assert (nonbooleanValue) 這樣的代碼檢查布爾值。相反,你應該總是顯示地檢查布爾值,比如像下面的代碼這樣:
// 檢查是否為空字符串。
var fullName = '';
assert(fullName.isEmpty);
// 檢查是否小于等于零。
var hitPoints = 0;
assert(hitPoints <= 0);
// 檢查是否為 null。
var unicorn;
assert(unicorn == null);
// 檢查是否為 NaN。
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);
Lists
數組 Array 是幾乎所有編程語言中最常見的集合類型,在 Dart 中數組由 List 對象表示。通常稱之為 List。
Dart 中 List 字面量看起來與 JavaScript 中數組字面量一樣。下面是一個 Dart List 的示例:\
var list = [1, 2, 3];
List 的下標索引從 0 開始,第一個元素的下標為 0,最后一個元素的下標為 list.length - 1。你可以像 JavaScript 中的用法那樣獲取 Dart 中 List 的長度以及元素:
var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);
list[1] = 1;
assert(list[1] == 1);
如果想要創建一個編譯時常量的 List,在 List 字面量前添加 const 關鍵字即可:
var constantList = const [1, 2, 3];
// constantList[1] = 1; // 取消注釋將導致出錯。
Dart 在 2.3 引入了 擴展操作符(...)和 null-aware 擴展操作符(...?),它們提供了一種將多個元素插入集合的簡潔方法。
例如,你可以使用擴展操作符(...)將一個 List 中的所有元素插入到另一個 List 中:
var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);
如果擴展操作符右邊可能為 null ,你可以使用 null-aware 擴展操作符(...?)來避免產生異常:
var list;
var list2 = [0, ...?list];
assert(list2.length == 1);
Dart 在 2.3 還同時引入了 Collection If 和 Collection For,在構建集合時,可以使用條件判斷(if)和循環(for)。
下面示例是使用 Collection If 來創建一個 List 的示例,它可能包含 3 個或 4 個元素:
var nav = [
'Home',
'Furniture',
'Plants',
if (promoActive) 'Outlet'
];
下面示例是使用 Collection For 將列表中的元素修改后添加到另一個列表中的示例:
var listOfInts = [1, 2, 3];
var listOfStrings = [
'#0',
for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');
List 類中有許多用于操作 List 的便捷方法,你可以查閱泛型和集合獲取更多與之相關的信息。
Sets
Dart 中使用 Set 來表示無序且元素唯一的集合,Dart 支持 Set 字面量以及 Set 類型兩種形式的 Set。
下面是使用 Set 字面量來創建一個 Set 集合的方法:
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
Dart 推斷
halogens變量是一個
Set<String>類型的集合,如果往該 Set 中添加類型不正確的對象則會報錯。你可以查閱類型推斷獲取更多與之相關的內容。
可以使用在 {} 前加上類型參數的方式創建一個空的 Set,或者將 {} 賦值給一個 Set 類型的變量:
var names = <String>{};// 類型+{}的形式創建Set。
// Set<String> names = {}; // 聲明類型變量的形式創建 Set。
// var names = {}; // 這樣的形式將創建一個 Map 而不是 Set。
Map 字面量語法同 Set 字面量語法非常相似。因為先有的 Map 字面量語法,所以 {} 默認是 Map 類型。如果忘記在 {} 上注釋類型或賦值到一個未聲明類型的變量上,那么 Dart 會創建一個類型為 Map<dynamic, dynamic> 的對象。
向一個已存在的 Set 中添加項目可以使用 add() 方法或 addAll() 方法:
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 字面量前添加 const 關鍵字創建一個 Set 編譯時常量:
final constantSet = const {
'fluorine',
'chlorine',
'bromine',
'iodine',
'astatine',
};
// constantSet.add('helium'); // 取消注釋將導致出錯。
從 Dart 2.3 開始,Set 可以像 List 一樣支持使用擴展操作符(...
和 ...?
)以及 Collection If 和 Collection For 操作。你可以查閱 List 擴展操作符和List 集合操作符獲取更多相關信息。
Maps
通常來說, Map 是用來關聯 keys 和 values 的對象。 keys 和 values 可以是任何類型的對象。在一個 Map 對象中一個 key 只能出現一次。但是 value 可以出現多次。 Dart 中 Map 通過 Map 字面量和 Map 類型來實現。通常來說,Map 是一個鍵值對相關的對象。其中鍵和值都可以是任何類型的對象。每個 鍵 只能出現一次但是 值 可以重復出現多次。Dart 中 Map 提供了 Map 字面量以及 Map 類型兩種形式的 Map。
下面是一對使用 Map 字面量創建 Map 的例子:
var gifts = {
// 鍵: 值
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
- Dart 將
gifts
變量的類型推斷為Map<String, String>
,而降nobleGases
的類型推斷為Map<int, String>
。如果你向這兩個 Map 對象中添加不正確的類型值,將導致運行時異常。你可以閱讀類型推斷獲取更多相關信息。
你也可以使用 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()
而不是使用new Map()
構造 Map 對象。因為從 Dart2 開始,構造對象的new
關鍵字可以被省略掉。你可以查閱構造函數的使用獲取更多相關信息。
向現有的 Map 中添加鍵值對與 JavaScript 的操作類似:
var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds'; // 添加鍵值對
從一個 Map 中獲取一個值的操作也與 JavaScript 類似。
var gifts = {'first': 'partridge'};
assert(gifts['first'] == 'partridge');
如果檢索的 Key 不存在于 Map 中則會返回一個 null:
var gifts = {'first': 'partridge'};
assert(gifts['fifth'] == null);
使用 .length 可以獲取 Map 中鍵值對的數量:
var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);
在一個 Map 字面量前添加 const 關鍵字可以創建一個 Map 編譯時常量:
final constantMap = const {
2: 'helium',
10: 'neon',
18: 'argon',
};
// constantMap[2] = 'Helium'; // 取消注釋將導致出錯。
從 Dart 2.3 Map 可以像 List 一樣支持使用擴展操作符(...
和 ...?
)以及 Collection If 和 Collection For 操作。你可以查閱 List 擴展操作符和 List 集合操作符獲取更多相關信息。
Runes
Dart 使用 Runes 符文來表示 UTF-32 編碼的字符串。
Unicode 編碼為每一個字母、數字和符號都定義了一個唯一的數值。因為 Dart 中的字符串是一個 UTF-16 的字符序列,所以如果想要表示 32 位的 Unicode 數值則需要一種特殊的語法。
通常使用 \uXXXX
來表示 Unicode 字符,XXXX 是一個四位數的 16 進制數字。例如心形字符(?)的 Unicode 為 \u2665
。對于不是四位數的 16 進制數字,需要使用大括號將其括起來。例如大笑的 emoji 表情(??)的 Unicode 為 \u{1f600}
。
String 類中有一些屬性可以用來提取字符串的 Rune 符文信息。codeUnitAt
和 codeUnit
屬性返回 16 位代碼單元。runes
屬性可以獲取字符串的 Runes 符文。
Symbols
Symbol 表示 Dart 中聲明的操作符或者標識符,該類型的對象幾乎不會被使用到,但是如果需要按名稱引用它們的 API 時就非常有用。因為代碼壓縮后會改變這些符號的名稱但不會改變具體的符號。
可以使用在標識符前加 # 前綴來獲取 Symbol:
#radix
#bar
Symbol 字面量是編譯時常量。
Functions
Dart 是一種真正面向對象的語言,所以即便函數也是對象并且類型為 Function,這意味著函數可以被賦值給變量或者作為其它函數的參數。你也可以像調用函數一樣調用 Dart 類的實例。詳情請查閱 可調用的類。
下面是定義一個函數的例子:
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
雖然高效 Dart 指南建議在公開的 API 上定義返回類型,不過即便不定義,該函數也依然有效:
isNoble(atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
如果函數體內只包含一個表達式,你可以使用簡寫語法:
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
語法 => 表達式 是 { return 表達式; } 的簡寫,=> 有時也稱之為胖箭頭語法。
函數可以有兩種形式的參數:必要參數 和 可選參數。必要參數定義在參數列表前面,可選參數則定義在必要參數后面。可選參數可以是 命名的 或 位置的。
可選參數
可選參數分為命名參數和位置參數,可在參數列表中任選其一使用,但兩者不能同時出現在參數列表中。
命名參數
當你調用函數時,可以使用 參數名: 參數值 的形式來指定命名參數。例如:
enableFlags(bold: true, hidden: false);
定義函數時,使用 {param1, param2, …} 來指定命名參數:
/// 設置 [bold] 和 [hidden] 標識……
void enableFlags({bool bold, bool hidden}) {...}
雖然命名參數是可選參數的一種類型,但是你仍然可以使用 @required 注解來標識一個命名參數是必須的參數,此時調用者則必須為該參數提供一個值。例如:
const Scrollbar({Key key, @required Widget child})
如果調用者想要通過 Scrollbar
的構造函數構造一個 Scrollbar 對象而不提供 child
參數,則會導致編譯錯誤。
@required 注解定義在 meta 包中,可以直接導入 package:meta/meta.dart
包使用。
位置參數
使用 []
將一系列參數包裹起來作為位置參數:
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。
下面是設置可選參數默認值示例:
/// 設置 [bold] 和 [hidden] 標識……
void enableFlags({bool bold = false, bool hidden = false}) {...}
// bold 的值將為 true;而 hidden 將為 false。
enableFlags(bold: true);
- 在老版本的 Dart 代碼中會使用冒號(
:
)而不是=
來設置命名參數的默認值。原因在于剛開始的時候命名參數只支持:
。不過現在這個支持已經過時,所以我們建議你現在都 使用=
來指定默認值。
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 同樣也可以作為默認值。下面的示例定義了一個名為 doStuff() 的函數,并為其名為 list 和 gifts 的參數指定了一個 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() 函數
每個 Dart 程序都必須有一個 main() 頂級函數作為程序的入口,main() 函數返回值為 void 并且有一個 List<String> 類型的可選參數。
下面是一個 Web 應用的 main() 函數示例:
void main() {
querySelector('#sample_text_id')
..text = 'Click me!'
..onClick.listen(reverseText);
}
- 上述代碼中的
..
語法稱之為 級聯調用。使用級聯訪問可以在一個對象上執行多個操作。
下面是使用命令行訪問帶參數的 main() 函數示例:
// 使用命令 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');
}
你可以通過使用 參數庫 來定義和解析命令行參數。
函數作為一級對象
可以將函數作為參數傳遞給另一個函數。例如:
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
// 將 printElement 函數作為參數傳遞。
list.forEach(printElement);
你也可以將函數賦值給一個變量,比如:
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');
該示例中使用了匿名函數。下一節會有更多與其相關的介紹。
匿名函數
大多數方法都是有名字的,比如 main()
或 printElement()
。你可以創建一個沒有名字的方法,稱之為 匿名函數,或 Lambda表達式 或 Closure閉包。你可以將匿名方法賦值給一個變量然后使用它,比如將該變量添加到集合或從中刪除。
匿名方法看起來與命名方法類似,在括號之間可以定義參數,參數之間用逗號分割。
后面大括號中的內容則為函數體:
([[*類型*] *參數*[, …]]) { *函數體*; };
下面代碼定義了只有一個參數 item
且沒有參數類型的匿名方法。List 中的每個元素都會調用這個函數,打印元素位置和值的字符串:
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
詞法作用域
Dart 是詞法有作用域語言,變量的作用域在寫代碼的時候就確定了,大括號內定義的變量只能在大括號內訪問,與 Java 類似。
下面是一個嵌套函數中變量在多個作用域中的示例:
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);
}
}
}
注意 nestedFunction()
函數可以訪問包括頂層變量在內的所有的變量。
詞法閉包
閉包 即一個函數對象,即使函數對象的調用在它原始作用域之外,依然能夠訪問在它詞法作用域內的變量。
函數可以封閉定義到它作用域內的變量。接下來的示例中,函數 makeAdder()
捕獲了變量 addBy
。無論函數在什么時候返回,它都可以使用捕獲的 addBy
變量。
/// 返回一個將 [addBy] 添加到該函數參數的函數。
Function makeAdder(num addBy) {
return (num i) => addBy + i;
}
void main() {
// 生成加 2 的函數。
var add2 = makeAdder(2);
// 生成加 4 的函數。
var add4 = makeAdder(4);
assert(add2(3) == 5);
assert(add4(3) == 7);
}
測試函數是否相等
下面是頂級函數,靜態方法和示例方法相等性的測試示例:
void foo() {} // 定義頂層函數
class A {
static void bar() {} // 定義靜態方法
void baz() {} // 定義實例方法
}
void main() {
var x;
// 比較頂層函數是否相等。
x = foo;
assert(foo == x);
// 比較靜態方法是否相等。
x = A.bar;
assert(A.bar == x);
// 比較實例方法是否相等。
var v = A(); // A 的實例 #1
var w = A(); // A 的實例 #2
var y = w;
x = w.baz;
// 這兩個閉包引用了相同的實例對象,因此它們相等。
assert(y.baz == x);
// 這兩個閉包引用了不同的實例對象,因此它們不相等。
assert(v.baz != w.baz);
}
返回值
所有的函數都有返回值。沒有顯示返回語句的函數最后一行默認為執行 return null;。
foo() {}
assert(foo() == null);
運算符
下表是 Dart 中定義的運算符,很多運算符都可以重寫。詳情參考重寫運算符。
描述 運算符
一元后綴 表達式++ 表達式-- () [] . ?.
一元前綴 -表達式 !表達式 ~表達式 ++表達式 --表達式
乘除法 * / % ~/
加減法 + -
位運算 << >> >>>
二進制與 &
二進制異或 ^
二進制或 |
關系和類型測試 >= > <= < as is is!
相等判斷 == !=
邏輯與 &&
邏輯或 ||
空判斷 ??
條件表達式 表達式 1 ? 表達式 2 : 表達式 3
級聯 ..
賦值 = *= /= += -= &= ^= 等等……
一旦你使用了運算符,就創建了表達式。下面是一些運算符表達式的示例:
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) ...
- 對于有兩個操作數的運算符,左邊的操作數決定了運算符的功能。比如如果有一個 Vector 對象和一個 Point 對象,表達式 aVector + aPoint 中所使用的是 Vector 對象中定義的 + 運算符。
算術運算符
Dart 支持常用的算術運算符:
運算符 描述
- 加
– 減
-表達式 一元負, 也可以作為反轉(反轉表達式的符號)
- 乘
/ 除
~/ 除并取整
% 取模
assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // 結果是一個浮點數
assert(5 ~/ 2 == 2); // 結果是一個整數
assert(5 % 2 == 1); // 取余
assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');
Dart 還支持自增自減操作。
Operator
++var
var = var + 1 (表達式的值為 var + 1)
var++ var = var + 1 (表達式的值為 var)
--var var = var – 1 (表達式的值為 var – 1)
var-- var = var – 1 (表達式的值為 var)
var a, b;
a = 0;
b = ++a; // 在 b 賦值前將 a 增加 1。
assert(a == b); // 1 == 1
a = 0;
b = a++; // 在 b 賦值后將 a 增加 1。
assert(a != b); // 1 != 0
a = 0;
b = --a; // 在 b 賦值前將 a 減少 1。
assert(a == b); // -1 == -1
a = 0;
b = a--; // 在 b 賦值后將 a 減少 1。
assert(a != b); // -1 != 0
關系運算符
下表列出了關系運算符及含義:
|
Operator
==
|
相等
|
| --- | --- |
| !=
| 不等 |
| >
| 大于 |
| <
| 小于 |
| >=
| 大于等于 |
| <=
| 小于等于 |
要判斷兩個對象 x 和 y 是否表示相同的事物使用 ==
即可。(在極少數情況下,可能需要使用 identical() 函數來確定兩個對象是否完全相同。)。下面是 ==
運算符的一些規則:
假設有變量 x 和 y,且 x 和 y 至少有一個為 null,則當且僅當 x 和 y 均為 null 時 x == y 才會返回 true,否則只有一個為 null 則返回 false。
*x*.==(*y*)
將會返回值,這里不管有沒有 y,即 y 是可選的。也就是說==
其實是 x 中的一個方法,并且可以被重寫。詳情請查閱重寫運算符。
下面的代碼給出了每一種關系運算符的示例:
assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(2 <= 3);
類型判斷運算符
as
、is
、is!
運算符是在運行時判斷對象類型的運算符。
|
Operator
as
|
類型轉換(也用作指定類前綴))
|
| --- | --- |
| is
| 如果對象是指定類型則返回 true |
| is!
| 如果對象是指定類型則返回 false |
當且僅當 obj
實現了 T
的接口,obj is T
才是 true。例如 obj is Object
總為 true,因為所有類都是 Object 的子類。
使用 as
操作符可以把對象轉換為特定的類型。一般情況下可以將其當做 is
判定類型后調用所判定對象的函數的縮寫形式。假設有如下代碼:
if (emp is Person) {
// 類型檢查
emp.firstName = 'Bob';
}
你可以使用 as 運算符進行縮寫:
(emp as Person).firstName = 'Bob';
賦值運算符
可以使用 = 來賦值,同時也可以使用 ??= 來為值為 null 的變量賦值。
// 將 value 賦值給 a
a = value;
// 當且僅當 b 為 null 時才賦值
b ??= value;
像 += 這樣的賦值運算符將算數運算符和賦值運算符組合在了一起。
var a = 2; // 使用 = 賦值
a *= 3; // 賦值并做乘法運算:a = a * 3
assert(a == 6);
邏輯運算符
使用邏輯運算符你可以反轉或組合布爾表達式。
運算符 描述
!!表達式 對表達式結果取反(即將 true 變為 false,false 變為 true)
|| 邏輯或
&& 邏輯與
if (!done && (col == 0 || col == 3)) {
// ...Do something...
}
按位和移位運算符
在 Dart 中,二進制位運算符可以操作二進制的某一位,但僅適用于整數。
& 按位與
| 按位或
^ 按位異或
~表達式 按位取反(即將 “0” 變為 “1”,“1” 變為 “0”)
<< 位左移
位右移
final value = 0x22;
final bitmask = 0x0f;
assert((value & bitmask) == 0x02); // 按位與
assert((value & ~bitmask) == 0x20); // 取反后按位與
assert((value | bitmask) == 0x2f); // 按位或
assert((value ^ bitmask) == 0x2d); // 按位異或
assert((value << 4) == 0x220); // 位左移
assert((value >> 4) == 0x02); // 位右移
條件表達式
Dart 有兩個特殊的運算符可以用來替代 if-else 語句:
condition ? expr1 : expr2
條件 ? 表達式 1 : 表達式 2:如果條件為 true,執行表達式 1并返回執行結果,否則執行表達式 2 并返回執行結果。
expr1 ?? expr2
表達式 1 ?? 表達式 2:如果表達式 1 為非 null 則返回其值,否則執行表達式 2 并返回其值。
如果賦值是根據布爾表達式則考慮使用 ?
:。
var visibility = isPublic ? 'public' : 'private';
如果賦值是根據判定是否為 null 則考慮使用??
。
String playerName(String name) => name ?? 'Guest';
// 相對使用 ?: 運算符來說稍微長了點。
String playerName(String name) => name != null ? name : 'Guest';
// 如果使用 if-else 則更長。
String playerName(String name) {
if (name != null) {
return name;
} else {
return 'Guest';
}
}
級聯運算符(..)
級聯運算符(..)可以讓你在同一個對象上連續調用多個對象的變量或方法。
比如下面的代碼:
querySelector('#confirm') // 獲取對象。
..text = 'Confirm' // 使用對象的成員。
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
第一個方法 querySelector 返回了一個 Selector 對象,后面的級聯操作符都是調用這個 Selector 對象的成員并忽略每個操作的返回值。
上面的代碼相當于:
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'); // 出錯:void 對象中沒有方法 write。
上述代碼中的 sb.write() 方法返回的是 void,返回值為 void 的方法則不能使用級聯運算符。
其他運算符
大多數其它的運算符,已經在其它的示例中使用過:
運算符 | 名字 | 描述 |
---|---|---|
() |
使用方法 | 代表調用一個方法 |
[] |
訪問 List | 訪問 List 中特定位置的元素 |
. |
訪問成員 | 成員訪問符 |
?. |
條件訪問成員 | 與上述成員訪問符類似,但是左邊的操作對象不能為 null,例如 foo?.bar,如果 foo 為 null 則返回 null ,否則返回 bar |
更多關于 .
, ?.
和 ..
運算符介紹,請參考類.
流程控制語句
你可以使用下面的語句來控制 Dart 代碼的執行流程:
if
和else
for
循環while
和do
-while
循環break
和continue
switch
和case
assert
使用 try-catch
和 throw
也能影響控制流,詳情參考異常部分。
If 和 Else
Dart 支持 if - else
語句,其中 else
是可選的,比如下面的例子。你也可以參考條件表達式。
if (isRaining()) {
you.bringRainCoat();
} else if (isSnowing()) {
you.wearJacket();
} else {
car.putTopDown();
}
與 JavaScript 不同的是,Dart 的 if 語句中的條件必須是一個布爾值,不能是其它類型。詳情請查閱布爾值。
For 循環
你可以使用標準的 for 循環進行迭代。例如:
var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
message.write('!');
}
在 Dart 語言中,for 循環中的閉包會自動捕獲循環的 索引值 以避免 JavaScript 中一些常見的陷阱。假設有如下代碼:\
var callbacks = [];
for (var i = 0; i < 2; i++) {
callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());
上述代碼執行后會輸出 0
和 1
,但是如果在 JavaScript 中執行同樣的代碼則會輸出兩個 2
。
如果要遍歷的對象實現了 Iterable 接口,則可以使用 forEach() 方法,如果不需要使用到索引,則使用 forEach
方法是一個非常好的選擇:
candidates.forEach((candidate) => candidate.interview());
像 List 和 Set 等實現了 Iterable 接口的類還支持 for-in
形式的迭代:
var collection = [0, 1, 2];
for (var x in collection) {
print(x); // 0 1 2
}
While 和 Do-While
while 循環會在執行循環體前先判斷條件:
while (!isDone()) {
doSomething();
}
do-while 循環則會先執行一遍循環體 再 判斷條件:
do {
printLine();
} while (!atEndOfPage());
Break 和 Continue
while (true) {
if (shutDownRequested()) break;
processIncomingRequests();
}
使用continue
可以跳過本次循環直接進入下一次循環:
for (int i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
if (candidate.yearsExperience < 5) {
continue;
}
candidate.interview();
}
上述代碼中的 candidates 如果像 List 或 Set 一樣實現了 Iterable 接口則可以簡單地使用下述寫法:
candidates
.where((c) => c.yearsExperience >= 5)
.forEach((c) => c.interview());
Switch 和 Case
Switch 語句在 Dart 中使用 ==
來比較整數、字符串或編譯時常量,比較的兩個對象必須是同一個類型且不能是子類并且沒有重寫 ==
操作符。枚舉類型非常適合在 Switch
語句中使用。
每一個非空的 case 子句都必須有一個 break 語句,也可以???過 continue、throw 或者 return 來結束非空 case 語句。
當沒有 case 語句匹配時,可以使用 default 子句來匹配這種情況:
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();
}
下面的例子忽略了 case 子句的 break 語句,因此會產生錯誤:
var command = 'OPEN';
switch (command) {
case 'OPEN':
executeOpen();
// 錯誤: 沒有 break
case 'CLOSED':
executeClosed();
break;
}
但是,Dart 支持空的 case 語句,允許其以 fall-through 的形式執行。
var command = 'CLOSED';
switch (command) {
case 'CLOSED': // case 語句為空時的 fall-through 形式。
case 'NOW_CLOSED':
// case 條件值為 CLOSED 和 NOW_CLOSED 時均會執行該語句。
executeNowClosed();
break;
}
在非空 case 語句中想要實現 fall-through 的形式,可以使用 continue 語句配合 lable 的方式實現:
var command = 'CLOSED';
switch (command) {
case 'CLOSED':
executeClosed();
continue nowClosed;
// 繼續執行標簽為 nowClosed 的 case 子句。
nowClosed:
case 'NOW_CLOSED':
// case 條件值為 CLOSED 和 NOW_CLOSED 時均會執行該語句。
executeNowClosed();
break;
}
每個 case 子句都可以有局部變量且僅在該 case 語句內可見。
斷言
在開發過程中,可以在條件表達式為 false 時使用 - assert(條件, 可選信息); - 語句來打斷代碼的執行。你可以在本文中找到大量使用 assert 的例子。下面是相關示例:
// 確保變量值不為 null。
assert(text != null);
// 確保變量值小于 100。
assert(number < 100);
// 確保這是一個 https 地址。
assert(urlString.startsWith('https'));
assert 的第二個參數可以為其添加一個字符串消息。
assert(urlString.startsWith('https'),
'URL ($urlString) should start with "https".');