Flutter-一天入門dart語言

參考網(wǎng)址 http://dart.goodev.org/guides/language/language-tour 相關(guān)數(shù)據(jù)所有權(quán)歸該網(wǎng)站所有,當(dāng)前文章不會產(chǎn)生收益。

該文章不會被標(biāo)注被原創(chuàng),如果其他人復(fù)制或轉(zhuǎn)載或以其他方式獲取當(dāng)前文章內(nèi)容并標(biāo)注為原創(chuàng),由此與原作者產(chǎn)生的糾紛與本人無關(guān)。

文章將會被同步至微信公眾號:Android部落格,也不會被標(biāo)注為原創(chuàng)。

1、Variables(變量)

var name = 'Bob';

變量是一個引用。上面名字為 name 的變量引用了 一個內(nèi)容為 “Bob” 的 String 對象。

  • 1)Default value(默認值)

沒有初始化的變量自動獲取一個默認值為 null。類型為數(shù)字的 變量如何沒有初始化其值也是 null,不要忘記了 數(shù)字類型也是對象。
int lineCount;
assert(lineCount == null);

2、Optional types(可選的類型)

在聲明變量的時候,你可以選擇加上具體 類型:
String name = 'Bob';

添加類型可以更加清晰的表達你的意圖

3、Final and const

如果你以后不打算修改一個變量,使用 final 或者 const。 一個 final 變量只能賦值一次;一個 const 變量是編譯時常量。(Const 變量同時也是 final 變量。) 頂級的 final 變量或者類中的 final 變量在 第一次使用的時候初始化。

注意: 實例變量可以為 final 但是不能是 const 。

下面是 final 變量的示例:

final name = 'Bob'; // Or: final String name = 'Bob';

const 變量為編譯時常量。 如果 const 變量在類中,請定義為 static const。 可以直接定義 const 和其值,也 可以定義一個

const var message = new StringBuffer("Dart is fun");
for (var i = 0; i < 5; i++) {
  message.write('!');
} 

變量使用其他 const 變量的值來初始化其值。

const bar = 1000000;       // Unit of pressure (dynes/cm2)
const atm = 1.01325 * bar; // Standard atmosphere

const 關(guān)鍵字不僅僅只用來定義常量。 有可以用來創(chuàng)建不變的值, 還能定義構(gòu)造函數(shù)為 const 類型的,這種類型 的構(gòu)造函數(shù)創(chuàng)建的對象是不可改變的。任何變量都可以有一個不變的值。

// Note: [] creates an empty list.
// const [] creates an empty, immutable list (EIA).
var foo = const [];   // foo is currently an EIA.
final bar = const []; // bar will always be an EIA.
const baz = const []; // baz is a compile-time constant EIA.

// You can change the value of a non-final, non-const variable,
// even if it used to have a const value.
foo = [];

// You can't change the value of a final or const variable.
// bar = []; // Unhandled exception.
// baz = []; // Unhandled exception.

4、Built-in types(內(nèi)置的類型)

Dart 內(nèi)置支持下面這些類型:

  • numbers
  • strings
  • booleans
  • lists (也被稱之為 arrays)
  • maps
  • runes (用于在字符串中表示 Unicode 字符)
  • symbols

你可以直接使用字母量來初始化上面的這些類型。 例如 'this is a string' 是一個字符串字面量, true 是一個布爾字面量。
由于 Dart 中每個變量引用的都是一個對象 – 一個類的實例, 你通常使用構(gòu)造函數(shù)來初始化變量。

一些內(nèi)置的類型具有自己的構(gòu)造函數(shù)。例如, 可以使用 Map()構(gòu)造函數(shù)來創(chuàng)建一個 map, 就像這樣 new Map()。

1)Numbers(數(shù)值)

Dart 支持兩種類型的數(shù)字:

  • int
    整數(shù)值,其取值通常位于 -253 和 253 之間。
  • double
    64-bit (雙精度) 浮點數(shù),符合 IEEE 754 標(biāo)準。
    int 和 double 都是 num 的子類。

num 類型定義了基本的操作符,例如 +, -, /, 和 *, 還定義了 abs()、 ceil()、和 floor() 等 函數(shù)。

整數(shù)是不帶小數(shù)點的數(shù)字。下面是一些定義 整數(shù)的方式:

var x = 1;
var hex = 0xDEADBEEF;
var bigInt = 34653465834652437659238476592374958739845729;

如果一個數(shù)帶小數(shù)點,則其為 double, 下面是定義 double 的一些方式:

var y = 1.1;
var exponents = 1.42e5;

下面是字符串和數(shù)字之間轉(zhuǎn)換的方式:

// 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');

整數(shù)類型支持傳統(tǒng)的位移操作符,(<<, >>), AND (&), 和 OR (|) 。例如:

assert((3 << 1) == 6);  // 0011 << 1 == 0110
assert((3 >> 1) == 1);  // 0011 >> 1 == 0001
assert((3 | 4)  == 7);  // 0011 | 0100 == 0111

數(shù)字字面量為編譯時常量。 很多算術(shù)表達式 只要其操作數(shù)是常量,則表達式結(jié)果 也是編譯時常量:

const msPerSecond = 1000;
const secondsUntilRetry = 5;
const msUntilRetry = secondsUntilRetry * msPerSecond;

2)Strings(字符串)

Dart 字符串是 UTF-16 編碼的字符序列。

可以使用單引號或者雙引號來創(chuàng)建字符串:

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}。如果表達式是一個標(biāo)識符,可以省略 {}。 如果表達式的結(jié)果為一個對象,則 Dart 會調(diào)用對象的 toString() 函數(shù)來獲取一個字符串。

var s = 'string interpolation';

assert('Dart has $s, which is very handy.' ==
       'Dart has string interpolation, ' +
       'which is very handy.');
assert('That deserves all caps. ' +
       '${s.toUpperCase()} is very handy!' ==
       'That deserves all caps. ' +
       'STRING INTERPOLATION is very handy!');

注意: == 操作符判斷兩個對象的內(nèi)容是否一樣。

如果兩個字符串包含一樣的字符編碼序列, 則他們是相等的。
可以使用 + 操作符來把多個字符串鏈接為一個,也可以把多個 字符串放到一起來實現(xiàn)同樣的功能:

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.');

使用三個單引號或者雙引號也可以 創(chuàng)建多行字符串對象:

var s1 = '''
You can create
multi-line strings like this one.
''';

var s2 = """This is also a
multi-line string.""";

通過提供一個 r 前綴可以創(chuàng)建一個 “原始 raw” 字符串:
var s = r"In a raw string, even \n isn't special.";

字符串字面量是編譯時常量, 帶有字符串插值的字符串定義,若干插值表達式引用的為編譯時常量則其結(jié)果也是編譯時常量:

// These work in a const string.
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';

// These do NOT work in a const string.
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = const [1, 2, 3];

const validConstString = '$aConstNum $aConstBool $aConstString';
// const invalidConstString = '$aNum $aBool $aString $aConstList';

3)Booleans(布爾值)

為了代表布爾值,Dart 有一個名字為 bool 的類型。

只有兩個對象是布爾類型的:true 和 false 所創(chuàng)建的對象, 這兩個對象也都是編譯時常量。

當(dāng) Dart 需要一個布爾值的時候,只有 true 對象才被認為是 true。 所有其他的值都是 flase。

