Dart指南

Dart是Google出品的語言,用於服務端,web端,移動端,物聯網等多個領域,是一門面向對象的類定義的單繼承的語言.Dart作爲Flutter的開發語言,隨着Flutter在18年年底發行的1.0正式版,也是逐漸走入大家的視野.所謂"工欲善其事,必先利其器",本篇就是我們走向Flutter開發的第一步.讓我們拿起Dart這把武器,開始打怪升級,攢經驗吧.

變量

創建一個變量

因爲在Dart語言中,一切皆對象,一個字符串,一個數字甚至null都是一個對象.因此,變量存儲的是對象的引用.

var s = 'Hello Dart';
print(s);  // Hello Dart

雖然Dart是一門強類型的語言,但它具有類型推導的功能,即我們在定義的時候,不一定非要強制定義變量的具體類型.像上面的代碼中,我們使用var來聲明一個變量,Dart會自行幫我們判斷出這是一個String類型的變量.上面的代碼我們也可以直接指定一個具體的類型:

String s = 'Hello Dart';
print(s);

但我們再次給它賦值其他類型的時候,是會報錯的.我們在上面的代碼後面添加上如下代碼,再次運行:

s = 23;
print(s);

會出現Error: A value of type 'int' can't be assigned to a variable of type 'String'.的錯誤提示.它告訴我們,不能將一個int整型賦值給一個類型爲String的變量.但是下面的場景中,類型是可以改變的:

var s;
print(s);  // null
s = 'Hello Dart';
print(s);  // Hello Dart
s = 12;
print(s);  // 12

當我們用var聲明一個變量,但是不給它初始化,後續的賦值操作中,可以是多種類型的.並且,我們可以看到,未被初始化的變量,它的默認值爲null.
一般我們建議,在聲明的時候指定一個具體的類型.如果暫時還不知道類型的話,可以使用對象類型和動態類型.

對象類型 Object

Object s = 'Hello Dart';
print(s);  // Hello Dart
s = 12;
print(s);  // 2

動態類型 dynamic

dynamic s = 'Hello Dart';
print(s);  // Hello Dart
s = 12;
print(s);  // 2

這兩者雖然都是可以讓變量接收不同的類型,但是它們是有本質的區別的.因爲在Dart中,一切皆對象,所有的類型都是繼承自Object,所以對象類型可以接收不同的類型.而dynamic則是讓代碼在編譯階段不執行類型檢查.

創建一個常量

通常,創建一個常量,我們使用constfinal關鍵字,而不是var或者其他類型.

const

const n = 1;
n = 2;

編譯器會報錯:Error: Can't assign to the const variable 'n'..告訴我們無法分配給一個常量

final

final n = 1;
n = 2;

編譯器會報同樣的錯誤.
那麼這兩者有什麼區別呢?
const是編譯時常量,即它的值在編譯時就已經固定了,而final則在第一次使用時被初始化(當然,const算是隱式final類型).所以實例變量可以是final類型但不能是const類型,且必須在構造函數體執行之前初始化final實例變量.

類型

Dart語言支持多種內建類型

  • Number
  • String
  • Booleab
  • List
  • Set
  • Map
  • Rune

Number

intdouble這兩種類型,它們都是num的亞類型.其中 int 代表整數, double(也是採用 IEEE 754 標準)代表浮點數.num擁有各種基本運算方法: + , - , * , /,~/ , abs() , ceil() , floor()等.

int

聲明一個整數

var a = 1
int b = 1;

double

聲明一個浮點數

var a = 1.1;
int b = 1.1;

運算

/除法運算, %除法取餘, ~/除法取整

int a = 7;
int b = 2;
print(a / b);  // 3.5
print(a % b);  // 1
print(a ~/ b);  // 3

String

可以採用單引號' , 雙引號 " , 三引號 ''' 來創建字符串,其中三引號實現多行字符串對象的創建.

String a = 'Hello Dart';
String b = "Hello Dart";
String c = 'Hello I\'m Dart';
String d = '''
  Hello World
  I'm Dart
''';

字符串通過 ${expression} 內嵌表達式,如果表達式是一個標識符,則{}可以省略

String language = 'Dart';
print('Hello ${language}');  // Hello Dart
print('Hello $language');  // Hello Dart

