概括
重要的概念
在學(xué)習(xí) Dart 語言時(shí), 應(yīng)該基于以下事實(shí)和概念:
任何保存在變量中的都是一個(gè) 對象,并且所有的對象都是對應(yīng)一個(gè) 類 的實(shí)例。無論是數(shù)字,函數(shù)和
null都是對象。所有對象繼承自 Object 類。盡管 Dart 是強(qiáng)類型的,但是 Dart 可以推斷類型,所以類型注釋是可選的。 在上面的代碼中,
number被推斷為int類型。 如果要明確說明不需要任何類型, 需要使用特殊類型dynamic。Dart 支持泛型
與 Java 不同,Dart 沒有關(guān)鍵字
public,protected和private。 如果標(biāo)識符以下劃線(_)開頭,則它相對于庫是私有的,class是沒有私有變量的
class Test {
// 沒有用 還是可以在外部訪問
Object _name;
}
函數(shù)的定義
printInteger(int aNumber) {
print('The number is $aNumber.'); // 打印到控制臺。
}
main() {
var number = 42; // 聲明并初始化一個(gè)變量。
printInteger(number); // 調(diào)用函數(shù)。
}
// 字符串常量。
'...' (or "...")
// 字符串插值: 包括字符串文字內(nèi)部的變量或表達(dá)式的字符串。 有關(guān)更多信息,參考 Strings.
$variableName (或 ${expression})
// 程序開始執(zhí)行函數(shù),該函數(shù)是特定的、必須的、頂級函數(shù)。 有關(guān)更多信息,參考 The main() function.
main()
// 定義變量,通過這種方式定義變量不需要指定變量類型。
var
Final 和 const
使用過程中從來不會被修改的變量, 可以使用 final 或 const, 而不是 var 或者其他類型, final 變量的值只能被設(shè)置一次; const 變量在編譯時(shí)就已經(jīng)固定 (Const 變量 是隱式 final 的類型.) 最高級 final 變量或類變量在第一次使用時(shí)被初始化。
var foo = const [];
final bar = const [];
const baz = []; // Equivalent to `const []`
內(nèi)建類型
-
Numbers
Dart 語言的 numbers 有兩種類型: int 和 double// int var x = 1; var hex = 0xDEADBEEF; // double var y = 1.1; var exponents = 1.42e5; -
Strings
Dart 字符串是一組 UTF-16 單元序列。 字符串通過單引號或者雙引號創(chuàng)建。// 單引號 var single_spear = 'Single quotes work well for string literals.'; // 雙引號 var double_spear = "Double quotes work just as well."; // 使用連續(xù)三個(gè)單引號或者三個(gè)雙引號實(shí)現(xiàn)多行字符串對象的創(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."; Booleans
Dart 使用 bool 類型表示布爾值。 Dart 只有字面量 true and false 是布爾類型, 這兩個(gè)對象都是編譯時(shí)常量。
Dart 的類型安全意味著不能使用 if (nonbooleanValue) 或者 assert (nonbooleanValue)。 而是應(yīng)該像下面這樣,明確的進(jìn)行值檢查
// 檢查空字符串。
var fullName = '';
assert(fullName.isEmpty);
// 檢查 0 值。
var hitPoints = 0;
assert(hitPoints <= 0);
// 檢查 null 值。
var unicorn;
assert(unicorn == null);
// 檢查 NaN 。(檢查參數(shù)中是否是非數(shù)字值)
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);
-
Lists
幾乎每種編程語言中最常見的集合可能是 array 或有序的對象集合。 在 Dart 中的 array 就是 List 對象, 通常稱之為 lists 。
Dart 中的 list 字面量非常像 JavaScript 和 iOS 中的 array 字面量。 下面是一個(gè) Dart list 的示例:
var list = [1, 2, 3];
提示: 分析器推斷 list 的類型為 List<int> 。 如果嘗試將非整數(shù)對象添加到此 list 中, 則分析器或運(yùn)行時(shí)會引發(fā)錯誤。 有關(guān)更多信息,請閱讀 類型推斷。
Lists 的下標(biāo)索引從 0 開始,第一個(gè)元素的索引是 0。 list.length - 1 是最后一個(gè)元素的索引。 訪問 list 的長度和元素與 JavaScript 中的用法一樣:
var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);
list[1] = 1;
assert(list[1] == 1);
Maps
通常來說, map 是用來關(guān)聯(lián) keys 和 values 的對象。 keys 和 values 可以是任何類型的對象。在一個(gè) map 對象中一個(gè) key 只能出現(xiàn)一次。 但是 value 可以出現(xiàn)多次。 Dart 中 map 通過 map 字面量 和 Map 類型來實(shí)現(xiàn)。
下面是使用 map 字面量的兩個(gè)簡單例子:
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
提示: 分析器會將 gifts 的類型推斷為 Map<String, String>, nobleGases 的類型推斷為 Map<int, String>。 如果嘗試在上面的 map 中添加錯誤類型,那么分析器或者運(yùn)行時(shí)會引發(fā)錯誤。 有關(guān)更多信息,請閱讀類型推斷。。
以上 map 對象也可以使用 Map 構(gòu)造函數(shù)創(chuàng)建:
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';
-
=> expr簡寫語法 (=> 符號 有時(shí)也被稱為 箭頭 語法。)
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
// 等價(jià)于
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
- 命名可選參數(shù)
// 可選參數(shù)可以是命名參數(shù)或者位置參數(shù),但一個(gè)參數(shù)只能選擇其中一種方式修飾。
enableFlags(bold: true, hidden: false);
// 定義函數(shù)是,使用 {param1, param2, …} 來指定命名參數(shù):
// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold, bool hidden}) {...}
- 位置可選參數(shù) (只能放最后一個(gè))
// 將參數(shù)放到 [] 中來標(biāo)記參數(shù)是可選的:
String say(String from, String msg, [String device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
- 默認(rèn)參數(shù)值
// 在定義方法的時(shí)候,可以使用 = 來定義可選參數(shù)的默認(rèn)值。
// 默認(rèn)值只能是編譯時(shí)常量。
// 如果沒有提供默認(rèn)值,則默認(rèn)值為 null。
// 下面是設(shè)置可選參數(shù)默認(rèn)值示例:
void enableFlags({bool bold = false, bool hidden = false}) {...}
- 函數(shù)也是對象
一個(gè)函數(shù)可以作為另一個(gè)函數(shù)的參數(shù)。 例如:
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
// 將 printElement 函數(shù)作為參數(shù)傳遞。
list.forEach(printElement);
同樣可以將一個(gè)函數(shù)賦值給一個(gè)變量,例如:
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');
- 匿名函數(shù)
多數(shù)函數(shù)是有名字的, 比如 main() 和 printElement()。 也可以創(chuàng)建沒有名字的函數(shù),這種函數(shù)被稱為 匿名函數(shù), 有時(shí)候也被稱為 lambda 或者 closure 。 匿名函數(shù)可以被復(fù)制到一個(gè)變量中, 舉個(gè)例子,在一個(gè)集合中可以添加或者刪除一個(gè)匿名函數(shù)。匿名函數(shù)和命名函數(shù)看起來類似— 在括號之間可以定義一些參數(shù)或可選參數(shù),參數(shù)使用逗號分割。
后面大括號中的代碼為函數(shù)體:
([[Type] param1[, …]]) {
codeBlock;
};
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
// 一個(gè)簡單的block
doSome(VoidCallback voidCallBack) {
voidCallBack();
}
如果函數(shù)只有一條語句, 可以使用箭頭簡寫。粘貼下面代碼到 DartPad 中 并點(diǎn)擊運(yùn)行按鈕,驗(yàn)證兩個(gè)函數(shù)是等價(jià)性。
list.forEach(
(item) => print('${list.indexOf(item)}: $item'));
- 詞法閉包
閉包 即一個(gè)函數(shù)對象,即使函數(shù)對象的調(diào)用在它原始作用域之外, 依然能夠訪問在它詞法作用域內(nèi)的變量。
函數(shù)可以封閉定義到它作用域內(nèi)的變量。 接下來的示例中, makeAdder() 捕獲了變量 addBy。
無論在什么時(shí)候執(zhí)行返回函數(shù),函數(shù)都會使用捕獲的 addBy 變量。
/// 返回一個(gè)函數(shù),返回的函數(shù)參數(shù)與 [addBy] 相加。
Function makeAdder(num addBy) {
return (num i) => addBy + i;
}
void main() {
// 創(chuàng)建一個(gè)加 2 的函數(shù)。
var add2 = makeAdder(2);
// 創(chuàng)建一個(gè)加 4 的函數(shù)。
var add4 = makeAdder(4);
assert(add2(3) == 5);
assert(add4(3) == 7);
}
- If 和 else
Dart 支持 if - else 語句,其中 else 是可選的, 比如下面的例子
if (isRaining()) {
you.bringRainCoat();
} else if (isSnowing()) {
you.wearJacket();
} else {
car.putTopDown();
}
注意:和 JavaScript 不同, Dart 的判斷條件必須是布爾值,不能是其他類型
- 類
Dart 是一種基于類和 mixin 繼承機(jī)制的面向?qū)ο蟮恼Z言。 每個(gè)對象都是一個(gè)類的實(shí)例,所有的類都繼承于 Object. 。
基于 *Mixin繼承* 意味著> 每個(gè)類(除 Object 外) 都只有一個(gè)超類, 一個(gè)類中的代碼可以在其他多個(gè)繼承類中重復(fù)使用。
- 使用類的成員變量
對象的由函數(shù)和數(shù)據(jù)(即方法和實(shí)例變量)組成。 方法的調(diào)用要通過對象來完成: 調(diào)用的方法可以訪問其對象的其他函數(shù)和數(shù)據(jù)。
使用 (.) 來引用實(shí)例對象的變量和方法:
var p = Point(2, 2);
// 為實(shí)例的變量 y 設(shè)置值。
p.y = 3;
// 獲取變量 y 的值。
assert(p.y == 3);
// 調(diào)用 p 的 distanceTo() 方法。
num distance = p.distanceTo(Point(4, 4));
// 使用 ?. 來代替 . , 可以避免因?yàn)樽筮厡ο罂赡転?null , 導(dǎo)致的異常:
// 如果 p 為 non-null,設(shè)置它變量 y 的值為 4。
p?.y = 4;
- 使用構(gòu)造函數(shù)
通過 構(gòu)造函數(shù) 創(chuàng)建對象。 構(gòu)造函數(shù)的名字可以是 ClassName 或者 ClassName.identifier。
例如, 以下代碼使用 Point 和 Point.fromJson() 構(gòu)造函數(shù)創(chuàng)建 Point 對象:
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
- 獲取對象的類型
使用對象的 runtimeType 屬性, 可以在運(yùn)行時(shí)獲取對象的類型, runtimeType 屬性回返回一個(gè) Type 對象。
print('The type of a is ${a.runtimeType}');
- 重寫類成員
子類可以重寫實(shí)例方法,getter 和 setter。 可以使用 @override 注解指出想要重寫的成員:
class SmartTelevision extends Television {
@override
void turnOn() {...}
// ···
}
- 重寫運(yùn)算符
下面示例演示一個(gè)類重寫 + 和 - 操作符:
class Vector {
final int x, y;
Vector(this.x, this.y);
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
// 運(yùn)算符 == 和 hashCode 部分沒有列出。 有關(guān)詳情,請參考下面的注釋。
// ···
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
- noSuchMethod()
當(dāng)代碼嘗試使用不存在的方法或?qū)嵗兞繒r(shí), 通過重寫 noSuchMethod() 方法,來實(shí)現(xiàn)檢測和應(yīng)對處理:
class A {
// 如果不重寫 noSuchMethod,訪問
// 不存在的實(shí)例變量時(shí)會導(dǎo)致 NoSuchMethodError 錯誤。
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: ' +
'${invocation.memberName}');
}
}
- 枚舉類型
枚舉類型也稱為 enumerations 或 enums , 是一種特殊的類,用于表示數(shù)量固定的常量值。
enum Color {
red,
green,
blue
}
-
Mixin繼承是什么意思呢?
mixins是一個(gè)很強(qiáng)大的概念,可以讓您跨多個(gè)類層次結(jié)構(gòu)重用代碼。
Mixins通過普通的類聲明隱式定義:
class Walker {
void walk() {
print("I'm walking");
}
}
要使用mixin的話,你需要使用with關(guān)鍵字,后跟一個(gè)或多個(gè)mixin的名稱:
class Cat extends Mammal with Walker {}
class Dove extends Bird with Walker, Flyer {}
理解后就覺得很簡單,但是這個(gè)設(shè)計(jì)思想很值得學(xué)習(xí)
class A {
String getMessage() => 'A';
}
class B {
String getMessage() => 'B';
}
class P {
String getMessage() => 'P';
}
class AB extends P with A, B {}
class BA extends P with B, A {}
void main() {
String result = '';
AB ab = AB();
result += ab.getMessage();
BA ba = BA();
result += ba.getMessage();
print(result);
}
AB和BA類都使用A和B mixins繼承至P類,但順序不同。所有的A(3個(gè)),B和P類都有一個(gè)名為getMessage的方法。
首先,我們調(diào)用AB類的getMessage方法,然后調(diào)用BA類的getMessage方法。
- A. BA
- B. AB
- C. BAAB
- D. ABBA
線性化
實(shí)際上,這段代碼等同于
class PA = P with A;
class PAB = PA with B;
class AB extends PAB {}
class PB = P with B;
class PBA = PB with A;
class BA extends PBA {}
- 指定庫前綴
如果導(dǎo)入兩個(gè)存在沖突標(biāo)識符的庫, 則可以為這兩個(gè)庫,或者其中一個(gè)指定前綴。
例如,如果 library1 和 library2 都有一個(gè) Element 類, 那么可以通過下面的方式處理:
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// 使用 lib1 中的 Element。
Element element1 = Element();
// 使用 lib2 中的 Element。
lib2.Element element2 = lib2.Element();
導(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;
延遲加載庫
lazy loading 可以讓應(yīng)用在需要的時(shí)候再加載庫。 下面是一些使用延遲加載庫的場景:
- 減少 APP 的啟動時(shí)間。
- 執(zhí)行 A/B 測試,例如 嘗試各種算法的 不同實(shí)現(xiàn)。
- 加載很少使用的功能,例如可選的屏幕和對話框。
要延遲加載一個(gè)庫,需要先使用 deferred as 來導(dǎo)入:
import 'package:greetings/hello.dart' deferred as hello;
當(dāng)需要使用的時(shí)候,使用庫標(biāo)識符調(diào)用 loadLibrary() 函數(shù)來加載庫:
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
在前面的代碼,使用 await 關(guān)鍵字暫停代碼執(zhí)行一直到庫加載完成。 關(guān)于 async 和 await 的更多信息請參考 異步支持。
在一個(gè)庫上你可以多次調(diào)用loadLibrary()函數(shù)。但是該庫只是載入一次。
使用延遲加載庫的時(shí)候,請注意一下問題:
- 延遲加載庫的常量在導(dǎo)入的時(shí)候是不可用的。 只有當(dāng)庫加載完畢的時(shí)候,庫中常量才可以使用.
- 在導(dǎo)入文件的時(shí)候無法使用延遲庫中的類型。 如果你需要使用類型,則考慮把接口類型移動到另外一個(gè)庫中, 讓兩個(gè)庫都分別導(dǎo)入這個(gè)接口庫。
- Dart 隱含的把
loadLibrary()函數(shù)導(dǎo)入到使用deferred as的命名空間 中。loadLibrary()方法返回一個(gè) Future。
- 處理 Stream
當(dāng)需要從 Stream 中獲取數(shù)據(jù)值時(shí), 可以通過以下方式:
異步for循環(huán)的使用形式
await for (varOrType identifier in expression) {
// Executes each time the stream emits a value.
}
上面 表達(dá)式 返回的值必須是 Stream 類型。 執(zhí)行流程如下:
- 等待,直到流發(fā)出一個(gè)值。
- 執(zhí)行 for 循環(huán)體,將變量設(shè)置為該發(fā)出的值
- 重復(fù)1和2,直到關(guān)閉流。
- 可調(diào)用類
通過實(shí)現(xiàn)類的 call() 方法, 能夠讓類像函數(shù)一樣被調(diào)用。
在下面的示例中,WannabeFunction 類定義了一個(gè) call() 函數(shù), 函數(shù)接受三個(gè)字符串參數(shù),函數(shù)體將三個(gè)字符串拼接,
字符串間用空格分割,并在結(jié)尾附加了一個(gè)感嘆號。
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');
}
- 元數(shù)據(jù)
使用元數(shù)據(jù)可以提供有關(guān)代碼的其他信息。 元數(shù)據(jù)注釋以字符 @ 開頭, 后跟對編譯時(shí)常量 (如 deprecated) 的引用或?qū)ΤA繕?gòu)造函數(shù)的調(diào)用。
對于所有 Dart 代碼有兩種可用注解:@deprecated 和 @override。 關(guān)于 @override 的使用, 參考 擴(kuò)展類(繼承)。 下面是使用
@deprecated(被棄用的) 注解的示例:
class Television {
/// _Deprecated: Use [turnOn] instead._
@deprecated
void activate() {
turnOn();
}
/// Turns the TV's power on.
void turnOn() {...}
}
可以自定義元數(shù)據(jù)注解。 下面的示例定義了一個(gè)帶有兩個(gè)參數(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');
}
所有代碼中可用的注解
- @deprecated 被棄用的
- @override 重載
- @proxy 代理