Dart 這樣設(shè)計布爾值,是為了避免奇怪的行為。很多 JavaScript 代碼 都遇到這種問題。

對于你來說,在寫代碼的時候你不用這些寫代碼:if (nonbooleanValue),你應(yīng)該顯式的 判斷變量是否為布爾值類型。

例如:

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

4)Lists(列表)

也許 array (或者有序集合)是所有編程語言中最常見的集合類型。 在 Dart 中數(shù)組就是 List 對象。所以 通常我們都稱之為 lists。

Dart list 字面量和 JavaScript 的數(shù)組字面量類似。下面是一個 Dart list 的示例:
var list = [1, 2, 3];

Lists 的下標(biāo)索引從 0 開始,第一個元素的索引是 0. list.length - 1 是最后一個元素的索引。 訪問 list 的長度和元素與 JavaScript 中的用法一樣:

var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);

list[1] = 1;
assert(list[1] == 1);

在 list 字面量之前添加 const 關(guān)鍵字,可以 定義一個不變的 list 對象(編譯時常量):

var constantList = const [1, 2, 3];
// constantList[1] = 1; // Uncommenting this causes an error.

5)Maps

通常來說,Map 是一個鍵值對相關(guān)的對象。 鍵和值可以是任何類型的對象。每個 鍵 只出現(xiàn)一次, 而一個值則可以出現(xiàn)多次。Dart 通過 map 字面量 和 Map 類型支持 map。
下面是一些創(chuàng)建簡單 map 的示例:

var gifts = {
// Keys      Values
  'first' : 'partridge',
  'second': 'turtledoves',
  'fifth' : 'golden rings'
};

var nobleGases = {
// Keys  Values
  2 :   'helium',
  10:   'neon',
  18:   'argon',
};

使用 Map 構(gòu)造函數(shù)也可以實現(xiàn)同樣的功能:

var gifts = new Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var nobleGases = new Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';

往 map 中添加新的鍵值對和在 JavaScript 中的用法一樣:

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds'; // Add a key-value pair

獲取 map 中的對象也和 JavaScript 的用法一樣:

var gifts = {'first': 'partridge'};
assert(gifts['first'] == 'partridge');

如果所查找的鍵不存在,則返回 null:

var gifts = {'first': 'partridge'};
assert(gifts['fifth'] == null);

使用 .length 來獲取 map 中鍵值對的數(shù)目:

var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);

同樣使用 const 可以創(chuàng)建一個 編譯時常量的 map:

final constantMap = const {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

// constantMap[2] = 'Helium'; // Uncommenting this causes an error.

6)Runes

在 Dart 中,runes 代表字符串的 UTF-32 code points。
Unicode 為每一個字符、標(biāo)點符號、表情符號等都定義了 一個唯一的數(shù)值。

由于 Dart 字符串是 UTF-16 code units 字符序列, 所以在字符串中表達 32-bit Unicode 值就需要 新的語法了。

通常使用 \uXXXX 的方式來表示 Unicode code point, 這里的 XXXX 是4個 16 進制的數(shù)。 例如,心形符號 (?) 是 \u2665。 對于非 4 個數(shù)值的情況, 把編碼值放到大括號中即可。 例如,笑臉 emoji (??) 是 \u{1f600}。

String 類 有一些屬性可以提取 rune 信息。 codeUnitAt 和 codeUnit 屬性返回 16-bit code units。 使用 runes 屬性來獲取字符串的 runes 信息。

下面是示例演示了 runes、 16-bit code units、和 32-bit code points 之間的關(guān)系。

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

結(jié)果是:

??

[55357, 56399]

[128079]

? ?? ?? ?? ?? ??

7)Symbols

一個 Symbol object 代表 Dart 程序中聲明的操作符或者標(biāo)識符。

你也許從來不會用到 Symbol,但是該功能對于通過名字來引用標(biāo)識符的情況 是非常有價值的,特別是混淆后的代碼, 標(biāo)識符的名字被混淆了,但是 Symbol 的名字不會改變。
使用 Symbol 字面量來獲取標(biāo)識符的 symbol 對象,也就是在標(biāo)識符 前面添加一個 # 符號:

#radix
#bar

Symbol 字面量定義是編譯時常量。

5、Functions(方法)

Dart 是一個真正的面向?qū)ο笳Z言,方法也是對象并且具有一種 類型, Function。

這意味著,方法可以賦值給變量,也可以當(dāng)做其他方法的參數(shù)。 也可以把 Dart 類的實例當(dāng)做方法來調(diào)用。

下面是定義方法的示例:

bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

當(dāng)然也可以選擇忽略類型定義:

isNoble(atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

對于只有一個表達式的方法,你可以選擇 使用縮寫語法來定義:

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

這個 => expr 語法是 { return expr; } 形式的縮寫。=> 形式 有時候也稱之為 胖箭頭 語法。

注意: 在箭頭 (=>) 和冒號 (;) 之間只能使用一個 表達式 – 不能使用 語句。 例如:你不能使用 if statement,但是可以 使用條件表達式conditional expression。

方法可以有兩種類型的參數(shù):必需的和可選的。 必需的參數(shù)在參數(shù)列表前面, 后面是可選參數(shù)。

1)Optional parameters(可選參數(shù))

可選參數(shù)可以是命名參數(shù)或者基于位置的參數(shù),但是這兩種參數(shù)不能同時當(dāng)做可選參數(shù)。

Optional named parameters(可選命名參數(shù))
調(diào)用方法的時候,你可以使用這種形式 paramName: value 來指定命名參數(shù)。例如:
enableFlags(bold: true, hidden: false)

在定義方法的時候,使用 {param1, param2, …} 的形式來指定命名參數(shù):

/// Sets the [bold] and [hidden] flags to the values
/// you specify.
enableFlags({bool bold, bool hidden}) {
  // ...
}

2)Optional positional parameters(可選位置參數(shù))

把一些方法的參數(shù)放到 [] 中就變成可選 位置參數(shù)了:

String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

下面是不使用可選參數(shù)調(diào)用上面方法 的示例:
assert(say('Bob', 'Howdy') == 'Bob says Howdy')

下面是使用可選參數(shù)調(diào)用上面方法的示例:

assert(say('Bob', 'Howdy', 'smoke signal') ==
    'Bob says Howdy with a smoke signal');

3)Default parameter values(默認參數(shù)值)

在定義方法的時候,可以使用 = 來定義可選參數(shù)的默認值。 默認值只能是編譯時常量。 如果沒有提供默認值,則默認值為 null。

下面是設(shè)置可選參數(shù)默認值的示例:

/// Sets the [bold] and [hidden] flags to the values you
/// specify, defaulting to false.
void enableFlags({bool bold = false, bool hidden = false}) {
  // ...
}

// bold will be true; hidden will be false.
enableFlags(bold: true);

版本問題: 就版本代碼可能需要使用一個冒號 (:) 而不是 = 來設(shè)置參數(shù)默認值。 原因在于 Dart SDK 1.21 之前的版本,命名參數(shù)只支持 :。 :

設(shè)置命名默認參數(shù)值在以后版本中將不能使用, 所以我們推薦你 使用 = 來設(shè)置默認值, 并 指定 Dart SDK 版本為 1.21 或者更高的版本。

