本文是【從零開始學(xué)習(xí),開發(fā)個(gè)Flutter App】路上的第 1 篇文章。
這篇文章介紹了 Dart 的基礎(chǔ)特性,目的在于讓大家建立對(duì) Dart 語言的總體認(rèn)知,初步掌握 Dart 的語法。
我們假定讀者已經(jīng)有一定的編程基礎(chǔ),如果你了解 JAVAScript 或者 Java 等面向?qū)ο笳Z言,那 Dart 學(xué)習(xí)起來應(yīng)該很有親切感。
Dart 是一門采取眾家之長(zhǎng)的編程語言。盡管 Dart 很多語法和 JavaScript 很相似,但 Dart 語言同時(shí)是一門強(qiáng)類型的語言,它同時(shí)結(jié)合了像 Java 這樣強(qiáng)類型面向?qū)ο笳Z言的特性,這使得它能勝任大型應(yīng)用開發(fā),同時(shí)它沒有 Java 的臃腫,Dart 語言在設(shè)計(jì)上非常簡(jiǎn)潔、靈活和高效。
JavaScript 從簡(jiǎn)單的瀏覽器腳本到服務(wù)端(nodejs),慢慢延伸到PC客戶端(electron)、App (React Native)甚至小程序開發(fā),它已然成為一門真正意義上的全棧開發(fā)語言。
如果說 JavaScript 是在漫長(zhǎng)的時(shí)光里野蠻生長(zhǎng),那 Dart 從一開始就是精心設(shè)計(jì)出來的。如果說有一門語言取代JavaScript的位置,那很可能就是Dart。
Talk is cheep,下面就讓我們來親自感受一下這門語言的吧。
變量
你可以像 JavaScript 那樣聲明一個(gè)變量:
var name = 'Bob';
編譯器會(huì)推導(dǎo)出 name 的類型是String 類型,等價(jià)于:
String name = 'Bob';
我們可以從下面代碼窺見 Dart 是強(qiáng)類型語言的特性:
var name = 'Bob';
// 調(diào)用 String 的方法
print(name.toLowerCase());
// 編譯錯(cuò)誤
// name = 1;
前面我們說過,Dart 除了具備簡(jiǎn)潔的特點(diǎn),而且也可以是非常靈活的,如果你想變換一個(gè)變量的類型,你也可以使用dynamic 來聲明變量,這就跟 JavaScript 一樣了:
dynamic name = 'Bob'; //String 類型
name = 1;// int 類型
print(name);
上面的代碼可以正常編譯和運(yùn)行,但除非你有足夠的理由,請(qǐng)不要輕易使用。
final 的語義和 Java 的一樣,表示該變量是不可變的:
// String 可以省略
final String name = 'Bob';
// 編譯錯(cuò)誤
// name = 'Mary';
其中 String 可以省略,Dart 編譯器足夠聰明地知道變量name 的類型。
如果要聲明常量,可以使用const 關(guān)鍵詞:
const PI = '3.14';
class Person{
static const name = 'KK';
}
如果類變量,則需要聲明為static const 。
內(nèi)置類型
不像Java把類型分的特別細(xì),比如整數(shù)類型,就有byte、short、int 、long 。Dart 的類型設(shè)計(jì)相當(dāng)簡(jiǎn)潔,這也是 Dart 容易上手的原因之一,可以理解為通過犧牲空間來換取效率吧。
數(shù)值類型
Dart 內(nèi)置支持兩種數(shù)值類型,分別是int 和double ,它們的大小都是64位。
var x = 1;
// 0x開頭為16進(jìn)制整數(shù)
var hex = 0xDEADBEEF;
var y = 1.1;
// 指數(shù)形式
var exponents = 1.42e5;
需要注意的是,在Dart中,所有變量值都是一個(gè)對(duì)象,int和double類型也不例外,它們都是num類型的子類,這點(diǎn)和Java和JavaScript都不太一樣:
// 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');
字符串
Dart 字符串使用的是UTF-16編碼。
var s = '中';
s.codeUnits.forEach((ch) => print(ch));
// 輸出為UNICODE值
20013
Dart 采用了 JavaScript 中類似模板字符串的概念,可以在字符串通過${expression}語法插入變量:
var s = "hello";
print('${s}, world!');
//可以簡(jiǎn)化成:
print('$s, world!');
//調(diào)用方法
print('${s.toUpperCase()}, world!');
Dart 可以直接通過==來比較字符串:
var s1 = "hello";
var s2 = "HELLO";
assert(s1.toUpperCase() == s2);
布爾類型
Dart 布爾類型對(duì)應(yīng)為bool關(guān)鍵詞,它有true和false兩個(gè)值,這點(diǎn)和其他語言區(qū)別不大。值得一提的是,在Dart的條件語句if和assert表達(dá)式里面,它們的值必須是bool類型,這點(diǎn)和 JavaScript 不同。
var s = '';
assert(s.isEmpty);
if(s.isNotEmpty){
// do something
}
//編譯錯(cuò)誤,在JavaScript常用來判斷undefined
if(s){
}
Lists
你可以把Dart中的List對(duì)應(yīng)到 JavaScript 的數(shù)組或者 Java 中的ArrayList,但 Dart 的設(shè)計(jì)更為精巧。
你可以通過類似 JavaScript 一樣聲明一個(gè)數(shù)組對(duì)象:
var list = [];
list.add('Hello');
list.add(1);
這里L(fēng)ist容器接受的類型是dynamic,你可以往里面添加任何類型的對(duì)象,但如果像這樣聲明:
var iList = [1,2,3];
iList.add(4);
//編譯錯(cuò)誤 The argument type 'String' can't be assigned to the parameter type 'int'
//iList.add('Hello');
那么Dart就會(huì)推導(dǎo)出這個(gè)List是個(gè)List<int>,從此這個(gè)List就只能接受int類型數(shù)據(jù)了,你也可以顯式聲明List的類型:
var sList = List<String>();
//在Flutter類庫中,有許多這樣的變量聲明:
List<Widget> children = const <Widget>[];
上面右邊那個(gè) const 的意思表示常量數(shù)組,在這里你可以理解為一個(gè)給children賦值了一個(gè)編譯期常量空數(shù)組,這樣的做法可以很好的節(jié)省內(nèi)存,下面的例子可以讓大家更好的理解常量數(shù)組的概念:
var constList = const <int>[1,2];
constList[0] = 2; //編譯通過, 運(yùn)行錯(cuò)誤
constList.add(3); //編譯通過, 運(yùn)行錯(cuò)誤
Dart2.3 增加了擴(kuò)展運(yùn)算符 (spread operator) ... 和...?,通過下面的例子你很容易就明白它們的用法:
var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);
如果擴(kuò)展對(duì)象可能是null,可以使用...?:
var list;
var list2 = [0, ...?list];
assert(list2.length == 1);
你可以直接在元素內(nèi)進(jìn)行判斷,決定是否需要某個(gè)元素:
var promoActive = true;
var nav = [
'Home',
'Furniture',
'Plants',
promoActive? 'About':'Outlet'
];
甚至使用for來動(dòng)態(tài)添加多個(gè)元素:
var listOfInts = [1, 2, 3];
var listOfStrings = [
'#0',
for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');
這種動(dòng)態(tài)的能力使得 Flutter 在構(gòu)建 Widget 樹的時(shí)候非常方便。
Sets
Set的語意和其他語言的是一樣的,都是表示在容器中對(duì)象唯一。在Dart中,Set默認(rèn)是LinkedHashSet實(shí)現(xiàn),表示元素按添加先后順序排序。
聲明Set對(duì)象:
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
遍歷Set,遍歷除了上面提到的for...in,你還可以使用類似 Java 的 lambada 中的 forEach 形式:
halogens.add('bromine');
halogens.add('astatine');
halogens.forEach((el) => print(el));
輸出結(jié)果:
fluorine
chlorine
bromine
iodine
astatine
除了容器的對(duì)象唯一特性之外,其他基本和List是差不多的。
// 添加類型聲明:
var elements = <String>{};
var promoActive = true;
// 動(dòng)態(tài)添加元素
final navSet = {'Home', 'Furniture', promoActive? 'About':'Outlet'};
Maps
Map對(duì)象的聲明方式保持了 JavaScript 的習(xí)慣,Dart 中Map的默認(rèn)實(shí)現(xiàn)是LinkedHashMap,表示元素按添加先后順序排序。
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
assert(gifts['first'] == 'partridge');
添加一個(gè)鍵值對(duì):
gifts['fourth'] = 'calling birds';
遍歷Map:
gifts.forEach((key,value) => print('key: $key, value: $value'));
函數(shù)
在 Dart 中,函數(shù)本身也是個(gè)對(duì)象,它對(duì)應(yīng)的類型是Function,這意味著函數(shù)可以當(dāng)做變量的值或者作為一個(gè)方法入傳參數(shù)值。
void sayHello(var name){
print('hello, $name');
}
void callHello(Function func, var name){
func(name);
}
void main(){
// 函數(shù)變量
var helloFuc = sayHello;
// 調(diào)用函數(shù)
helloFuc('Girl');
// 函數(shù)參數(shù)
callHello(helloFuc,'Boy');
}
輸出:
hello, Girl
hello, Boy
對(duì)于只有一個(gè)表達(dá)式的簡(jiǎn)單函數(shù),你還可以通過=>讓函數(shù)變得更加簡(jiǎn)潔,=> expr在這里相當(dāng)于{ return expr; } ,我們來看一下下面的語句:
String hello(var name ) => 'hello, $name';
相當(dāng)于:
String hello(var name ){
return 'hello, $name';
}
參數(shù)
在Flutter UI庫里面,命名參數(shù)隨處可見,下面是一個(gè)使用了命名參數(shù)(Named parameters)的例子:
void enableFlags({bool bold, bool hidden}) {...}
調(diào)用這個(gè)函數(shù):
enableFlags(bold: false);
enableFlags(hidden: false);
enableFlags(bold: true, hidden: false);
命名參數(shù)默認(rèn)是可選的,如果你需要表達(dá)該參數(shù)必傳,可以使用@required:
void enableFlags({bool bold, @required bool hidden}) {}
當(dāng)然,Dart 對(duì)于一般的函數(shù)形式也是支持的:
void enableFlags(bool bold, bool hidden) {}
和命名參數(shù)不一樣,這種形式的函數(shù)的參數(shù)默認(rèn)是都是要傳的:
enableFlags(false, true);
你可以使用[]來增加非必填參數(shù):
void enableFlags(bool bold, bool hidden, [bool option]) {}
另外,Dart 的函數(shù)還支持設(shè)置參數(shù)默認(rèn)值:
void enableFlags({bool bold = false, bool hidden = false}) {...}
String say(String from, [String device = 'carrier pigeon', String mood]) {}
匿名函數(shù)
顧名思意,匿名函數(shù)的意思就是指沒有定義函數(shù)名的函數(shù)。你應(yīng)該對(duì)此不陌生了,我們?cè)诒闅vList和Map的時(shí)候已經(jīng)使用過了,通過匿名函數(shù)可以進(jìn)一步精簡(jiǎn)代碼:
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
閉包
Dart支持閉包。沒有接觸過JavaScript的同學(xué)可能對(duì)閉包(closure)比較陌生,這里給大家簡(jiǎn)單解釋一下閉包。
閉包的定義比較拗口,我們不去糾結(jié)它的具體定義,而是打算通過一個(gè)具體的例子去理解它:
Function closureFunc() {
var name = "Flutter"; // name 是一個(gè)被 init 創(chuàng)建的局部變量
void displayName() { // displayName() 是內(nèi)部函數(shù),一個(gè)閉包
print(name); // 使用了父函數(shù)中聲明的變量
}
return displayName;
}
void main(){
//myFunc是一個(gè)displayName函數(shù)
var myFunc = closureFunc(); //(1)
// 執(zhí)行displayName函數(shù)
myFunc(); // (2)
}
結(jié)果如我們所料的那樣打印了Flutter。
在(1)執(zhí)行完之后,name作為一個(gè)函數(shù)的局部變量,引用的對(duì)象不是應(yīng)該被回收掉了嗎?但是當(dāng)我們?cè)趦?nèi)函數(shù)調(diào)用外部的name時(shí),它依然可以神奇地被調(diào)用,這是為什么呢?
這是因?yàn)镈art在運(yùn)行內(nèi)部函數(shù)時(shí)會(huì)形成閉包,閉包是由函數(shù)以及創(chuàng)建該函數(shù)的詞法環(huán)境組合而成,這個(gè)環(huán)境包含了這個(gè)閉包創(chuàng)建時(shí)所能訪問的所有局部變量 。
我們簡(jiǎn)單變一下代碼:
Function closureFunc() {
var name = "Flutter"; // name 是一個(gè)被 init 創(chuàng)建的局部變量
void displayName() { // displayName() 是內(nèi)部函數(shù),一個(gè)閉包
print(name); // 使用了父函數(shù)中聲明的變量
}
name = 'Dart'; //重新賦值
return displayName;
}
結(jié)果輸出是Dart,可以看到內(nèi)部函數(shù)訪問外部函數(shù)的變量時(shí),是在同一個(gè)詞法環(huán)境中的。
返回值
在Dart中,所有的函數(shù)都必須有返回值,如果沒有的話,那將自動(dòng)返回null:
foo() {}
assert(foo() == null);
流程控制
這部分和大部分語言都一樣,在這里簡(jiǎn)單過一下就行。
if-else
if(hasHause && hasCar){
marry();
}else if(isHandsome){
date();
}else{
pass();
}
循環(huán)
各種for:
var list = [1,2,3];
for(var i = 0; i != list.length; i++){}
for(var i in list){}
while和循環(huán)中斷(中斷也是在for中適用的):
var i = 0;
while(i != list.length){
if(i % 2 == 0){
continue;
}
print(list[i]);
}
i = 0;
do{
print(list[i]);
if(i == 5){
break;
}
}while(i != list.length);
如果對(duì)象是Iterable類型,你還可以像Java的 lambada 表達(dá)式一樣:
list.forEach((i) => print(i));
list.where((i) =>i % 2 == 0).forEach((i) => print(i));
switch
switch可以用于int、double、String 和enum等類型,switch 只能在同類型對(duì)象中進(jìn)行比較,進(jìn)行比較的類不要覆蓋==運(yùn)算符。
var color = '';
switch(color){
case "RED":
break;
case "BLUE":
break;
default:
}
assert
在Dart中,assert語句經(jīng)常用來檢查參數(shù),它的完整表示是:assert(condition, optionalMessage),如果condition為false,那么將會(huì)拋出[AssertionError]異常,停止執(zhí)行程序。
assert(text != null);
assert(urlString.startsWith('https'), 'URL ($urlString) should start with "https".');
assert 通常只用于開發(fā)階段,它在產(chǎn)品運(yùn)行環(huán)境中通常會(huì)被忽略。在下面的場(chǎng)景中會(huì)打開assert:
- Flutter 的 debug mode。
- 一些開發(fā)工具比如dartdevc默認(rèn)會(huì)開啟。
- 一些工具,像dart 和dart2js ,可以通過參數(shù)--enable-asserts 開啟。
異常處理
Dart 的異常處理和Java很像,但是Dart中所有的異常都是非檢查型異常(unchecked exception),也就是說,你不必像 Java 一樣,被強(qiáng)制需要處理異常。
Dart 提供了Exception 和 Error 兩種類型的異常。 一般情況下,你不應(yīng)該對(duì)Error類型錯(cuò)誤進(jìn)行捕獲處理,而是盡量避免出現(xiàn)這類錯(cuò)誤。
比如OutOfMemoryError、StackOverflowError、NoSuchMethodError等都屬于Error類型錯(cuò)誤。
前面提到,因?yàn)?Dart 不像 Java 那樣可以聲明編譯期異常,這種做法可以讓代碼變得更簡(jiǎn)潔,但是容易忽略掉異常的處理,所以我們?cè)诰幋a的時(shí)候,在可能會(huì)有異常的地方要注意閱讀API文檔,另外自己寫的方法,如果有異常拋出,要在注釋處進(jìn)行聲明。比如類庫中的File類其中一個(gè)方法注釋:
/**
* Synchronously read the entire file contents as a list of bytes.
*
* Throws a [FileSystemException] if the operation fails.
*/
Uint8List readAsBytesSync();
拋出異常
throw FormatException('Expected at least 1 section');
throw除了可以拋出異常對(duì)象,它還可以拋出任意類型對(duì)象,但建議還是使用標(biāo)準(zhǔn)的異常類作為最佳實(shí)踐。
throw 'Out of llamas!';
捕獲異常
可以通過on 關(guān)鍵詞來指定異常類型:
var file = File("1.txt");
try{
file.readAsStringSync();
} on FileSystemException {
//do something
}
使用catch關(guān)鍵詞獲取異常對(duì)象,catch有兩個(gè)參數(shù),第一個(gè)是異常對(duì)象,第二個(gè)是錯(cuò)誤堆棧。
try{
file.readAsStringSync();
} on FileSystemException catch (e){
print('exception: $e');
} catch(e, s){ //其余類型
print('Exception details:n $e');
print('Stack trace:n $s');
}
使用rethrow 拋給上一級(jí)處理:
try{
file.readAsStringSync();
} on FileSystemException catch (e){
print('exception: $e');
} catch(e){
rethrow;
}
finally
finally一般用于釋放資源等一些操作,它表示最后一定會(huì)執(zhí)行的意思,即便try...catch中有return,它里面的代碼也會(huì)承諾執(zhí)行。
try{
print('hello');
return;
} catch(e){
rethrow;
} finally{
print('finally');
}
輸出:
hello
finally
類
Dart 是一門面向?qū)ο蟮木幊陶Z言,所有對(duì)象都是某個(gè)類的實(shí)例,所有類繼承了Object類。
一個(gè)簡(jiǎn)單的類:
class Point {
num x, y;
// 構(gòu)造器
Point(this.x, this.y);
// 實(shí)例方法
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
類成員
Dart 通過. 來調(diào)用類成員變量和方法的。
//創(chuàng)建對(duì)象,new 關(guān)鍵字可以省略
var p = Point(2, 2);
// Set the value of the instance variable y.
p.y = 3;
// Get the value of y.
assert(p.y == 3);
// Invoke distanceTo() on p.
num distance = p.distanceTo(Point(4, 4));
你還可以通過.?來避免null對(duì)象。在Java 里面,經(jīng)常需要大量的空判斷來避免NullPonterException,這是讓人詬病Java的其中一個(gè)地方。而在Dart中,可以很方便地避免這個(gè)問題:
// If p is non-null, set its y value to 4.
p?.y = 4;
在 Dart 中,沒有private、protected、public這些關(guān)鍵詞,如果要聲明一個(gè)變量是私有的,則在變量名前添加下劃線_,聲明了私有的變量,只在本類庫中可見。
class Point{
num _x;
num _y;
}
構(gòu)造器(Constructor)
如果沒有聲明構(gòu)造器,Dart 會(huì)給類生成一個(gè)默認(rèn)的無參構(gòu)造器,聲明一個(gè)帶參數(shù)的構(gòu)造器,你可以像 Java這樣:
class Person{
String name;
int sex;
Person(String name, int sex){
this.name = name;
this.sex = sex;
}
}
也可以使用簡(jiǎn)化版:
Person(this.name, this.sex);
或者命名式構(gòu)造器:
Person.badGirl(){
this.name = 'Bad Girl';
this.sex = 1;
}
你還可以通過factory關(guān)鍵詞來創(chuàng)建實(shí)例:
Person.goodGirl(){
this.name = 'good Girl';
this.sex = 1;
}
factory Person(int type){
return type == 1 ? Person.badGirl(): Person.goodGirl();
}
factory對(duì)應(yīng)到設(shè)計(jì)模式中工廠模式的語言級(jí)實(shí)現(xiàn),在 Flutter 的類庫中有大量的應(yīng)用,比如Map:
// 部分代碼
abstract class Map<K, V> {
factory Map.from(Map other) = LinkedHashMap<K, V>.from;
}
如果一個(gè)對(duì)象的創(chuàng)建過程比較復(fù)雜,比如需要選擇不同的子類實(shí)現(xiàn)或則需要緩存實(shí)例等,你就可以考慮通過這種方法。在上面Map例子中,通過聲明 factory來選擇了創(chuàng)建子類LinkedHashMap(LinkedHashMap.from也是一個(gè)factory,里面是具體的創(chuàng)建過程)。
如果你想在對(duì)象創(chuàng)建之前的時(shí)候還想做點(diǎn)什么,比如參數(shù)校驗(yàn),你可以通過下面的方法:
Person(this.name, this.sex): assert(sex == 1)
在構(gòu)造器后面添加的一些簡(jiǎn)單操作叫做initializer list。
在Dart中,初始化的順序如下:
- 執(zhí)行initializer list;
- 執(zhí)行父類的構(gòu)造器;
- 執(zhí)行子類的構(gòu)造器。
class Person{
String name;
int sex;
Person(this.sex): name = 'a', assert(sex == 1){
this.name = 'b';
print('Person');
}
}
class Man extends Person{
Man(): super(1){
this.name = 'c';
print('Man');
}
}
void main(){
Person person = Man();
print('name : ${person.name}');
}
上面的代碼輸出為:
Person
Man
name : c
如果子類構(gòu)造器沒有顯式調(diào)用父類構(gòu)造器,那么默認(rèn)會(huì)調(diào)用父類的默認(rèn)無參構(gòu)造器。顯式調(diào)用父類的構(gòu)造器:
Man(height): this.height = height, super(1);
重定向構(gòu)造器:
Man(this.height, this.age): assert(height > 0), assert(age > 0);
Man.old(): this(12, 60); //調(diào)用上面的構(gòu)造器
Getter 和 Setter
在 Dart 中,對(duì) Getter 和 Setter 方法有專門的優(yōu)化。即便沒有聲明,每個(gè)類變量也會(huì)默認(rèn)有一個(gè)get方法,在隱含接口章節(jié)會(huì)有體現(xiàn)。
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}
void main() {
var rect = Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
抽象類
Dart 的抽象類和Java差不多,除了不可以實(shí)例化,可以聲明抽象方法之外,和一般類沒有區(qū)別。
abstract class AbstractContainer {
num _width;
void updateChildren(); // 抽象方法,強(qiáng)制繼承子類實(shí)現(xiàn)該方法。
get width => this._width;
int sqrt(){
return _width * _width;
}
}
隱含接口
Dart 中的每個(gè)類都隱含了定義了一個(gè)接口,這個(gè)接口包含了這個(gè)類的所有成員變量和方法,你可以通過implements關(guān)鍵詞來重新實(shí)現(xiàn)相關(guān)的接口方法:
class Person {
//隱含了 get 方法
final _name;
Person(this._name);
String greet(String who) => 'Hello, $who. I am $_name.';
}
class Impostor implements Person {
// 需要重新實(shí)現(xiàn)
get _name => '';
// 需要重新實(shí)現(xiàn)
String greet(String who) => 'Hi $who. Do you know who I am?';
}
實(shí)現(xiàn)多個(gè)接口:
class Point implements Comparable, Location {...}
繼承
和Java基本一致,繼承使用extends關(guān)鍵詞:
class Television {
void turnOn() {
doSomthing();
}
}
class SmartTelevision extends Television {
@override
void turnOn() {
super.turnOn(); //調(diào)用父類方法
doMore();
}
}
重載操作符
比較特別的是,Dart 還允許重載操作符,比如List類支持的下標(biāo)訪問元素,就定義了相關(guān)的接口:
E operator [](int index);
我們通過下面的實(shí)例來進(jìn)一步說明重載操作符:
class MyList{
var list = [1,2,3];
operator [](int index){
return list[index];
}
}
void main() {
var list = MyList();
print(list[1]); //輸出 2
}
擴(kuò)展方法
這個(gè)特性也是Dart讓人眼前一亮的地方(Dart2.7之后才支持),可以對(duì)標(biāo)到 JavaScript 中的 prototype。通過這個(gè)特性,你甚至可以給類庫添加新的方法:
//通過關(guān)鍵詞 extension 給 String 類添加新方法
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
}
后面String對(duì)象就可以調(diào)用該方法了:
print('42'.parseInt());
枚舉類型
枚舉類型和保持和Java的關(guān)鍵詞一致:
enum Color { red, green, blue }
在switch中使用:
// color 是 enmu Color 類型
switch(color){
case Color.red:
break;
case Color.blue:
break;
case Color.green:
break;
default:
break;
}
枚舉類型還有一個(gè)index的getter,它是個(gè)連續(xù)的數(shù)字序列,從0開始:
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
新特性:Mixins
這個(gè)特性進(jìn)一步增強(qiáng)了代碼復(fù)用的能力,如果你有寫過Android的布局XML代碼或者Freemaker模板的話,那這個(gè)特性就可以理解為其中inlclude 的功能。
聲明一個(gè)mixin類:
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}
通過with關(guān)鍵詞進(jìn)行復(fù)用:
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
mixin類甚至可以通過on關(guān)鍵詞實(shí)現(xiàn)繼承的功能:
mixin MusicalPerformer on Musician {
// ···
}
類變量和類方法
class Queue {
//類變量
static int maxLength = 1000;
// 類常量
static const initialCapacity = 16;
// 類方法
static void modifyMax(int max){
_maxLength = max;
}
}
void main() {
print(Queue.initialCapacity);
Queue.modifyMax(2);
print(Queue._maxLength);
}
泛型
在面向?qū)ο蟮恼Z言中,泛型主要的作用有兩點(diǎn):
1、類型安全檢查,把錯(cuò)誤扼殺在編譯期:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
//編譯錯(cuò)誤
names.add(42);
2、增強(qiáng)代碼復(fù)用,比如下面的代碼:
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
你可以通過泛型把它們合并成一個(gè)類:
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
在Java中,泛型是通過類型擦除來實(shí)現(xiàn)的,但在Dart中實(shí)打?qū)嵉姆盒停?/p>
var names = <String>[];
names.addAll(['Tom',"Cat"]);
// is 可以用于類型判斷
print(names is List<String>); // true
print(names is List); // true
print(names is List<int>); //false
你可以通過extends關(guān)鍵詞來限制泛型類型,這點(diǎn)和Java一樣:
abstract class Animal{}
class Cat extends Animal{}
class Ext<T extends Animal>{
T data;
}
void main() {
var e = Ext(); // ok
var e1 = Ext<Animal>(); // ok
var e2 = Ext<Cat>(); // ok
var e3 = Ext<int>(); // compile error
}
使用類庫
有生命力的編程語言,它背后都有一個(gè)強(qiáng)大的類庫,它們可以讓我們站在巨人的肩膀上,又免于重新造輪子。
導(dǎo)入類庫
在Dart里面,通過import關(guān)鍵詞來導(dǎo)入類庫。
內(nèi)置的類庫使用dart:開頭引入:
import 'dart:io';
了解更多內(nèi)置的類庫可以查看這里。
第三方類庫或者本地的dart文件用package:開頭:
比如導(dǎo)入用于網(wǎng)絡(luò)請(qǐng)求的dio庫:
import 'package:dio/dio.dart';
Dart 應(yīng)用本身就是一個(gè)庫,比如我的應(yīng)用名是ccsys,導(dǎo)入其他文件夾的類:
import 'package:ccsys/common/net_utils.dart';
import 'package:ccsys/model/user.dart';
如果你使用IDE來開發(fā),一般這個(gè)事情不用你來操心,它會(huì)自動(dòng)幫你導(dǎo)入的。
Dart 通過pub.dev來管理類庫,類似Java世界的Maven 或者Node.js的npm一樣,你可以在里面找到非常多實(shí)用的庫。
解決類名沖突
如果導(dǎo)入的類庫有類名沖突,可以通過as使用別名來避免這個(gè)問題:
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)入部分類
在一個(gè)dart文件中,可能會(huì)存在很多個(gè)類,如果你只想引用其中幾個(gè),你可以增加show或者h(yuǎn)ide來處理:
//文件:my_lib.dart
class One {}
class Two{}
class Three{}
使用show導(dǎo)入One和Two類:
//文件:test.dart
import 'my_lib.dart' show One, Two;
void main() {
var one = One();
var two = Two();
//compile error
var three = Three();
}
也可以使用hide排除Three,和上面是等價(jià)的:
//文件:test.dart
import 'my_lib.dart' hide Three;
void main() {
var one = One();
var two = Two();
}
延遲加載庫
目前只有在web app(dart2js)中才支持延遲加載,F(xiàn)lutter、Dart VM是不支持的,我們這里僅做一下簡(jiǎn)單介紹。
你需要通過deferred as來聲明延遲加載該類庫:
import 'package:greetings/hello.dart' deferred as hello;
當(dāng)你需要使用的時(shí)候,通過loadLibrary()加載:
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
你可以多次調(diào)用loadLibrary,它不會(huì)被重復(fù)加載。
異步支持
這個(gè)話題稍微復(fù)雜,我們將用另外一篇文章獨(dú)立討論這個(gè)問題,請(qǐng)留意下一篇內(nèi)容。
參考資料
- 學(xué)習(xí)Dart的十大理由
- A tour of the Dart language
- Dart 常量構(gòu)造器的理解
- JavaScript 閉包
關(guān)于AgileStudio
我們是一支由資深獨(dú)立開發(fā)者和設(shè)計(jì)師組成的團(tuán)隊(duì),成員均有扎實(shí)的技術(shù)實(shí)力和多年的產(chǎn)品設(shè)計(jì)開發(fā)經(jīng)驗(yàn),提供可信賴的軟件定制服務(wù)。






