Dart 開發語言概覽(學習)

一個簡單的 Dart 程序

// 定義一個函數。
printInteger(int aNumber) {
  print('The number is $aNumber.'); // 打印輸出到控制臺。
}

// Dart 程序從 main() 函數開始執行。
main() {
  var number = 42; // 聲明并初始化一個變量。
  printInteger(number); // 調用一個函數。
}

// This is a comment.

// 注釋。
以雙斜杠開頭的一行語句稱為單行注釋。Dart 同樣支持多行注釋和文檔注釋。

int

表示一種數據類型。Dart 中一些其他的內置類型包括 StringListbool

42

表示一個數字字面量。數字字面量是一種編譯時常量。

print()

一種便利的將信息輸出顯示的方式。

'...' (或 "...")

表示字符串字面量。

variableName (或{expression})

表示字符串插值:字符串字面量中包含的變量或表達式。

main()

一個特殊且 必須的 頂級函數,Dart 應用程序總是會從該函數開始執行。

var

用于定義變量,通過這種方式定義變量不需要指定變量類型。

重要概念

在學習 Dart 語言時, 應該牢記以下幾點:

  • 所有變量引用的都是 對象,每個對象都是一個 的實例。數字、函數以及 null 都是對象。所有的類都繼承于 Object 類。
  • 盡管 Dart 是強類型語言,但是在聲明變量時指定類型是可選的,因為 Dart 可以進行類型推斷。在上述代碼中,變量 number 的類型被推斷為 int 類型。如果想顯式地聲明一個不確定的類型,可以使用特殊類型 dynamic

  • Dart 支持泛型,比如 List<int>(表示一組由 int 對象組成的列表)或 List<dynamic>(表示一組由任何類型對象組成的列表)。

  • Dart 支持頂級函數(例如 main 方法),同時還支持定義屬于類或對象的函數(即 靜態實例方法)。你還可以在函數中定義函數(嵌套局部函數)。

  • Dart 支持頂級 變量,以及定義屬于類或對象的變量(靜態和實例變量)。實例變量有時稱之為域或屬性。

  • Dart 沒有類似于 Java 那樣的 publicprotectedprivate 成員訪問限定符。如果一個標識符以下劃線 (_) 開頭則表示該標識符在庫內是私有的。可以查閱 庫和可見性 獲取更多相關信息。

  • 標識符 可以以字母或者下劃線 (_) 開頭,其后可跟字符和數字的組合。

  • Dart 中 表達式語句 是有區別的,表達式有值而語句沒有。比如條件表達式 expression condition ? expr1 : expr2 中含有值 expr1expr2。與 if-else 分支語句相比,if-else 分支語句則沒有值。一個語句通常包含一個或多個表達式,但是一個表達式不能只包含一個語句。

  • Dart 工具可以顯示 警告錯誤 兩種類型的問題。警告表明代碼可能有問題但不會阻止其運行。錯誤分為編譯時錯誤和運行時錯誤;編譯時錯誤代碼無法運行;運行時錯誤會在代碼運行時導致異常

變量

下面的示例代碼將創建一個變量并將其初始化:

var name = 'Bob';

變量僅存儲對象的引用。這里名為 name 的變量存儲了一個 String 類型對象的引用,“Bob” 則是該對象的值。

name 變量的類型被推斷為 String,但是你可以為其指定類型。如果一個對象的引用不局限于單一的類型,可以根據設計指南將其指定為 Objectdynamic 類型。

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 標準。
intdouble 都是 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 符文信息。codeUnitAtcodeUnit 屬性返回 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 表達式; } 的簡寫,=> 有時也稱之為胖箭頭語法。

  • 在 => 與 ; 之間的只能是 表達式 而非 語句。比如你不能將一個 if語句 放在其中,但是可以放置 條件表達式

函數可以有兩種形式的參數:必要參數 和 可選參數。必要參數定義在參數列表前面,可選參數則定義在必要參數后面。可選參數可以是 命名的 或 位置的。

可選參數

可選參數分為命名參數和位置參數,可在參數列表中任選其一使用,但兩者不能同時出現在參數列表中。

命名參數

當你調用函數時,可以使用 參數名: 參數值 的形式來指定命名參數。例如:

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() 函數來確定兩個對象是否完全相同。)。下面是 == 運算符的一些規則:

  1. 假設有變量 xy,且 x 和 y 至少有一個為 null,則當且僅當 x 和 y 均為 null 時 x == y 才會返回 true,否則只有一個為 null 則返回 false。

  2. *x*.==(*y*) 將會返回值,這里不管有沒有 y,即 y 是可選的。也就是說 == 其實是 x 中的一個方法,并且可以被重寫。詳情請查閱重寫運算符

下面的代碼給出了每一種關系運算符的示例:

assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(2 <= 3);

類型判斷運算符

asisis! 運算符是在運行時判斷對象類型的運算符。

|

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 代碼的執行流程:

  • ifelse

  • for 循環

  • whiledo-while 循環

  • breakcontinue

  • switchcase

  • assert

使用 try-catchthrow 也能影響控制流,詳情參考異常部分。

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());

上述代碼執行后會輸出 01,但是如果在 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".');
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容

  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學習記錄文檔,今天18年5月份再次想寫文章,發現簡書還為我保存起的...
    Jenaral閱讀 2,808評論 2 9
  • Dart重要概念:1,在變量中可以放置的所有東西都是對象,而每個對象都是類的實例。無論數字、函數、和null都是對...
    哥哥是歐巴Vitory閱讀 818評論 0 1
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,532評論 1 51
  • 此文章是v1.0+時編寫,年代久遠,小心有毒,謹慎食用!!! 一些重要概念 所有的東西都是對象,所有的對象都是類的...
    soojade閱讀 10,084評論 2 27
  • 一個基本的Dart程序 下面的代碼中使用了很多Dart最基本的特性: 重要概念 所有你能夠賦值給一個變量的都是一個...
    YZune閱讀 14,518評論 2 14