Dart For Objcer
本文檔是一個(gè)面向Objcer 的更加精煉的 Dart 語言概覽,刪除了相同的語法概念,僅剩差異部分,或 dart 獨(dú)有的部分。未提及的,均可以沿用 objc 經(jīng)驗(yàn)。
數(shù)據(jù)類型
所有數(shù)據(jù)類型都繼承自 Object類。(包括int、String、函數(shù)以及 null 等等)。All is Object.
常用內(nèi)置數(shù)據(jù)類型:
- num
- int
- double
- String
- bool (true | false)
- List ,數(shù)組
- Set,集合
- Map,字典
變量 & 常量
變量
dart 默認(rèn)聲明的為變量,變量聲明主要有以下幾種形式:
-
類型約定:
String name; // 默認(rèn)值為 null:未初始化的所有變量擁有一個(gè)默認(rèn)的初始化值:null,包括 num。 name = 'Bob'; int age = 18;
-
類型推斷,通過 關(guān)鍵詞
var來聲明var name = 'Bob'; //name is String name = 123; //編譯報(bào)錯(cuò)
-
使用動(dòng)態(tài)類型,通過 關(guān)鍵詞
dynamic來聲明, 類似 OC 中的iddynamic name = 'Bob'; //name is String name = 123; //name is int
一般情況下,我們使用 var 來聲明變量,讓編譯器幫我們推斷其類型;
常量
常量聲明涉及到兩個(gè)關(guān)鍵字 final 和 const。使用示例如下:
final String name; // 編譯報(bào)錯(cuò):未初始化
final String name = 'dann'; //?
name = 'dann2'; //編譯報(bào)錯(cuò),提示僅能賦值一次
const String name; // 編譯報(bào)錯(cuò):未初始化
const String name = 'dann'; //?
name = 'dann2'; //編譯報(bào)錯(cuò),常量變量不能被賦值
- final 用于聲明==運(yùn)行時(shí)==常量;
- const 用于聲明==編譯時(shí)==常量;
示例說明如下:
final abc = 1 + 2; //?
const bb = 1 + 2; //?
final x = DateTime.now(); // ?
const x = DateTime.now(); // ?
函數(shù)
一般函數(shù)形式:
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
//函數(shù)體只包含單個(gè)表達(dá)式的簡寫,同上
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
函數(shù)參數(shù)分為兩種:
-
必要參數(shù)
bool isNoble(int atomicNumber/* 必要參數(shù) */){ ... } -
可選參數(shù):一個(gè)函數(shù)==僅可==包含一種可選參數(shù)
-
命名參數(shù):
{type param1, type param2, ...}bool isNoble(int atomicNumber/* 必要參數(shù) */, {bool bold = false, @required Widget child}){ ... } -
位置參數(shù):
[type param1, type param2, ...],bool isNoble(int atomicNumber/* 必要參數(shù) */, [bool bold]){ ... }
-
函數(shù)注意點(diǎn):
必要在前,可選在后;
可對(duì)參數(shù)設(shè)置默認(rèn)值
isNoble(int atomicNumber = 110)即使是可選參數(shù),也可通過關(guān)鍵字
@required將其約束為 必要的(調(diào)用函數(shù)時(shí),必須對(duì)該參數(shù)賦值)-
函數(shù)也是對(duì)象,可作為參數(shù)傳遞
void printElement(int element) { print(element); } var list = [1, 2, 3]; // 將 printElement 函數(shù)作為參數(shù)傳遞。 list.forEach(printElement);
匿名函數(shù)
類似 OC 中的 Block
void main() {
var list = ['apples', 'bananas', 'oranges'];
// forEach(void Function(String) f) -> void
list.forEach((item){
print(item);
});
}
表達(dá)式
表達(dá)式基本繼承自類 C 語言,OC中沒有的表達(dá)式,有如下幾個(gè):
-
~/除并取整print(5/2 == 2.5); //true, 此處不同于 OC ,返回整數(shù) print(5 ~/ 2 == 2); //true -
as類型轉(zhuǎn)換 |isis!類型判斷// 類型檢查 if (emp is Person) { emp.firstName = 'Bob'; } // 類型檢查 if (emp is! Person) { //TODO: } //此時(shí)要確保 emp 是 Person 類型,否則會(huì)拋出異常 (emp as Person).firstName = 'Bob';
-
..級(jí)聯(lián) 在 同一個(gè)對(duì)象 上連續(xù) 調(diào)用 多個(gè)對(duì)象的變量或方法。querySelector('#confirm') // 獲取對(duì)象 (Get an object). ..text = 'Confirm' // 使用對(duì)象的成員 (Use its members). ..classes.add('important') ..onClick.listen((e) => window.alert('Confirmed!')); //equal to var button = querySelector('#confirm'); button.text = 'Confirm'; button.classes.add('important'); button.onClick.listen((e) => window.alert('Confirmed!')); //返回值為 void 的方法則不能使用級(jí)聯(lián)運(yùn)算符 var sb = StringBuffer(); sb.write('foo') ..write('bar'); // 出錯(cuò):void 對(duì)象中沒有方法 write
-
?? 空判斷
expr1 ?? expr2expr1 非空,則返回其值,否則執(zhí)行 expr2 并返回值var num; print(num??123); // 123 num = 456; print(num??123); // 456
-
??=空則賦值var a = 123; a ??= 456; print(a); //123 var b; b ??= 456; print(b); //456 -
?.條件訪問成員List numbs; print(numbs?.length); // null numbs = [1, 2, 3]; print(numbs?.length); // 3
流程控制
流程控制語句也基本繼承自 C 語言;按已有 OC 習(xí)慣使用即可;
異常
關(guān)鍵字:
throwtrycatchfinally
流程及使用習(xí)慣,類 Java,如下:
void breedMoreLlamas(){
if(1){
//TODO:
}else{
throw 'Out of llamas!';
}
}
try {
breedMoreLlamas();
} catch (e) {
print('Error: $e'); // 先處理異常。
} finally {
cleanLlamaStalls(); // 然后清理。
}
類
- 一個(gè)類只有一個(gè)父類;
- 類可擴(kuò)展,同OC;
成員使用
var p = Point(2, 2);
// 為實(shí)例變量 y 賦值。
p.y = 3;
// 獲取 y 的值。
assert(p.y == 3);
// 調(diào)用變量 p 的 distanceTo() 方法。
num distance = p.distanceTo(Point(4, 4));
構(gòu)造函數(shù)
class Point {
num x, y;
Point(num x, num y) {
this.x = x; //使用 this 關(guān)鍵字引用當(dāng)前實(shí)例; OC 中的self
this.y = y;
}
// 語法糖: 在構(gòu)造函數(shù)體執(zhí)行前用于設(shè)置 x 和 y 的, equal to above
Point(this.x, this.y);
}
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
// new 關(guān)鍵字可選
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
常量構(gòu)造函數(shù)
兩個(gè)使用相同構(gòu)造函數(shù)相同參數(shù)值構(gòu)造的編譯時(shí)常量是同一個(gè)對(duì)象:
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // 同一個(gè)實(shí)例!
命名式構(gòu)造函數(shù)
- 聲明一個(gè)類多個(gè)命名式構(gòu)造函數(shù)來表達(dá)更明確的意圖;
- 不能被繼承;
class Point {
num x, y;
Point(this.x, this.y);
// 命名式構(gòu)造函數(shù)
Point.origin() {
x = 0;
y = 0;
}
}
初始化列表
除了調(diào)用父類構(gòu)造函數(shù)之外,還可以在構(gòu)造函數(shù)體執(zhí)行之前初始化實(shí)例變量。每個(gè)實(shí)例變量之間使用逗號(hào)分隔。
// Initializer list sets instance variables before
// the constructor body runs.
// 使用初始化列表在構(gòu)造函數(shù)體執(zhí)行前設(shè)置實(shí)例變量。
Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
其他構(gòu)造函數(shù)
詳情參見Dart API 文檔:
-
重定向構(gòu)造函數(shù):類似OC 中的 非指定構(gòu)造函數(shù) -
工廠構(gòu)造函數(shù):關(guān)鍵字factory, 使用該構(gòu)造函數(shù)構(gòu)造類的實(shí)例時(shí)==并非==總是會(huì)返回新的實(shí)例對(duì)象。例如,工廠構(gòu)造函數(shù)可能會(huì)從緩存中返回一個(gè)實(shí)例,或者返回一個(gè)子類型的實(shí)例。==工廠中不可以訪問 this==
獲取對(duì)象類型
關(guān)鍵字 runtimeType
print('The type of a is ${a.runtimeType}');
方法 & 屬性
使用同OC, 但使用 . 語法
每個(gè)屬性都有 Getter 方法, 對(duì)于 非 final 屬性,還有 Setter 方法。 可以通過 關(guān)鍵字 get 和 set 為額外的屬性(如計(jì)算屬性),添加 Getter 和 Setter 方法:
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// 定義兩個(gè)計(jì)算產(chǎn)生的屬性:right 和 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;
}
void main() {
var rect = Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
抽象類
使用關(guān)鍵字 abstract 標(biāo)識(shí)類可以讓該類成為 抽象類,抽象類將無法被實(shí)例化。抽象類常用于聲明接口方法、有時(shí)也會(huì)有具體的方法實(shí)現(xiàn)。如果想讓抽象類同時(shí)可被實(shí)例化,可以為其定義工廠構(gòu)造函數(shù)。
abstract class Doer {
// 定義實(shí)例變量和方法等等……
void doSomething(); // 定義一個(gè)抽象方法。
}
class EffectiveDoer extends Doer {
void doSomething() {
// 提供一個(gè)實(shí)現(xiàn),所以在這里該方法不再是抽象的……
}
}
隱式接口
騷操作
接口 即 OC 中的協(xié)議。
==每一個(gè)類都隱式地定義了一個(gè)接口并實(shí)現(xiàn)了該接口,這個(gè)接口包含所有這個(gè)類的實(shí)例成員以及這個(gè)類所實(shí)現(xiàn)的其它接口。如果想要?jiǎng)?chuàng)建一個(gè) A 類支持調(diào)用 B 類的 API 且不想繼承 B 類,則可以實(shí)現(xiàn) B 類的接口。==
一個(gè)類可以通過關(guān)鍵字 implements 來實(shí)現(xiàn)一個(gè)或多個(gè)接口并實(shí)現(xiàn)每個(gè)接口定義的 API:
// Person 類的隱式接口中包含 greet() 方法。
class Person {
// _name 變量同樣包含在接口中,但它只是庫內(nèi)可見的。
final _name;
// 構(gòu)造函數(shù)不在接口中。
Person(this._name);
// greet() 方法在接口中。
String greet(String who) => '你好,$who。我是$_name。';
}
// Person 接口的一個(gè)實(shí)現(xiàn)。
class Impostor implements Person {
get _name => '';
String greet(String who) => '你好$who。你知道我是誰嗎?';
}
String greetBob(Person person) => person.greet('小芳');
void main() {
print(greetBob(Person('小蕓')));
print(greetBob(Impostor()));
}
實(shí)現(xiàn)多個(gè)接口,使用逗號(hào)分隔:
class Point implements Comparable, Location {...}
擴(kuò)展
子類化
使用 extends 關(guān)鍵字來創(chuàng)建一個(gè)子類,并可使用 super 關(guān)鍵字引用一個(gè)父類:
class TV{
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTV extends TV {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
重寫
子類可以重寫父類的實(shí)例方法、Getter 以及 Setter 方法。使用 @override 來標(biāo)示:
class SmartTV extends TV {
@override
void turnOn() {...}
// ···
}
擴(kuò)展方法
一種向現(xiàn)有庫添加功能的方式。extension extensionName on ClassName{...}
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
}
print('42'.padLeft(5)); // 使用 原始類 自帶方法
print('42'.parseInt()); // 使用 擴(kuò)展方法
Mixin
一種在多重繼承中復(fù)用某個(gè)類中代碼的方法模式。
定義一個(gè)類繼承自 Object 并且不為該類定義構(gòu)造函數(shù),這個(gè)類就是 Mixin 類,除非你想讓該類與普通的類一樣可以被正常地使用,否則可以使用關(guān)鍵字 mixin 替代 class 讓其成為一個(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)鍵字并在其后跟上 Mixin 類的名字來使用 Mixin 模式:
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
使用關(guān)鍵字 on 來指定哪些類可以使用該 Mixin 類,比如有 Mixin 類 A,但是 A 只能被 B 類使用,則可以這樣定義 A:
mixin MusicalPerformer on Musician {
// ···
}
枚舉
枚舉具有較大的局限性,其值不可自定義。
每一個(gè)枚舉值都有一個(gè)名為 index 成員變量的 Getter 方法,該方法將會(huì)返回以 0 為基準(zhǔn)索引的位置值。例如,第一個(gè)枚舉值的索引是 0 ,第二個(gè)枚舉值的索引是 1。以此類推。
enum Color { red, green, blue }
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
類變量及類方法
使用 static 修飾
類成員
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
靜態(tài)變量在其首次被使用的時(shí)候才被初始化。
類方法
import 'dart:math';
class Point {
num x, 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);
}
}
void main() {
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(2.8 < distance && distance < 2.9);
print(distance);
}
泛型
如今的每個(gè)現(xiàn)代編程語言都支持泛型。日常使用中,最常見于集合。常用于需要要求類型安全的情況,好處還有:
- 適當(dāng)?shù)刂付ǚ盒涂梢愿玫貛椭a生成。
- 使用泛型可以減少代碼重復(fù)。
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error
在上述代碼中,為集合指定類型為 String,方便編譯器檢查,避免插入錯(cuò)誤類型數(shù)據(jù);
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
在上述代碼中,T 是一個(gè)替代類型。其相當(dāng)于類型占位符,在開發(fā)者調(diào)用該接口的時(shí)候會(huì)指定具體類型。
泛型約束
可能會(huì)想限制泛型的類型范圍,這時(shí)候可以使用 extends 關(guān)鍵字:
class Foo<T extends SomeBaseClass> {
// 具體實(shí)現(xiàn)……
String toString() => "'Foo<$T>' 的實(shí)例";
}
class Extender extends SomeBaseClass {...}
//這時(shí)候就可以使用 SomeBaseClass 或者它的子類來作為泛型參數(shù):
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
泛型方法
在方法中,使用泛型
T first<T>(List<T> ts) {
// 處理一些初始化工作或錯(cuò)誤檢測(cè)……
T tmp = ts[0];
// 處理一些額外的檢查……
return tmp;
}
異步
不同于 OC 中 通過 GCD 和 NSOperation 來支持異步編程。
Dart 中 使用 Future 和 await/async 來支持異步編程。
Future
Future 基本使用上類似于 GCD,如下:
void main() {
Future(() => print('立刻在Event queue中運(yùn)行的Future'));
Future.delayed(const Duration(seconds:1), () => print('1秒后在Event queue中運(yùn)行的Future'));
Future.microtask(() => print('在Microtask queue里運(yùn)行的Future'));
Future.sync(() => print('同步運(yùn)行的Future'));
Future(()=> print('task'))
.then((_)=> print('callback1'))
.then((_)=> print('callback2'))
.catchError((error)=>print('$error'))
.whenComplete(()=> print('whenComplete'));
}
//print as follow:
/*
同步運(yùn)行的Future
在Microtask queue里運(yùn)行的Future
立刻在Event queue中運(yùn)行的Future
task
callback1
callback2
whenComplete
1秒后在Event queue中運(yùn)行的Future
*/
其中Dart的事件循環(huán),類似 OC 中的 Runloop, 大概如下:
1、Dart的入口是main函數(shù),所以main函數(shù)中的代碼會(huì)優(yōu)先執(zhí)行;
2、main函數(shù)執(zhí)行完后,會(huì)啟動(dòng)一個(gè)事件循環(huán)(Event Loop)就會(huì)啟動(dòng),啟動(dòng)后開始執(zhí)行隊(duì)列中的任務(wù);
3、首先,會(huì)按照先進(jìn)先出的順序,執(zhí)行 微任務(wù)隊(duì)列(Microtask Queue)中的所有任務(wù);
4、其次,會(huì)按照先進(jìn)先出的順序,執(zhí)行 事件隊(duì)列(Event Queue)中的所有任務(wù);
為了避免 地獄回調(diào) 的問題,dart 又通過引入 await/async 來實(shí)現(xiàn)以同步的代碼風(fēng)格,實(shí)現(xiàn)異步調(diào)用。
await/async
Future<String> getNetworkData() async {
var result = await Future.delayed(Duration(seconds: 3), () {
return "network data";
});
return "請(qǐng)求到的數(shù)據(jù):" + result;
}
main() async {
print('print network data...');
print(await getNetworkData());
}
其他
-
isolatesdart 中獨(dú)有的概念,用于代替線程使用,詳情參見官網(wǎng);