使用r前綴,創建原始raw字符串

String s = r'Hello \n world';
print(s);  // Hello \n world

類型轉換

字符串與數字之間的類型轉換

// String => int
String s = '1';
int a = int.parse(s);
assert(a == 1);

// String => double
String s2 = '1.2';
double b = double.parse(s2);
assert(b == 1.2);

// int => String
int n = 1;
String c = n.toString();
assert(c == '1');

// double => String
double m = 1.2345;
String d = m.toStringAsFixed(2);
assert(d == '1.23');

其中assert是斷言,在生產環境會被忽略,在開發環境下assert(condition)會在非true的情況下拋出異常.

Boolean

使用 bool 類型表示布爾值,有字面量 truefalse ,都是編譯時常量.Dart的類型安全意味着不能使用if(nonbooleanValue)assert(nonbooleanValue) ,而應該明確檢查.

String s = '';
assert(s.isEmpty);

int n = 0;
assert(n == 0);

var o = null;
assert(o == null);

var t = 0 / 0;
assert(t.isNaN);

在JavaScript中,因爲存在隱式轉換,所以我們可以有如下操作

let arr = []
if(arr){
  console.log('1')
}else{
  console.log('2')
}

結果會輸出1,但是在Dart裏面這樣就是行不通的,if(flag)括號裏面的flag必須是個很明確的bool值.

List

就是Dart中的數組類型,用List來定義.

List list = [1,2];
print(list);  // [1, 2]
list.add(3);
print(list);  // [1, 2, 3]
list.addAll([4,5]);
print(list);  // [1, 2, 3, 4, 5]
print(list.length);  // 5
list[2] = 22;
print(list);  // [1, 2, 22, 4, 5]

也可以約束List中的每一個元素的類型

List<int> list = [1,2];
list.add('2');  //Error: The argument type 'String' can't be assigned to the parameter type 'int'.

這裏我們嘗試將一個錯誤類型的值添加進去,則會提示錯誤.但其實如果我們將代碼改成如下:

var list = [1,2];
list.add('2');

也還是會報同樣的錯誤提示.這是因爲我們之前講到過的,有默認的類型推導,判斷出list的類型爲LIst<int>,而現在添加的卻是一個String類型的字符串.

Set

是一個元素唯一的無序集合

var set = {'zhangsan','lisi'};  // 創建一個set

var set = {'zhangsan','lisi'}; 這裏會有個默認的類型推導,判斷出set的類型爲 Set<String> ,如果嘗試將錯誤類型添加進去,則會提示錯誤.

要創建一個空集,使用前面帶有類型參數的{} 或將{}賦值給Set類型的變量

var set = <String>{};
Set<String> set2 = {}; 
var o = {};  //創建的是一個Map,而不是一個Set

使用add()addAll()添加元素,使用 .length獲取Set中元素的個數

var set = <String>{};
var set2 = {'list'};
set.add('zhangsan');
set.addAll(set2);
print(set);  // {zhangsan, list}
print(set.length);  // 2

在Set字面量之前添加const,可以創建一個編譯時Set常量,此後便不再可以更改set的內容.

final set = const {
  'zhangsan'
};

Map

關聯keys和values的對象,keys和values可以是任何類型的對象

var map = {
  'name':'zhangsan',
  'location':'China'
};

這裏會有個默認的類型推導,判斷出map的類型爲 Map<String,String> ,如果嘗試將錯誤類型添加進去,則會提示錯誤.

使用Map構造函數創建,Dart一切皆是對象(都是類的實例),所以變量可以使用構造函數進行初始化

var map = Map();
map['name'] = 'zhangsan';
map['age'] = 18;
print(map);  // {name: zhangsan, age: 18}

在獲取values值時,如不存在的返回null:

print(map['name']);
print(map['gender']);  // null

使用 .length獲取鍵值對數量

print(map.length);  // 2

Rune

用來表示字符串中的UTF-32編碼字符.由於Dart字符串是UTF-16編碼單元,因此要在字符串中表示32位的Unicode需要特殊語法支持: \uXXXX用4個16進制數來表示,對於非4個數值的情況,用大括號把編碼值括起來即可.

print('\u2665');  // ♥
print('\u{1f44d}');  // 👍
Runes input = new Runes('\u2665 \u{1f47b} \u{1f44d}');
print(new String.fromCharCodes(input));  // ♥ 👻 👍