下面的示例顯示了如何設(shè)置位置參數(shù)的默認值:

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 參數(shù)指定了 默認值:

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');
}

6、The main() function(入口函數(shù))

每個應(yīng)用都需要有個頂級的 main() 入口方法才能執(zhí)行。 main() 方法的返回值為 void 并且有個可選的 List<String> 參數(shù)。
下面是一個 web 應(yīng)用的 main() 方法:

void main() {
  querySelector("#sample_text_id")
    ..text = "Click me!"
    ..onClick.listen(reverseText);
}

注意: 前面代碼中的 .. 語法為 級聯(lián)調(diào)用(cascade)。 使用級聯(lián)調(diào)用語法, 你可以在一個對象上執(zhí)行多個操作。

下面是一個命令行應(yīng)用的 main() 方法,并且使用了 方法參數(shù)作為輸入?yún)?shù):

// 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');
}

7、Functions as first-class objects(一等方法對象)

可以把方法當(dāng)做參數(shù)調(diào)用另外一個方法。例如:

printElement(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 !!!');

8、Anonymous functions(匿名方法)

大部分方法都帶有名字,例如 main() 或者 printElement()。 你有可以創(chuàng)建沒有名字的方法,稱之為 匿名方法,有時候也被稱為 lambda 或者 closure 閉包。 你可以把匿名方法賦值給一個變量, 然后你可以使用這個方法,比如添加到集合或者從集合中刪除。

匿名函數(shù)和命名函數(shù)看起來類似— 在括號之間可以定義一些參數(shù),參數(shù)使用逗號 分割,也可以是可選參數(shù)。 后面大括號中的代碼為函數(shù)體:

([[Type] param1[, …]]) { 
  codeBlock; 
}; 

下面的代碼定義了一個參數(shù)為i (該參數(shù)沒有指定類型)的匿名函數(shù)。 list 中的每個元素都會調(diào)用這個函數(shù)來 打印出來,同時來計算了每個元素在 list 中的索引位置:

var list = ['apples', 'oranges', 'grapes', 'bananas', 'plums'];
list.forEach((i) {
  print(list.indexOf(i).toString() + ': ' + i);
});

如果方法只包含一個語句,可以使用胖箭頭語法縮寫。 把下面的代碼粘貼到 DartPad 中運行,可以看到結(jié)果是一樣的:

list.forEach((i) => print(list.indexOf(i).toString() + ': ' + i));

9、Lexical scope(靜態(tài)作用域)

Dart 是靜態(tài)作用域語言,變量的作用域在寫代碼的時候就確定過了。

基本上大括號里面定義的變量就 只能在大括號里面訪問,和 Java 作用域 類似。

下面是作用域的一個 示例:

var topLevel = true;

main() {
  var insideMain = true;

  myFunction() {
    var insideFunction = true;

    nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}

注意 nestedFunction() 可以訪問所有的變量, 包含頂級變量。

10、Lexical closures(詞法閉包)

一個 閉包 是一個方法對象,不管該對象在何處被調(diào)用, 該對象都可以訪問其作用域內(nèi) 的變量。

方法可以封閉定義到其作用域內(nèi)的變量。

下面的示例中,makeAdder() 捕獲到了變量 addBy。

不管你在那里執(zhí)行 makeAdder() 所返回的函數(shù),都可以使用 addBy 參數(shù)。

/// Returns a function that adds [addBy] to the
/// function's argument.
///num is a int or double pointer
Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

main() {
  // Create a function that adds 2.
  var add2 = makeAdder(2);//num point to add2,equal 2

  // Create a function that adds 4.
  var add4 = makeAdder(4);//num point to add4,equal 4

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

11、Testing functions for equality(測試函數(shù)是否相等)

下面是測試頂級方法、靜態(tài)函數(shù)和實例函數(shù) 相等的示例:

foo() {}               // A top-level function

class A {
  static void bar() {} // A static method
  void baz() {}        // An instance method
}

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 = new A(); // Instance #1 of A
  var w = new 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);
}

12、Return values(返回值)

所有的函數(shù)都返回一個值。如果沒有指定返回值,則 默認把語句 return null; 作為函數(shù)的最后一個語句執(zhí)行。

13、Operators(操作符)

下表是 Dart 中定義的操作符。 很多操作符都可以重載:
描述 操作符

unary postfix expr++  expr--  ()  []  .  ?.
unary prefix -expr  !expr  ~expr  ++expr  --expr
multiplicative *  /  %  ~/
additive +  -
shift <<  >>
bitwise AND &
bitwise XOR ^
bitwise OR |
relational and type test
>=  >  <=  <  as  is  is!
equality ==  != 
logical AND &&
logical OR ||
if null ??
conditional expr1 ? expr2 : expr3
cascade ..
assignment =  *=  /=  ~/=  %=  +=  -=  <<=  >>=  &=  ^=  |=  ??=

1)Type test operators(類型判定操作符)

as、 is、 和 is! 操作符是在運行時判定對象 類型的操作符:

操作符 解釋
as 類型轉(zhuǎn)換
is 如果對象是指定的類型返回 True
is! 如果對象是指定的類型返回 False

只有當(dāng) obj 實現(xiàn)了 T 的接口, obj is T 才是 true。例如 obj is Object 總是 true。

使用 as 操作符把對象轉(zhuǎn)換為特定的類型。 一般情況下,你可以把它當(dāng)做用 is 判定類型然后調(diào)用 所判定對象的函數(shù)的縮寫形式。
例如下面的 示例:

if (emp is Person) { // Type check
  emp.firstName = 'Bob';
}

使用 as 操作符可以簡化上面的代碼:
(emp as Person).firstName = 'Bob'

注意: 上面這兩個代碼效果是有區(qū)別的。如果 emp 是 null 或者不是 Person 類型, 則第一個示例使用 is 則不會執(zhí)行條件里面的代碼,而第二個情況使用 as 則會拋出一個異常。

2)Assignment operators(賦值操作符)

使用 = 操作符來賦值。 但是還有一個 ??= 操作符用來指定 值為 null 的變量的值。

a = value;   // 給 a 變量賦值
b ??= value; // 如果 b 是 null,則賦值給 b;
             // 如果不是 null,則 b 的值保持不變

3)Conditional expressions(條件表達式)

Dart 有兩個特殊的操作符可以用來替代 if-else 語句:

  • condition ? expr1 : expr2

如果 condition 是 true,執(zhí)行 expr1 (并返回執(zhí)行的結(jié)果); 否則執(zhí)行 expr2 并返回其結(jié)果。

  • expr1 ?? expr2

如果 expr1 是 non-null,返回其值; 否則執(zhí)行 expr2 并返回其結(jié)果。
如果你需要基于布爾表達式 的值來賦值, 考慮使用 ?:。
var finalStatus = m.isFinal ? 'final' : 'not final'

如果布爾表達式是測試值是否為 null, 考慮使用 ??。
String toString() => msg ?? super.toString()

下面是一樣效果的實現(xiàn):

// Slightly longer version uses ?: operator.
String toString() => msg == null ? super.toString() : msg;

// Very long version uses if-else statement.
String toString() {
  if (msg == null) {
    return super.toString();
  } else {
    return msg;
  }
}

4)Cascade notation (..)(級聯(lián)操作符)

