語法概述
- 在Dart中,一切都是對象,一切對象都是class的實例,哪怕是數字類型、方法甚至null都是對象,所有的對象都是繼承自Object
- 雖然Dart是強類型語言,但變量類型是可選的因爲Dart可以自動推斷變量類型
- Dart支持範型,List<int>表示一個整型的數據列表,List<dynamic>則是一個對象的列表,其中可以裝任意對象
- Dart支持頂層方法(如main方法),也支持類方法或對象方法,同時你也可以在方法內部創建方法
- Dart支持頂層變量,也支持類變量或對象變量
- 跟Java不同的是,Dart沒有public protected private等關鍵字,如果某個變量以下劃線(_)開頭,代表這個變量在庫中是私有的,具體可以看這裏
- Dart中變量可以以字母或下劃線開頭,後面跟着任意組合的字符或數字
變量
變量定義
以下代碼是Dart中定義變量的方法:
main() {
var a = 1;
int b = 10;
String s = "hello";
dynamic c = 0.5;
}
你可以明確指定某個變量的類型,如int bool String,也可以用var或 dynamic來聲明一個變量,Dart會自動推斷其數據類型。
變量的默認值
注意:沒有賦初值的變量都會有默認值null
final和const
如果你絕不想改變一個變量,使用final或const,不要使用var或其他類型,一個被final修飾的變量只能被賦值一次,一個被const修飾的變量是一個編譯時常量(const常量毫無疑問也是final常量)。可以這麼理解:final修飾的變量是不可改變的,而const修飾的表示一個常量。
注意:實例變量可以是final的但不能是const的
下面用代碼說明:
var count = 10;
final Num = count; // final 只能賦值一次
const Num1 = 10; // const賦值必須是編譯時常量
final和const的區別:
區別一:final 要求變量只能初始化一次,並不要求賦的值一定是編譯時常量,可以是常量也可以不是。而 const 要求在聲明時初始化,並且賦值必需爲編譯時常量。
區別二:final 是惰性初始化,即在運行時第一次使用前才初始化。而 const 是在編譯時就確定值了。
內建數據類型
Dart有如下幾種內建的數據類型:
- numbers
- strings
- booleans
- lists(或者是arrays)
- maps
- runes(UTF-32字符集的字符)
- symbols
下面用一段代碼來演示以上各類數據類型:
main() {
// numbers
var a = 0;
int b = 1;
double c = 0.1;
// strings
var s1 = 'hello';
String s2 = "world";
// booleans
var real = true;
bool isReal = false;
// lists
var arr = [1, 2, 3, 4, 5];
List<String> arr2 = ['hello', 'world', "123", "456"];
List<dynamic> arr3 = [1, true, 'haha', 1.0];
// maps
var map = new Map();
map['name'] = 'zhangsan';
map['age'] = 10;
Map m = new Map();
m['a'] = 'a';
//runes,Dart 中 使用runes 來獲取UTF-32字符集的字符。String的 codeUnitAt and codeUnit屬性可以獲取UTF-16字符集的字符
var clapping = '\u{1f44f}';
print(clapping); // 打印的是拍手emoji的表情
// symbols
print(#s == new Symbol("s")); // true
}
函數
函數的返回值
Dart是一個面向對象的編程語言,所以即使是函數也是一個對象,也有一種類型Function,這就意味着函數可以賦值給某個變量或者作爲參數傳給另外的函數。雖然Dart推薦你給函數加上返回值,但是不加返回值的函數同樣可以正常工作,另外你還可以用=>代替return語句,比如下面的代碼:
// 聲明返回值
int add(int a, int b) {
return a + b;
}
// 不聲明返回值
add2(int a, int b) {
return a + b;
}
// =>是return語句的簡寫
add3(a, b) => a + b;
main() {
print(add(1, 2)); // 3
print(add2(2, 3)); // 5
print(add3(1, 2)); // 3
}
命名參數、位置參數、參數默認值
命名參數
sayHello({String name}) {
print("hello, my name is $name");
}
sayHello2({name: String}) {
print("hello, my name is $name");
}
main() {
// 打印 hello, my name is zhangsan
sayHello(name: 'zhangsan');
// 打印 hello, my name is wangwu
sayHello2(name: 'wangwu');
}
可以看到,定義命名參數時,你可以以 {type paramName}
或者 {paramName: type}
兩種方式聲明參數,而調用命名參數時,需要以 funcName(paramName: paramValue)
的形式調用。
命名參數的參數並不是必須的,所以上面的代碼中,如果調用sayHello()不帶任何參數,也是可以的,只不過最後打印出來的結果是:hello, my name is null,在Flutter開發中,你可以使用@required註解來標識一個命名參數,這代表該參數是必須的,你不傳則會報錯,比如下面的代碼:
const Scrollbar({Key key, @required Widget child})
位置參數
使用中括號[]括起來的參數是函數的位置參數,代表該參數可傳可不傳,位置參數只能放在函數的參數列表的最後面,如下代碼所示:
sayHello(String name, int age, [String hobby]) { // 位置參數可以有多個,比如[String a, int b]
StringBuffer sb = new StringBuffer();
sb.write("hello, this is $name and I am $age years old");
if (hobby != null) {
sb.write(", my hobby is $hobby");
}
print(sb.toString());
}
main() {
// hello, this is zhangsan and I am 20 years old
sayHello("zhangsan", 20);
// hello, this is zhangsan and I am 20 years old, my hobby is play football
sayHello("zhangsan", 20, "play football");
}
參數默認值
你可以爲命名參數或者位置參數設置默認值,如下代碼所示:
// 命名參數的默認值
int add({int a, int b = 3}) { // 不能寫成:int add({a: int, b: int = 3})
return a + b;
}
// 位置參數的默認值
int sum(int a, int b, [int c = 3]) {
return a + b + c;
}
main()函數
不論在Dart還是Flutter中,必須都需要一個頂層的main()函數,它是整個應用的入口函數,main()函數的返回值是void,還有一個可選的參數,參數類型是List<String>。
函數作爲一類對象
你可以將一個函數作爲參數傳給另一個函數,比如下面的代碼:
printNum(int a) {
print("$a");
}
main() {
// 依次打印:
// 1
// 2
// 3
var arr = [1, 2, 3];
arr.forEach(printNum);
}
你也可以將一個函數賦值給某個變量,比如下面的代碼:
printNum(int a) {
print("$a");
}
main() {
var f1 = printNum;
Function f2 = printNum;
var f3 = (int a) => print("a = $a");
f1(1);
f2(2);
f3(6);
}
匿名函數
大多數函數都是有名稱的,比如main() printName()等,但是你也可以寫匿名函數,如果你對Java比較熟悉,那下面的Dart代碼你肯定也不會陌生:
test(Function callback) {
callback("hello");
}
main() {
test((param) {
// 打印hello
print(param);
});
}
匿名函數類似於Java中的接口,往往在某個函數的參數爲函數時使用到。
函數返回值
所有的函數都有返回值,如果沒有指定return語句,那麼該函數的返回值爲null。
運算符
Dart中的運算符與Java中的類似,比如++a a == b b ? a : b,但是也有一些與Java不太一樣的運算符,下面用代碼說明:
main() {
// 與Java相同的運算符操作
int a = 1;
++a;
a++;
var b = 1;
print(a == b); // false
print(a * b); // 3
bool real = false;
real ? print('real') : print('not real'); // not real
print(real && a == b); // false
print(real || a == 3); // true
print(a != 2); // true
print(a <= b); // false
var c = 9;
c += 10;
print("c = $c"); // c = 19
print(1<<2); // 4
// 與Java不太一樣的運算符操作
// is運算符用於判斷一個變量是不是某個類型的數據
// is!則是判斷變量不是某個類型的數據
var s = "hello";
print(s is String); // true
var num = 6;
print(num is! String); // true
// ~/纔是取整運算符,如果使用/則是除法運算,不取整
int k = 1;
int j = 2;
print(k / j); // 0.5
print(k ~/ j); // 0
// as運算符類似於Java中的cast操作,將一個對象強制類型轉換
(emp as Person).teach();
// ??=運算符 如果 ??= 運算符前面的變量爲null,則賦值,否則不賦值
var param1 = "hello", param2 = null;
param1 ??= "world";
param2 ??= "world";
print("param1 = $param1"); // param1 = hello
print("param2 = $param2"); // param2 = world
// ?.運算符
var str1 = "hello world";
var str2 = null;
print(str1?.length); // 11
print(str2?.length); // null
print(str2.length); // 報錯
}
..運算符(級聯操作)
如果你對Java中的建造者模式比較熟悉的話,Dart中的..運算符也很好理解,先看下面的代碼:
class Person {
eat() {
print("I am eating...");
}
sleep() {
print("I am sleeping...");
}
study() {
print("I am studying...");
}
}
main() {
// 依次打印
// I am eating...
// I am sleeping...
// I am studying...
new Person()..eat()
..sleep()
..study();
}
可以看到,使用..調用某個對象的方法(或者成員變量)時,返回值是這個對象本身,所以你可以接着使用..調用這個對象的其他方法,這不就類似於Java中的建造者模式,每次build某個屬性時,都返回一個this對象嗎。
控制流程
if / else switch for /while try / catch語句跟Java中都類似,try / catch語句可能稍有不同,下面用一段代碼說明:
main() {
// if else語句
int score = 80;
if (score < 60) {
print("so bad!");
} else if (score >= 60 && score < 80) {
print("just so so!");
} else if (score >= 80) {
print("good job!");
}
// switch語句
String a = "hello";
// case語句中的數據類型必須是跟switch中的類型一致
switch (a) {
case "hello":
print("haha");
break;
case "world":
print("heihei");
break;
default:
print("WTF");
}
// for語句
List<String> list = ["a", "b", "c"];
for (int i = 0; i < list.length; i++) {
print(list[i]);
}
for (var i in list) {
print(i);
}
// 這裏的箭頭函數參數必須用圓括號擴起來
list.forEach((item) => print(item));
// while語句
int start = 1;
int sum = 0;
while (start <= 100) {
sum += start;
start++;
}
print(sum);
// try catch語句
try {
print(1 ~/ 0);
} catch (e) {
// IntegerDivisionByZeroException
print(e);
}
try {
1 ~/ 0;
} on IntegerDivisionByZeroException { // 捕獲指定類型的異常
print("error"); // 打印出error
} finally {
print("over"); // 打印出over
}
}
類(Class)
類的定義與構造方法
Dart中的類沒有訪問控制,所以你不需要用private, protected, public等修飾成員變量或成員函數,一個簡單的類如下代碼所示:
class Person {
String name;
int age;
String gender;
Person(this.name, this.age, this.gender);
sayHello() {
print("hello, this is $name, I am $age years old, I am a $gender");
}
}
上面的Person類中有3個成員變量,一個構造方法和一個成員方法,看起來比較奇怪的是Person的構造方法,裏面傳入的3個參數都是this.xxx,而且沒有大括號{}包裹的方法體,這種語法是Dart比較獨特而簡潔的構造方法聲明方式,它等同於下面的代碼:
Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
要調用Person類的成員變量或成員方法,可以用下面的代碼:
var p = new Person("zhangsan", 20, "male");
p.sayHello(); // hello, this is zhangsan, I am 20 years old, I am a male
p.age = 50;
p.gender = "female";
p.sayHello(); // hello, this is zhangsan, I am 50 years old, I am a female
類除了有跟類名相同的構造方法外,還可以添加命名的構造方法,如下代碼所示:
class Point {
num x, y;
Point(this.x, this.y);
// 類的命名構造方法
Point.origin() {
x = 0;
y = 0;
}
}
main() {
// 調用Point類的命名構造方法origin()
var p = new Point.origin();
var p2 = new Point(1, 2);
}
Dart中使用extends關鍵字做類的繼承,如果一個類只有命名的構造方法,在繼承時需要注意,如下代碼:
class Human {
String name;
Human.fromJson(Map data) {
print("Human's fromJson constructor");
}
}
class Man extends Human {
Man.fromJson(Map data) : super.fromJson(data) {
print("Man's fromJson constructor");
}
}
由於Human類沒有默認構造方法,只有一個命名構造方法fromJson,所以在Man類繼承Human類時,需要調用父類的fromJson方法做初始化,而且必須使用Man.fromJson(Map data) : super.fromJson(data)這種寫法,而不是像Java那樣將super寫到花括號中。
有時候你僅僅只是在某個類的構造方法中,調用這個類的另一個構造方法,你可以這麼寫:
class Point {
num x, y;
Point(this.x, this.y);
// 命名構造方法調用了默認的構造方法
Point.alongXAxis(num x) : this(x, 0);
}
類的成員方法
一個類的成員方法是一個函數,爲這個類提供某些行爲。上面的代碼中已經有了一些類的成員方法的定義,這些定義方式跟Java很類似,你可以爲某個類的成員變量提供getter/setter方法,如下代碼:
class Rectangle {
num left, top, width, height;
// 構造方法傳入left, top, width, height幾個參數
Rectangle(this.left, this.top, this.width, this.height);
// right, bottom兩個成員變量提供getter/setter方法
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}
抽象類和抽象方法
使用abstract修飾一個類,則這個類是抽象類,抽象類中可以有抽象方法和非抽象方法,抽象方法沒有方法體,需要子類去實現,如下代碼:
abstract class Doer {
// 抽象方法,沒有方法體,需要子類去實現
void doSomething();
// 普通的方法
void greet() {
print("hello world!");
}
}
class EffectiveDoer extends Doer {
// 實現了父類的抽象方法
void doSomething() {
print("I'm doing something...");
}
}
運算符重載
Dart中有類似於C++中的運算符重載語法,比如下面的代碼定義了一個向量類,重載了向量的+ -運算:
class Vector {
num x, y;
Vector(this.x, this.y);
Vector operator +(Vector v) => new Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => new Vector(x - v.x, y - v.y);
printVec() {
print("x: $x, y: $y");
}
}
main() {
Vector v1 = new Vector(1, 2);
Vector v2 = new Vector(3, 4);
(v1 - v2).printVec(); // -2, -2
(v1 + v2).printVec(); // 4, 6
}
枚舉類
使用enum關鍵字定義一個枚舉類,這個語法跟Java類似,如下代碼:
enum Color { red, green, blue }
mixins
mixins是一個重複使用類中代碼的方式,比如下面的代碼:
class A {
a() {
print("A's a()");
}
}
class B {
b() {
print("B's b()");
}
}
// 使用with關鍵字,表示類C是由類A和類B混合而構成
class C = A with B;
main() {
C c = new C();
c.a(); // A's a()
c.b(); // B's b()
}
靜態成員變量和靜態成員方法
// 類的靜態成員變量和靜態成員方法
class Cons {
static const name = "zhangsan";
static sayHello() {
print("hello, this is ${Cons.name}");
}
}
main() {
Cons.sayHello(); // hello, this is zhangsan
print(Cons.name); // zhangsan
}
泛型(Generics)
Java和C++語言都有泛型,Dart語言也不例外,使用泛型有很多好處,比如:
正確指定泛型類型會產生更好的生成代碼。
泛型可以減小代碼的複雜度
Dart內置的數據類型List就是一個泛型數據類型,你可以往List中塞任何你想的數據類型比如整型、字符串、布爾值等
關於Dart更多的泛型知識點,可以查看這裏。
Dart庫(Libraries)
Dart目前已經有很多的庫提供給開發者,許多功能不需要開發者自己去實現,只需要導入對應的包即可,使用import語句來導入某個包,比如下面的代碼:
import 'dart:html';
如果你想導入自己寫的某個代碼文件,使用相對路徑即可,例如當前有一個demo.dart文件,跟該文件同級目錄下有個util.dart文件,文件代碼如下:
// util.dart文件內容
int add(int a, int b) {
return a + b;
}
在demo.dart文件中如果要引用util.dart文件,使用下面的方式導入:
// demo.dart
import './util.dart';
main() {
print(add(1, 2));
}
你可以使用as關鍵字爲導入的某個包設置一個前綴,或者說別名,比如下面的代碼:
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// Uses Element from lib1.
Element element1 = Element();
// Uses Element from lib2.
lib2.Element element2 = lib2.Element();
你也可以在導入包時使用show hide關鍵字來導入某個包中的部分功能,比如下面的代碼:
// 只導入foo
import 'package:lib1/lib1.dart' show foo;
// 導入除了foo的所有其他部分
import 'package:lib2/lib2.dart' hide foo;
導入包時使用deferred as可以讓這個包懶加載,懶加載的包只會在該包被使用時得到加載,而不是一開始就加載,比如下面的代碼:
import 'package:greetings/hello.dart' deferred as hello;
異步
Dart提供了類似ES7中的async await等異步操作,這種異步操作在Flutter開發中會經常遇到,比如網絡或其他IO操作,文件選擇等都需要用到異步的知識。
async和await往往是成對出現的,如果一個方法中有耗時的操作,你需要將這個方法設置成async,並給其中的耗時操作加上await關鍵字,如果這個方法有返回值,你需要將返回值塞到Future中並返回,如下代碼所示:
Future checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
下面的代碼使用Dart從網絡獲取數據並打印出來:
import 'dart:async';
import 'package:http/http.dart' as http;
Future<String> getNetData() async{
http.Response res = await http.get("http://www.baidu.com");
return res.body;
}
main() {
getNetData().then((str) {
print(str);
});
}