函數

main() 函數

Dart支持top-level函數,就是main(),任何應用都必須擁有一個main()函數,默認返回值爲空,參數爲一個可選的 List<String>

void main(List<String> arguments){
  print(arguments);
}

我們在運行文件的時候,執行

dart index.dart 1 2 

這裏文件名後面跟的1和2,就是兩個參數

函數返回值

定義一個普通函數,下面的函數規定了參數的類型以及函數返回值的類型,都是String

String getColor(String color){
  return color;
}

所有的函數都有返回值,那麼沒有明確指定返回值的函數會在函數體的最後隱式的添加 return null ,則返回類型爲void,表示空.

函數參數

函數除了上面提到的最普通的參數以外,還有一些其他的參數.比如:可選參數,默認參數等.其中可選參數又可以是命名參數或者位置參數,但一個參數只能選擇其中一種方式修飾

命名可選參數

定義函數時,使用{param1, params2, ...}這樣的形式.
調用函數時,使用指定命名參數 paramName: value 的形式.如{param1: true, param2 : 'test'},可以不按照順序傳入.

String getColor(String color,{double height}){
  print(color);
  print(height);
  return color;
}

getColor('red');
getColor('red',height:88.8);

使用 @required註解表示參數是必填的

String getColor(String color,{double height, @required double width}){
  print(color);
  print(height);
  print(width);
  return color;
}

其中 required 被定義在 meta package.所以無論是直接引入,還是引入其他package,而那個package中輸出了meta都可以.
直接引用:

import 'package:meta/meta.dart';

在Flutter中引入包後也可直接使用 @required 註解

import 'package:flutter/material.dart';

位置可選參數

定義函數時,將參數放到 [] 中來標記參數是可選的,如[param1, param2]
調用函數時,就是正常的fn('必選','可選1','可選2')這樣的形式.必選參數放前面,可選參數放後面,且必須按照順序傳入.若其中某個不是最後的參數不存在的話,需傳入null.

String getColor(String color,[double height]){
  print(color);
  print(height);
  return color;
}

getColor('red');
getColor('red',88.8);

默認參數

在定義的時候,可以提供參數默認值,但默認值只能是編譯時常量.如果沒有提供默認值的話,則默認是null

void getPerson(String name, {String gender='male'}){
  print('$name $gender');
}

getPerson('zhangsan');  // zhangsan male
getPerson('lisi',gender:'female');  // lisi female

listmap也可以作爲默認值傳遞

void foo({
  List<int> list = const [1,2],
  Map<String, double> map = const {
    'height':180,
    'width':150
  }
}){
  print('list: $list');
  print('map: $map');
}
foo();

函數是一等對象

一個函數可以作爲另一個函數的參數

void printEle(Object ele){
  print(ele);
}
List<Object> list = [1,'t',true];
list.forEach(printEle);

也可以將一個函數賦值給一個變量

var printEle = (ele) => print(ele);
List<Object> list = [1,'t',true];
list.forEach(printEle);

匿名函數

又叫lambda或者closure,修改上面的例子

List<Object> list = [1,'t',true];
list.forEach((item){
  print(item);
});

運算符

算術運算符

操作符 含義
+ 加法
- 減法
* 乘法
/ 除法
~/ 除法,返回整數結果
% 除法,獲取餘數(取模)
++var 自增,先增再運算
var++ 自增,先運算再增
–var 自減,先減再運算
var– 自減,先運算再減
print(1+2);  // 3
print(1-2);  // -1
print(2*3);  // 6
print(7/2);  // 3.5
print(7~/2);  // 3
print(7%2);  // 1
var n = 2;
print(n++);  // 2
print(n);  // 3
print(--n);  // 2

關係運算符

操作符 含義
== 等於
!= 不等於
> 大於
< 小於
>= 大於等於
<= 小於等於

要判斷兩個對象是否表示相同的事物,使用 ==運算符(有時,要確定兩個對象是否完全相同,用identical()函數)

print(2 == 2);
print(3 != 2);
print(3 > 2);
print(2 < 3);
print(2 >= 2);
print(2 <= 2);

類型判定運算符

操作符 含義
as 類型轉換
is 如果對象具有指定的類型則爲true
is! 如果對象具有指定的類型則爲false