級聯(lián)操作符 (..) 可以在同一個對象上 連續(xù)調(diào)用多個函數(shù)以及訪問成員變量。 使用級聯(lián)操作符可以避免創(chuàng)建 臨時變量, 并且寫出來的代碼看起來 更加流暢:
例如下面的代碼:

querySelector('#button') // Get an object.
  ..text = 'Confirm'   // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

第一個方法 querySelector() 返回了一個 selector 對象。 后面的級聯(lián)操作符都是調(diào)用這個對象的成員, 并忽略每個操作 所返回的值。
上面的代碼和下面的代碼功能一樣:

var button = querySelector('#button');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

級聯(lián)調(diào)用也可以嵌套:

final addressBook = (new AddressBookBuilder()
      ..name = 'jenny'
      ..email = 'jenny@example.com'
      ..phone = (new PhoneNumberBuilder()
            ..number = '415-555-0100'
            ..label = 'home')
          .build())
    .build();

在方法上使用級聯(lián)操作符需要非常小心, 例如下面的代碼就是不合法的:

// Does not work
var sb = new StringBuffer();
sb.write('foo')..write('bar');

sb.write() 函數(shù)返回一個 void, 無法在 void 上使用級聯(lián)操作符。

注意: 嚴格來說, 兩個點的級聯(lián)語法不是一個操作符。 只是一個 Dart 特殊語法。

5)Other operators(其他操作符)

下面是其他操作符:
Operator Name Meaning

  • () 使用方法 代表調(diào)用一個方法
  • [] 訪問 List 訪問 list 中特定位置的元素
  • . 訪問 Member 訪問元素,例如foo.bar代表訪問foo的bar成員
  • ?. 條件成員訪問 和.類似,但是左邊的操作對象不能為 null,例如foo?.bar如果foo為 null 則返回 null,否則返回bar成員

14、Control flow statements(流程控制語句)

可以使用下面的語句來控制 Dart 代碼的流程:

  • if and else
  • for loops
  • while and do-while loops
  • break and continue
  • switch and case
  • assert
  • 使用 try-catch 和 throw 還能影響控制流程的 跳轉(zhuǎn)。

1)For loops

可以使用標(biāo)準的 for 循環(huán):

var message = new StringBuffer("Dart is fun");
for (var i = 0; i < 5; i++) {
  message.write('!');
}

Dart for 循環(huán)中的閉包會捕獲循環(huán)的 index 索引值, 來避免 JavaScript 中常見的問題。例如:

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());

如果要遍歷的對象實現(xiàn)了 Iterable 接口,則可以使用 forEach() 方法。如果沒必要當(dāng)前遍歷的索引,則使用 forEach() 方法 是個非常好的選擇:
candidates.forEach((candidate) => candidate.interview())

List 和 Set 等實現(xiàn)了 Iterable 接口的類還支持 for-in 形式的 遍歷:

var collection = [0, 1, 2];
for (var x in collection) {
  print(x);
}

2)Break and continue

使用 break 來終止循環(huán):

while (true) {
  if (shutDownRequested()) break;
  processIncomingRequests();
}

使用 continue 來開始下一次循環(huán):

for (int i = 0; i < candidates.length; i++) {
  var candidate = candidates[i];
  if (candidate.yearsExperience < 5) {
    continue;
  }
  candidate.interview();
}

上面的代碼在實現(xiàn) Iterable 接口對象上可以使用下面的寫法:

candidates.where((c) => c.yearsExperience >= 5)//where apply condition to create new collection
          .forEach((c) => c.interview());

3)Switch and case

Dart 中的 Switch 語句使用 == 比較 integer、string、或者編譯時常量。

比較的對象必須都是同一個類的實例(并且不是 其之類),class 必須沒有覆寫 == 操作符。
每個非空的 case 語句都必須有一個 break 語句。

另外還可以通過 continue、 throw 或 者 return 來結(jié)束非空 case 語句。

當(dāng)沒有 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 語句, 編譯的時候?qū)霈F(xiàn)一個錯誤:

var command = 'OPEN';
switch (command) {
  case 'OPEN':
    executeOpen();
    // ERROR: Missing break causes an exception!!

  case 'CLOSED':
    executeClosed();
    break;
}

但是,在 Dart 中的空 case 語句中可以不要 break 語句:

var command = 'CLOSED';
switch (command) {
  case 'CLOSED': // Empty case falls through.
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

如果你需要實現(xiàn)這種繼續(xù)到下一個 case 語句中繼續(xù)執(zhí)行,則可以 使用 continue 語句跳轉(zhuǎn)到對應(yīng)的標(biāo)簽(label)處繼續(xù)執(zhí)行:

var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed;
    // Continues executing at the nowClosed label.

nowClosed:
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

每個 case 語句可以有局部變量,局部變量 只有在這個語句內(nèi)可見。

15、Exceptions(異常)

代碼中可以出現(xiàn)異常和捕獲異常。異常表示一些 未知的錯誤情況。如果異常沒有捕獲, 則異常會拋出,導(dǎo)致 拋出異常的代碼終止執(zhí)行。

和 Java 不同的是,所有的 Dart 異常是非檢查異常。 方法不一定聲明了他們所拋出的異常, 并且你不要求捕獲任何異常。

Dart 提供了 Exception 和 Error 類型, 以及一些子類型。你還 可以定義自己的異常類型。但是, Dart 代碼可以 拋出任何非 null 對象為異常,不僅僅是實現(xiàn)了 Exception 或者 Error 的對象。

1)Throw

下面是拋出或者 扔出一個異常的示例:
throw new FormatException('Expected at least 1 section')

還可以拋出任意的對象:
throw 'Out of llamas!'

由于拋出異常是一個表達式,所以可以在 => 語句中使用,也可以在其他能使用表達式的地方拋出異常。

distanceTo(Point other) =>
    throw new UnimplementedError();

2)Catch

捕獲異??梢员苊猱惓@^續(xù)傳遞(你重新拋出rethrow異常除外)。 捕獲異常給你一個處理 該異常的機會:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}

對于可以拋出多種類型異常的代碼,你可以指定 多個捕獲語句。每個語句分別對應(yīng)一個異常類型, 如果捕獲語句沒有指定異常類型,則 該可以捕獲任何異常類型:

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 來 捕獲異常對象。

函數(shù) catch() 可以帶有一個或者兩個參數(shù), 第一個參數(shù)為拋出的異常對象, 第二個為堆棧信息 (一個 StackTrace 對象)。

{
  ...
} on Exception catch (e) {
  print('Exception details:\n $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

使用 rethrow 關(guān)鍵字可以 把捕獲的異常給 重新拋出。
final foo = '';

void misbehave() {
  try {
    foo = "You can't change a final variable's value.";
  } 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}.');
  }
}

16、Classes

Dart 是一個面向?qū)ο缶幊陶Z言,同時支持基于 mixin 的繼承機制。

每個對象都是一個類的實例,所有的類都繼承于 Object。

基于 Mixin 的繼承 意味著每個類(Object 除外) 都只有一個超類,一個類的代碼可以在其他 多個類繼承中重復(fù)使用。

1)構(gòu)造函數(shù)

由于把構(gòu)造函數(shù)參數(shù)賦值給實例變量的場景太常見了, Dart 提供了一個語法糖來簡化這個操作:

