版權聲明:本文為博主原創文章,未經博主允許不得轉載。http://www.lxweimin.com/p/44ae73a58ebc
轉載請標明出處:
http://www.lxweimin.com/p/44ae73a58ebc
本文出自 AWeiLoveAndroid的博客
本文首發在公眾號Flutter那些事,未經允許,嚴禁轉載。
前言
Flutter1.0穩定版昨晚的終于發布了。我們為此感到高興。對于開發者來說,有了穩定版相當于一個定心丸。本文主要介紹Fllutter1.0的一些功能和相關工具。
Flutter系列博文鏈接 ↓:
工具安裝:
Flutter基礎篇:
- 谷歌Flutter1.0正式版發布
- Flutter基礎篇(1)-- 跨平臺開發框架和工具集錦
- Flutter基礎篇(2)-- 老司機用一篇博客帶你快速熟悉Dart語法
- Flutter基礎篇(3)-- Flutter基礎全面詳解
- Flutter基礎篇(4)-- Flutter填坑全面總結
- Flutter基礎篇(5)-- Flutter代碼模板,解放雙手,提高開發效率必備
- Flutter基礎篇(6)-- 水平和垂直布局詳解
- Flutter基礎篇(7)-- Flutter更新錯誤全面解決方案(圖文+視頻講解)
- Flutter基礎篇(8)-- Flutter for Web詳細介紹
- Flutter基礎篇(9)-- 手把手教你用Flutter實現Web頁面編寫
- Flutter1.9升級體驗總結(Flutter Web 1.9最新版本填坑指南)
Flutter進階篇:
Dart語法系列博文鏈接 ↓:
Dart語法基礎篇:
Dart語法進階篇:
本文代碼同步發布在Github: https://github.com/AweiLoveAndroid/Flutter-learning/tree/master/projects/dart_demo
上一篇主要講了數據類型、運算符、流程語句等,由于文字太多,我就把剩下的內容分開寫一篇文章。
這一篇我們講Dart的類與函數,內容較多,希望大家可以耐心看完。我也是花了很長時間研究的。喜歡的就點個贊,打個賞吧。感謝大家支持。
八、Dart的類與函數
Dart是一種面向對象的語言,具有類和基于mixin的繼承。每個對象都是一個類的實例,所有類都來自
Object
。 基于Mixin
的繼承意味著雖然每個類(除了Object
)只有一個超類,但是類體可以在多個類層次結構中重用。
(一)類的分類
(1)普通類
1).Dart使用class
關鍵字表示一個類,對象具有由函數和數據(分別為方法和實例變量)組成的成員。
我們通過new關鍵字來創建一個類對象,然后使用(.)調用類里面的變量或者普通(實例,非靜態)函數(注:函數和方法意思是一樣的。),以便訪問該對象的函數和數據。例如類Test里面有一個普通函數tests(),我們可以使用new Test().tests();來調用這個tests()函數,在Dart2里面創建對象時可以省略new關鍵字。
例如:
class Test{
void tests(){}
}
void main(){
new Test().tests();
}
2).要在運行時獲取對象的類型,可以使用Object類的runtimeType
屬性,該屬性返回一個Type對象。
如果沒有指明具體類型,Dart具有類型推斷機制,會自動推斷變量類型。
示例如下:
var a = 10;
var b = 10.0;
var c = '10';
var d = true;
var e = [12.5,13.1];
var f = {3:'5',5:'11'};
var t = new Test();// 這里就直接使用上面那個Test類 不再去重復寫這個類了
print('a 的類型是: ${a.runtimeType}'); // a 的類型是: int
print('b 的類型是: ${b.runtimeType}'); // b 的類型是: double
print('c 的類型是: ${c.runtimeType}'); // c 的類型是: String
print('d 的類型是: ${d.runtimeType}'); // d 的類型是: bool
print('e 的類型是: ${e.runtimeType}'); // e 的類型是: List<double>
print('f 的類型是: ${f.runtimeType}'); // f 的類型是: _InternalLinkedHashMap<int, String>
print('t 的類型是: ${t.runtimeType}'); // t 的類型是: Test
class Test{}
3). Dart和Java一樣,使用extends關鍵字,表示一個類繼承另一個類。
使用@override
注解聲明你要重寫的函數,在這個函數內部可以使用super調用重寫的這個父類的函數。
實例如下:
class Test {
void test() {/*這里省略方法內部的邏輯操作*/}
// 其他邏輯
}
class TestChild extends Test {
@override //@override標注在test()函數上面 表示test()函數是重寫父類的。
void test() {
super.test();// 調用父類的test()函數
/*這里省略方法內部的邏輯操作*/
}
// 其他邏輯
}
@override
是元數據。元數據注解以字符開頭@,后跟對編譯時常量(如deprecated
)的引用或對常量構造函數的調用。元數據可以出現在庫,類,typedef,類型參數,構造函數,工廠,函數,字段,參數或變量聲明之前以及導入或導出指令之前。您可以使用反射在運行時檢索元數據。
所有Dart代碼都有兩個注解:@deprecated
和 @override
。
以下是使用@deprecated 注解的示例:
class Television {
/// _Deprecated: Use [turnOn] instead._
@deprecated
void activate() {
turnOn();
}
// Turns the TV's power on.
void turnOn() {
//...
}
}
您可以定義自己的元數據注釋。
這是一個定義帶有兩個參數的@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');
}
4).靜態變量
使用static
關鍵字修飾的類范圍的變量。
靜態變量(類變量)對于類范圍的狀態和常量很有用。靜態變量在使用之前不會初始化。
class Test {
static const num = 16;
// ···
}
void main() {
print(Test. num); // 16
}
5).重寫操作符
您可以重寫下表中顯示的運算符。
操作符名稱 | |||
---|---|---|---|
< | + | | | [] |
> | / | ^ | []= |
<= | ~/ | & | ~ |
>= | * | << | == |
- | % | >> |
注意:!=
不是可重寫的運算符。表達式e1 != e2
是!(e1==e2)
的語法糖。
以下是一個重寫+和-運算符的類的示例:
void main(){
final a = Testoperator(2, 3);
final b = Testoperator(2, 2);
var num1 = Testoperator(4, 5);
var num2= Testoperator(0,1);
print(a + b == num1); // true
print(a - b == num2); // true
}
class Testoperator {
final int x, y;
Testoperator(this.x, this.y);
Testoperator operator +(Testoperator o) => Testoperator(x + o.x, y + o.y);
Testoperator operator -(Testoperator o) => Testoperator(x - o.x, y - o.y);
// Override hashCode using strategy from Effective Java, Chapter 11.
@override
int get hashCode {
int result = 17;
result = 37 * result + x.hashCode;
result = 37 * result + y.hashCode;
return result;
}
// 如果重寫了 hashCode,應該重寫==操作符。
@override
bool operator ==(dynamic other) {
if (other is! Testoperator) return false;
Testoperator person = other;
return (person.x == x &&
person.y == y);
}
}
6).noSuchMethod()
要在代碼嘗試使用不存在的方法或實例變量時檢測或做出反應,您可以重寫noSuchMethod()
void main() {
TestMethod test = new TestMethod();
dynamic f = test.foo;
// Invokes `Object.noSuchMethod`, not `TestMethod.noSuchMethod`, so it throws.
f(42);
}
class TestMethod {
// 除非你重寫noSuchMethod,否則使用不存在的成員會導致NoSuchMethodError
// Unless you override noSuchMethod, using a
// non-existent member results in a NoSuchMethodError.
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: ' +
'${invocation.memberName}');
}
dynamic foo();
}
你不能調用未實現的方法,除非以下的某一條是true:
- 1.接收處有靜態類型dynamic
- 2.接收處定義了一個未實現的方法(abstract也是OK的)的靜態類型dynamic,接收器的動態類型的實現與類
noSuchMethod()
中的實現不同Object。
有關更多信息,請參閱非正式noSuchMethod
轉發規范
(2)抽象類
1).使用abstract修飾符定義抽象類(無法實例化的類)。抽象類對于定義接口非常有用,通常還有一些實現。如果希望抽象類看起來是可實例化的,請定義工廠構造函數。
抽象類通常有抽象方法。這是一個聲明具有抽象方法的抽象類的示例:
// 此類聲明為abstract,因此無法實例化
abstract class Test {
//定義構造函數,字段,方法...
// 抽象方法
void test();
}
2).隱式接口
每個類都隱式定義一個接口,該接口包含該類的所有實例成員及其實現的任何接口。如果要在不繼承B實現的情況下創建支持B類API的A類,則A類應實現B接口。
一個類通過在implements子句中聲明它們然后提供接口所需的API來實現一個或多個接口。
例如:
void main() {
print(sayHello(Person('李四'))); // 你好 張三. 我是 李四.
print(sayHello(PersonImpl())); // 你好 張三 你知道我是誰嗎?
}
// Person類 隱式接口包含hello()
class Person {
// 在接口中,但是僅在此庫中可見。
final _name;
// 不在接口中,因為這是一個構造函數
Person(this._name);
// 在接口中
String hello(String who) => '你好 $who. 我是 $_name.';
}
// Person接口的實現
class PersonImpl implements Person {
get _name => '';
String hello(String name) => '你好 $name 你知道我是誰嗎?';
}
String sayHello(Person person) => person.hello('張三');
一個類也可以實現多個接口,例如:
class ZhangSan implements Run,Life {
//...
}
class Run {}
class Life {}
(3)可調用的類(Callable Class)
更多詳情可以查看Dart官網: 在Dart中模擬函數
要允許像函數一樣調用Dart類,請實現該call()
方法。
在下面的示例中,Test類定義了一個call()
方法,它接受三個字符串并連接它們,用空格分隔每個字符串,并附加一個感嘆號。
void main() {
var test = new Test();
var result = test(166.6665,"Flutter真好玩",672);
print("$result");// 666.666 Flutter真好玩 666
}
class Test {
// 必須是call函數
call(double a, String b, int c) => '${a*4} ${b} ${c-6}';
}
(4)枚舉類型
1.使用enum關鍵字聲明枚舉類型:
例如:enum Color { red, green, blue }
2). 枚舉中的每個值都有一個index getter,它返回枚舉聲明中值的從零開始的位置。例如,第一個值具有索引0,第二個值具有索引1。
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
3).要獲取枚舉中所有值的列表,請使用枚舉values常量。
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);
4).您可以在switch語句中使用枚舉,如果您不處理所有枚舉值,您將收到警告。
var aColor = Color.blue;
switch (aColor) {
case Color.red:
print('Red');
break;
case Color.green:
print('Green');
break;
default: // 你沒有這個 你會看到一個警告
print(aColor); // 'Color.blue'
}
5).枚舉類型的限制條件:
- 1.你不能在子類,
mixin
或者實現枚舉。 - 2.你不能顯式實例化枚舉。
(5)mixin
更多關于mixin
的資料,可以查看 Dart2.1 mixin規范
Mixins是一種在多個類層次結構中重用類代碼的方法。
1).要實現 mixin,請創建一個擴展Object的類,并且不聲明構造函數。除非您希望mixin可用作常規類,否則請使用mixin關鍵字而不是class。
例如:
// 聲明mixin
// 專家
mixin Expert {
// 發現和解決難題
bool solveProblems = false;
// 精通數據結構和算法
bool dataStructureAndAlgorithms = false;
// 會架構設計
bool architectureDesign = false;
// 性能優化
bool performanceOptimization = false;
// 熟練掌握計算機系統
bool computerSystem = false;
void develop() {
// 娛樂節目
if (solveProblems) {
print('發現和解決難題');
}
if (dataStructureAndAlgorithms) {
print('精通數據結構和算法');
}
if (architectureDesign) {
print('會架構設計');
}
if (performanceOptimization) {
print('熟練掌握性能優化');
}
if (computerSystem) {
print('熟練掌握計算機系統');
}
}
}
// 特性
// 有效率的
mixin Efficient {
void getEfficientAttrs() {
print('有效率的');
}
}
// 和藹的
mixin Kind {
void getKindAttrs() {
print('和藹的');
}
}
// 軟件架構師
class SoftwareArchitect {
SoftwareArchitect() {
print('軟件架構師');
}
}
2).要使用 mixin,請使用with關鍵字后跟一個或多個mixin名稱。以下示例顯示了兩個使用mixins的類。
void main() {
// 姓名:張三
// 發現和解決難題
// 精通數據結構和算法
// 會架構設計
ACompanySoftwareArchitect architect1 = new ACompanySoftwareArchitect('張三');
architect1.develop();
print('====');
// 姓名:李四
// 發現和解決難題
// 精通數據結構和算法
// 會架構設計
// 熟練掌握性能優化
// 熟練掌握計算機系統
// 有效率的
// 和藹的
BCompanySoftwareArchitect architect2 = new BCompanySoftwareArchitect('李四');
architect2.develop();
architect2.getEfficientAttrs();
architect2.getKindAttrs();
}
// 使用mixin
// A公司的軟件架構師,繼承自軟件架構師,擁有專家的特性。
class ACompanySoftwareArchitect extends SoftwareArchitect with Expert {
String name;
ACompanySoftwareArchitect(String name) {
this.name = name;
print('姓名:' + name);
solveProblems = true;
dataStructureAndAlgorithms = true;
architectureDesign = true;
}
@override
void develop() {
super.develop();
}
}
// B公司的軟件架構師,繼承自軟件架構師,
class BCompanySoftwareArchitect extends SoftwareArchitect
with Expert, Efficient, Kind {
String name;
BCompanySoftwareArchitect(String name) {
this.name = name;
print('姓名:' + name);
solveProblems = true;
dataStructureAndAlgorithms = true;
architectureDesign = true;
performanceOptimization = true;
computerSystem = true;
}
}
3).要指定只有某些類型可以使用mixin。例如,所以你的mixin可以調用它沒有定義的方法, 用于on指定所需的超類。
mixin SoftwareDeveloper on ACompanySoftwareArchitect{}
(二)泛型
如果您查看基本數組類型的API文檔 List,您會看到該類型實際上是
List<E>
。<...>
表示法將List標記為 泛型(或參數化)類型 - 具有正式類型參數的類型。按照慣例,大多數類型變量都有單字母名稱,例如E,
T,
S,
K和
V`.
(1)為什么使用泛型?
類型安全通常需要泛型,但它們比僅允許代碼運行有更多好處:
1).正確指定泛型類型可以生成更好的代碼。
如果您希望列表只包含字符串,則可以將其聲明為List<String>(將其讀作“字符串列表”)。這樣一來,工具可以檢測到將非字符串分配給列表可能是一個錯誤。
例子:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
// 報錯 The argument type 'int' can't be assigned to the parameter type 'String'.
names.add(42);
2).您可以使用泛型來減少代碼重復。
泛型允許您在多種類型之間共享單個接口和實現,同時仍然利用靜態分析。
例如:創建了一個用于緩存對象的接口:
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);
}
如果還有其他更改,就要寫很多接口。
class Test1 extends ObjectCache {
@override
Object getByKey(String key) {
return 'object cache';
}
@override
void setByKey(String key, Object value) {
return null;
}
}
class Test2 extends StringCache {
@override
String getByKey(String key) {
return 'String cache';
}
@override
void setByKey(String key, String value) {
return null;
}
}
泛型可以省去創建所有這些接口的麻煩。你可以創建一個帶有類型參數的接口。
示例如下:T是一個占位符,您可以將其視為開發人員稍后定義的類型。
abstract class Cache<T> {
T getByKey(String key);
}
使用泛型以前的調用方式
print(new Test1().getByKey('123'));
print(new Test2().getByKey('456'));
(2)使用集合文字
list
和map
文字可以參數化。參數化文字就像你已經看到的文字一樣,除了你在開始括號之前添加 <type>
(對于list)或 <keyType, valueType>
(對于map)。
以下是使用類型文字(typed literals)的示例:
var numbers = <String>['11', '22', '33'];
var pages = <String, String>{
'index.html': 'Homepage',
'store.html': 'Store',
'mine.html': 'Mine'
};
(3)使用帶有構造函數的參數化類型
要在使用構造函數時指定一個或多個類型,請將類型放在類名稱后面的尖括號<...>中。
例如:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = Set<String>.from(names);
以下代碼創建一個具有整數的key和View類型的value的map:
var views = Map<int, View>();
(4)泛型集合及其包含的類型
Dart的泛型類型是具體的。也就說,它們在運行時會會攜帶類型信息。
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
print(names.runtimeType); // List<String>
注意:相反,Java中的泛型使用擦除,這意味著在運行時刪除泛型類型參數。在Java中,您可以測試對象是否為List,但您無法測試它是否是List<String>
。
(5)限制參數類型
實現泛型類型時,您可能希望限制其參數的類型。你可以在<>里面使用extends。
例如:
abstract class SomeBaseClass {
// 其他操作
}
class Foo<T extends SomeBaseClass> {
String toString() {
return "Instance of Foo<$T>";
}
}
class Extender extends SomeBaseClass {
//其他操作
}
現在可以使用SomeBaseClass
或它的任何子類作為泛型參數。
例如:
void main() {
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
print(someBaseClassFoo.toString());// Instance of Foo<SomeBaseClass>
print(extenderFoo.toString());// Instance of Foo<SomeBaseClass>
}
也可以不指定泛型參數。
例如:
var foo = Foo();
//等同于print(foo.toString());
print(foo);// Instance of Foo<SomeBaseClass>
如果指定任何非SomeBaseClass類型會導致錯誤。
例如:var foo = Foo<Object>;
(6)使用泛型方法
新版本的Dart的泛型方法,允許在方法和函數上使用類型參數。(但它同樣適用于實例方法,靜態方法,頂級函數,本地函數甚至lambda表達式。)
例如:
T first<T>(List<T> data) {
// 做一些初始工作或錯誤檢查...
T tmp = data[0];
// 做一些額外的檢查或處理...
return tmp;
}
在first(<T>)上的的泛型類型參數,允許你在以下幾個地方使用類型參數T:
- 1). 在函數的返回類型(T)中
- 2). 在參數類型(List<T>)中
- 3). 在局部變量的類型(T tmp)
泛型方法可以聲明類方法(實例和靜態)以相同的方式獲取泛型參數。
class Test {
static int f<M, N>(int x) => 3;
int m<M, N>(int y) => 5;
}
泛型方法也適用于函數類型參數,本地函數和函數表達式。
// 作為參數
void functionTypedParameter<T>(T callback){}
// 聲明一個本地泛型函數本身
。
void localFunction(){
T itself<T>(T thing) => thing;
}
// 將泛型函數表達式綁定到局部變量。
void functionExpression(){
var lambda = <T>(T thing) => thing;
}
使用泛型方法:
void main() {
List<String> data = ["張三","李四","王五"];
var result = first(data);
print(result);
}
(三)函數
(1)普通函數
Dart是一種真正的面向對象的語言,所以即使是函數也是對象,函數屬于Function
類型。可以通過函數指定變量或者把函數作為其他函數的參數。
1)函數的簡寫。
1.對于只有一個表達式的函數,可以簡寫。
例如flutter新建工程里面的main.dart
,找到里面的runApp
函數,可以使用 =>
這樣的箭頭函數去操作,如下所示:
操作前:
void main(){
runApp(new MyApp());
}
操作后:(main.dart文件里面默認使用的是==>箭頭函數)
void main() => runApp(new MyApp());
【注意:】main
函數是程序的入口,不管是純Dart代碼,還是Flutter項目,或者其他語言,基本都是main
函數是入口函數。
2.返回值為void
時,可以省略void
關鍵字(開發中不建議這么做)。
函數的返回值可以是void
,也可以是null
,也可以是具體對象
。如果沒有指定返回值,則該函數返回的是null
。例如flutter新建工程里面的main.dart
, _incrementCounter()
函數,可以省略關鍵字void
,如下所示:
操作前:
void _incrementCounter(){
//...
}
操作后:
_incrementCounter(){
//...
}
我們使用assert(_incrementCounter()==null);
測試一下,發現程序運行正常,可以看出該函數返回值為null
【注意】函數屬于Function類型,可以通過斷言assert(XXX is Funtion);判斷出結果,返回值必須是具體類型或者省略,如果返回值寫為void,編譯器有錯誤提示。
舉例如下:
void testMethod (){
//...
}
例如我們:assert(testMethod () is Function);
//這時候編譯器會報錯。
2)普通參數與可選參數
Dart的函數最好玩的就是這個可選參數了,就是可以聲明多個參數,使用時可以調用其中的某一個或者多個參數,與參數位置無關。
1.可選參數的基本使用
可選參數的定義方式:{參數1,參數2,,...}
,使用方式:函數名(paramName1: value1, paramName2: value2, paramName3: value3...);
下面我們來看一個簡單的示例對比一下普通函數和可選參數:
操作前:
// 工作:地址、公司名、工資、工作時長、公司人數
void work(
String address,
String cpompany,
double money,
String workTime,
int workerNumbers) {
//TODO:...
}
使用:
void main() {
// 缺一個參數都會報錯
work('hangzhou','XXCompany',1000000.00,'9:00-5:00',500);
}
操作后:
void work2({
String address,
String cpompany,
double money,
String workTime,
int workerNumbers}) {
//TODO:...
}
使用:
void main() {
//你隨意使用其中的參數都是可以的,例如我使用了其中的參數1,參數4和參數5
work2(address:'hangzhou', workTime:'9:00-5:00', workerNumbers:500);
}
2.可選參數默認的值
可以使用 =
為任意的可選參數設置默認值,默認值必須是編譯時常量,如果沒有提供默認值,則默認值為null。
例如下例就是給參數1和參數2設置了默認值:
void work3({
String address = 'hangzhou',
String cpompany = ' XXCompany ',
double money,
String workTime,
int workerNumbers}) {
//TODO:...
}
3.普通函數參數為list或者map的默認值
如果普通函數的參數是一個匿名List集合(也叫數組)
,也可以使用 =
設置默認值,數組不能被包含在可選參數里面。
例如:
void work4(
String address,
[String cpompany = 'XXCompany',
double money,
String workTime,
int workerNumbers]) {
//TODO:...
}
4.可變參數為list或者map的默認值
可變參數可以是顯示聲明的List集合或者map
,但是list或者map的值比如是const
修飾。
舉例如下:
void work5({
List<int> list = const [10, 20, 30],
Map<String, String> gifts = const {
'cpompany':'XXCompany',
'money':'50000',
'workTime':'9:00-5:00',
}}) {
//TODO:...
}
3)函數作為一個參數傳給另一個函數
這個就類似于java里面的回調功能。例如flutter新建工程里面的main.dart
,我們看看這段代碼就知道了:
// 函數作為參數傳給另一個函數
void main() {
// 例如main.dart里面FloatingActionButton的onPressed參數引入了一個_incrementCounter()函數
// floatingActionButton: new FloatingActionButton(onPressed: _incrementCounter,),
}
void _incrementCounter() {
// setState(() {
// _counter++;
// });
}
4)匿名函數
我們還是以flutter新建工程里面的main.dart
為例,我們看看這里的setState
函數,這里面的參數是一個(){}
。小括號里面沒有參數,我們去看看setState
源碼你會發現它的參數是一個Function
,這里沒有傳入任何參數。這里面其實就是一種匿名函數的用法。
void _incrementCounter() {
setState(() {
_counter++;
});
}
再比如常見的list.foreach用法也會用到匿名函數,例如下例中的forEach()我們這里寫的是無類型參數的匿名函數item,forEach
源碼是:forEach (void f(E element))
,它的參數是一個函數。
List list = [10, 7, 23];
list.forEach((item) {
print('$item');
});
以上語句可以簡寫成:list.forEach((item) => print('$item'));
5)函數作用域
Dart是一種具有語法范圍的語言,變量的范圍是靜態確定的,只需通過代碼布局來確定。通過花括號向外查看,可以確定變量是否在范圍內。
以下是一個嵌套函數的例子,每個作用域級別上都有變量,變量作用域為函數內部,外部無法訪問。我們可以看看日志就清楚了:
// main函數里面可以輸出topLevel和insideMain的值。
// myFunction函數里面可以輸出topLevel、insideMain和insideFunction的值。
// nestedFunction函數里面可以輸出topLevel、insideMain、insideFunction和insideNestedFunction的值。
bool topLevel = true;
void main() {
var insideMain = true;
void myFunction() {
var insideFunction = true;
void nestedFunction() {
var insideNestedFunction = true;
print('topLevel\r');
print(topLevel);
print('insideMain\r');
print(insideMain);
print('insideFunction\r');
print(insideFunction);
print('insideNestedFunction\r');
print(insideNestedFunction);
}
// print('topLevel\r');
// print(topLevel);
// print('insideMain\r');
// print(insideMain);
// print('insideFunction\r');
// print(insideFunction);
// 調用函數nestedFunction
nestedFunction();
}
// 調用函數myFunction
myFunction();
// print('topLevel\r');
// print(topLevel);
// print('insideMain\r');
// print(insideMain);
}
6)閉包
當函數定義和函數表達式位于另一個函數的函數體內。而且這些內部函數可以訪問它們所在的外部函數中聲明的所有局部變量、參數和聲明的其他內部函數。當其中一個這樣的內部函數在包含它們的外部函數之外被調用時,就會形成閉包。
- a. 內部函數為有參數的匿名函數示例:
void main() {
var result = test();
print(result(2.0));//結果為:12.56
}
Function test(){
const PI = 3.14;
return (double r) => r * r * PI;
}
- b. 內部函數為無參數的匿名函數示例:
void main() {
var result2 = test2();
print(result2());//結果為:3.14
}
Function test2() {
const PI = 3.14;
return () => PI;
}
7)等價函數
//函數也是對象
void topMethod() {} // 一個頂級函數
class Demo {
static void staticMethod() {} //一個靜態方法
void caseMethod() {} //實例方法
}
void main() {
var compareVar;
// 比較頂級的函數
compareVar = topMethod;
print(topMethod == compareVar);
// 比較靜態方法
compareVar = Demo.staticMethod;
print(Demo.staticMethod == compareVar);
// 比較實例方法
var demo1 = Demo(); // Demo類的實例1
var demo2 = Demo(); // Demo類的實例2
var y = demo2;
compareVar = demo2.caseMethod;
//這些閉包指向同一個實例demo2,所以它們相等。
print(y.caseMethod == compareVar);
//這些閉包是指不同的實例,所以他們不平等。
print(demo1.caseMethod != demo2.caseMethod);
}
8)函數別名
在Dart中,函數是對象,就像字符串一樣,數字是對象。一個類型定義,或功能型的別名,給出了一個函數類型聲明字段時,您可以使用和返回類型的名稱。當函數類型分配給變量時,typedef
會保留類型信息。
以下代碼,它不使用typedef
:我們可以看到funs是一個函數,但它是哪一種類型的函數?不是很清楚。
class Demo {
Function funs;
Demo (int f(Object a, Object b)) {
funs = f;
}
}
int test(Object a, Object b) => 0;
void main() {
Demo demo = Demo(test);
// funs是一個函數,但它是哪一種類型的函數?
print(demo.funs is Function); // true
}
可以使用typedef
給函數取個別名,這一點讓我想起了C語言里面的函數指針。接下來使用typedef
改造一下,我們將代碼更改為使用顯式名稱并保留類型信息,開發人員和工具都可以使用該信息。
- 給Function取一個別名叫做
TypedefFuns
typedef TypedefFuns = int Function(Object a, Object b);
- Demo類里的構造方法使用這個別名
class Demo {
TypedefFuns funs;
Demo(this.funs);
}
- 使用Demo類,傳入一個函數。這里給Demo類傳入了一個函數
test
。如果想知道然后判斷demo.funs
屬于哪一種類型。
int test(Object a, Object b) => 0;
void main() {
Demo demo = Demo(test);
print(demo.funs is Function); // true
print(demo.funs is Demo); // false
}
目前:typedef
僅限于函數類型。
因為typedef
只是別名,Dart提供了一種檢查任何函數類型的方法。
例如:
typedef TypedefFuns2<T> = int Function(T a, T b);
int test2(int a, int b) => a - b;
void main() {
print(test2 is TypedefFuns2<int>); // True
}
9)getter 和 setter
getter
和setter
是提供對象屬性的讀寫訪問權限的特殊方法。所有實例變量都生成一個隱式getter方法。非final實例變量也會生成隱式setter方法。使用get和set關鍵字通過實現getter和setter來創建其他屬性。
- 1.使用getter和setter,可以從實例變量開始。
例如:
void main() {
var rect = Rectangle(3, 4, 20, 15);
print(rect.left == 3); // true
rect.right = 12;
print(rect.left == -8); // true
}
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;
}
注意: 無論是否明確定義了getter,增量(++)等運算符都以預期的方式工作。為避免任何意外的副作用,只需調用一次getter,將其值保存在臨時變量中。
- 2.實例變量的隱式getter和setter方法
所有實例變量都生成一個隱式getter方法。非final實例變量也會生成隱式setter方法。
例如:
class Point {
num x;
num y;
}
void main() {
var point = Point();
point.x = 4; // Use the setter method for x.
assert(point.x == 4); // Use the getter method for x.
assert(point.y == null); // Values default to null.
}
如果初始化聲明它的實例變量(而不是構造函數或方法),則在創建實例時設置該值,該實例在構造函數及其初始化列表執行之前。
例如:
class Point2 {
num x = 10;
num y = 5;
Point2 p = new Point2();//p在構造函數之前執行
Point2(){}
}
void main() {
var point2 = Point2();
point2.x = 4; //
}
10)抽象方法
實例,getter
和setter
方法可以是抽象的,定義一個接口,但將其實現留給其他類。抽象方法只能存在于抽象類中。要使方法抽象,請使用分號(;
)而不是方法體。
abstract class Test {
//定義實例變量和方法...
//定義一個抽象方法
void doSomething();
}
class TestImpl extends Test {
// 抽象方法的實現
void doSomething(){
// 具體的實現...
}
}
11)靜態方法
靜態方法:使用static
關鍵字修飾的方法,也叫類方法,它不對實例進行操作,因此無權訪問this
。
void main() {
print(Point.area(5, 4));// 10
}
class Point {
static num area(num x, num y) {
return (x * y)/2;
}
}
注意:對于常用或廣泛使用的實用程序和功能,請考慮使用頂級函數而不是靜態方法。您可以使用靜態方法作為編譯時常量。例如,您可以將靜態方法作為參數傳遞給常量構造函數。
(2)構造函數
通過創建一個與其類同名的函數來聲明構造函數(另外,還有一個額外的標識符,如命名構造函數中所述)。
1).最常見的構造函數形式,即生成構造函數,創建一個類的新實例。
例如:
class Test {
int x, y;
Test(int x, int y) {
this.x = x;//this.x指向的是當前Test類里面的變量int x
this.y = y;
}
}
void main(){
// 使用Test構造函數創建Test對象
var test = new Test(5, 15);
}
2).Dart具有語法糖,可以將構造函數參數賦值給實例變量。
class Test {
num x, y;
// 構造函數運行之前設置x和y
Test(this.x, this.y) {
print('x:$x, y:$y');
}
}
如果沒有內容體,可以使用簡寫形式,示例如下:
class Test {
num x, y;
// 構造函數運行之前設置x和y
Test(this.x, this.y);
}
3).默認構造函數(空參構造)
如果您未聲明構造函數,則會為您提供默認構造函數。默認構造函數沒有參數,并在超類中調用無參數構造函數。
注意:如果定義了空參構造,再去寫實參構造,會報錯(這一點和java不一樣)。
class Test{
// 如果不寫 默認就是空參構造
Test(){}
}
4).命名構造函數
Java可以做到方法重載(也就是:多個同名不同參數構造函數),但是Dart不可以這么做。Dart提供了命名構造。使用命名構造函數為類實現多個構造函數或提供更多的解釋說明。
例如:
class Test{
num x, y;
// 命名構造
Test.help() {
x = 5;
y = 10;
print('x=${x}, y = ${y}');
}
}
void main(){
Test.help();// 調用命名構造函數
}
構造函數不是繼承的,也就是說超類的命名構造函數不會被子類繼承。如果希望使用超類中定義的命名構造函數創建子類,則必須在子類中實現該構造函數。
例如:Test
類里面有一個Test.help()
命名構造,它的子類是TestChild
,如果使用new TestChild.help()
就會報錯,因為構造函數不能繼承。只有在TestChild
類里面寫一個TestChild.help()
命名構造函數,才可以使用該命名構造。
示例如下:
void main() {
// 先執行Test類的空參構造, 再執行TestChild類的空參構造。
new TestChild();
// 調用時會報錯
//new TestChild.help();
}
class Test{
var x, y;
Test(){
print('這是 Test 類的空參構造');
}
// 命名構造不能被繼承
Test.help(){
x = 5;
y = 10;
print('Test.help() 命名函數 x=${x}, y = ${y}');
}
}
class TestChild extends Test{
var x, y;
TestChild(){
print('這是 TestChild 類的空參構造');
}
// 加上與父類相同的命名構造就不會錯 注釋了就會報錯
// TestChild.help(){
// x = 3;
// y = 2;
// print('TestChild.help() 命名函數 x=${x}, y = ${y}');
// }
}
5).構造函數不是繼承的
子類不從其超類繼承構造函數。聲明沒有構造函數的子類只有默認(無參數,無名稱)構造函數。
6).構造函數調用流程
默認情況下,子類中的構造函數調用超類的無參構造函數。超類的構造函數在構造函數體的開頭被調用。如果 還使用初始化列表,則在調用超類之前執行。
執行順序如下:
初始化列表 -> 超類的無參數構造函數 -> 主類的無參數構造函數
,
超類必須要有一個空參構造,如果超類沒有未命名的無參數構造函數,則必須手動調用超類中的一個構造函數。在冒號(:
)之后,在構造函數體(如果有)之前指定超類構造函數。
例如下面的示例:TestChild
類和其超類Test
類。
運行結果為:
Test 空參構造
TestChild 有參構造
面積為:6.0
示例代碼:
void main() {
var result = new TestChild.area(3, 4);
print('面積為:${result.area}');
}
class Test {
num width;
num height;
num area;
// 必須加上空參構造,如果注釋掉 它的子類會報錯
Test() {
print('Test 空參構造');
}
Test.area(width, height)
: width = width,
height = height,
area = width * height {
print('Test 有參構造');
}
}
class TestChild extends Test {
num width;
num height;
num area;
TestChild() {
print('TestChild 空參構造');
}
TestChild.area(num width, num height)
: area = (width * height)/2 {
print('TestChild 有參構造');
}
}
注意事項:
- 在調用構造函數之前會計算超類構造函數的參數,所以參數可以是一個表達式。
void main(){
new TestChild();
}
class Test{
static String data;
Test() {
print('Test 空參構造,data: $data');
}
Test.area(String datas) {
print('Test.area 命名函數,data: $datas');
}
}
class TestChild extends Test {
// 參數可以是一個表達式
TestChild() : super.area(getDefaultData()) {
print('TestChild 空參函數 調用父類的命名構造');
}
static String getDefaultData(){
return 'TestChild的數據 getDefaultData';
}
}
執行結果為:
Test.area 命名函數,data: TestChild的數據 getDefaultData
TestChild 空參函數 調用父類的命名構造
- 2.超類構造參數不能使用this關鍵字。例如:參數可以調用靜態方法,但是不能調用實例方法。
例如上例中的Test.area
,修改一下就會報錯:
Test.area(this.width, this.height)
: width = width,
height = height,
area = width * height {
print('Test 有參構造');
}
7).重定向構造函數
有時構造函數的唯一目的是重定向到同一個類中的另一個構造函數。重定向構造函數的主體是空的,構造函數調用出現在冒號(:)之后。
示例如下:
void main() {
var result = new Test(4, true, '數字', 10);
print('abcd分別是:${result.a},${result.b},${result.c},${result.d}');
}
class Test {
num a;
bool b;
String c;
num d;
// 主構造函數
Test(this.a, this.b, this.c, this.d);
// 委托給主構造函數
Test.test1(num x,bool y) : this(x, y,'', 0);
Test.test2(num a,bool b, String c) : this(a, b, c, 0);
Test.test3(num a,bool b, String c,num d) : this(a, b, c, d);
}
結果是:abcd分別是:4,true,數字,10
8).常量構造函數
- 1.如果您的類生成永遠不會更改的對象,則可以使這些對象成為編譯時常量。為此,請定義
const
構造函數并確保所有實例變量都是final
。要使用const構造函數創建編譯時常量,請將const
關鍵字放在構造函數名稱之前。
示例代碼:
void main() {
var result = new Test(4, 10);
print('x=${result.x}, y:=${result.y}'); // x=4, y=10
print(Test.origin.x); // 5
print(Test.origin.y); // 1
var a = const Test(1, 1);
var b = const Test(1, 1);
// 他們是相同示實例
print(a == b); // true
class Test {
static final Test origin = const Test(5, 1);
final num x;
final num y;
const Test(this.x, this.y);
}
- 2.常量上下文中的const構造函數
在常量上下文中,您可以省略const
構造函數或文字之前的內容。
常量上下文,可以簡單的理解為:const
后面包裹的語句一定是連續的一個整體,例如聲明一個list或者map。
例如,查看此代碼,該代碼創建一個const
的map:
// 這里有很多const關鍵字
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
class ImmutablePoint{
const ImmutablePoint(int a, int b);
}
您可以省略除const
關鍵字的第一次使用之外的所有內容:
// 只有一個const, 它建立了常量上下文
const pointAndLine2 = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
如果一個常量構造函數在常量上下文之外,并且在沒有常量的情況下被調用,則會創建一個非常量對象:
var a = const ImmutablePoint(1, 1); //創建一個常量
var b = ImmutablePoint(1, 1); // 不創建常量
assert(!identical(a, b)); // 不是同一個實例
9).工廠構造函數
factory
是在實現不總是創建其類的新實例的構造函數時使用關鍵字。例如,工廠構造函數可能從緩存中返回實例,或者它可能返回子類型的實例。
class Test{
final String name;
static Map<String, Test> _cache = new Map<String, Test>();
factory Test(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final symbol = new Test._internal(name);
_cache[name] = symbol;
return symbol;
}
}
Test._internal(this.name);
void test(){
print('調用了test()');
}
}
void main(){
var a = new Test('abc');
var b = new Test('abc');
// 檢查兩個是否引用的相同的對象
print(identical(a, b)); // true
new Test('abc').test();
}
運行結果:
true
調用了test()
說明:
- 1.工廠構造函數無權訪問
this
。 - 2.可以創建子類的實例(例如:取決于傳遞的參數)。
- 3.返回緩存的實例而不是新的實例。
- 4.可以使用new關鍵字,也可以不使用。(上例中可以這樣寫:Test('abc').test())
- 5.工廠構造函數沒有初始化列表(沒有 :super())
以下是Dart的工廠函數實現的單例:
// factory實現的單例
class Singleton {
factory Singleton() => const Singleton._internal_();
const Singleton._internal_();
}
void main() {
print(new Singleton() == new Singleton());
print(identical(new Singleton(), new Singleton()));
}
(3)初始化列表
1).可以在構造函數體運行之前初始化實例變量,用逗號分隔初始化。
例如:
有參構造的初始化:
class Test1 {
var x, y;
Test1(var x, var y)
: x = x,
y = y {
print('Test1 有參構造初始化');
}
}
命名構造的初始化:
class Test2{
var x,y;
Test2.from(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('Test2.from(): ($x, $y)');
}
}
2). 在實際應用開發中,可以使用assert
在初始化列表用來校驗輸入參數。
例如:
有參構造使用assert
校驗參數:
class Test1 {
var x, y;
Test1(var x, var y) : assert(x >= 0) {
print('Test1(): ($x, $y)');
}
}
命名構造使用assert
校驗參數:
class Test2 {
var x, y;
Test2.withAssert(this.x, this.y) : assert(x >= 0) {
print('Test2.withAssert(): ($x, $y)');
}
}
3).如果沒有更多實際操作內容,可以簡寫。
有參構造使用assert
校驗參數的簡寫形式:
class Test1 {
var x, y;
Test1(var x, var y) : assert(x >= 0);
}
命名構造使用assert
校驗參數的簡寫形式:
class Test2{
var x, y;
Test2.withAssert(this.x, this.y) : assert(x >= 0);
}
4).如果要把構造的初始化和assert
校驗同時使用,可以采用這種方式:
使用assert
簡寫,然后在內容體里面執行初始化操作。
class Test1{
var x, y;
Test1(var x, var y) : assert(x > 0){
this.x = x;
this.y = y;
print('Test1 有參構造初始化');
}
}
5).設置final
字段,初始化程序時更方便。
import 'dart:math';
// 設置final字段,初始化程序時更方便
void main() {
var p = new Test1(4, 3);
print('長方形的對角線長度:${p.hypotenuse}');
}
class Test1 {
final num width;
final num height;
final num hypotenuse;
Test1(width, height)
: width = width,
height = height,
hypotenuse = sqrt(width * width + height * height);
}