這幾個運算符用於在運行時處理類型檢查

void main(){
  var p = new Person('zhangsan');
  assert(p is Person);
  var s = new Student(12);
  assert(s is Student);

  if(p is Person){
    print(p.name);
  }
}

class Person {
  String name;
  Person(this.name);
}
class Student {
  int age;
  Student(this.age);
}

其中

if(p is Person){
  print(p.name);
}

可以變成

print((p as Person).name);

但是這兩者並不是相等的,因爲在第二種情況中,如果p不是Person的實例,那麼是會拋出異常的

賦值運算符

使用=爲變量賦值

var a = 1;

使用??=運算符,當被賦值的變量爲null時纔會賦值給它,否則還是原本的值

var a = 1;
var b = 2;
var c;
b ??= a;
c ??= a;
print(b);  // 2
print(c);  // 1

邏輯運算符

操作符 含義
&& 與,兩個都成立則成立
|| 或,只要有一個成立就成立
! 非,將表達式反轉,true變成false, false變成true

這個和其他的語言基本一樣

條件表達式

操作符 含義
condition ? expr1 : expr2 三目運算符,如果condition爲true,則執行expr1,否則執行expr2
expr1 ?? expr2 如果expr1是null,則返回expr2的值,否則返回expr1的值
var f = false;
var a = f ? 'Yes' : 'No';
print(a);  // No

var n = null;
print(n ?? 666);  // 666

級聯運算符

實現對同一個對象進行一系列操作.除了調用函數,還能訪問字段屬性,有點像jQuery的鏈式調用.好處是可以節省創建臨時變量的步驟,使編寫出來的代碼更加的流暢.

List<int> list = [1,2]
  ..add(3)
  ..add(4);
print(list);  // [1, 2, 3, 4]

其他運算符

?. 當運算符左邊不爲null的時候,獲取其運算符右邊的屬性值.當爲null時,返回null

var p = new Person('zhangsan');
var p2 = null;
print(p?.name);  // zhangsan
print(p2?.name);  // null

控制流程語句

顧名思義,就是用來控制dart程序流程走向的語句

if和else

var a = 2;
if(a == 1){
  print(1);
}else if(a == 2){
   print(2);
}else{
  print(3);
}

for循環

var list = ['zhangsan','lis'];
for(var i = 0; i < list.length; i++) {
  print(list[i]);
}

如果要迭代一個實現了 Iterable 接口的對象,可以使用forEach()方法 ,例如 List, Set 等

var list = ['zhangsan','lis'];
list.forEach((item) => print(item));

var s = {'zhangsan','lisi'};
s.forEach((item) => print(item));

同樣的,它們也支持使用 for-in 來進行迭代

var s = {'zhangsan','lisi'};
for(var p in s){
  print(p);
}

while和do-while

和其他語言的基本一樣,while就是先判斷再執行, do-while就是先執行再判斷

var a = 1;
while(a < 5){
  print(a++);  // 1 2 3 4
}

var b = 1;
do{
  print(b++);  // 1
}while(b < 1);

break和continue

和其他語言的基本一樣,break停止循環,continue跳轉到下次迭代

for(var i = 0; i < 5; i++){
  if(i == 2){
    break;
  }
  print(i);  // 0 1
}

for(var i = 0; i < 5; i++){
  if(i == 2){
    continue;
  }
  print(i);  // 0 1 3 4
}

switch和case

每個非空的case語句結尾必須要跟一個break語句.除break以外還有continue, throw , return,否則會報錯

var a = 1;
switch(a){
  case 1:
    print(1);
    break;
  case 2:
    print(2);
  default:
    print('other');
}

上面的代碼會報錯:Error: Switch case may fall through to the next case.
若匹配到某一個爲空的case語句,則順延執行下一個case語句,下面的代碼打印出other

var a = 2;
switch(a){
  case 1:
    print(1);
    break;
  case 2:
  default:
    print('other');
}

可以使用continue語句跳轉到具體的一個標籤處

var a = 2;
switch(a){
  label:
  case 1:
    print(1);
    break;
  case 2:
    print(2);
    continue label;
  default:
    print('other');
}

assert