class Point {
  num x;
  num y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}

2)Constructors aren’t inherited(構(gòu)造函數(shù)不會繼承)

子類不會繼承超類的構(gòu)造函數(shù)。 子類如果沒有定義構(gòu)造函數(shù),則只有一個默認構(gòu)造函數(shù) (沒有名字沒有參數(shù))。

3)Named constructors(命名構(gòu)造函數(shù))

使用命名構(gòu)造函數(shù)可以為一個類實現(xiàn)多個構(gòu)造函數(shù), 或者使用命名構(gòu)造函數(shù)來更清晰的表明你的意圖:

class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // Named constructor
  Point.fromJson(Map json) {
    x = json['x'];
    y = json['y'];
  }
}

注意:構(gòu)造函數(shù)不能繼承,所以超類的命名構(gòu)造函數(shù) 也不會被繼承。如果你希望 子類也有超類一樣的命名構(gòu)造函數(shù), 你必須在子類中自己實現(xiàn)該構(gòu)造函數(shù)。

4)Invoking a non-default superclass

constructor(調(diào)用超類構(gòu)造函數(shù))
默認情況下,子類的構(gòu)造函數(shù)會自動調(diào)用超類的 無名無參數(shù)的默認構(gòu)造函數(shù)。

超類的構(gòu)造函數(shù)在子類構(gòu)造函數(shù)體開始執(zhí)行的位置調(diào)用。 如果提供了一個 initializer list(初始化參數(shù)列表) ,則初始化參數(shù)列表在超類構(gòu)造函數(shù)執(zhí)行之前執(zhí)行。

下面是構(gòu)造函數(shù)執(zhí)行順序:

initializer list(初始化參數(shù)列表)
super class’s no-arg constructor(超類的無名構(gòu)造函數(shù))
main class’s no-arg constructor(主類的無名構(gòu)造函數(shù))

如果超類沒有無名無參數(shù)構(gòu)造函數(shù), 則你需要手工的調(diào)用超類的其他構(gòu)造函數(shù)。

在構(gòu)造函數(shù)參數(shù)后使用冒號 (:) 可以調(diào)用 超類構(gòu)造函數(shù)。
下面的示例中,Employee 類的構(gòu)造函數(shù)調(diào)用 了超類 Person 的命名構(gòu)造函數(shù)。

class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // Prints:
  // in Person
  // in Employee
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
}

由于超類構(gòu)造函數(shù)的參數(shù)在構(gòu)造函數(shù)執(zhí)行之前執(zhí)行,所以 參數(shù)可以是一個表達式或者 一個方法調(diào)用:

class Employee extends Person {
  // ...
  Employee() : super.fromJson(findDefaultData());
}

注意: 如果在構(gòu)造函數(shù)的初始化列表中使用 super(),需要把它放到最后。
警告: 調(diào)用超類構(gòu)造函數(shù)的參數(shù)無法訪問 this。 例如,參數(shù)可以為靜態(tài)函數(shù)但是不能是實例函數(shù)。

5)Initializer list(初始化列表)

在構(gòu)造函數(shù)體執(zhí)行之前除了可以調(diào)用超類構(gòu)造函數(shù)之外,還可以 初始化實例參數(shù)。 使用逗號分隔初始化表達式。

class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // Initializer list sets instance variables before
  // the constructor body runs.
  Point.fromJson(Map jsonMap)
      : x = jsonMap['x'],
        y = jsonMap['y'] {
    print('In Point.fromJson(): ($x, $y)');
  }
}

警告: 初始化表達式等號右邊的部分不能訪問 this。
初始化列表非常適合用來設(shè)置 final 變量的值。 下面示例代碼中初始化列表設(shè)置了三個 final 變量的值。

import 'dart:math';

class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = new Point(2, 3);
  print(p.distanceFromOrigin);
}

6)Redirecting constructors(重定向構(gòu)造函數(shù))

重定向構(gòu)造函數(shù)的時候一個構(gòu)造函數(shù)會調(diào)動類中的其他構(gòu)造函數(shù)。 一個重定向構(gòu)造函數(shù)是沒有代碼的,在構(gòu)造函數(shù)聲明后,使用 冒號調(diào)用其他構(gòu)造函數(shù)。

class Point {
  num x;
  num y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(num x) : this(x, 0);
}

7)Constant constructors(常量構(gòu)造函數(shù))

如果你的類提供一個狀態(tài)不變的對象,你可以把這些對象 定義為編譯時常量。要實現(xiàn)這個功能,需要定義一個 const 構(gòu)造函數(shù), 并且聲明所有類的變量為 final。

class ImmutablePoint {
  final num x;
  final num y;
  const ImmutablePoint(this.x, this.y);
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);
}

8)Factory constructors(工廠方法構(gòu)造函數(shù))

如果一個構(gòu)造函數(shù)并不總是返回一個新的對象,則使用 factory 來定義 這個構(gòu)造函數(shù)。例如,一個工廠構(gòu)造函數(shù) 可能從緩存中獲取一個實例并返回,或者 返回一個子類型的實例。

下面代碼演示工廠構(gòu)造函數(shù) 如何從緩存中返回對象。

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 = new Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) {
      print(msg);
    }
  }
}

注意: 工廠構(gòu)造函數(shù)無法訪問 this。

使用 new 關(guān)鍵字來調(diào)用工廠構(gòu)造函數(shù)。

var logger = new Logger('UI');
logger.log('Button clicked');

17、Methods(函數(shù))

函數(shù)是類中定義的方法,是類對象的行為。

1)Instance methods(實例函數(shù))

對象的實例函數(shù)可以訪問 this。 例如下面示例中的 distanceTo() 函數(shù) 就是實例函數(shù):

import 'dart:math';

class Point {
  num x;
  num 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);
  }
}

2)Getters and setters

Getters 和 setters 是用來設(shè)置和訪問對象屬性的特殊 函數(shù)。每個實例變量都隱含的具有一個 getter, 如果變量不是 final 的則還有一個 setter。 你可以通過實行 getter 和 setter 來創(chuàng)建新的屬性, 使用 get 和 set 關(guān)鍵字定義 getter 和 setter:

class Rectangle {
  num left;
  num top;
  num width;
  num 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;
}

main() {
  var rect = new Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

getter 和 setter 的好處是,你可以開始使用實例變量,后來 你可以把實例變量用函數(shù)包裹起來,而調(diào)用你代碼的地方不需要修改。

3)Abstract methods(抽象函數(shù))

實例函數(shù)、 getter、和 setter 函數(shù)可以為抽象函數(shù), 抽象函數(shù)是只定義函數(shù)接口但是沒有實現(xiàn)的函數(shù),由子類來 實現(xiàn)該函數(shù)。如果用分號來替代函數(shù)體則這個函數(shù)就是抽象函數(shù)。

abstract class Doer {
  // ...Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // ...Provide an implementation, so the method is not abstract here...
  }
}

調(diào)用一個沒實現(xiàn)的抽象函數(shù)會導(dǎo)致運行時異常。

4)Overridable operators(可覆寫的操作符)

下表中的操作符可以被覆寫。 例如,如果你定義了一個 Vector 類, 你可以定義一個 + 函數(shù)來實現(xiàn)兩個向量相加。

< + | []
> / ^ []=
<= ~/ & ~
>= * << ==
– % >>  

