學(xué)Flutter就和學(xué)iOS一樣,先學(xué)基本語言語法的使用,再學(xué)習(xí)搭UI,iOS是OC和Swift,F(xiàn)lutter么就是Dart,語言學(xué)多了語法都差球不多,特別是這些新的語言,很多東西都是相似的,但是長時間不用容易忘記,與其每次去網(wǎng)上瞎jb找,不如自己總結(jié)一些,以后看自己寫的東西就行了。文中個別結(jié)論是我自己總結(jié)出來的,不能保證準(zhǔn)確性,看到的同學(xué)僅供參考。
本章提綱:
1.類和對象
2.構(gòu)造函數(shù)
3.mixin
類的定義
官網(wǎng)對Dart中的類說的很明確了,基本能看到的東西都是class。
- Everything you can place in a variable is an object, and every object is an instance of a class. Even numbers, functions, and
nullare objects. All objects inherit from the Object class.
構(gòu)造函數(shù)
構(gòu)造函數(shù)的名字可以是 ClassName 或者 ClassName.identifier,當(dāng)類中沒有明確指定構(gòu)造方法時,將默認(rèn)擁有一個無參的構(gòu)造方法。這個和Swift中的類一樣,但Swift的結(jié)構(gòu)體會默認(rèn)提供一個帶參的構(gòu)造函數(shù)。從Dart2開始,創(chuàng)建對象時new關(guān)鍵字可以省略。
在類的方法中訪問成員變量可以用this,和OC中的self一樣,但是在沒有同名外部變量時this可以省略。
//定義一個Person類
class Person {
String name;
eat() {
print('$name在吃翔');
}
}
main(List<String> args) {
// 1.創(chuàng)建類的對象
var p = new Person(); // 直接使用Person()也可以創(chuàng)建
// 2.給對象的屬性賦值
p.name = 'tong';
// 3.調(diào)用對象的方法
p.eat();
}
1.Use ?.instead of . to avoid an exception when the leftmost operand is null:
這個用法和Swift中的option是不是很相似,都是更加安全的用法,直接看英文,不翻譯了。
// If p is non-null, set its y value to 4.
p?.y = 4;
2.Dart為最常用的給屬性賦值的構(gòu)造函數(shù)提供了一種便捷的語法糖方式。
Person(String name, int age) {
this.name = name;
this.age = age;
}
// 等同于
Person(this.name, this.age);
命名構(gòu)造函數(shù)
使用命名構(gòu)造函數(shù)可為一個類實現(xiàn)多個構(gòu)造函數(shù), 也可以使用命名構(gòu)造函數(shù)來更清晰的表明函數(shù)意圖,就是一般常用的自定義的構(gòu)造函數(shù)。
class Point {
num x, y;
Point(this.x, this.y);
// 命名構(gòu)造函數(shù)
Point.origin() {
x = 0;
y = 0;
}
}
1.初始化列表
可以在構(gòu)造函數(shù)體執(zhí)行之前初始化實例變量, 各參數(shù)的初始化用逗號分隔。基本語法是在構(gòu)造方法后面加一個(:),后面跟上需要初始化的參數(shù),以逗號分隔。有個注意點(diǎn):在初始化列表中是沒有辦法訪問this的。
// 在構(gòu)造函數(shù)體執(zhí)行之前,
// 通過初始列表設(shè)置實例變量。
Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
初始化列表還有一個場景,就是當(dāng)類的成員變量是final類型的時候,用普通的構(gòu)造函數(shù)是沒法為變量賦值的,這個時候需要用初始化列表的方式。
class Point {
final num x;
final num y;
final num distanceFromOrigin;
Point(x, y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
// Point(this.x, this.y) {
// distanceFromOrigin = sqrt(x * x + y * y);
// }
}
可以使用 assert 來驗證輸入的初始化列表。
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
簡單的分析一下初始化列表這個東西,Dart這個初始化列表應(yīng)該是借鑒了C++的初始化列表,第一次接觸我有點(diǎn)懵,除了感覺初始化常量的時候有點(diǎn)用之外,對于‘’他優(yōu)先于構(gòu)造方法之前執(zhí)行”這個特性還不是很理解,對于這個特性其實讓我想起來了Swift中的兩段式構(gòu)造方式,稍微來回顧一下:
第一個階段,確保當(dāng)前類和父類所有存儲屬性都被初始化。當(dāng)每個存儲型屬性的初始值被確定后,第二階段開始,它給每個類一次機(jī)會,在新實例準(zhǔn)備使用之前進(jìn)一步定制它們的存儲型屬性。
class Man {
var name:String
//指定構(gòu)造方法
init(name:String) {
self.name = name
}
}
class SuperMan: Man {
var age:Int
init(age:Int) {
print("SuperMan第一階段開始")
//對子類引入的屬性初始化
self.age = age
//代碼會報錯, 因為調(diào)用self.name之前還沒有對父類的name進(jìn)行初始化
//即便在這個地方修改, 也會被后面的初始化語句覆蓋
// if age > 30 {
// self.name = "hjq"
// }
//對父類引入的屬性進(jìn)行初始化
super.init(name: "han")
print("SuperMan第二階段開始")
if age > 30 {
self.name = "hello xiaohange"
}
}
}
看了一下C++也有這種類似的兩段式初始化過程:
初始化階段和計算階段,初始化階段先于計算階段。
初始化階段 ,所有類類型(class type)的成員都會在初始化階段初始化,即使該成員沒有出現(xiàn)在構(gòu)造函數(shù)的初始化列表中.
計算階段 ,一般用于執(zhí)行構(gòu)造函數(shù)體內(nèi)的賦值操作。
這么設(shè)計的好處就是可以防止屬性在對象被初始化前訪問,更加安全,說了這么多我覺得初始化列表也是用來在對象初始化之前先初始化一些東西。
另外看其他文章說使用初始化列表還有一個好處主要是性能問題:
對于內(nèi)置類型,如int, float等,使用初始化類表和在構(gòu)造函數(shù)體內(nèi)初始化差別不是很大,但是對于類類型來說,最好使用初始化列表它會比在函數(shù)體內(nèi)初始化派生類成員更快,這是因為在分配內(nèi)存后,在函數(shù)體內(nèi)又多進(jìn)行了一次賦值操作,這對于數(shù)據(jù)密集型的類來說,是非常高效的。
這個好處后期在慢慢體會吧,暫時還理解不了。
構(gòu)造函數(shù)調(diào)用順序
默認(rèn)情況下,子類不會繼承父類的構(gòu)造函數(shù),但會自動調(diào)用父類的默認(rèn)構(gòu)造函數(shù)(匿名,無參數(shù))。 父類的構(gòu)造函數(shù)在子類構(gòu)造函數(shù)體開始執(zhí)行的位置被調(diào)用,如果父類中沒有匿名無參的構(gòu)造函數(shù), 則需要手工在當(dāng)前構(gòu)造函數(shù)冒號 (:) 之后,函數(shù)體之前,聲明調(diào)用父類構(gòu)造函數(shù)。 如果提供了一個 initializer list (初始化參數(shù)列表), 則初始化參數(shù)列表在父類構(gòu)造函數(shù)執(zhí)行之前執(zhí)行。 總之,執(zhí)行順序如下:
- initializer list (初始化參數(shù)列表)
- superclass’s no-arg constructor (父類的無名構(gòu)造函數(shù))
- main class’s no-arg constructor (主類的無名構(gòu)造函數(shù))
通過幾個例子來講的更明白一點(diǎn):
1.父類和子類都只有一個默認(rèn)的無參構(gòu)造函數(shù),子類會默認(rèn)調(diào)用父類的無參構(gòu)造函數(shù)。
class Person {
Person(){
print('default person');
}
}
class Employee extends Person { }
main() {
var emp = Employee();
}
//打印default person
2.父類只有一個默認(rèn)無參構(gòu)造函數(shù),子類有一個命名構(gòu)造函數(shù),子類的命名構(gòu)造函數(shù)會調(diào)用父類的無參構(gòu)造函數(shù)。
class Person {
Person(){
print('default person');
}
}
class Employee extends Person {
Employee.fromJson(Map data) {
print('fromJson Employee');
}
}
main() {
var emp = Employee.fromJson({});
// var emp = Employee(); 調(diào)用會報錯,子類不會繼承父類的構(gòu)造方法
// 打印 default person
// fromJson Employee
}
3.父類只有一個命名構(gòu)造函數(shù),子類有一個默認(rèn)無參構(gòu)造函數(shù),這個時候因為子類要去調(diào)用父類的無參構(gòu)造函數(shù),而父類沒有,所以該場景不管子類用何方式初始化都會報錯。
class Person {
Person.fromJson(Map data) {
print('fromJson Person');
}
}
class Employee extends Person {
Employee();
}
main() {
// var emp = Employee.fromJson({});調(diào)用會報錯,子類不會繼承父類的構(gòu)造方法
// var emp = Employee(); 調(diào)用也會報錯,'Person', has no unnamed constructor
// that takes no arguments.
}
父類必須也提供一個無參構(gòu)造函數(shù)供子類調(diào)用
class Person {
Person(){
print('default person');
}
Person.fromJson(Map data) {
print('fromJson Person');
}
}
class Employee extends Person {
Employee();
}
main() {
// var emp = Employee.fromJson({});調(diào)用會報錯,子類不會繼承父類的構(gòu)造方法
var emp = Employee();
// 打印 default person
}
4.父類只有一個命名構(gòu)造函數(shù),子類也想使用這個父類的命名構(gòu)造函數(shù),這個時候有兩種方式,要么父類提供一個無參構(gòu)造函數(shù)供子類調(diào)用;
class Person {
Person(){
print('default person');
}
Person.fromJson(Map data) {
print('fromJson Person');
}
}
class Employee extends Person {
Employee.fromJson(Map data) {
print('fromJson Employee');
}
}
main() {
var emp = Employee.fromJson({});
// var emp = Employee(); 調(diào)用會報錯
// 打印 default person
// fromJson Employee
}
要么子類在實現(xiàn)這個同名命名構(gòu)造函數(shù)的的時候在初始化列表里顯示調(diào)用父類的命名構(gòu)造函數(shù):
class Person {
// 無參構(gòu)造函數(shù)可有可無
// Person(){
// print('default person');
// }
Person.fromJson(Map data) {
print('fromJson Person');
}
}
class Employee extends Person {
Employee.fromJson(Map data) : super.fromJson(data) {
print('fromJson Employee');
}
}
main() {
var emp = Employee.fromJson({});
// var emp = Employee(); 調(diào)用會報錯
// 打印 fromJson Person
// fromJson Employee
}
排列組合基本就是這幾種情況,以后搞不清了可以回來再看看,估計這塊也可以搞成一道面試題。
常量構(gòu)造函數(shù)
在某些情況下,傳入相同值時,我們希望返回同一個對象,這個時候,可以使用常量構(gòu)造方法。
默認(rèn)情況下,創(chuàng)建對象時,即使傳入相同的參數(shù),創(chuàng)建出來的也不是同一個對象,看下面代碼:
- 這里我們使用identical(對象1, 對象2)函數(shù)來判斷兩個對象是否是同一個對象:
main(List<String> args) {
var p1 = Person('why');
var p2 = Person('why');
print(identical(p1, p2)); // false
}
class Person {
String name;
Person(this.name);
}
如果將構(gòu)造方法前加const進(jìn)行修飾,那么可以保證同一個參數(shù),構(gòu)造兩個相同的編譯時常量會產(chǎn)生一個唯一的,標(biāo)準(zhǔn)的實例:
main(List<String> args) {
var p1 = const Person('why');
var p2 = const Person('why');
print(identical(p1, p2)); // true
}
class Person {
final String name;
const Person(this.name);
}
常量構(gòu)造方法有一些注意點(diǎn):
注意一:擁有常量構(gòu)造方法的類中,所有的成員變量必須是final修飾的。
注意二:如果是將結(jié)果賦值給const修飾的標(biāo)識符時,const可以省略。
重定向構(gòu)造函數(shù)
有時構(gòu)造函數(shù)的唯一目的是重定向到同一個類中的另一個構(gòu)造函數(shù)。 重定向構(gòu)造函數(shù)的函數(shù)體為空, 構(gòu)造函數(shù)的調(diào)用在冒號 : 之后。
class Point {
num x, y;
// 類的主構(gòu)造函數(shù)。
Point(this.x, this.y);
// 指向主構(gòu)造函數(shù)
Point.alongXAxis(num x) : this(x, 0);
}
工廠構(gòu)造函數(shù)
當(dāng)執(zhí)行構(gòu)造函數(shù)并不總是創(chuàng)建這個類的一個新實例時,則使用 factory 關(guān)鍵字。 例如,一個工廠構(gòu)造函數(shù)可能會返回一個 cache 中的實例, 或者可能返回一個子類的實例。后面講到抽象類的時候就會用到工廠構(gòu)造方方法。
main(List<String> args) {
var p1 = Person('why');
var p2 = Person('why');
print(identical(p1, p2)); // true
}
class Person {
String name;
static final Map<String, Person> _cache = <String, Person>{};
factory Person(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final p = Person._internal(name);
_cache[name] = p;
return p;
}
}
Person._internal(this.name);
}
提示: 工廠構(gòu)造函數(shù)無法訪問 this。
抽象類
使用abstract修飾符來定義抽象類 — 抽象類不能實例化,如果希望抽象類能夠被實例化,那么可以通過定義一個 工廠構(gòu)造函數(shù) 來實現(xiàn),抽象類中的抽象方法必須被子類實現(xiàn)。抽象類這個東西在OC里沒有明確的關(guān)鍵字可以標(biāo)明,只能通過人為定義一套規(guī)則來實現(xiàn),這個我感覺是OC比較古老的地方之一。
// 這個類被定義為抽象類,
// 所以不能被實例化。
abstract class AbstractContainer {
// 定義構(gòu)造函數(shù),字段,方法...
void updateChildren(); // 抽象方法。
}
方法
1.setter和getter
在方法名之前加上set和get。
main(List<String> args) {
final d = Dog("黃色");
d.setColor = "黑色";
print(d.getColor);
}
class Dog {
String color;
String get getColor {
return color;
}
set setColor(String color) {
this.color = color;
}
Dog(this.color);
}
2.抽象方法
實現(xiàn)抽象類的抽象方法時加上@override關(guān)鍵字,這個關(guān)鍵字OC也沒有,這樣在編譯器層面就不好保持父類和子類方法名的一致性,比如我父類的方法名改了,子類不知道或者忘了沒改,這樣在編譯期間冇問題,一運(yùn)行就掛了,這個我覺得是OC古老的地方之二。
abstract class Shape {
getArea();
}
class Circle extends Shape {
double r;
Circle(this.r);
@override
getArea() {
return r * r * 3.14;
}
}
3.noSuchMethod()
當(dāng)代碼嘗試使用不存在的方法或?qū)嵗兞繒r, 通過重寫 noSuchMethod() 方法,來實現(xiàn)檢測和應(yīng)對處理。這貨還有幾個特殊的使用場景,先不寫了,后期實戰(zhàn)的時候遇到了再補(bǔ)充。
class A {
// 如果不重寫 noSuchMethod,訪問
// 不存在的實例變量時會導(dǎo)致 NoSuchMethodError 錯誤。
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: ' +
'${invocation.memberName}');
}
}
隱式接口
Dart中的接口比較特殊, 沒有一個專門的關(guān)鍵字來聲明接口。
默認(rèn)情況下,定義的每個類都相當(dāng)于默認(rèn)也聲明了一個接口,可以由其他的類來實現(xiàn)(因為Dart不支持多繼承),實現(xiàn)這個接口就可以使用類中的屬性和方法。
abstract class Runner {
eat();
}
abstract class Flyer {
la();
}
class SuperMan implements Runner, Flyer {
@override
eat() {
print('超人在吃翔');
}
@override
la() {
print('超人在拉翔');
}
}
為類添加功能: Mixin
某些情況下,一個類可能希望直接使用已有類的原有實現(xiàn)方案,怎么做呢?,在通過implements實現(xiàn)某個類時,類中所有的方法都必須被重新實現(xiàn),這個其實相當(dāng)于實現(xiàn)了一個接口,接口的方法必須實現(xiàn)。使用繼承嗎?但是Dart只支持單繼承,那么意味著你只能使用一個類的實現(xiàn)。Dart提供了另外一種方案: Mixin混入的方式,通過創(chuàng)建一個繼承自 Object 且沒有構(gòu)造函數(shù)的類,來實現(xiàn) 一個 Mixin,如果 Mixin 不希望作為常規(guī)類被使用,使用關(guān)鍵字 mixin 替換 class 。 例如:
mixin Runner {
run() {
print('在奔跑');
}
}
mixin Flyer {
fly() {
print('在飛翔');
}
}
// implements的方式要求必須對其中的方法進(jìn)行重新實現(xiàn)
// class SuperMan implements Runner, Flyer {}
class SuperMain with Runner, Flyer {
}
main(List<String> args) {
var superMan = SuperMain();
superMan.run();
superMan.fly();
}
指定只有某些類型可以使用的 Mixin - 比如,Mixin 可以調(diào)用 Mixin 自身沒有定義的方法 - 使用 on 來指定可以使用 Mixin 的父類類型:
mixin MusicalPerformer on Musician {
// ···
}
類變量和方法
使用 static 關(guān)鍵字實現(xiàn)類范圍的變量和方法。這個和其他語言基本一致,就是OC比較特別,有的時候發(fā)現(xiàn)其實第一門語言學(xué)OC后面學(xué)其他語言的時候不如第一門語言學(xué)別的語言來的快,OC很多地方確實比較獨(dú)特,沒有通用性。
class Queue {
//靜態(tài)變量
static const initialCapacity = 16;
//靜態(tài)方法
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() {
assert(Queue.initialCapacity == 16);
}