斷言語句,生產環境無效,只在開發環境中有效
可以有兩個參數:
第一個參數是一個解析成bool類型的任何表達式.若爲true,斷言成功,程序繼續執行,若爲false,斷言失敗,拋出異常
第二個參數就是斷言失敗拋出異常時的自定義提示

var a = 1;
assert(a == 1);
assert(a != 1,'a的值爲1,所以此次斷言失敗');

Dart是一種基於類和mixin繼承機制的面向對象的語言.每個對象都是一個類的實例,所有的類都繼承於Object

定義和實例化

定義一個類

class Person {}

實例化一個對象

var p = new Person();
print(p);  // Instance of 'Person'

所有的實例變量都生成隱式的getter方法,非final的實例變量同樣會生成隱式setter方法

void main() {
  var p = new Person();
  p.name = 'zhangsan';
  print(p.name);
}
class Person {
  String name;
}

這裏在 p.name = 'zhangsan'的時候,就調用了setter方法, print(p.name)調用了getter方法

構造函數

在沒有聲明構造函數的情況下,會有一個默認的構造函數, 默認構造函數沒有參數並會調用父類的無參構造函數
通過創建一個與其類名相同的函數來聲明構造函數或者附加一個額外標識符(即命名構造函數)

void main() {
  var p = new Person('zhangsan');
  print(p.name);  // zhangsan
}
class Person {
  String name;
  Person(String name){
    this.name = name;  // 這裏的this指的是當前實例
  }
}

Dart具有精簡構造函數寫法的語法糖.通常情況下,我們都會這麼做,只有當存在命名衝突的時候,才建議使用this.一般採用下面的寫法

void main() {
  var p = new Person('zhangsan');
  print(p.name);
}
class Person {
  String name;
  Person(this.name);
}

構造函數不會被繼承,即子類不會繼承父類的構造函數.所以若子類不聲明構造函數,那麼它就只有默認構造函數(匿名,無參)

命名構造函數

使用命名構造函數可爲一個類實現多個構造函數,同時也可以更清晰的表明函數意圖

void main() {
  var p = new Person('zhangsan');
  var p2 = new Person.nickname('xiaozhang');  //通過命名構造函數實例化一個對象
  print(p.name);  // zhangsan
  print(p2.name);  // xiaozhang
}
class  Person {
  String name;
  Person(this.name);
  Person.nickname(this.name);  // 命名構造函數
}

調用父類構造函數

默認情況下,子類的構造函數會自動調用父類的默認構造函數(匿名,無參).父類的構造函數在子類構造函數體開始執行的位置被調用.若有初始化參數列表,則初始化參數列表在父類構造函數之前執行

初始化參數列表 => 父類默認構造函數 => 主類的無名構造函數

如果父類沒有默認構造函數,那麼就需要子類手動調用父類的其他構造函數,在當前構造函數冒號:之後,函數體之前,聲明調用父類構造函數

void main(){
  var s = new Student('zhangsan','math');
  print(s);
  print(s.name);
  print(s.job);
}

class Person {
  String name;
  Person(this.name);
}

class  Student extends Person {
  String job;
  Student(String name,String job):super(name){
    this.job = job;
  }
}

初始化列表

可以初始化實例變量,位於函數體之前,各參數用逗號隔開

void main(){
  var p = new Person('zhangsan',12);
  print(p.name);
  print(p.age);
}

class Person {
  String name;
  int age;
  Person(String name, int age)
      :name = name + ' shuai',
       age = age;
}

在開發階段,使用assert來驗證輸入的初始化列表

class Person {
  String name;
  int age;
  Person(String name, int age)
    :assert(age >= 18);
}

使用初始化列表設置final修飾的常量

class Person {
  final String name;
  final String sex;
  Person(name, sex)
    :name = name,
     sex = sex;
}

重定向構造函數

重定向到同一個類的其他構造函數去,重定向構造函數體爲空,構造函數的調用是在冒號(:)之後

void main(){
  var p = new Person.nav('zhangsan');
  print(p.name);
  print(p.age);
}

class Person {
  String name;
  int age;
  Person(this.name, this.age);
  Person.nav(name): this(name, 12);
}

常量構造函數

如果該類實例化的對象是不變的,可以把這些對象定義爲編譯時常量
定義一個Point類,包含一個常量構造函數,注意其成員都是final類型,且構造函數用const修飾