下面是覆寫了 + 和 - 操作符的示例:

class Vector {
  final int x;
  final int y;
  const Vector(this.x, this.y);

  /// Overrides + (a + b).
  Vector operator +(Vector v) {
    return new Vector(x + v.x, y + v.y);
  }

  /// Overrides - (a - b).
  Vector operator -(Vector v) {
    return new Vector(x - v.x, y - v.y);
  }
}

main() {
  final v = new Vector(2, 3);
  final w = new Vector(2, 2);

  // v == (2, 3)
  assert(v.x == 2 && v.y == 3);

  // v + w == (4, 5)
  assert((v + w).x == 4 && (v + w).y == 5);

  // v - w == (0, 1)
  assert((v - w).x == 0 && (v - w).y == 1);
}

如果你覆寫了 == ,則還應(yīng)該覆寫對象的 hashCode getter 函數(shù)。

18、Abstract classes(抽象類)

使用 abstract 修飾符定義一個 抽象類—一個不能被實例化的類。 抽象類通常用來定義接口, 以及部分實現(xiàn)。如果你希望你的抽象類 是可示例化的,則定義一個 工廠 構(gòu)造函數(shù)。
抽象類通常具有 抽象函數(shù)。 下面是定義具有抽象函數(shù)的 抽象類的示例:

// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
  // ...Define constructors, fields, methods...

  void updateChildren(); // Abstract method.
}

下面的類不是抽象的,但是繼承了一個抽象類,這樣 的類是可以被實例化的:

class SpecializedContainer extends AbstractContainer {
  // ...Define more constructors, fields, methods...

  void updateChildren() {
    // ...Implement updateChildren()...
  }

  // Abstract method causes a warning but
  // doesn't prevent instantiation.
  void doSomething();
}

19、Implicit interfaces(隱式接口)

每個類都隱式的定義了一個包含所有實例成員的接口, 并且這個類實現(xiàn)了這個接口。如果你想 創(chuàng)建類 A 來支持 類 B 的 api,而不想繼承 B 的實現(xiàn), 則類 A 應(yīng)該實現(xiàn) B 的接口。
一個類可以通過 implements 關(guān)鍵字來實現(xiàn)一個或者多個接口, 并實現(xiàn)每個接口定義的 API。 例如:

// 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(who) => 'Hello, $who. I am $_name.';
}

// An implementation of the Person interface.
class Imposter implements Person {
  // We have to define this, but we don't use it.
  final _name = "";

  String greet(who) => 'Hi $who. Do you know who I am?';
}

greetBob(Person person) => person.greet('bob');

main() {
  print(greetBob(new Person('kathy')));
  print(greetBob(new Imposter()));
}

下面是實現(xiàn)多個接口 的示例:

class Point implements Comparable, Location {
  // ...
}

20、Extending a class(擴展類)

使用 extends 定義子類, supper 引用 超類:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ...
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ...
}

子類可以覆寫實例函數(shù),getter 和 setter。 下面是覆寫 Object 類的 noSuchMethod() 函數(shù)的例子, 如果調(diào)用了對象上不存在的函數(shù),則就會觸發(fā) noSuchMethod() 函 數(shù)。

class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  void noSuchMethod(Invocation mirror) {
    print('You tried to use a non-existent member:' +
          '${mirror.memberName}');
  }
}

還可以使用 @override 注解來 表明你的函數(shù)是想覆寫超類的一個函數(shù):

class A {
  @override
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}

如果你使用 noSuchMethod() 函數(shù)來實現(xiàn)每個可能的 getter 、setter、 以及其他類型的函數(shù),你可以使用 @proxy 注解來避免警告信息:

@proxy
class A {
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}

如果你知道編譯時的具體類型,則可以 實現(xiàn)這些類來避免警告,和 使用 @proxy 效果一樣:

class A implements SomeClass, SomeOtherClass {
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}

21、Enumerated types(枚舉類型)

枚舉類型通常稱之為 enumerations 或者 enums, 是一種特殊的類,用來表現(xiàn)一個固定 數(shù)目的常量。

1)Using enums

使用 enum 關(guān)鍵字來定義枚舉類型:

enum Color {
  red,
  green,
  blue
}

枚舉類型中的每個值都有一個 index getter 函數(shù), 該函數(shù)返回該值在枚舉類型定義中的位置(從 0 開始)。 例如,第一個枚舉值的位置為 0, 第二個為 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);

以在 switch 語句 中使用枚舉。 如果在 switch (e) 中的 e 的類型為枚舉類, 如果你沒有處理所有該枚舉類型的值的話,則會拋出一個警告:

enum Color {
  red,
  green,
  blue
}
// ...
Color aColor = Color.blue;
switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // Without this, you see a WARNING.
    print(aColor);  // 'Color.blue'
}

枚舉類型具有如下的限制:
無法繼承枚舉類型、無法使用 mix in、無法實現(xiàn)一個枚舉類型
無法顯示的初始化一個枚舉類型

22、Adding features to a class:

mixins(為類添加新的功能)

Mixins 是一種在多類繼承中重用 一個類代碼的方法。
使用 with 關(guān)鍵字后面為一個或者多個 mixin 名字來使用 mixin。 下面是示例顯示了如何使用 mixin:

class Musician extends Performer with Musical {
  // ...
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

定義一個類繼承 Object,該類沒有構(gòu)造函數(shù), 不能調(diào)用 super ,則該類就是一個 mixin。例如:

abstract class 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');
    }
  }
}

23、Class variables and methods(類變量和函數(shù))

使用 static 關(guān)鍵字來實現(xiàn)類級別的變量和函數(shù)。

1)Static variables(靜態(tài)變量)

靜態(tài)變量對于類級別的狀態(tài)是 非常有用的:

class Color {
  static const red =
      const Color('red'); // A constant static variable.
  final String name;      // An instance variable.
  const Color(this.name); // A constant constructor.
}

main() {
  assert(Color.red.name == 'red');
}

靜態(tài)變量在第一次使用的時候才被初始化。
注意: 這里準守代碼風(fēng)格推薦 命名規(guī)則,使用 lowerCamelCase 來命名常量。

2)Static methods(靜態(tài)函數(shù))

靜態(tài)函數(shù)不再類實例上執(zhí)行, 所以無法訪問 this。例如:

import 'dart:math';

class Point {
  num x;
  num 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);
  }
}

main() {
  var a = new Point(2, 2);
  var b = new Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(distance < 2.9 && distance > 2.8);
}

注意: 對于通用的或者經(jīng)常使用的靜態(tài)函數(shù),考慮 使用頂級方法而不是靜態(tài)函數(shù)。

靜態(tài)函數(shù)還可以當(dāng)做編譯時常量使用。例如, 你可以把靜態(tài)函數(shù)當(dāng)做常量構(gòu)造函數(shù)的參數(shù)來使用。

24、Generics(泛型)

1)Using collection literals(使用集合字面量)

List 和 map 字面量也是可以參數(shù)化的。 參數(shù)化定義 list 需要在中括號之前 添加 <type> , 定義 map 需要在大括號之前 添加 <keyType,valueType>。

如果你需要更加安全的類型檢查,則可以使用 參數(shù)化定義。下面是一些示例:

