開始前請至少準備:
0.一定的面向對象編程基礎
Flutter應用使用Dart語言進行開發
那么我們來快速熟悉一下Dart語言,以免之后在學習Flutter時被語言關卡住
如果你熟悉java和python語法,那么學習Dart語言幾乎沒有什么難度
這篇筆記的所有內容總結自Dart官網,如果有更多細節方面的問題,可以移步https://www.dartlang.org/guides/language/language-tour閱讀官方說明
注釋語法:
// 單行注釋
/*
多行注釋
*/
/// 文檔注釋
關鍵字:
保留關鍵字:
assert break case catch class const continue
default do else enum extends false final?
finally for if in is new null rethrow return super?
switch this throw true try var void while with
上下文關鍵字(contextual keywords):
僅在特定位置被理解為關鍵字
async hide on show sync
內建標識符關鍵字(built-in identifiers):
不可作為class名或type名或import前綴
abstract as covariant deferred
dynamic export external factory
Function get implements import
interface library mixin operator
part set static typedef
異步支持關鍵字:
await yield
變量:
聲明方式:
類型/var/dynamic 變量名 = 變量值;
變量存儲引用,所有的變量值都是object
變量的默認值都是null
var聲明的變量在被賦值時,編譯器會自動推斷類型,一旦類型推斷完畢,變量的類型將不能發生變化
dynamic聲明的變量類型是不固定的
相同類型多變量的聲明:
int x,y = 1;
一起聲明的變量將被賦予相同的值
常量:
final或const關鍵字修飾的變量是常量
常量最多被賦值一次
賦值后無法改變
const常量的特殊之處:
const常量要求在編譯期就能夠完全確定它的值
相同的const常量對象可以多次聲明,但只會被創建一次
const修飾的集合和構造類實例中的所有值也必須都是const常量或在編譯器可以確定的值
Dart內建類型:
int和double:
int: 64字節整型-2^63 - 2^63 - 1
double: 64字節雙精度浮點型
int和double支持+,-,*,/,~/運算符
int支持位運算符,支持10進制和16進制(數值加0x前綴)表示法
double支持科學記數法,如1.1e2表示1.1*10^2
零除不會出現異常,而是得到NaN
字符串(String)類型:
使用雙引號或者單引號包含字符串文本
在字符串中使用占位符傳遞變量或表達式
String str='Dart Language';
print('字符串 $str 的長度是: ${str.length}');//打印結果:字符串 Dart Language 的長度是: 13
const字符串中占位符表示的變量也必須是const的
字符串比較使用==
String str1='Dart';
String str2='Dart';
print(str1==str2);//打印結果:true
字符串可使用+進行拼接
使用'''或"""創建多行字符串
String multiLine='''
Dart
Flutter
''';
使用r前綴的字符串避免轉義
String str=r'Dart\nFlutter';
print(str);//打印結果:?Dart\nFlutter
字符串與數值互相轉換:
int str2int=int.parse('1');//String轉int
double str2double=double.parse('3.14');//String轉double
String int2str=1.toString();//int轉字符串
String double2str=3.1415926.toString();//double轉字符串
String double2strFixed=3.1415926.toStringAsFixed(2);//double轉字符串并保留小數點后2位
布爾(bool)類型:
true/false
數組(List)類型:
List list=[1,2,3];
Map類型:
Map map={
????'key1':'value1',
????'key2':'value2'
};
map['key3']='value3';//加入新的鍵值
Runes類型:
用于表示unicode碼
Symbol類型:
沒看懂,也不常用,以后用到了再回來補
函數:
函數也是對象,類型是Function
聲明方式:
返回值類型 函數名(參數表){
????函數體
}
返回值類型可省略,類型將被推斷
任何函數沒有return語句,可以編譯通過,但是運行期返回值是null
Dart函數僅能返回一個結果
=>語法用于聲明僅有一行函數體的函數,函數體不需要return語句
int add(int i1,int i2) => i1+i2;
函數可選參數:
可以是位置的或者命名的,但兩種可選擇參數在函數中不能同時存在
可選參數必須放在必填參數之后
可選參數可以給定默認值
否則默認值都是null
void positionalOptParamExample(String param1,[int param2,bool param3=false]){
????//位置可選參數聲明方式
????//param3被給定默認值false
}
void namedOptParamExample(String param1,{int param2=0,bool param3}){
????//命名可選參數聲明方式
????//param2被給定默認值0
}
含有多個位置可選參數的函數在調用時,只能按順序省略處于可選參數表后面的參數
比如一個函數有三個位置可選參數,你可以只傳遞第一個,省略后兩個
如果你要傳遞第三個參數,那么前兩個參數也必須要傳遞
含有命名可選參數的函數在調用時需要以名值對(key:value)的形式傳遞命名參數
namedOptParamExample('param1',param2:1);
對于命名參數的傳遞順序沒有要求
Flutter中Widget構造函數的傳參方式就是可選命名關鍵字
main函數:
程序入口,有一個可選的List<String>參數
函數作為對象
將函數(或者lambda表達式)賦值給變量
通過該變量進行函數調用
main(){
????var hello=func;
????var append=(String s1,String s2)=>print(s1+' '+s2);
????hello();//打印結果:?hello world
????append('hello','dart');//打印結果:?hello dart
}
void func(){
????print('hello world');
}
Dart支持閉包
closure(int i){
????return (double d)=>d*i;//使用lambda表達式產生一個匿名函數,并作為外層函數的結果返回
}
運算符:
算術運算符:
+,-,*,%,++,--不細說
除:/得到的結果是double類型
地板除:~/得到的結果是int類型
比較運算符:
==,!=,>,<,>=,<=
類型測試運算符:
is: obj is Type,判斷對象是否是某個類型的實例
is!: obj is! Type,判斷對象是否不是某個類型的實例
as: 類型轉換,obj as Type,將對象轉換為指定類型
賦值運算符:
=
復合賦值運算符"
+=,-=,*=,/=,~/=,%=.??=...
這里有個值得一說的??=運算符
a ??=value;// a如果為null,則賦值為value,否則,a的值不變
邏輯運算符:
&&, ||, !
位運算符:
& | ^(異或) ~(取反) << >>
條件運算符:
? :(三目運算符),??
a??b的含義是若a為null,返回b,否則返回a
其他值得一提的操作符
?.
在進行對象的方法或者屬性調用時,如果對象為null,直接返回null,若對象非空,返回方法結果或者屬性值
流程控制:
分支:
if(exp){
}
if(exp){
}else{
}
if(exp){
}else if(exp){
}else{
}
switch(obj){
case case0:
????break;
case case1:
????break;
????......
default:
????....
}
循環:
for(var i=0;i<3;i++){
}
for(var x in collection){
}
while(exp){
}
do{
}while(exp)
支持break/continue控制流程,支持標簽語法
斷言
用于調式,debug模式下如果斷言失敗,拋出異常
生產環境下斷言失效
assert(booleanExpr);
assert(booleanExpr,"斷言異常時附加的自定義信息")
異常:
dart的所有異常都是運行期異常
在函數簽名中,無法像java那樣聲明可能產生的異常
dart可以拋出任意非空對象作為異常,這些對象無需是Exception的實例,但是不建議你在生產中這樣做
throw FormatException("自定義異常信息");//異常拋出示例
throw "任意非空對象作為異常示例";//拋出任意非空對象
try-catch-finally語法:
try{
}on AException catch (e){
}on BException catch (e){
}finally{
}
如果希望catch所有類型的異常,可以省略on XxException
如果不需要在catch語句塊中調用異常對象,則可以省略catch(e)
catch語句可以指定兩個參數catch (e,s),第二個參數表示異常棧對象
可以使用多個catch并列來分別捕獲不同的異常
注意并列異常之間的繼承關系,子類異常應處于父類異常之前
如果希望在捕獲到異常后再次拋出,可以在catch語句塊中使用rethrow;
可以在try語句塊或catch語句塊之后追加finally語句塊
類:
類的聲明:
class Person {
????String name;
????bool gender;
}
成員變量和方法:
定義在類中的變量稱為成員變量
定義在類中的函數稱為方法
類成員的訪問控制:
Dart沒有public,private等訪問控制關鍵詞
變量名或方法名如果以下劃線_開頭,則認為是私有的
getter/setter:
類的成員變量都有一個隱式的getter方法
用于讀取該變量的值
非final成員變量有一個隱式setter方法
用于寫入該變量的值
可以通過實現getter/setter方法來為類附加額外的成員變量(這種成員變量通常是通過對其他成員進行運算得到),使用關鍵字get和set
class Person{
????String get name=>this.name;
????set name(String name)=>this.name=name;
}
類的構造函數:
類通過構造函數實例化對象
在Dart2中,調用構造函數時,可以省略new關鍵字
一般構造函數的聲明:
class Person {
????String name;
????bool gender;
????//構造函數的聲明
????Person(String name, bool gender) {
????????this.name = name;
????}
}
Dart還提供一種語法糖來簡化構造函數的聲明
class Person {
????String name;
????bool gender;
????Person(this.name, this.gender) ;
}
若不聲明構造函數
類會具有一個隱式的未命名的無參構造
命名構造函數:
上面說過Dart不允許方法重載,如果希望在一個類中聲明多個構造函數,可以使用命名構造函數
class Person {
????String name;
????bool gender;
????Person(this.name, this.gender) ;
????//命名構造函數
????Person.init(String name){
????????this.name=name;
????}
}
調用父類構造函數:
構造函數不會被繼承
默認情況下,子類的構造函數會隱式調用父類的未命名無參構造,如果父類沒有未命名無參構造,就必須顯式地調用一個父類的構造函數
//這里假設Person繼承了一個父類,這個父類有一個id屬性,且沒有未命名無參構造
Person(int id,String name, bool gender) : super(id){
????this.name=name;
????this.gender=gender;
}
構造函數的初始化列表:
在Dart中還可以為構造函數聲明初始化列表,在對象實例化之前完成一些校驗或者賦值操作
初始化列表通常用于類的final屬性的賦值
class Student{
????final int id;
????final String name;
? ? //使用初始化列表的構造函數
????Student(int id,String name):this.id=id,this.name=name;
}
總結一下一個具有初始化列表的構造函數在被調用時發生了什么:
1.運行初始化列表
2.調用父類構造函數
3.執行當前構造函數中的邏輯
構造函數的重定向:
通常一個類聲明多個構造函數時,只是簡單地調用已經聲明的構造函數中的賦值邏輯,這時,可以將要聲明構造函數重定向至其他構造函數,避免相同的邏輯被多次編寫
注意:
1.僅能重定向至未命名的構造函數
2.重定向構造函數的方法體必須是空的
class Student{
????final int id;
????final String name;
????Student(int id,String name):this.id=id,this.name=name;
? ? //init構造函數被重定向至上面的構造函數
????Student.init(String name):this(0,name);
}
const構造函數:
如果希望一個類產生的實例是不變的,可以聲明const構造函數
const構造函數要求類的所有成員都是final的
調用const構造器時需要const關鍵字來修飾,否則實例化的對象將不是const的
main(){
????const School school1=const School('北京大學','地址...');//const對象
????School school2=School('清華大學', '地址...');//非const對象
}
class School{
????final Stringname;
????final Stringaddress;
????const School(this.name,this.address);//const構造函數
}
工廠構造函數:
工廠模式構造函數使用factory關鍵字修飾
通常,這種構造函數不會在每次調用時返回一個新的實例,而是可能從緩存中獲取已經被創建的實例
class Logger {
????final String name;
????bool mute =false;
? ? static final Map_cache = {};
? ? factory Logger(String name) {
????????if (_cache.containsKey(name)) {
????????????return _cache[name];
????????}else {
????????????final logger =Logger._internal(name);
???????????? _cache[name] = logger;
????????????return logger;
????????}
????}
????Logger._internal(this.name);
}
抽象方法和抽象類
沒有方法體的類成員方法是抽象方法,使用abstract關鍵字修飾
包含抽象方法的類也需要由abstract關鍵字修飾
抽象方法可以由非抽象的子類進行實現
但是如果希望抽象類也可以被實例化,則需要聲明一個factory構造函數
隱式接口:
你可以用一個類實現另一個類(不一定是抽象類),因為每個類都有一個隱式的接口,這樣,一個api就有了多種實現
接口實現使用implements關鍵字
class A {
????String myFunc(String str) {
????????return "...$str...";
????}
}
class B implements A {
????String myFunc(String str) {
????????return "!!!$str!!!";
????}
}
繼承:
Dart支持單繼承,使用extends關鍵字
子類可以重寫父類中的方法
可以使用@override注解對方法重寫進行檢查
操作符重寫:
在Dart中,操作符也可以被理解為對對象進行方法調用,因此操作符也是可以重寫的
< > <= >= - + / ~/ * % | ^ & << >> [] []= ~ ==等操作符支持重寫
class Apple {
????ApplePenoperator +(Pen pen) =>ApplePen();
}
class Pen {}
class ApplePen {}
main(){
????Apple apple=Apple();
????Pen pen=Pen();
????ApplePen applePen=apple+pen;
}
==操作符的重寫類似于java中的equals()方法重寫,需要同時重寫hashCode的getter方法
枚舉:
聲明方式:
enum Color {red, green, blue }
使用.index獲取枚舉的聲明順序,從0開始
使用Color.value獲取包含所有枚舉項的List
枚舉可以用于switch
mixin:
上面說過Dart是單繼承的,如果一個類已經繼承了另一個類,但是還希望復用第三個,第四個類中的屬性和方法,就可以使用mixin(mix in兩個英文單詞的連寫,以為混入,不要讀成迷信)
mixin類的聲明使用mixin關鍵字,在類中混入其他類使用with關鍵字
//聲明父類
class SuperClass {
????void funcA() {
????????print("funcA called");
? ? }
}
//聲明第一個mixin類
mixin MixinClassA {
????void funcB() {
????????print("funcB in MixinClassA called");
????}
}
//聲明第二個Mixin類
mixin MixinClassB {
????void funcB() {
????????print("funcB in MixinClassB called");
????}
????void funcC() {
????????print("funcC called");
????}
}
//這個類繼承了 SuperClass類,同時混入了MixinClassA和 MixinClassB兩個類,它的實例可以調用這三個類的方法,注意 MixinClassA和?MixinClassB中都實現了 funcB方法, SubClass實例將調用 MixinClassB對于 funcB的實現,即后混入的方法實現會覆蓋先混入的方法實現
class SubClass extends SuperClass with MixinClassA, MixinClassB {}
類變量和類方法:
在類中使用static關鍵字修飾的變量和方法可以直接使用類名.的形式調用,無需實例化對象
static變量在真正調用前不會初始化
在java中通常會用static來修飾工具類方法
Dart建議用頂級函數來實現工具方法,而不是寫在類中
頂級變量,頂級函數:
Dart中不是所有變量和函數都要定義在類中
直接定義在dart文件中的變量,函數被稱為頂級變量,頂級函數
泛型:
泛型集合:
泛型集合使編譯器在編譯階段可以確定集合元素的類型
List list=[1,2,3];
Map map={1:'value1',2:'value2'};
泛型類和泛型方法:
class GenericClass<T extends SuperClass> {
????void func(T param) {
? ? ? ? ...
????}
}
庫:
庫的導入:
使用import關鍵字
Dart內建庫的導入
import "dart:xxx";
導入其他第三方庫"
import "package:xxx/xxx.dart";
庫別名:
別名用于解決導入的兩個庫中存在沖突的標識符的問題,使用as關鍵字
import "package:lib1/lib1.dart";
import "package:lib2/lib2.dart as lib2";
Element e1=Element();
lib2.Element e2=lib2.Element();
庫的部分導入:
import "package:lib1/lib1.dart" show foo,bar;//只導入庫的部分內容
import "package:lib1/lib1.dart" hide foo,bar;//不導入庫的部分內容
庫的懶加載:
使用deferred關鍵字
在需要時加載庫,而不是運行應用時加載庫,可以提高應用的啟動效率
對于很少使用的庫,懶加載可以避免資源的浪費
import 'package:lib1/lib1.dart' deferred as lib1
當需要調用庫時,調用lib1.loadLibrary()方法完成加載
可以多次調用loadLibrary方法,但庫只會加載一次
異步:
Dart中并不支持嚴格意義上的異步,而是將邏輯延遲執行,這些被延遲執行的邏輯被放入事件隊列,并在最終被遍歷執行.Dart實際上是使用單線程模型來實現所謂的異步的.(這一部分我理解的還不是很透徹,以后用到了再回來補充.)
使用關鍵字await和async
異步方法返回一個Future<T>對象
Future<String> version() async {
????return "1.0.0";
}
在上面的async方法體中,返回值是String類型,但是會被封裝為Futrue<String>
如果異步方法什么也不打算返回,則返回值類型為Future<void>
異步方法調用時,可附加await關鍵字,如果在一個方法的方法體中使用了await關鍵字,則這個方法需要聲明為async方法
生成器:
Dart支持兩種生成器
1.同步生成器,返回一個Iterable對象,使用sync*關鍵字
2.異步生成器,返回一個Stream對象,使用async*關鍵字
同步生成器:一個自然數生成器的實現
Iterable<int>?naturalsGenerator(int limit) sync* {
????int k =0;
? ? while (k < limit) {
????????//使用yield而不是return返回值
????????yield k++;
? }
}
Stream<int>?naturailsGeneratorAsync(int limit) async* {
????int k =0;
????while (k < limit) {
????????yield k++;
????}
}
使用await-for語句對Stream進行處理
var stream = naturailsGeneratorAsync(10);
await for (var i in stream) {
? ? ? ?...
}
Callable類:
在類中實現call方法,該類對象可以視為方法進行調用
Isolates:
上面說過Dart是一種單線程模型的語言,每個Isolates分配獨立的內存,不同的Isolates之間不能互相訪問對方內存中的資源
Typedef:
Dart中,方法也是對象,這個對象的類型是Function
但是Function類不會記錄具體方法對象的傳參方式和返回值類型
使用typedef聲明一個方法類型,所有與該方法類型傳參方式,返回值類型一致的方法,都是該方法類型的對象
typedef int tdFunc(int i, String str);
任何參數為int i,String str,且返回值為int的方法,都是類型為tdFunc的對象
元數據注解:
使用@符號附加的類,變量,標注與類型/方法/參數/變量前,與反射搭配使用,常用的注解有@override(檢查重寫),@deprecated(過時的類/方法/變量),@required(必傳參數)
舉例實現一個元數據注解
class Todo {
????final Stringwho;
????final Stringwhat;
????const Todo(this.who, this.what);
}
@Todo("我", "實現這個方法")
void anUnimplMethod(){
}
級聯標記:
用于連續對同一個對象進行多次屬性設置或方法調用
可以簡化語法
使用方法為:
obj
..屬性/方法調用
..屬性/方法調用
.......;
比如我們聲明一個有很多成員的類:
class Student{
????int id;
????String name;
????bool gender;
????String address;
????double height;
????double weight;
}
在java中,你在構造這個對象后,需要調用一大堆setter方法來設置成員的值
在Dart中,我們使用級聯語法來完成這個任務
main(){
????Student s=Student();
????s
????..id=1
????..name='小明'
????..address='家庭住址'
????..gender=true
????..height=170.5
????..weight=99.9;
}