class Point {
  final int x;
  const Point(this.x);
}

構造兩個相同的編譯時常量會產生一個唯一的標準的實例

void main(){
  var p1  = const Point(2);
  var p2  = const Point(2);
  print(p1);
  print(p2);
  print(identical(p1,p2));  // true 說明這倆是同一個實例
}
void main(){
  var p1  = Point(2);
  var p2  = const Point(2);
  print(p1);
  print(p2);
  print(identical(p1,p2));  // false  說明這倆不是同一個實例
}

獲取對象類型
runtimeType 屬性,可以在運行時獲取對象的類型

var p = new Person();
print(p.runtimeType); 

工廠構造函數

當執行構造函數並不總是創建這個類的一個新實例時,則使用factory 關鍵字。 例如,一個工廠構造函數可能會返回一個 cache 中的實例, 或者可能返回一個子類的實例。

void main(){
  var m = new Machine('test');
  m.produce('computer');
}

class Machine {
  final String name;
  static final Map<String , Machine> _cache = <String , Machine>{};
  factory Machine(String name) {
    if(_cache.containsKey(name)){
      return _cache[name];
    }else{
      final machine = Machine._internal(name);
      _cache[name] = machine;
      return machine;
    }
  }
  Machine._internal(this.name);
  void produce(String sample){
    print(sample);
  }
}

實例方法

對象的實例方法可以訪問this和實例變量

void main(){
  var p = new Person('zhangsan');
  p.say();
}

class Person {
  String name;
  Person(this.name);
  say(){
    print(name);
  }
}

Getter 和 Setter

用於對象屬性的讀和寫,使用get和set關鍵字實現Getter和Setter

void main(){
  var t = new T(1,2);
  print(t.z);  // 3
  t.z = 4;
  print(t.x);  // 2
}

class T {
  int x,y;
  T(this.x, this.y);
  int get z => x + y;
  set z(int value) => x = value - y;
}

抽象類

修飾符 abstract 來定義一個抽象類,抽象類不能被實例化,只有繼承它的子類可以,通常用來定義接口,以及部分實現,下面是一個具有抽象方法的抽象類

void main(){
  Dog dog = new Dog();
  dog.eat();
}

abstract class Animal {
  // 抽象方法 
  void eat();
}
class Dog extends Animal {
  eat(){
    print('dog eat');
  }
}

沒有方法體的方法我們稱爲抽象方法.如果子類繼承抽象類必須得實現裏面的抽象方法.抽象類主要是用來約束子類.下面的例子中,抽象類中的普通方法是不需要被重寫的.

void main(){
  Dog dog = new Dog();
  dog.eat();
  dog.fn();
}

abstract class Animal {
  void eat();  // 抽象方法 
  fn(){  // 普通方法 
    print('la la la');
  }
}
class Dog extends Animal {
  eat(){
    print('dog eat');
  }
}

extends抽象類 和 implements的區別:

  • 如果要複用抽象類裏面的方法,並且要用抽象方法約束子類的話我們就用extends繼承抽象類
  • 如果只是把抽象類當作標準的話我們就用implements實現抽象類

一個類實現多個接口

class Dog implements Animal,Animal2 {
  ...
}

extends關鍵字創建子類, super關鍵字引用父類

class Test1 {
  fn(){
    print('fn');
  }
}

class Test2 extends Test1 {
  fn(){
    super.fn();
    print('test2-fn');
  }
}

枚舉類型

枚舉類型是一種特殊的類,用於表示數量固定的常量值
定義一個枚舉類型

enum Animal { dog, cat, fish}

返回枚舉值的索引

print(Animal.fish.index);

使用枚舉的values常量,獲取所有枚舉值列表

List<Animal> animal = Animal.values;

mixin混合

複用類代碼的一種方式,複用的類在不同層級之間,不存在繼承關係.通過創建一個繼承自Object且沒有構造函數的類來實現一個mixin.
用法: with後跟一個或多個mixin的名字

void main(){
  var s = new Student('zhangsan', 12);
  print(s.age);
  print(s.isBoy);
  s.fn();
}

mixin M {
  bool isBoy = true;
}
mixin M2 {
  void fn(){
    print('M2 - fn');
  }
}

class Person {
  String name;
  Person(this.name);
}