var names = <String>['Seth', 'Kathy', 'Lars'];
var pages = <String, String>{
  'index.html': 'Homepage',
  'robots.txt': 'Hints for web robots',
  'humans.txt': 'We are people, not machines'
};

2)Using parameterized types withconstructors(在構(gòu)造函數(shù)中使用泛型)

在調(diào)用構(gòu)造函數(shù)的時候, 在類名字后面使用尖括號(<...>)來指定 泛型類型。例如:

var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = new Set<String>.from(names);

下面代碼創(chuàng)建了一個 key 為 integer, value 為 View 類型 的 map:

var views = new Map<int, View>();

3)Generic collections and the types they contain

Dart 的泛型類型是固化的,在運行時有也 可以判斷具體的類型。例如在運行時(甚至是成產(chǎn)模式) 也可以檢測集合里面的對象類型:

var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

注意 is 表達式只是判斷集合的類型,而不是集合里面具體對象的類型。 在生產(chǎn)模式,List<String> 變量可以包含 非字符串類型對象。對于這種情況, 你可以選擇分別判斷每個對象的類型或者 處理類型轉(zhuǎn)換異常 (參考 Exceptions)。

4)Restricting the parameterized type(限制泛型類型)

當(dāng)使用泛型類型的時候,你 可能想限制泛型的具體類型。 使用 extends 可以實現(xiàn)這個功能:

// T must be SomeBaseClass or one of its descendants.
class Foo<T extends SomeBaseClass> {...}

class Extender extends SomeBaseClass {...}

void main() {
  // It's OK to use SomeBaseClass or any of its subclasses inside <>.
  var someBaseClassFoo = new Foo<SomeBaseClass>();
  var extenderFoo = new Foo<Extender>();

  // It's also OK to use no <> at all.
  var foo = new Foo();

  // Specifying any non-SomeBaseClass type results in a warning and, in
  // checked mode, a runtime error.
  // var objectFoo = new Foo<Object>();
}

5)Using generic methods(使用泛型函數(shù))

一開始,泛型只能在 Dart 類中使用。 新的語法也支持在函數(shù)和方法上使用泛型了。

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>) 泛型可以在如下地方使用 參數(shù) T :
函數(shù)的返回值類型 (T).
參數(shù)的類型 (List<T>).
局部變量的類型 (T tmp).

25、Libraries and visibility(庫和可見性)

使用 import 和 library 指令可以幫助你創(chuàng)建 模塊化的可分享的代碼。庫不僅僅提供 API,
還是一個私有單元:以下劃線 (_) 開頭的標(biāo)識符只有在庫 內(nèi)部可見。每個 Dart app 都是一個庫, 即使沒有使用 library 命令也是一個庫。

庫可以使用 Dart package 工具部署。

1)Using libraries(使用庫)

使用 import 來指定一個庫如何使用另外 一個庫。
例如, Dart web 應(yīng)用通常使用 dart:html 庫,然后可以這樣導(dǎo)入庫:

import 'dart:html';

import 必須參數(shù)為庫 的 URI。

對于內(nèi)置的庫,URI 使用特殊的 dart: scheme。 對于其他的庫,你可以使用文件系統(tǒng)路徑或者 package:scheme。 package: scheme 指定的庫通過包管理器來提供, 例如 pub 工具。

import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';

2)Specifying a library prefix(指定庫前綴)

如果你導(dǎo)入的兩個庫具有沖突的標(biāo)識符, 則你可以使用庫的前綴來區(qū)分。 例如,如果 library1 和 library2 都有一個名字為 Element 的類, 你可以這樣使用:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// ...
Element element1 = new Element();           // Uses Element from lib1.
lib2.Element element2 = new lib2.Element(); // Uses Element from lib2.

3)Importing only part of a library(導(dǎo)入庫的一部分)

如果你只使用庫的一部分功能,則可以選擇需要導(dǎo)入的 內(nèi)容。例如:

// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;

4)Lazily loading a library(延遲載入庫)

Deferred loading (也稱之為 lazy loading) 可以讓應(yīng)用在需要的時候再 加載庫。 下面是一些使用延遲加載庫的場景:

  • 減少 APP 的啟動時間。

執(zhí)行 A/B 測試,例如 嘗試各種算法的 不同實現(xiàn)。
加載很少使用的功能,例如可選的屏幕和對話框。
要延遲加載一個庫,需要先使用 deferred as 來 導(dǎo)入:

import 'package:deferred/hello.dart' deferred as hello;

當(dāng)需要使用的時候,使用庫標(biāo)識符調(diào)用 loadLibrary() 函數(shù)來加載庫:

greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}

在前面的代碼, 使用 await 關(guān)鍵字暫停代碼執(zhí)行一直到庫加載完成。
在一個庫上你可以多次調(diào)用 loadLibrary() 函數(shù)。 但是該庫只是載入一次。

使用延遲加載庫的時候,請注意一下問題:
延遲加載庫的常量在導(dǎo)入的時候是不可用的。 只有當(dāng)庫加載完畢的時候,庫中常量才可以使用。

在導(dǎo)入文件的時候無法使用延遲庫中的類型。 如果你需要使用類型,則考慮把接口類型移動到另外一個庫中, 讓兩個庫都分別導(dǎo)入這個接口庫。

Dart 隱含的把 loadLibrary() 函數(shù)導(dǎo)入到使用 deferred as 的命名空間 中。 loadLibrary() 方法返回一個 Future。

26、Asynchrony support(異步支持)

Dart 有一些語言特性來支持 異步編程。 最常見的特性是 async 方法和 await 表達式。

Dart 庫中有很多返回 Future 或者 Stream 對象的方法。 這些方法是 異步的: 這些函數(shù)在設(shè)置完基本的操作 后就返回了, 而無需等待操作執(zhí)行完成。

例如讀取一個文件,在打開文件后就返回了。

有兩種方式可以使用 Future 對象中的 數(shù)據(jù):

  • 使用 async 和 await
  • 使用 Future API

同樣,從 Stream 中獲取數(shù)據(jù)也有兩種 方式:

  • 使用 async 和一個 異步 for 循環(huán) (await for)
  • 使用 Stream API

使用 async 和 await 的代碼是異步的, 但是看起來有點像同步代碼。

例如,下面是一些使用 await 來 等待異步方法返回的示例:
await lookUpVersion()

要使用 await,其方法必須帶有 async 關(guān)鍵字:

checkVersion() async {
  var version = await lookUpVersion();
  if (version == expectedVersion) {
    // Do something.
  } else {
    // Do something else.
  }
}

可以使用 try, catch, 和 finally 來處理使用 await 的異常:

try {
  server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044);
} catch (e) {
  // React to inability to bind to the port...
}

1)Declaring async functions(聲明異步方法)

一個 async 方法 是函數(shù)體被標(biāo)記為 async 的方法。 雖然異步方法的執(zhí)行可能需要一定時間,但是 異步方法立刻返回 - 在方法體還沒執(zhí)行之前就返回了。

checkVersion() async {
  // ...
}

lookUpVersion() async => /* ... */;

在一個方法上添加 async 關(guān)鍵字,則這個方法返回值為 Future。 例如,下面是一個返回字符串 的同步方法:

String lookUpVersionSync() => '1.0.0';

如果使用 async 關(guān)鍵字,則該方法 返回一個 Future,并且 認為該函數(shù)是一個耗時的操作。

