第一天把Flutter環境搭建了,並簡單實現第運行第一個Flutter項目,感受很不錯,一些基本操做和原生體驗差很少。用Flutter框架寫過App項目的開發者都知道,Flutter是一個使用Dart語言開發的跨平臺移動UI框架,經過自建繪製引擎,能高性能、高保真地進行Android和iOS開發。可能不少開發者都會像我同樣,會有個疑問?爲何Flutter會選擇Dart呢?我特地查了下網上資料,總結如下下面六點:html
那知道Flutter爲何選擇Dart以後,做爲開發Flutter項目來說,把Dar學好就頗有必要了,下面就從基本語法開始對Dart入門。java
我這邊本地是用IntelliJ IDEA
來編寫Dart項目,我這邊簡單說一下環境配置流程:web
File
--Setting
--Plugins
搜索Dart下載,而Mac下選擇屏幕左上角IntelliJ IDEA
--Perferences
--Plugins
,以下圖:Dart SDK
位置,Windows下選擇File
--Setting
--Languages & Frameworks
--Dart
--Dart SDK Path
配置,而Mac下選擇屏幕左上角IntelliJ IDEA
--Perferences
--Languages & Frameworks
,以下圖:File
--
New
--
Project
--
Dart
,選擇
Dart
語言,以下圖:
Finish
按鈕,會看到項目已經被建立了,以下圖:
除了上面經過IntelliJ IDEA
本地配置環境外,若是以爲上面配置Dart環境太麻煩就走網頁環境,就是下面這個連接直接在線編寫Dart小語法練習項目dartpad.dartlang.org/,下面例子是循環輸出5個hello,示意圖以下:算法
Object
,也就是全部可以使用變量引用的都是對象,每一個對象都是一個了類的實例。在Dart中甚至數字、方法和null都是對象。null
_
下劃線開頭,後面能夠是其餘字符和數字的組合。main
方法,同時還支持在類中定義函數(靜態函數和實例函數),還能夠在方法中定義方法,Dart支持頂層變量,也支持類變量或對象變量。public
、protected
、private
關鍵字。若是某個變量如下劃線_
開頭,表明這個變量是在庫中是私有的。final
的值只能被設定一次。const
是一個編譯時的常量,能夠經過const
來建立常量值,var n = const[]
,這裏n仍是一個變量,只是被賦值了一個常量值,它仍是能夠符其餘值。實例變量能夠是final,但不能是const。var
或者dynamic
來聲明一個變量,Dart會自動推斷其數據類型,dynamic
相似C#下表是Dart語言的關鍵字express
const | null | class | new | this |
---|---|---|---|---|
as | default | final | continue | throw |
assert | deferred | finally | operator | true |
async | do | for | part | try |
async* | dynamic | get | rethrow | typedef |
await | else | if | return | var |
break | enum | implements | set | void |
case | export | import | static | while |
catch | external | in | super | with |
false | extends | is | switch | yield |
abstract | factory | library | sync* | yield* |
//定義打印數字方法
printNumber(num Number){
print('The number is $Number');
}
//執行程序入口
void main(){
//定義初始化一個變量
var number = 6.76;
//調用打印數字方法
printNumber(number);
}
複製代碼
上面是一個簡單基本的Dart
程序,雖然簡單可是用到了Dart
不少特性:編程
//
這是註釋符號,還能夠用/*...*/
這是不少語言的註釋符號num
這是數值型,String
,int
,bool
是另外其餘幾種類型。注意:數值型num
包含整形int
和浮點型double
。print()
打印內容的方法"..."
或者是'...'
表示字符串常量$variableName
或者是${expression}
是字符串插值:在字符串常量中引用變量或者表達式var
一種不指定類型聲明變量的方式main()
是Dart程序的入口方法,每一個程序都須要一個這樣得分方法var name = 'knight';
上面是聲明變量並賦值的示例,變量是一個引用,上面的名字爲name
的變量引用一個內容爲"knight"
的String對象。json
沒有初始化的變量自動獲取一個默認值爲null
。類型爲數字的變量若是沒有初始化那麼默認的值也是null
,由於數字類型也是對象,上面直接上代碼:api
//定義打印數字方法
printNumber(num Number){
print("The number is $Number");
}
//執行程序入口
void main(){
//定義初始化一個變量
var number;
//調用打印數字方法
printNumber(number);
}
複製代碼
上面打印的結果是The number is null
。數組
聲明變量的時候,能夠選擇加上具體類型,以下面:瀏覽器
//定義初始化一個變量
double number = 6.666;
複製代碼
添加類型能夠更加清晰表達你的意圖。IDE編譯器等工具備可使用類型來更好的幫助,提供提示代碼補全,提早發現bug等功能。
若是之後不打算修改一個變量,使用final
或者const
。一個final
變量只能賦值一次;一個const
變量是編譯時常量。注意:const
變量同時也是final
變量,實例變量能夠爲final
但不能是const
。直接上例子:
//定義初始化一個變量
final double number = 6.666;
number = 6.667;
//調用打印數字方法
printNumber(number);
複製代碼
上面例子用final
修飾number
並賦值,但number = 6.67
的時候,想從新給number
再賦值的時候,編譯錯報錯:number,a final variable,can only be set once.
,意思和上面所說的同樣就是final
變量只能賦值一次!下面改成定義爲const
來修飾number
:
//定義初始化一個變量
const double number = 6.666;
number = 6.667;
//調用打印數字方法
printNumber(number);
複製代碼
一樣number = 6.667
編譯器會報錯Constant variables can not be assigned a value
意思是常量值不能賦值,上面也說了,由於const
變量同時也是final
變量。若是const
變量在類中,請定義爲static const
。能夠直接定義const
和旗初始值,也能夠定義一個const
變量使用其餘const
變量的值來初始化其值,以下面:
//定義初始化一個變量
const double number = 6.66;
const double number1 = 2 * number;
複製代碼
上面例子的number1
就是用了number
來將本身初始化值,const
關鍵字不只僅只用來定義常量。有能夠用來建立不變的值,還能定義構造函數爲const
類型 ,這中類型的構造函數建立的對象是不可改變的,任何變量均可以有一個不變的值。
在Dart有幾種內置的數據類型:數值型-Number、布爾型-boolean、鍵值對-Map、字符串-String、列表-List、其餘類型-Runes、Symbols
Dart中提供了兩種類型:
//執行程序入口
void main(){
//整形,其取值一般位於-2的53次方到2的53之間。
num x = 777;
//浮點數 64位
x = 777.7;
int y = 777;
y = 777.7; //這一行編譯器會報錯,由於將int型的數據轉爲double型
double n = 77,7;
d = 77; //這個地方會報錯,由於將double型的數據轉爲int型
int x1 = 7;
int x2 = 77;
int x3 = 777;
int x4 = 7777;
print('${x1.bitLength}'); //佔了3個bit 至關於00000000 00000111
print('${x2.bitLength}'); //佔了7個bit 至關於00000000 01001101
print('${x3.bitLength}'); //佔了10個bit 至關於00000011 00001001
print('${x4.bitLength}'); //佔了13個bit 至關於00011110 01100001
}
複製代碼
上面例子能夠看到三個點:
num
聲明的變量,能夠隨意的轉換類型,若是使用int
或者double
明確的聲明,那就不能轉換了int
值須要多少位時,可使用bitLength
運算符:+、-、*、/、~/、% 經常使用屬性:isNaN、isEven、isOdd 經常使用方法:abs()、round()、floor()、ceil()、toInt()、toDouble()
//執行程序入口
void main(){
int i =7;
double d = 10.1;
print(i / d); //0.6930693069306931
print(i ~/ d); //0 這個操做是取整 就是得出商
print(i.isOdd); // 判斷是奇數
print(i.isEven); // 判斷是偶數
//String -> int
var x1 = int.parse("7");
print(x1 == 7); //輸出true
//Sting -> double
var x2 = double.parse("7.7");
print(x2 == 7.7); //輸出true
//int -> String
var x3 = 7.toString();
print(x3 == '7'); //輸出true
//double -> String
var x4 = 7.1234.toStringAsFixed(2);
print(x4 == '7.12'); //輸出true
//求絕對值
var x5 = (-7).abs();
print(x5 == 7);
//四捨五入1
var x6 = (7.7).round();
print(x6); //輸出8
//四捨五入2
var x7 = (7.3).round();
print(x7); //輸出7
//求小於它的最大整數
var x8 = (7.7).floor();
print(x8); //輸出7
//求大於它的最小整數
var x9 = (7.7).ceil();
print(x9); //輸出8
double num1 = 7.77;
print(num1); //結果是7.77
double num2 = 7;
print(num2); //結果是7.0
int num3 = 7;
print(num3.toDouble()); //int 轉 double 結果是7.0
double num4 = 7.77;
print(num4.toInt()); //double 轉 int 結果是7
}
複製代碼
上面列出了一些平時遇到最多的操做,如求餘,求整,類型轉換等。
Dart字符串是UTF-16編碼的字符序列,可使用單引號或者雙引號來建立字符串:
//執行程序入口
void main(){
String m_str1 = '單引號字符串';
String m_str2 = "雙引號字符串";
print(m_str1); //輸出:單引號字符串
print(m_str2); //輸出:雙引號字符串
}
複製代碼
String中單、雙引號互相嵌套狀況
//執行程序入口
void main(){
String m_str1 = '單引號中的"雙引號"字符串';
String m_str2 = "雙引號中的'單引號'字符串";
print(m_str1); //輸出:單引號中的"雙引號"字符串
print(m_str2); //輸出:雙引號中的'單引號'字符串
//單引號裏面有單引號,必須在前面加反斜槓
String m_str3 = '單引號中的\'單引號\'';
String m_str4 = "雙引號裏面有雙引號,\"雙引號\"";
print(m_str3); //輸出:單引號中的'單引號'
print(m_str4); //輸出:雙引號裏面有雙引號,"雙引號"
}
複製代碼
單引號嵌套單引號之間不容許出現空串(不是空格),雙引號嵌套雙引號之間不容許出現空串:
//String m_str5 = '單引號''''單引號'; //報錯
String m_str6 = '單引號'' ''單引號';
print(m_str6); //輸出: 單引號 單引號
String m_str7 = '單引號''*''單引號';
print(m_str7); //輸出: 單引號*單引號
//String m_str8 = "雙引號""""雙引號"; //報錯
String m_str9 = "雙引號"" ""雙引號";
print(m_str9); //輸出: 雙引號 雙引號
String m_str10 = "雙引號""*""雙引號";
print(m_str10); //輸出: 雙引號*雙引號
複製代碼
單雙引號混合嵌套空串是能夠的,以下:
String m_str11 = '單引號""""單引號';
print(m_str11); //輸出: 單引號""""單引號
String m_str12 = '單引號"" ""單引號';
print(m_str12); //輸出: 單引號"" ""單引號
String m_str13 = '單引號""*"""單引號';
print(m_str13); //輸出: 單引號""*"""單引號
String m_str14 = "雙引號''''雙引號";
print(m_str14); //輸出: 雙引號''''雙引號
String m_str15 = "雙引號'' ''雙引號";
print(m_str15); //輸出: 雙引號'' ''雙引號
String m_str16 = "雙引號''*''雙引號";
print(m_str16); //輸出: 雙引號''*''雙引號
複製代碼
字符串拼接方式,以下:
//使用空格拼接,多個空格也是能夠地
String m_str1 = '單引號字符串' '拼接' '---';
print(m_str1); //輸出:單引號字符串拼接---
//使用換行符和空格
String m_str2 = '單引號字符串'
'換行''加空格' '';
print(m_str2); //輸出: 單引號字符串換行加空格
//單雙引號 空格拼接
String m_str3 = "單雙引號字符串加空格" '拼接' "----";
print(m_str3); //輸出: 雙引號字符串加空格拼接----
//單雙引號 換行 空格
String m_str4 = "單雙引號字符串"
'換行' '加空格' '***';
print(m_str4); //輸出: 單雙引號字符串換行加空格***
//使用三個單引號建立多行字符串
String m_str5 = '''
三個單引號+
拼接
''';
print(m_str5); /*輸出 三個單引號+
拼接
*/
//使用三個雙引號建立多行字符串
String m_str6 = """
三個雙引號+
拼接
""";
print(m_str6); /*輸出 三個雙引號+
拼接
*/
String m_str7 = "正常拼接"+",用加號了來拼接";
print(m_str7); //輸出: 正常拼接,用加號了來拼接
複製代碼
經過提供一個r
前綴能夠建立"原始raw"字符串,在字符串加字符,或者在\
前面再加一個\
,能夠避免\
的轉義做用,以下:
String m_str1 = r"sdsdsds";
print(m_str1); //輸出 sdsdsds
print("換行:\n"); //輸出:換行
print(r"換行:\n"); //輸出:換行:\n
print("換行:\\n"); //輸出:換行:\n
複製代碼
${表達式的使用}
,相似JS中ES6的表達式使用,使用$
能夠獲取字符串的內容,用${表達式}
能夠將表達式的值放入字符串中,使用${表達式}
也可使用字符串拼接。下面也是直接上例子:
bool flag = true;
String m_str1 = "字符串";
print("看看這個值:${m_str1} ""看看這個值flag:${flag}"); //輸出:字符串:字符串 看看這個值flag:true
//使用$+字符串
String name = "knight";
print("$name" + "CTO"); //輸出:knightCTO;
//使用字符串拼接,運用了String類中的toUpperCase函數,把字母變成大寫
String m_str = "Android";
assert('${m_str.toUpperCase()} is very good' ==
'ANDROID is very good');
複製代碼
==操做符判斷兩個對象的內容是否同樣,若是了;兩個字符串包含同樣的字符編碼序列,則他們是相等的。在生產模式 assert()
語句被忽略了。在檢查模式assert(condition)
會執行,若是條件不爲 true 則會拋出一個異常。
Dart中以bool
表明布爾值,只有兩個對象是布爾類型的,那就是true
和false
所建立的對象,這兩個對象都是編譯時常量。當Dart須要一個布爾值,只有true
對象才被認爲是true,全部其餘值都是false。看下下面的例子:
String name ="knight";
//報錯 由於name不是bool類型
if(name){
print(name);
}
複製代碼
上面代碼會拋出異常,提示name
不是布爾值,Dart使用的是先式的檢查值,以下圖:
// 檢查是否爲空字符串
var fullName = '';
assert(fullName.isEmpty);
// 檢查是否小於等於0
var hitPoints = 0;
assert(hitPoints <= 0);
// 檢查是否爲 null.
var unicorn;
assert(unicorn == null);
// 檢查是否爲 NaN.
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);
複製代碼
assert
是語言內置的斷言的函數,僅在檢查模式有效,在開發過程當中,除非條件爲真,不然會引起異常。(斷言失敗則程序馬上終止)
array
是編程語言中最多見的集合類型,我相信身爲開發者,都用過。在Dart
中數組就是List
對象,因此通常稱爲lists
,下面直接上Dart list的示例:
//建立一個int類型的list 並賦值爲0,1,2,3,4
List list = [0,1,2,3,4];
//使用構建的方式建立list
List list1 = new List();
//建立一個常量的List,不能夠改變的List
List list2 = const[0,1,2,3];
//增長泛型
List list3 = new List<String>();
//建立固定的長度的數組列表,不能移除或者增長
List list4 = new List(5);
//建立包含全部如下元素的可改變的長度列表
List list5 = new List.from([0,1,2,3]);
//建立在固定範圍內改變長度的列表
List list6 = new List()..length = 10;
//建立包含全部元素的固定長度列表
List list7 = new List.unmodifiable([0,1,2]);
//用生成器給全部元素賦初始值
List list8 = new List<int>.generate(5, (int i){
return i + i;
});
複製代碼
List經常使用的一些api:
//在列表中存放不一樣類型的對象
List list = [1,2,3,false,"Kinght"];
print(list); //輸出:[1, 2, 3, false, Kinght]
//在列表中添加元素
list.add(7);
print(list); //輸出:[1, 2, 3, false, Kinght, 7]
//修改列表下標爲1的值
list[1] = "paul";
print(list); //輸出:[1, paul, 3, false, Kinght, 7]
//移除列表的指定值得的元素
list.remove("paul");
print(list); //輸出:[1, 3, false, Kinght, 7]
//移除列表指定下標下的元素
list.removeAt(0);
print(list); //輸出:[3, false, Kinght, 7]
//獲取列表的長度
print(list.length); //輸出:4
//向列表中的指定位置添加元素 在第0的位置上插入Android
list.insert(0, "Android");
print(list); //輸出:[Android, 3, false, Kinght, 7]
//判斷數組中是否有某元素
print(list.indexOf("Android")); //這裏存在,輸出對應的下標,若是沒有則輸出-1
//排序
List list1 = [3,1,2,6,7];
// 根據語法提示: List.sort([(int, int) → int compare]) → void
list1.sort((a,b) => a.compareTo(b));
print(list1); //輸出:[1, 2, 3, 6, 7]
複製代碼
簡單總結:
一般來說,Map是一個鍵值對相關的對象,鍵和值能夠是任何類型的對象。每一個鍵只出現一次,而一個值則能夠出現屢次。上面直接上Map集合的建立方式:
//1.經過構建器來建立Map
Map map1 = new Map();
//添加值 賦值
map1["one"] = 'Android';
map1["two"] = 'IOS';
map1["three"] = 'Flutter';
print(map1); //輸出:{one: Android, two: IOS, three: Flutter}
//2.經過複製的形式
Map map2 = Map.of(map1);
print(map2); //輸出:{one: Android, two: IOS, three: Flutter}
//3.跟上面形式同樣 Object.fromEntries() 函數傳入一個鍵值對的列表,並返回一個帶有這些鍵值對的新對象。
// 這個迭代參數應該是一個可以實現@iterator方法的的對象,返回一個迭代器對象。它
// 生成一個具備兩個元素的相似數組的對象,第一個元素是將用做屬性鍵的值,第二個元素是與該屬性鍵關聯的值。
Map map3 = Map.fromEntries(map1.entries);
print(map3);
//4.直接聲明,直接賦值key爲String類型的map
Map map4 = {'one':'Android',
'two':'IOS',
'three':'Flutter'};
print(map4); //輸出:{one: Android, two: IOS, three: Flutter}
//5.建立一個空的Map
Map map5 = Map.identity();
print(map5); //輸出:{}
//6.建立不可變的Map
Map map6 = const {'one':'Android','two':'IOS','three':'flutter'};
print(map6); //輸出:{one: Android, two: IOS, three: flutter}
//7.在目標的map6建立(複製)新的不可修改map7
Map map7 = Map.unmodifiable(map6);
print(map7); //輸出:{one: Android, two: IOS, three: flutter}
//8.建立key爲int值得map
Map map8 = {1:'Android',
2:'IOS',
3:'Flutter'};
print(map8); //輸出:{1: Android, 2: IOS, 3: Flutter}
//9.根據list所提供的key value來建立map
List<String> keys = ['one','two'];
List<String> values = ['Android','IOS'];
Map map9 = Map.fromIterables(keys, values);
print(map9); //輸出:{one: Android, two: IOS}
//經過構建器來建立Map
Map map10 = new Map();
//添加值 賦值 賦值不一樣類型的Map
map10["one"] = 'Android';
map10["two"] = 'IOS';
map10["three"] = 'Flutter';
map10[4] = 'RN';
print(map10); //輸出:{one: Android, two: IOS, three: Flutter, 4: RN}
複製代碼
Map經常使用的一些api:
//建立Map key是int類型,value是String類型
var map1 = new Map<int,String>();
//對Map第一個位置賦值,中括號是key
map1[0] = 'Android';
//對Map第二個位置賦值
map1[1] = 'IOS';
//對Map第三個值賦值
map1[2] = 'flutter';
//對Map賦空值
map1[3] = null;
//由於Map中的鍵值是惟一的,當第二次輸入的key若是存在,Value會覆蓋以前
map1[2] = 'RN';
print(map1); //{0: Android, 1: IOS, 2: RN, 3: null}
//獲取Map的長度
print(map1.length); //輸出:4
//判斷Map是否爲空
print(map1.isNotEmpty); //輸出結果:true
//判斷Map是否不爲空
print(map1.isEmpty); //輸出結果:false
//檢索Map是否含有某個Key
print(map1.containsKey(1)); //輸出:true
//檢索Map是否包含某個Value
print(map1.containsValue('Android')); //輸出:true
//刪除某個鍵值對
map1.remove(0);
print(map1); //輸出:{1: IOS, 2: RN, 3: null}
//獲取全部的key
print(map1.keys); //輸出:(1, 2, 3)
//獲取全部的values
print(map1.values); //輸出:(IOS, RN, null)
//循環打印
/* key:1, value:IOS key:2, value:RN key:3, value:null */
map1.forEach((key,value) {
print("key:${key}, value:${value}");
});
複製代碼
簡單總結:
在Dart中,runes表明字符串的UTF-32 code points。Unicode爲每個字符、標點符號、表情符號等都定義了一個惟一的數值。什麼意思呢?也就是在書寫系統中,每個字母,數字都是有惟一的數值。因爲在Dart字符串是UTF-16 code units字符序列,因此在字符串表達32-bit Unicode值就須要新的語法,一般用\uXXXX
的方式表示Unicode code point,這裏的XXXX是4個16進制的數。如:心形符號💗是\u2665
。對於非4個數值的狀況,把編號放到大括號便可。例如,笑臉(😁)是\u{1f600}
。String
類有一些屬性能夠提取rune
信息。codeUnitAt
和codeUnit
屬性返回16-bit code units。使用runes
屬性來獲取字符串的runes
信息。下面示例演示了runes
、16-bit code units
、和32-bit code points之間的關係:
//執行程序入口
void main(){
var clapp = '\u{1f44f}';
print(clapp); //輸出:👏
print(clapp.codeUnits); //輸出: [55357, 56399]
print(clapp.runes.toList()); //輸出: [128079]
//使用String. fromCharCodes方法顯示字符圖形
Runes input = new Runes(
'\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}');
print(new String.fromCharCodes(input)); //輸出:♥ 😅 😎 👻 🖖 👍
}
複製代碼
一個Symbol
object表明Dart程序聲明的操做符或者標識符。你也許歷來不會用到Symbol,可是該功能對於經過名字來引用標識符的狀況是很是有價值的,特別是混淆後的代碼,標識符的名字被混淆了,可是Symbol
的名字不會改變,下面列下例子:
//使用 Symbol 字面量來獲取標識符的 symbol 對象,也就是在標識符 前面添加一個 # 符號:
//#radix
//#bar
print(#n == new Symbol('n')); //輸出:true
var name = 'knight';
Symbol symbol = #name;
print(symbol); //輸出:Symbol("name")
print(#name); //輸出:Symbol("name")
複製代碼
Dart是一個真正的面嚮對象語言,方法也是對象而且具備一種類型,Function
。這意味着,方法能夠賦值給變量,也能夠當作其餘方法的參數。也能夠把Dart類的實例當作方法來調用,下面是定義方法的示例:
//定義一個方法 判斷列表對應下標是否爲null
bool isNoble(int atomicNumber) {
return list[atomicNumber] != null;
}
複製代碼
雖然在Effective Dart推薦在公開的APIs上使用靜態類型,固然也能夠選擇忽略類型定義:
//定義一個方法 判斷列表對應下標是否爲null 忽略類型定義
isNoble(int atomicNumber) {
return list[atomicNumber] != null;
}
複製代碼
對於只有一個表達式的方法,能夠選擇使用縮寫語法來定義:
//定義一個方法 判斷列表對應下標是否爲null 縮寫寫法
bool isNoble(int atomicNumber) => list[atomicNumber] != null;
複製代碼
=> expr
是語法{return expr;}
形式的縮寫。=>
形式也稱爲胖箭頭語法。注意:在箭頭(=>)和冒號(;)之間只能使用一個表達式,不能使用語句。方法能夠有兩種類型的參數:必需和可選的。必需的參數在參數列表前面,後面是可選參數。
什麼是可選參數麼?定義一個函數時,形參能夠定義爲可選參數,當調用這個方法時,能夠不傳這個可選參數。可選參數能夠是命名參數或者基於位置的參數,可是這兩種參數不能同時當作可選參數。
調用方法的時候,可使用這種形式paramName:value
來指定命名參數。例如:
enableFlags(bold: true, hidden: false);
複製代碼
定義方法時,使用{param1,param2,...}
的形式來指定可選命名參數:
enableFlags({bool bold, bool hidden}) {
// ...
}
複製代碼
把一些方法的參數放到[]中就變成可選位置參數了,例子以下:
//定義一個方法 [String device]是可選位置參數 也就是調用這個方法能夠不傳這個參數
String say(String from, String msg, [String device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
//不使用可選參數調用方法
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
//使用可選參數調用方法
assert(say('Bob', 'Howdy', 'smoke signal') ==
'Bob says Howdy with a smoke signal');
複製代碼
在定義方法的時候,可使用=
來定義可選參數的默認值。默認值只能是編譯時常量。若是沒有提供默認值,則默認值爲null
。下面是設置可選參數默認值的示例:
//定義一個返回類型爲空的方法 方法中hidden默認值爲false
void enableFlags({bool bold = false,bool hidden = false}){
}
//調用方法 沒有傳hidden的值,那默認值就是false
enableFlags(bold:true);
複製代碼
下面的示例顯示瞭如何設置位置參數的默認值:
//定義一個方法 這個方法位置可選位置參數device的默認參數是carrier pigon
//也就是當調用這個方法,沒有傳這個參數時,這個參數會取默認值
String say(String from, String msg, [String device = 'carrier pigeon', String mood]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
if (mood != null) {
result = '$result (in a $mood mood)';
}
return result;
}
//調用上面的方法
assert(say('Bob', 'Howdy') ==
'Bob says Howdy with a carrier pigeon');
複製代碼
還可使用list
或者map
做爲默認值,下面的示例定義一個方法doStuff()
,並分別爲list
和gifts
參數指定默認值:
//執行程序入口
void main(){
//調用判斷對應小標的值是否爲空
// print(isNoble(1)); //輸出:true
doStuff(); //輸出:list: [1, 2, 3]
//輸出:gifts: {first: paper, second: cotton, third: leather}
}
//List和Map都取了默認值
void doStuff( {List<int> list = const [1, 2, 3], Map<String, String> gifts = const { 'first': 'paper', 'second': 'cotton', 'third': 'leather' }}) {
print('list: $list');
print('gifts: $gifts');
}
複製代碼
每一個應用都須要有個頂級的main()
入口方法才能執行。main()
方法的返回值爲void
而且有個可選的List<String>
參數,下面是一個web應用的main()
方法:
void main() {
querySelector("#sample_text_id")
..text = "Click me!"
..onClick.listen(reverseText);
}
複製代碼
在上面代碼中..
y語法是級聯調用。使用級聯調用,能夠在一個對象上執行多個操做。下面是一個命令行應用的main()
方法,而且使用方法參數做爲輸入參數:
// Run the app like this: dart args.dart 1 test
void main(List<String> arguments) {
print(arguments);
assert(arguments.length == 2);
assert(int.parse(arguments[0]) == 1);
assert(arguments[1] == 'test');
}
複製代碼
能夠把方法當作參數調用另一個方法。如:
printElement(element) {
print(element);
}
var list = [1, 2, 3];
// 遍歷集合
list.forEach(printElement);
複製代碼
方法也能夠賦值給一個變量:
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');
複製代碼
大部分方法都帶有名字,例如main()
或者printElement()
。也能夠建立沒有名字的方法,稱爲匿名方法,有時候也被稱爲lambda
或者clourse
閉包。能夠把匿名方法賦值給一個變量,而後可使用這個方法,好比添加集合或者從集合中刪除。匿名函數和命名函數相似,在括號之間能夠定義一些參數·,參數使用逗號分割,也能夠是可選參數。後面大括號中的代碼爲函數體:
([[Type] param1[, …]]) {
codeBlock;
};
複製代碼
下面的代碼定義了一個參數爲i
的匿名函數。list中的每一個元素都會調用這個函數來打印出來,同時來計算了每一個元素在list中的索引位置。
var list = ['apples', 'oranges', 'grapes', 'bananas', 'plums'];
list.forEach((i) {
print(list.indexOf(i).toString() + ': ' + i);
});
//輸出:
0: apples
1: oranges
2: grapes
3: bananas
4: plums
複製代碼
上面說到若是方法只包含一個語句,可使用胖箭頭語法縮寫,把上面的代碼改爲下面一樣的意思:
list.forEach((i) => print(list.indexOf(i).toString() + ': ' + i));
複製代碼
Dart是靜態做用域語言,變量的做用域在寫代碼的時候就缺的過了。基本上大括號裏面定義的變量就只能在大括號裏面訪問,和java做用域相似。
var topLevel = true;
main() {
var insideMain = true;
myFunction() {
var insideFunction = true;
nestedFunction() {
var insideNestedFunction = true;
assert(topLevel);
assert(insideMain);
assert(insideFunction);
assert(insideNestedFunction);
}
}
}
複製代碼
注意nestedFunction
能夠訪問全部的變量,包含頂級變量。
一個閉包是一個方法對象,無論該對象在何處被調用,該對象均可以訪問其做用域內的變量,方法能夠封閉定義到其做用域內的變量。方法能夠封閉定義到其做用域內的變量。下面示例中,makeAdder()
捕獲到了變量addBy
。無論在哪裏執行makeAdder()
所返回的函數,均可以使用addBy
參數:
Function makeAdder(num addBy) {
return (num i) => addBy + i;
}
main() {
// Create a function that adds 2.
var add2 = makeAdder(2);
// Create a function that adds 4.
var add4 = makeAdder(4);
assert(add2(3) == 5);
assert(add4(3) == 7);
}
複製代碼
下面是測試頂級方法、靜態函數和實例函數相等的示例:
foo() {} // 頂級方法
class A {
static void bar() {} // 靜態方法
void baz() {} // 實例方法
}
void main() {
var x;
// 比較頂級函數
x = foo;
print(foo == x); //輸出:true
// 比較靜態方法
x = A.bar;
print(A.bar == x); //輸出:true
// 比較實例方法
var v = new A(); // 實例1
var w = new A(); // 實例2
var y = w;
x = w.baz;
//這些閉包引用相同的實例
//全部相等
print(y.baz == x); //輸出:true
// 閉包引用不一樣的實例,因此不相等`
print(v.baz != w.baz); //輸出:true
}
複製代碼
全部的函數都返回一個值。若是沒有指定返回值,則默認把語句return null;
做爲函數的最後☝一個語句執行。
%
操做符優先高於
==
,而等號高於
&&
,下面的代碼結果是同樣的:
void main() {
var n = 2;
var i = 2;
var d = 7;
if((n % i == 0) && (d % i == 0)){
print('符合條件');
}else{
print('不符合條件'); //進入這裏
}
if(n % i == 0 && d % i == 0){
print('符合條件');
}else{
print('不符合條件'); //進入這裏
}
}
複製代碼
obj
實現了
T
的接口,
obj is T
纔是true。例如
obj is Object
老是true。使用
as
操做符吧對象轉換爲特定的類型。通常狀況下,能夠把它當作
is
斷定類型而後調用所斷定對象的函數縮寫形式,以下面的示例:
if (emp is Person) { // Type check
emp.firstName = 'Bob';
}
複製代碼
使用as
操做符能夠簡化上面的代碼:
(emp as Person).firstName = 'Bob';
複製代碼
注意:上面的這兩個代碼效果是有區別的。若是emp
是null或者不是person
類型,則第一個示例使用is
則不會執行條件裏面的代碼,而第二個狀況使用as
則會拋出一個異常。
使用=
操做符賦值。可是還有一個??=
操做符來指定值爲null的變量值。
a = value; // 給 a 變量賦值
b ??= value; // 若是 b 是 null,則賦值給 b;
// 若是不是 null,則 b 的值保持不變
複製代碼
還有複合賦值符+=
等能夠賦值:
var a = 2; // Assign using =
a *= 3; // Assign and multiply: a = a * 3
assert(a == 6);
複製代碼
下面是複合賦值操做符工做原理解釋:
在Dart中能夠單獨操做數字的某一位,下面操做符一樣應用於整數:
Dart有兩個特殊的操做符能夠用來替代if-else
語句: condition ? expr1 : expr2
若是condition是true,執行expr1(並返回執行的結果);不然執行expr2並返回結果。 expr1 ?? expr2
若是expr1是non-null,返回其值;不然執行expr2並返回其結果。若是是基於布爾表達式的值來賦值,考慮使用?:
var finalStatus = m.isFinal ? 'final' : 'not final';
複製代碼
若是是基於布爾表達式是測試值是否爲null,考慮使用??
String toString() => msg ?? super.toString();
//上面代碼能夠用下面代碼來表示,意思效果是同樣的,代碼易懂可是不簡潔
String toString() => msg == null ? super.toString() : msg;
String toString() {
if (msg == null) {
return super.toString();
} else {
return msg;
}
}
複製代碼
級聯操做符(..)能夠在同一對象上連續調用多個函數以及訪問成員變量。使用級聯操做符能夠避免建立臨時變量,而且寫出來的代碼看起來更加流暢,如:
querySelector('#button') // Get an object.
..text = 'Confirm' // Use its members.
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
複製代碼
第一個方法quertSelector
返回一個selector對象。後面的級聯操做符都是調用這個對象的成員,並忽略每一個操做所返回的值。上面代碼和下面的代碼功能同樣:
var button = querySelector('#button');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
複製代碼
級聯調用也能夠嵌套:
final addressBook = (new AddressBookBuilder()
..name = 'jenny'
..email = 'jenny@example.com'
..phone = (new PhoneNumberBuilder()
..number = '415-555-0100'
..label = 'home')
.build())
.build();
複製代碼
在方法上使用級聯操做符須要很是當心,例以下面代碼是不合法的:
var sb = new StringBuffer();
sb.write('foo')..write('bar');
複製代碼
sb.write
函數返回一個void,沒法再void
上使用級聯操做符。注意級聯語法不是操做符,只是語法!
Dart中的控制流程語句和java語言很像,能夠說是差很少的:
if (isRaining()) {//條件語句
you.bringRainCoat();//內容體
} else if (isSnowing()) {//條件語句
you.wearJacket();//內容體
} else {
car.putTopDown();//內容體
}
複製代碼
可使用標準的for
循環:
var message = new StringBuffer("Dart is fun");
for (var i = 0; i < 5; i++) {
message.write('!');
}
//使用foreach循環 list 和 Set均可以用這種方式
List numbers = [1,2,3,4,5,6,7,8,9,10];
numbers.foreach((number)=> print(number));
//使用for in循環,通常List和Set都是用這種方式
List numbers = [1,2,3,4,5,6,7,8,9,10];
for(var number in numbers){
print(number);
}
複製代碼
while
循環在執行循環以前先判斷條件是否知足:
//判斷條件
while (!isDone()) {
//內容
doSomething();
}
//例子
var i = 0;
while(i > 5){
i++;
}
複製代碼
而do-while
循環是先執行循環代碼再判斷條件:
do {
printLine();//內容體
} while (!atEndOfPage());//條件判斷
//例子
var i = 0;
do{
i++;
}while(i > 7);
複製代碼
使用break
來終止循環:
while (true) {
if (shutDownRequested()) break;
processIncomingRequests();
}
複製代碼
使用continue
來開始下一次循環:
for (int i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
if (candidate.yearsExperience < 5) {
continue;
}
candidate.interview();
}
複製代碼
上面代碼在實現Iterable
接口對象(List和Map)可使用下面寫法:
candidates.where((c) => c.yearsExperience >= 5)
.forEach((c) => c.interview());
複製代碼
Dart中的Switch語句使用==
比較integer、String、或者編譯時常量。比較的兌現必須都是同一個類的實例(而且不是其之類),calss必須沒有覆寫==
操做符,每一個非空的case
語句都必須有一個break
語句。另外還能夠經過continue
、throw
、return
來介紹非空case
語句。當沒有case
語句匹配時,可使用default
語句來匹配這種默認狀況。
var command = 'OPEN';
switch (command) {
case 'CLOSED':
executeClosed();
break;
case 'PENDING':
executePending();
break;
case 'APPROVED':
executeApproved();
break;
case 'DENIED':
executeDenied();
break;
case 'OPEN':
executeOpen();
break;
default:
executeUnknown();
}
複製代碼
下面的示例代碼再case
省略了break
語句,編譯的時候會出現一個錯誤:
void main() {
var number = 1;
var i = 0;
switch(number){
case 1;
i++;
case 2:
i--;
break;
}
}
複製代碼
在Dart中的空case
語句中能夠不要break語句:
void main() {
var number = 1;
var i = 0;
switch(number){
case 1;
i++;
case 2:
i--;
break;
}
}
複製代碼
若是須要實現這種繼續到下一個case
語句中繼續執行,則可使用continue
語句跳轉對應的標籤處繼續執行:
void main() {
var number = 1;
var i = 0;
switch(number){
case 1;
i++;
case 2:
i--;
break;
}
}
複製代碼
每一個case
語句能夠有局部變量,局部變量只有在這個語句內可見。
若是條件表達式結果不知足須要,則可使用assert
語句倆打斷代碼的執行。示例以下:
// 確保變量是非空值
assert(text != null);
// 確保值是小於100
assert(number < 100);
// 確保這是一個 https 地址
assert(urlString.startsWith('https'));
複製代碼
assert
上面也說過了,assert
方法參數能夠爲任何布爾值的表達式或者方法。若是返回的值爲true,斷言執行經過,執行結束。若是返回值爲false,斷言執行失敗,會拋出異常,斷言只有在檢查模式運行有效,若是生產模式運行,則斷言不會執行。
代碼中能夠出現異常和捕獲異常。異常表示一些未知的錯誤狀況。若是異常沒有捕獲,則異常會拋出,致使拋出異常的代碼終止執行。和java不一樣的是,全部的Dart異常是非檢查異常。方法不必定聲明·來看他們所拋出的異常,而且你不要求捕獲異常,Dart提供了Exception
和Error
類型,以及一些子類型。還能夠本身定義異常類型。可是,Dart代碼能夠拋出任何非null對象爲異常,不只僅是實現了Exception
和Error
對象。
下面是拋出或者扔出一個異常的示例:
thow new FormatException('Expected at least 1 section');
複製代碼
還能夠拋出任意對象:
throw 'Out of llamas!';
複製代碼
因爲拋出異常時一個表達式,因此能夠在=>語句中使用,也能夠在其餘能使用表達式的地方拋出異常。
distanceTo(Point other) =>
throw new UnimplementedError();
複製代碼
捕獲異常能夠避免異常繼續傳遞(從新拋出rethrow)異常除外。捕獲異常給你一個處理該異常的機會:
try {
breedMoreLlamas();
} on OutOfLlamasException {
buyMoreLlamas();
}
複製代碼
對於能夠拋出多種類型異常的代碼,你能夠指定多個捕獲語句。每一個語句分別對應一個異常類型,若是捕獲語句沒有指定異常類型,則該能夠捕獲任何異常類型:
try {
breedMoreLlamas();
} on OutOfLlamasException {
// A specific exception
buyMoreLlamas();
} on Exception catch (e) {
// Anything else that is an exception
print('Unknown exception: $e');
} catch (e) {
// No specified type, handles all
print('Something really unknown: $e');
}
複製代碼
如以前代碼所示,可使用on
或者catch
來聲明捕獲語句,也能夠同時使用。使用on
來指定異常類型,使用catch
來捕獲異常對象。函數catch()
能夠帶有一個或者兩個參數,第一個參數爲拋出的異常對象,第二個爲堆棧信息。
...
} on Exception catch (e) {
print('Exception details:\n $e');
} catch (e, s) {
print('Exception details:\n $e');
print('Stack trace:\n $s');
}
複製代碼
使用rethrow
關鍵字能夠把捕獲的異常從新拋出:
final foo = '';
void misbehave() {
try {
foo = "You can't change a final variable's value.";
} catch (e) {
print('misbehave() partially handled ${e.runtimeType}.');
rethrow; // Allow callers to see the exception.
}
}
void main() {
try {
misbehave();
} catch (e) {
print('main() finished handling ${e.runtimeType}.');
}
}
複製代碼
不管是否拋出異常,要確保某些代碼都要執行,可使用finally
語句來實現。若是沒有catch
語句來捕獲異常,則在執行完finally
語句後,異常被拋出了:
try {
breedMoreLlamas();
} finally {
// 即便拋出異常也會執行
cleanLlamaStalls();
}
複製代碼
定義的finally
語句在任何匹配的catch
語句以後執行:
try {
breedMoreLlamas();
} catch(e) {
print('Error: $e'); // 優先處理異常
} finally {
cleanLlamaStalls(); // 而後再執行
}
複製代碼
Dart是一個面向對象編程語言,同時支持基於mixin的繼承機制。每一個對象都是一個類的實例,全部的類都繼承於object
。基於Mixin的繼承意味着每一個類(Object除外)都只有一個超類,一個類的代碼能夠在其餘多個類繼承中重複使用·。使用new
關鍵字和構造函數來建立新的對象。構造函數名字能夠爲ClassName
或者ClassName.identifier
。例如:
var jsonData = JSON.decode('{"x":1, "y":2}');
// 建立Point類型的對象
var p1 = new Point(2, 2);
// 根據json來建立Point對象
var p2 = new Point.fromJson(jsonData);
複製代碼
對象的成員包括方法和數據(函數和示例變量)。當你調用一個函數的時候,你是在一個對象上調用:函數須要訪問對象的方法和數據,使用(.)來引用對象的變量或者方法:
var p = new Point(2, 2);
// 對實例對象的變量y賦值
p.y = 3;
// 從成員變量獲得值
assert(p.y == 3);
// 從p複製實例對象
num distance = p.distanceTo(new Point(4, 4));
複製代碼
使用?.
來替代,能夠避免當左邊對象爲null時候拋出異常:
//若是P不是空對象,那麼對其變量y賦值
p?.y = 4;
複製代碼
有些類提供了常量構造函數。使用常量構造函數能夠建立編譯時常量,要使用常量構造函數只須要用const
替代new
便可:
var p = const ImmutablePoint(2, 2);
複製代碼
兩個同樣的編譯時常量實際上是同一個對象:
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
print(identical(a, b)); // 它們是相同的實例,輸出true
複製代碼
可使用Object的runtimeType
屬性來判斷實例的類型,該屬性返回一個Type
對象。
print('The type of a is ${a.runtimeType}');
複製代碼
下面是如何定義實例變量的示例:
class Point{
num x;//聲明實例變量x,初始化爲空
num y;//聲明實例變量y,溫馨化爲空
num z = 0;//聲明實例變量z,初始化爲0
}
複製代碼
全部沒有初始化時變量值都是null
。每一個實例變量都會自動生成一個getter
方法。Non-final
實例變量還會自定生成一個setter
方法:
class Point{
num x;//聲明實例變量x,初始化爲空
num y;//聲明實例變量y,溫馨化爲空
num z = 0;//聲明實例變量z,初始化爲0
}
void main() {
var point = new Point();
point.x = 4; //使用setter方法對x變量賦值
print(point.x == 4); //輸出true 使用getter獲取x變量的值
print(point.y == null); //輸出true
}
複製代碼
若是在實例變量定義的時候初始化該變量(不是在構造函數或者其餘方法中初始化),改值是在實例對象的時候初始化的,也就是在構造函數和初始化參數列表執行以前。
定義一個和類名字同樣的方法就定義一個構造函數還能夠帶有其餘可選的標識符。常見的構造函數生一個對象的新實例:
class Point {
num x;
num y;
Point(num x, num y) {
this.x = x;
this.y = y;
}
}
複製代碼
this
關鍵字指當前的實例。只有當名字衝突的時候才使用this。因爲構造函數參數賦值給實例變量的場景太常見了,Dart提供一個語法糖來簡化這個操做:
class Point {
num x;
num y;
// 設置x和y的語法糖
// 在構造函數主體運行以前
Point(this.x, this.y);
}
複製代碼
若是沒有定義構造函數,則會有個默認構造函數。默認構造函數沒有參數,而且會調用超類的沒有參數的構造函數
子類不會繼承超類的構造函數。子類若是沒有定義構造函數,則只有一個默認構造函數。
使用命名構造函數能夠爲一個類實現多個構造函數,或者使用命名構造函數來更清晰本身的意圖:
class Point {
num x;
num y;
Point(this.x, this.y);
// 命名構造函數
Point.fromJson(Map json) {
x = json['x'];
y = json['y'];
}
}
複製代碼
構造函數不能繼承,因此超類的命名構造函數也不會被繼承,若是子類也有超類同樣命名構造函數,就必須在子類中本身實現該構造函數。
默認狀況下,子類的構造函數會自動調用超類的無名無參數的默認構造函數。超類的構造函數在子類構造函數體開始執行的位置調用。若是提供了一個 initializer list(初始化參數列表) ,則初始化參數列表在超類構造函數執行以前執行。 下面是構造函數執行順序:
若是超類沒有無名無參構造函數,則須要手動去調用超類的其餘構造函數。在構造函數參數後使用冒號:
能夠調用超類構造函數,下面中,Employee
類的構造函數調用超類Person
的命名構造函數:
//定義Person類
class Person {
String firstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {
// Person 沒有默認構造函數
// you must call super.fromJson(data).
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}
main() {
var emp = new Employee.fromJson({});
// 打印輸出
// in Person
// in Employee
}
複製代碼
因爲超類構造函數的參數在構造函數執行以前執行,因此擦拭能夠是一個表達式或者一個方法調用:
class Employee extends Person {
// ...
Employee() : super.fromJson(findDefaultData());
}
複製代碼
若是在構造函數的初始化列表中使用 super(),須要把它放到最後。調用超類構造函數的參數沒法訪問 this。 例如,參數能夠爲靜態函數可是不能是實例函數。
在構造函數體執行以前除了能夠調用超類構造函數以外,還能夠 初始化實例參數。 使用逗號分隔初始化表達式:
class Point {
num x;
num y;
Point(this.x, this.y);
// 初始化列表在構造函數體運行以前設置實例變量。
Point.fromJson(Map jsonMap)
: x = jsonMap['x'],
y = jsonMap['y'] {
print('In Point.fromJson(): ($x, $y)');
}
}
複製代碼
初始化表達式等號右邊的部分不能訪問 this
。初始化列表很是適合用來設置 final 變量的值。 下面示例代碼中初始化列表設置了三個 final 變量的值:
import 'dart:math';
export 'src/DartProject_base.dart';
// TODO: Export any libraries intended for clients of this package.
class Point {
final num x;
final num y;
final num distanceFromOrigin;
Point(x, y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
main() {
var p = new Point(2, 3);
print(p.distanceFromOrigin);//輸出:3.605551275463989
}
複製代碼
有時候一個構造函數會調動類中的其餘構造函數。一個重定向構造函數是沒有代碼的,在構造函數聲明後,使用冒號調用其餘構造函數。
class Point {
num x;
num y;
// 主構造函數
Point(this.x, this.y);
// 調用主構造函數
Point.alongXAxis(num x) : this(x, 0);
}
複製代碼
若是你的類提供一個狀態不變的對象,你能夠把這些對象定義爲編譯時常量。要實現這個功能,須要定義一個const
構造函數, 而且聲明全部類的變量爲final
。
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
}
複製代碼
若是一個構造函數並不老是返回一個新的對象,則使用factory
來定義這個構造函數。例如,一個工廠構造函數可能從緩存中獲取一個實例並返回,或者返回一個子類型的實例。例子:
class Logger {
final String name;
bool mute = false;
static final Map<String, Logger> _cache =
<String, Logger>{};
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = new Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) {
print(msg);
}
}
}
複製代碼
使用new
關鍵字來調用工廠構造函數
var logger = new Logger('UI');
logger.log('Button clicked');
複製代碼
函數是類中定義的方法,是類對象的行爲。
對象的實例函數能夠訪問this
,下面例子中distanceTo
函數就是實例函數:
import 'dart:math';
class Point {
num x;
num y;
Point(this.x, this.y);
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
複製代碼
Getters
和setters
是用來設置和訪問對象屬性的特殊函數。每一個實例變量都隱含的具備一個getter
, 若是變量不是final
的則還有一個setter
。能夠經過實行getter
和setter
來建立新的屬性, 使用get
和set
關鍵字定義getter
和setter
:
class Rectangle {
num left;
num top;
num width;
num 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;
}
main() {
var rect = new Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
複製代碼
getter
和setter
的好處是,能夠開始使用實例變量,後來能夠把實例變量用函數包裹起來,而調用代碼的地方不須要修改。
實例函數、 getter
、和setter
函數能夠爲抽象函數,抽象函數是隻定義函數接口可是沒有實現的函數,由子類來實現該函數。若是用分號來替代函數體則這個函數就是抽象函數。
abstract class Doer {
// ...定義實例變量和方法...
void doSomething(); // 定義一個抽象方法.
}
class EffectiveDoer extends Doer {
void doSomething() {
// ...提供實現,所以此處的方法不是抽象的...
}
}
複製代碼
調用一個沒實現的抽象函數會致使運行時異常。
下表中的操做符能夠被覆寫。 例如,若是你定義了一個 Vector 類, 你能夠定義一個 + 函數來實現兩個向量相加。
下面舉了覆寫+
和
-
操做符的示例:
class Vector {
final int x;
final int y;
const Vector(this.x, this.y);
/// 覆寫 + (a + b).
Vector operator +(Vector v) {
return new Vector(x + v.x, y + v.y);
}
/// 覆寫 - (a - b).
Vector operator -(Vector v) {
return new Vector(x - v.x, y - v.y);
}
}
main() {
final v = new Vector(2, 3);
final w = new Vector(2, 2);
// v == (2, 3)
assert(v.x == 2 && v.y == 3);
// v + w == (4, 5)
assert((v + w).x == 4 && (v + w).y == 5);
// v - w == (0, 1)
assert((v - w).x == 0 && (v - w).y == 1);
}
複製代碼
使用abstract
修飾符定義一個抽象類,一個不能被實例化的類。抽象類一般用來定義接口,以及部分實現,若是抽象類是可實例化的,則定義一個工廠構造函數 抽象類一般具備抽象函數,下面是定義具備抽象函數的抽象類:
// 這個類是抽象類,不能實例化
abstract class AbstractContainer {
// ...定義構造函數, 變量, 方法...
void updateChildren(); // 抽象方法.
}
複製代碼
下面的類不是抽象的,可是定義了一個抽象函數,這樣的列是能夠被實例化:
class SpecializedContainer extends AbstractContainer {
// ...定義更多的構造方法, 方法...
void updateChildren() {
// ...實現 updateChildren()...
}
//Abstract method causes a warning but doesn't prevent instantiation.
抽象方法致使警告,但不阻止實例化。
void doSomething();
}
複製代碼
每一個類都隱式的定義了一個包含全部實例成員的接口,而且這個類實現了這個接口。若是你想建立類A來支持類B的api,而不想繼承 B 的實現, 則類A應該實現B的接口:
// A person. 隱式接口包含greet().
class Person {
// 在接口中,但僅在此庫中可見
final _name;
// 不在接口中,由於這是構造函數
Person(this._name);
// 在接口中
String greet(who) => 'Hello, $who. I am $_name.';
}
// 實現Person 接口
class Imposter implements Person {
// 咱們必須定義這個,但咱們不使用它。
final _name = "";
String greet(who) => 'Hi $who. Do you know who I am?';
}
greetBob(Person person) => person.greet('bob');
main() {
print(greetBob(new Person('kathy')));
print(greetBob(new Imposter()));
}
複製代碼
下面是實現多個接口的示例:
class Point implements Comparable, Location {
// ...
}
複製代碼
使用extends
定義子類,supper
引用超類:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ...
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ...
}
複製代碼
子類能夠覆寫實例函數,getter
和setter
。下面是覆寫Object類的noSuchMethod()
函數的例子, 若是調用了對象上不存在的函數,則就會觸發noSuchMethod()
函 數。
class A {
//除非重寫NoSuchMethod,不然使用不存在的函數將致使NoSuchMethodError。
void noSuchMethod(Invocation mirror) {
print('You tried to use a non-existent member:' +
'${mirror.memberName}');
}
}
複製代碼
還可使用@override
註解來代表函數是想覆寫超類的一個函數:
class A {
@override
void noSuchMethod(Invocation mirror) {
// ...
}
}
複製代碼
若是使用noSuchMethod
函數來實現每一個可能的getter
、setter、以及其餘類型的函數,可使用@proxy
註解來避免警告信息:
@proxy
class A {
void noSuchMethod(Invocation mirror) {
// ...
}
}
複製代碼
若是要知道編譯時的具體類型,能夠實現這些類來避免警告,和使用@proxy
效果同樣:
class A implements SomeClass, SomeOtherClass {
void noSuchMethod(Invocation mirror) {
// ...
}
}
複製代碼
枚舉類型一般稱爲enumerations
或者enums
是一種特殊的類,用來表現一個固定數目的常量。使用enum
關鍵字來定義枚舉類型:
enum Color {
red,
green,
blue
}
複製代碼
枚舉類型中的每一個值都有一個index
getter函數,該函數返回該值在枚舉類型定義中的位置(從0開始),例如,第一個枚舉值的位置爲0,第二個爲1:
print(Color.red.index == 0); //輸出:true
print(Color.green.index == 1);//輸出:true
print(Color.blue.index == 2);//輸出:true
複製代碼
枚舉values
常量能夠返回全部的枚舉值
List<Color> colors = Color.values;
print(colors[2] == Color.blue);//輸出:true
複製代碼
能夠在switch語句
中使用枚舉。若是在switch(e)
中的e
的類型爲枚舉類,若是沒有處理全部該枚舉類型的值的話,則會拋出一個警告:
enum Color {
red,
green,
blue
}
// ...
Color aColor = Color.blue;
switch (aColor) {
case Color.red:
print('Red as roses!');
break;
case Color.green:
print('Green as grass!');
break;
default: // 若是沒有這個,你會看到一個警告
print(aColor); // 'Color.blue'
}
複製代碼
枚舉類型具備如下限制:
mixin
、沒法實現一個枚舉類型Mixins是一種多類繼承中重用一個類代碼的方法,使用with
關鍵字後面爲一個或者多個mixin名字來使用mixin,上例子如何使用mixin:
class Musician extends Performer with Musical {
// ...
}
class Maestro extends Person with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
複製代碼
定義一個類繼承Object
,該類沒有構造函數,不能調用super
,則該類就是一個mixin。下面例子:
abstract class 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');
}
}
}
複製代碼
從Dart1.13開始,Mixins能夠繼承其餘類,再也不限制爲繼承Object
,Mixins能夠調用super()
使用static
關鍵字來實現級別的變量和函數。
靜態變量對於類別的狀態是很是有用的:
class Color {
static const red =
const Color('red'); // 靜態構造變量.
final String name; // 靜態實例變量.
const Color(this.name); // 構造方法.
}
main() {
print(Color.red.name == 'red'); //輸出:true
}
複製代碼
靜態變量在第一次使用的時候才被初始化。
靜態函數再也不實例是執行,因此沒法訪問this
:
import 'dart:math';
class Point {
num x;
num 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);
}
}
main() {
var a = new Point(2, 2);
var b = new Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(distance < 2.9 && distance > 2.8);
}
複製代碼
對於通用的或者常用的靜態函數,考慮使用頂級方法而不是靜態函數,靜態函數還能夠當作編譯時常量使用,如:把靜態函數當作常量構造函數的參數來使用。
在查看List
類型的API文檔,能夠看到實際的類型定義爲List<E>
。這個<..>
聲明list是一個泛型(或者參數化)類型。一般狀況下,使用一個字母來表明類型參數,例如E,T,S,K和V等。
在Dart中類型是可選的,你能夠選擇不用泛型。有不少狀況都要使用類型代表本身的意圖,無論是使用泛型仍是具體類型。如,若是本身但願List
只包含字符串對象。則能夠定義爲List<String>
表明("list of string")。這樣開發工具或者本身的同事能夠幫助檢查本身的代碼是否把非字符串類型對象給放到這個list
中,以下面:
main() {
List Tech = new List<String>();
Tech.addAll(['Android','IOS','Flutter']);
Tech.add(42);//運行時報錯
}
複製代碼
另外使用泛型的緣由是減小重複代碼。泛型能夠在多種類型之間定義同一個實現,同時還能夠繼續使用檢查模式和靜態分析工具提供的代碼分析功能。例如:建立一個保存緩存對象的接口:
abstract class ObjectCache {
Object getByKey(String key);
setByKey(String key, Object value);
}
複製代碼
後來發現須要一個用來緩存字符串的實現,那又要定義一個接口:
abstract class StringCache {
String getByKey(String key);
setByKey(String key, String value);
}
複製代碼
然而,又須要用一個用來緩存數字的實現,在後來,又須要另一個類型的緩存實現,等等。。。這時候,泛型的另外一個做用體現出來了,泛型能夠避免這種重複代碼,例子上:
abstract class Cache<T> {
T getByKey(String key);
setByKey(String key, T value);
}
複製代碼
在上面的代碼中,T
是一個備用類型,這是類型佔位符,本身調用該接口的時候會指定具體類型。
List
和Map
字面量也是能夠參數化的。參數化定義list須要在中括號之間添加<type>
,定義map須要在大括號以前添加<KeyType,valueType>。若是你須要更加安全的類型檢查,則可使用參數化定義。例子以下:
var names = <String>['Seth', 'Kathy', 'Lars'];
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
複製代碼
在調用構造函數的時候,在類名字後面使用尖括號(<...>)來指定泛型類型。例如:
var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = new Set<String>.from(names);
複製代碼
下面代碼建立了一個key
爲integer
,value
爲View
類型的map:
var views = new Map<int, View>();
複製代碼
Dart的泛型類型是固化的,在運行時有也能夠判斷具體的類型,例如在運行時也能夠檢測集合裏面的對象類型:
var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // 輸出:true
複製代碼
is
表達式只是判斷集合的類型,而不是集合裏面具體對象的類型。在成產模式,List<String>
變量能夠包含非字符串類型對象。對於這種狀況,能夠選擇分包判斷每一個對象的類型或者處理類型轉換異常,java
中的泛型信息是編譯時的,泛型信息在運行時是不純在。在java
中能夠測試一個對象是否爲List
,可是沒法測試一個對象是否爲List<String>
。
當使用泛型類型的時候,可能想限制泛型的具體類型,使用extends
能夠實現這個功能:
// T必須是SomebaseClass或其後代之一。
class Foo<T extends SomeBaseClass> {...}
class Extender extends SomeBaseClass {...}
void main() {
// 能夠在<>中使用somebaseclass或它的任何子類。
var someBaseClassFoo = new Foo<SomeBaseClass>();
var extenderFoo = new Foo<Extender>();
//也可使用no<>
var foo = new Foo();
//指定任何非SomeBaseClass類型將致使警告,在Debug檢查模式,運行時錯誤。
// var objectFoo = new Foo<Object>();
}
複製代碼
一開始,泛型只能在Dart
類中使用。新的語法也支持在函數和方法上使用泛型。
T first<T>(List<T> ts) {
// ...作一些初始化工做或者錯誤檢查...
T tmp ?= ts[0];
// ...作一些額外的檢查或者處理...
return tmp;
}
複製代碼
這裏first(<T>)
泛型能夠在以下地方使用參數T
:
T
)List<T>
)T tmp
)在Dart1.21開始使用泛型函數,若是須要使用泛型函數,須要設置SDK版本爲1.21或者以上。
使用import
和library
指令能夠幫助建立模塊化的可分享代碼。庫不只僅提供API
,仍是一個私有單元:如下劃線(_)
開頭的標識符只有在庫內部可見。每一個Dart app
都是一個庫,即便沒有使用library
命令也是一個庫。這裏的庫和Android所說的庫有類似的地方,簡單來說就是用人家寫好庫中的API
。例如:拍照庫,網絡請求庫等。
使用import
來指定一個庫如何使用另一個庫,例如:Dart web
應用一般使用dart:html
庫,而後能夠這樣導入庫:
import 'dart:html';
複製代碼
import
必須參數爲庫的URL。對於內置的庫,URI使用特殊的dart:scheme
。對於其餘的庫,可使用文件系統路徑或者package:scheme
。package:scheme
指定的庫經過包管理器來提供,如pub
工具,如:
import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';
複製代碼
若是導入的庫具備衝突的標識符,這個常常遇到,則可使用庫的前綴來區分。例如:若是library1
和library2
都有一個名字爲Element
的類,能夠這樣使用:
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// ...
Element element1 = new Element(); // 使用 lib1 中的 Element .
lib2.Element element2 = new lib2.Element(); // 使用 lib2 中的 Element.
複製代碼
若是隻使用庫的一部分功能,能夠選擇須要導入的內容,例如:
// 僅導入foo
import 'package:lib1/lib1.dart' show foo;
// 導入除foo之外的全部名稱。
import 'package:lib2/lib2.dart' hide foo;
複製代碼
Deferred loading
可讓應用在須要的時候再加載庫。這我就想到了App的開啓時間,下面是一些使用延遲加載庫的場景:
延遲加載一個庫,須要先使用deferred as
來導入:
import 'package:deferred/hello.dart' deferred as hello;
複製代碼
當須要使用的時候,使用庫標識符調用loadLibrary()
函數來加載庫:
greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
複製代碼
在上面代碼,使用await
關鍵字暫停代碼執行一直到庫加載完成。在一個庫上能夠屢次調用loadLibrary()
函數。可是該庫只是載入一次。在使用延遲加載庫的時候,要注意:
loadLibrary()
函數導入到使用deferred as 命名空間
。loadLibrary()
方法返回一個Future
。Dart有一些語言特性來支持異步編程。最多見的特性是async
方法和await
表達式。Dart庫中有不少返回Future或者Stream對象的方法。這些方法是異步:這些函數在設置完基本的操做後就返回了,而無需等待執行完成。例如讀取一個文件,在打開文件後就返回了。有兩種方式可使用Future對象中的數據:
async
和await
一樣,從Stream中獲取數據也有兩種方式:
async
和一個異步for循環(await for
)使用async
和await
的代碼是異步的,可是看起來有點像同步代碼。如:下面是使用await
來等待異步方法返回的示例:
await lookUpVersion() 複製代碼
要使用await
,其方法必須帶有async
關鍵字:
checkVersion() async {
var version = await lookUpVersion();
if (version == expectedVersion) {
// 主體內容.
} else {
// 主體內容.
}
}
複製代碼
可使用try
,catch
和finally
來處理使用await
的異常:
try {
server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044);
} catch (e) {
// 對沒法綁定到端口做出反應...
}
複製代碼
一個async
方法是函數體被標記爲async
的方法。雖然異步方法的執行可能須要必定時間,可是異步方法馬上返回-在方法體還沒執行以前就返回了。
checkVersion() async {
// ...
}
lookUpVersion() async => /* ... */;
複製代碼
在一個方法上添加async
關鍵字,則這個方法返回值爲Future。例如,下面是一個返回字符串的同步方法:
String lookUpVersionSync() => '1.0.0';
複製代碼
若是使用async
關鍵字,則該方法返回一個Future,而且認爲該函數是一個耗時操做。
Future<String> lookUpVersion() async => '1.0.0';
複製代碼
注意,方法的函數體並並不須要使用FutureAPI。Dart自動在須要的時候建立Future對象。
await表達式具備以下的形式:
await expression
複製代碼
在一個異步方法內可使用屢次await
表達水。例如,下面的示例使用三次await
表達式來執行相關的功能:
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
複製代碼
在await expression
中,expression
的返回值一般是一個Future;若是返回的值不是Future,則Dart會自動把該值放到Future中返回。Future對象表明返回一個對象的承諾。await expression
執行的結果爲這個返回的對象。await expression
會阻塞主,直到須要的對象返回爲止。若是await
沒法正常使用,請確保是在一個async
方法中。例如要在main()
方法中使用await
,則main()
方法的函數體必須標記爲async
:
main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}
複製代碼
異步for循環具備如下形式:
await for (variable declaration in expression) {
// 每次流發出時會執行.
}
複製代碼
上面的expression
返回的值必須是Stream類型。執行流程以下:
使用break
或者return
語句能夠中止接收stream數據,這樣就挑出了for循環而且從stream上取消註冊了。若是**異步for循環不能正常工做,確保是在一個async方法中使用。**如:要想在main()
方法中使用異步for循環,則須要把main()
方法的函數體標記爲async
:
main() async {
...
await for (var request in requestServer) {
handleRequest(request);
}
...
}
複製代碼
若是Dart類實現call()
函數則能夠當作方法來調用,下面例子中,wannabeFunction
類定義了一個call()
方法,該方法有三個字符串參數,而且返回三個字符串串聯起來的結果:
class WannabeFunction {
call(String a, String b, String c) => '$a $b $c!';
}
main() {
var wf = new WannabeFunction();
var out = wf("Hi","there,","gang");
print('$out'); //輸出:Hi there, gang!
}
複製代碼
現代的瀏覽器和移動瀏覽器運行在多核CPU系統上。要充分利用這些CPU,開發者通常使用共享內存數據來爆炸多線程的正確運行。然而,多線程共享數據一般會致使不少潛在的問題,並致使代碼運行出錯,結果不是預期。全部的Dart代碼在isolates
中運行而不是線程,每一個isolate
都有本身的堆內存,而且確保每一個isolate
的狀態都不能被其餘isolate
訪問。
在Dart語言中,方法也是對象。使用typedef
或者function-type-alias
來爲方法類型命名,而後可以使用命名的方法,當把方法類型賦值給一個變量的時候,typedef
保留類型信息。下面代碼沒有使用typedef
:
class SortedCollection {
Function compare;
SortedCollection(int f(Object a, Object b)) {
compare = f;
}
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
main() {
SortedCollection coll = new SortedCollection(sort);
// 咱們只知道 compare 是一個 Function 類型,
// 可是不知道具體是何種 Function 類型?
assert(coll.compare is Function);
}
複製代碼
當把f
賦值給compare
的時候,類型信息丟失了,f
的類型是(object,object)->int
固然該類型是一個Function
。若是使用顯式的名字並保留類型信息,開發者和工具可使用這些信息:
typedef int Compare(Object a, Object b);
class SortedCollection {
Compare compare;
SortedCollection(this.compare);
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
main() {
SortedCollection coll = new SortedCollection(sort);
assert(coll.compare is Function);
assert(coll.compare is Compare);
}
複製代碼
使用元數據給代碼添加額外信息,元數據註解是以@
字符開頭,後面是一個編譯時常量或者調用一個常量構造函數。有三個註解全部的Dart代碼均可使用:@deprecated
、@override
,@proxy
,下面直接上@deprecated
的示例:
class Television {
/// 已經棄用,請改用[打開]
@deprecated
void activate() {
turnOn();
}
/// 打開電視.
void turnOn() {
print('on!');
}
}
複製代碼
能夠定義本身的元數據註解。下面的示例定義一個帶有兩個參數的@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');
}
複製代碼
元數據能夠在library
、typedef
、type parameter
、constructor
、factory
、function
、field
、parameter
、或者variable
聲明以前使用,也能夠在import
或者export
指令以前使用,使用反射能夠再運行時獲取元數據信息。
Dart語法和Java語法很像,很容易上手,理解很簡單,用一天就把語法整理了一遍,我爲何要學習Dart
語法呢?一開始解釋很清楚了,無非就是把根基打穩。學什麼必定要帶有目的性去學,下面直接上一張圖:
學習資源:
Flutter有四種運行模式:Debug、Release、Profile和test。