class Student extends Person with M,M2 {
  int age;
  Student(String name, int age):super(name){
    this.age = age;
  }
}

類靜態變量和方法

它們不能被實例使用,因此也不能訪問this

void main(){
  Person.fn2();
  print(Person.a);
}

class Person {
  static const a = 2;
  static fn2(){
    print('fn2');
  }
}

call()方法

通過實現類的call()方法,讓類像函數一樣被調用

void main(){
  var t = new Test();
  print(t('aa','bb'));
}
class Test {
  call(String a, String b) => '$a$b';
}

泛型

泛型的好處是正確指定泛型可以提高代碼質量,減少重複的代碼
運行時中的泛型集合,Dart中泛型類型是固化的,即它們在運行時是攜帶類型信息的.

var list = List<int>();
list.addAll([2,3]);
assert(list is List<int>);

庫不僅提供API,還封裝代碼,下劃線(_)開頭的標識符只能在庫中可見,庫可通過包來分發

引用庫

引用內置庫

import 'dart:html';

引用其他庫

import 'package:pages/index.dart';

指定庫前綴

如果導入兩個存在衝突標識符的庫,可爲其制定庫前綴

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

Element ele1 = Element();  // 使用 lib1 中的 Element。
lib2.Element ele2 = lib2.Element();  // 使用 lib2 中的 Element。

導入部分庫

只導入fn

import 'package:pages/index.dart' show fn;

除了fn都導入

import 'package:pages/index.dart' hide fn;

延遲加載

在需要庫的時候再加載

import 'package:pages/index.dart' deferred as lib1;

當需要使用的時候

Future getLib() async {
  await lib1.loadLibrary();
  lib1.fn();
}

一個庫可以多次調用loadLibrary,但只會載入一次

異步編程

Future getLib() async {
  await lib1.loadLibrary();
  lib1.fn();
}

其中async 表明這是一個異步函數,await等待其完成後,再執行下面的lib1.fn(),這看起來很像同步代碼
假如我們要在主函數main中使用await,那麼main()的函數體就必須被標記

Future main() async {
  await xx();
}

在一個異步函數中可以使用多個await
在await表達式中,其值通常是一個Future對象,有點類似於JS中的Promise

聲明異步函數

同步寫法

String fn() => 'zhangsan';

異步寫法

Future<String> fn() async => 'zhangsan';

若函數沒有返回值,則返回類型聲明爲Future<void>

typedef

因爲在Dart中一切皆對象,函數也是,我們可以使用typedef爲函數起一個別名,用來聲明字段及返回值類型
未使用typedef

void main(){
  Test t = Test(foo);
  print(t.fn is Function);
}
class Test {
  Function fn;
  Test(bool f(String a, int b)){
    fn = f;
  }
}
bool foo(String a, int b) => true;

上面的代碼在將f賦值給fn時,類型信息就丟失了,但是如果我們使用顯式的名字保留類型信息,這樣開發者和工具都可以使用它們.

使用typedef

void main(){
  var t = new Test(foo);
  print(t.fn is Function);
  print(t.fn is Fn);
}
typedef Fn = bool Function(String a, int b);
class Test {
  Fn fn;
  Test(this.fn);
}
bool foo(String a, int b) => true;

元數據

元數據註釋以@開頭,Dart中有兩種可用註解: @deprecated@override
下面是@deprecated 的用法

void main(){
  var t = new Test();
  t.oldFn();  // 在vscode中,這裏會有一條刪除線提示我們這個方法是不推薦使用的,但它仍是能夠執行的
  t.newFn();
}

class Test {
  @deprecated
  void oldFn(){
    newFn();
  }
  
  newFn(){
    print(1);
  }
}

註釋

普通註釋

單行註釋

// print(1);

多行註釋

/*
print(1);
print(1);
*/

文檔註釋

單行註釋

/// 繼承自[Test2]

多行註釋

/** 
 * 繼承自[Test2]
 * 有個參數
**/

[Test2]會成爲一個鏈接,指向Test2類的API文檔

總結: 以上便是Dart的基本語法.相信大家看着這篇指南,應該能在Dart的世界裏面入個門吧.Dart作爲Flutter的開發語言,隨着Flutter應用的越來越流行,相信在不久的將來也會有Dart的一席之地.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章