Future<String> lookUpVersion() async => '1.0.0';

注意,方法的函數(shù)體并不需要使用 Future API。 Dart 會自動在需要的時候創(chuàng)建 Future 對象。

2)Using await expressions with Futures(使用 await 表達式)

await 表達式具有如下的形式:

await expression

在一個異步方法內(nèi)可以使用多次 await 表達式。 例如,下面的示例使用了三次 await 表達式 來執(zhí)行相關(guān)的功能:

var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);

在 await expression 中, expression 的返回值通常是一個 Future; 如果返回的值不是 Future,則 Dart 會自動把該值放到 Future 中返回。

Future 對象代表返回一個對象的承諾(promise)。 await expression 執(zhí)行的結(jié)果為這個返回的對象。 await expression 會阻塞住,直到需要的對象返回為止。

如果 await 無法正常使用,請確保是在一個 async 方法中。 例如要在 main() 方法中使用 await, 則 main() 方法的函數(shù)體必須標(biāo)記為 async:

main() async {
  checkVersion();
  print('In main: version is ${await lookUpVersion()}');
}

3)Using asynchronous for loops with Streams(在循環(huán)中使用異步)

異步 for 循環(huán)具有如下的形式:

await for (variable declaration in expression) {
  // Executes each time the stream emits a value.
}

上面 expression 返回的值必須是 Stream 類型的。 執(zhí)行流程如下:

1.等待直到 stream 返回一個數(shù)據(jù)
2.使用 stream 返回的參數(shù) 執(zhí)行 for 循環(huán)代碼,
3.重復(fù)執(zhí)行 1 和 2 直到 stream 數(shù)據(jù)返回完畢。
4.使用 break 或者 return 語句可以 停止接收 stream 的數(shù)據(jù), 這樣就跳出了 for 循環(huán)并且 從 stream 上取消注冊了。

如果異步 for 循環(huán)不能正常工作, 確保是在一個 async 方法中使用。 例如,要想在 main() 方法中使用異步 for 循環(huán),則需要把 main() 方法的函數(shù)體標(biāo)記為 async:

main() async {
  ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  ...
}

27、Callable classes(可調(diào)用的類)

如果 Dart 類實現(xiàn)了 call() 函數(shù)則 可以當(dāng)做方法來調(diào)用。
在下面的示例中,WannabeFunction 類定義了一個 call() 方法,該方法有三個字符串參數(shù),并且返回三個字符串 串聯(lián)起來的結(jié)果。

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');
}

28、Isolates

現(xiàn)代的瀏覽器以及移動瀏覽器都運行在多核 CPU 系統(tǒng)上。 要充分利用這些 CPU,開發(fā)者一般使用共享內(nèi)存 數(shù)據(jù)來保證多線程的正確執(zhí)行。

然而, 多線程共享數(shù)據(jù)通常會導(dǎo)致很多潛在的問題,并導(dǎo)致代碼運行出錯。

所有的 Dart 代碼在 isolates 中運行而不是線程。 每個 isolate 都有自己的堆內(nèi)存,并且確保每個 isolate 的狀態(tài)都不能被其他 isolate 訪問。

29、Typedefs

在 Dart 語言中,方法也是對象。 使用 typedef, 或者 function-type alias 來為方法類型命名, 然后可以使用命名的方法。

當(dāng)把方法類型賦值給一個變量的時候,typedef 保留類型信息。
下面的代碼沒有使用 typedef:

class SortedCollection {
  Function compare;

  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}

 // Initial, broken implementation.
 int sort(Object a, Object b) => 0;

main() {
  SortedCollection coll = new SortedCollection(sort);

  // 我們只知道 compare 是一個 Function 類型,
  // 但是不知道具體是何種 Function 類型?
  assert(coll.compare is Function);
}

當(dāng)把 f 賦值給 compare 的時候, 類型信息丟失了。 f 的類型是 (Object, Object) → int (這里 → 代表返回值類型), 當(dāng)然該類型是一個 Function。

如果我們使用顯式的名字并保留類型信息, 開發(fā)者和工具可以使用 這些信息:

typedef int Compare(Object a, Object b);

class SortedCollection {
  Compare compare;

  SortedCollection(this.compare);
}

 // Initial, broken implementation.
 int sort(Object a, Object b) => 0;

main() {
  SortedCollection coll = new SortedCollection(sort);
  assert(coll.compare is Function);
  assert(coll.compare is Compare);
}

由于 typedefs 只是別名,他們還提供了一種 判斷任意 function 的類型的方法。例如:

typedef int Compare(int a, int b);

int sort(int a, int b) => a - b;

main() {
  assert(sort is Compare); // True!
}

30、Metadata(元數(shù)據(jù))

使用元數(shù)據(jù)給你的代碼添加其他額外信息。 元數(shù)據(jù)注解是以 @ 字符開頭,后面是一個編譯時 常量(例如 deprecated)或者 調(diào)用一個常量構(gòu)造函數(shù)。

有三個注解所有的 Dart 代碼都可以使用: @deprecated、 @override、 和 @proxy。關(guān)于 @override 和 @proxy 示例請參考 Extending a class。 下面是使用 @deprecated 的 示例:

class Television {
  /// _Deprecated: Use [turnOn] instead._
  @deprecated
  void activate() {
    turnOn();
  }

  /// Turns the TV's power on.
  void turnOn() {
    print('on!');
  }
}

你還可以定義自己的元數(shù)據(jù)注解。 下面的示例定義了一個帶有兩個參數(shù)的 @todo 注解:

library todo;

class todo {
  final String who;
  final String what;

  const todo(this.who, this.what);
}

使用 @todo 注解的示例:

import 'todo.dart';

@todo('seth', 'make this do something')
void doSomething() {
  print('do something');
}

元數(shù)據(jù)可以在 library、 class、 typedef、 type parameter、 constructor、 factory、 function、 field、 parameter、或者 variable 聲明之前使用,也可以在 import 或者 export 指令之前使用。 使用反射可以在運行時獲取元數(shù)據(jù) 信息。

31、Comments(注釋)

1)Documentation comments

文檔注釋可以使用 /// 開始, 也可以使用 /** 開始 并以 */ 結(jié)束。

在文檔注釋內(nèi), Dart 編譯器忽略除了中括號以外的內(nèi)容。 使用中括號可以引用 classes、 methods、 fields、 top-level variables、 functions、 和 parameters。中括號里面的名字使用 當(dāng)前注釋出現(xiàn)地方的語法范圍查找對應(yīng)的成員。

下面是一個引用其他類和成員 的文檔注釋:

/// A domesticated South American camelid (Lama glama).
///
/// Andean cultures have used llamas as meat and pack
/// animals since pre-Hispanic times.
class Llama {
  String name;

  /// Feeds your llama [Food].
  ///
  /// The typical llama eats one bale of hay per week.
  void feed(Food food) {
    // ...
  }

  /// Exercises your llama with an [activity] for
  /// [timeLimit] minutes.
  void exercise(Activity activity, int timeLimit) {
    // ...
  }
}

在生成的文檔中,[Food] 變?yōu)橐粋€連接 到 Food 類 API 文檔的鏈接。
使用 SDK 中的 文檔生成工具可以解析文檔并生成 HTML 網(wǎng)頁。

微信公眾號二維碼:


image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容