Dart 語言基礎入門篇

葉大俠發表於2020-01-07

Dart 語言基礎入門篇

本文是【從零開始學習,開發個Flutter App】路上的第 1 篇文章。

這篇文章介紹了 Dart 的基礎特性,目的在於讓大家建立對 Dart 語言的總體認知,初步掌握 Dart 的語法。

我們假定讀者已經有一定的程式設計基礎,如果你瞭解 JavaScript 或者 Java 等面嚮物件語言,那 Dart 學習起來應該很有親切感。

Dart 是一門採取眾家之長的程式語言。儘管 Dart 很多語法和 JavaScript 很相似,但 Dart 語言同時是一門強型別的語言,它同時結合了像 Java 這樣強型別面嚮物件語言的特性,這使得它能勝任大型應用開發,同時它沒有 Java 的臃腫,Dart 語言在設計上非常簡潔、靈活和高效。

JavaScript 從簡單的瀏覽器指令碼到服務端(nodejs),慢慢延伸到PC客戶端(electron)、App (React Native)甚至小程式開發,它已然成為一門真正意義上的全棧開發語言。

如果說 JavaScript 是在漫長的時光裡野蠻生長,那 Dart 從一開始就是精心設計出來的。如果說有一門語言取代JavaScript的位置,那很可能就是Dart。

Talk is cheep,下面就讓我們來親自感受一下這門語言的吧。

變數

你可以像 JavaScript 那樣宣告一個變數:

var name = 'Bob';
複製程式碼

編譯器會推匯出 name 的型別是String 型別,等價於:

String name = 'Bob';
複製程式碼

我們可以從下面程式碼窺見 Dart 是強型別語言的特性:

var name = 'Bob';
  
// 呼叫 String 的方法
print(name.toLowerCase());

// 編譯錯誤
// name = 1;
複製程式碼

前面我們說過,Dart 除了具備簡潔的特點,而且也可以是非常靈活的,如果你想變換一個變數的型別,你也可以使用dynamic 來宣告變數,這就跟 JavaScript 一樣了:

dynamic name = 'Bob'; //String 型別
name = 1;// int 型別
print(name);
複製程式碼

上面的程式碼可以正常編譯和執行,但除非你有足夠的理由,請不要輕易使用。

final 的語義和 Java 的一樣,表示該變數是不可變的:

// String 可以省略
final String name = 'Bob'; 

// 編譯錯誤
// name = 'Mary';
複製程式碼

其中 String 可以省略,Dart 編譯器足夠聰明地知道變數name 的型別。

如果要宣告常量,可以使用const 關鍵詞:

const PI = '3.14';

class Person{
  static const name = 'KK';
}

複製程式碼

如果類變數,則需要宣告為static const

內建型別

不像Java把型別分的特別細,比如整數型別,就有byteshortintlong 。Dart 的型別設計相當簡潔,這也是 Dart 容易上手的原因之一,可以理解為通過犧牲空間來換取效率吧。

數值型別

Dart 內建支援兩種數值型別,分別是intdouble ,它們的大小都是64位。

var x = 1;
// 0x開頭為16進位制整數
var hex = 0xDEADBEEF;


var y = 1.1;
// 指數形式
var exponents = 1.42e5;
複製程式碼

需要注意的是,在Dart中,所有變數值都是一個物件,intdouble型別也不例外,它們都是num型別的子類,這點和JavaJavaScript都不太一樣:

// String -> int
var one = int.parse('1');
assert(one == 1);

// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);

// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');

// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');
複製程式碼

字串

Dart 字串使用的是UTF-16編碼。

var s = '中';
s.codeUnits.forEach((ch) => print(ch));
// 輸出為UNICODE值
20013
複製程式碼

Dart 採用了 JavaScript 中類似模板字串的概念,可以在字串通過${expression}語法插入變數:

var s = "hello";
  
print('${s}, world!');
  
//可以簡化成:
print('$s, world!');

//呼叫方法
print('${s.toUpperCase()}, world!');
複製程式碼

Dart 可以直接通過==來比較字串:

var s1 = "hello";
var s2 = "HELLO";
assert(s1.toUpperCase() == s2);
複製程式碼

布林型別

Dart 布林型別對應為bool關鍵詞,它有truefalse兩個值,這點和其他語言區別不大。值得一提的是,在Dart的條件語句ifassert表示式裡面,它們的值必須是bool型別,這點和 JavaScript 不同。

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

if(s.isNotEmpty){
// do something
}
  
//編譯錯誤,在JavaScript常用來判斷undefined
if(s){
}
複製程式碼

Lists

你可以把Dart中的List對應到 JavaScript 的陣列或者 Java 中的ArrayList,但 Dart 的設計更為精巧。

你可以通過類似 JavaScript 一樣宣告一個陣列物件:

var list = [];
list.add('Hello');
list.add(1);
複製程式碼

這裡List容器接受的型別是dynamic,你可以往裡面新增任何型別的物件,但如果像這樣宣告:

var iList = [1,2,3];
iList.add(4);
//編譯錯誤 The argument type 'String' can't be assigned to the parameter type 'int'
//iList.add('Hello');
複製程式碼

那麼Dart就會推匯出這個List是個List<int>,從此這個List就只能接受int型別資料了,你也可以顯式宣告List的型別:

var sList = List<String>();

//在Flutter類庫中,有許多這樣的變數宣告:
List<Widget> children = const <Widget>[];
複製程式碼

上面右邊那個 const 的意思表示常量陣列,在這裡你可以理解為一個給children賦值了一個編譯期常量空陣列,這樣的做法可以很好的節省記憶體,下面的例子可以讓大家更好的理解常量陣列的概念:

var constList = const <int>[1,2];
constList[0] = 2; //編譯通過, 執行錯誤
constList.add(3); //編譯通過, 執行錯誤
複製程式碼

Dart2.3 增加了擴充套件運算子 (spread operator) ......?,通過下面的例子你很容易就明白它們的用法:

var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);
複製程式碼

如果擴充套件物件可能是null,可以使用...?

 var list;
 var list2 = [0, ...?list];
 assert(list2.length == 1);
複製程式碼

你可以直接在元素內進行判斷,決定是否需要某個元素:

var promoActive = true;
var nav = [
    'Home',
    'Furniture',
    'Plants',
    promoActive? 'About':'Outlet'
];
複製程式碼

甚至使用for來動態新增多個元素:

var listOfInts = [1, 2, 3];
var listOfStrings = [
  '#0',
  for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');
複製程式碼

這種動態的能力使得 Flutter 在構建 Widget 樹的時候非常方便。

Sets

Set的語意和其他語言的是一樣的,都是表示在容器中物件唯一。在Dart中,Set預設是LinkedHashSet實現,表示元素按新增先後順序排序。

宣告Set物件:

var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
複製程式碼

遍歷Set,遍歷除了上面提到的for...in,你還可以使用類似 Java 的 lambada 中的 forEach 形式:

halogens.add('bromine');
halogens.add('astatine');
halogens.forEach((el) => print(el));
複製程式碼

輸出結果:

fluorine
chlorine
bromine
iodine
astatine
複製程式碼

除了容器的物件唯一特性之外,其他基本和List是差不多的。

// 新增型別宣告:
var elements = <String>{};

var promoActive = true;
// 動態新增元素
final navSet = {'Home', 'Furniture', promoActive? 'About':'Outlet'};
複製程式碼

Maps

Map物件的宣告方式保持了 JavaScript 的習慣,Dart 中Map的預設實現是LinkedHashMap,表示元素按新增先後順序排序。

var gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
};

assert(gifts['first'] == 'partridge');
複製程式碼

新增一個鍵值對:

gifts['fourth'] = 'calling birds';
複製程式碼

遍歷Map

gifts.forEach((key,value) => print('key: $key, value: $value'));
複製程式碼

函式

在 Dart 中,函式本身也是個物件,它對應的型別是Function,這意味著函式可以當做變數的值或者作為一個方法入傳引數值。

void sayHello(var name){
  print('hello, $name');
}

void callHello(Function func, var name){
  func(name);
}

void main(){
  // 函式變數
  var helloFuc = sayHello;
  // 呼叫函式
  helloFuc('Girl');
  // 函式引數
  callHello(helloFuc,'Boy');
}
複製程式碼

輸出:

hello, Girl
hello, Boy
複製程式碼

對於只有一個表示式的簡單函式,你還可以通過=>讓函式變得更加簡潔,=> expr在這裡相當於{ return expr; } ,我們來看一下下面的語句:

String hello(var name ) => 'hello, $name';
複製程式碼

相當於:

String hello(var name ){
  return 'hello, $name';
}
複製程式碼

引數

在Flutter UI庫裡面,命名引數隨處可見,下面是一個使用了命名引數(Named parameters)的例子:

void enableFlags({bool bold, bool hidden}) {...}
複製程式碼

呼叫這個函式:

enableFlags(bold: false);
enableFlags(hidden: false);
enableFlags(bold: true, hidden: false);
複製程式碼

命名引數預設是可選的,如果你需要表達該引數必傳,可以使用@required

void enableFlags({bool bold, @required bool hidden}) {}
複製程式碼

當然,Dart 對於一般的函式形式也是支援的:

void enableFlags(bool bold, bool hidden) {}
複製程式碼

和命名引數不一樣,這種形式的函式的引數預設是都是要傳的:

enableFlags(false, true);
複製程式碼

你可以使用[]來增加非必填引數:

void enableFlags(bool bold, bool hidden, [bool option]) {}
複製程式碼

另外,Dart 的函式還支援設定引數預設值:

void enableFlags({bool bold = false, bool hidden = false}) {...}

String say(String from, [String device = 'carrier pigeon', String mood]) {}
複製程式碼

匿名函式

顧名思意,匿名函式的意思就是指沒有定義函式名的函式。你應該對此不陌生了,我們在遍歷ListMap的時候已經使用過了,通過匿名函式可以進一步精簡程式碼:

var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});
複製程式碼

閉包

Dart支援閉包。沒有接觸過JavaScript的同學可能對閉包(closure)比較陌生,這裡給大家簡單解釋一下閉包。

閉包的定義比較拗口,我們不去糾結它的具體定義,而是打算通過一個具體的例子去理解它:

Function closureFunc() {
  var name = "Flutter"; // name 是一個被 init 建立的區域性變數
  void displayName() { // displayName() 是內部函式,一個閉包
    print(name); // 使用了父函式中宣告的變數
  }
  return displayName;
}

void main(){
  //myFunc是一個displayName函式
  var myFunc = closureFunc(); //(1)
  
  // 執行displayName函式
  myFunc(); // (2)
}
複製程式碼

結果如我們所料的那樣列印了Flutter

在(1)執行完之後,name作為一個函式的區域性變數,引用的物件不是應該被回收掉了嗎?但是當我們在內函式呼叫外部的name時,它依然可以神奇地被呼叫,這是為什麼呢?

這是因為Dart在執行內部函式時會形成閉包,閉包是由函式以及建立該函式的詞法環境組合而成,這個環境包含了這個閉包建立時所能訪問的所有區域性變數

我們簡單變一下程式碼:

Function closureFunc() {
  var name = "Flutter"; // name 是一個被 init 建立的區域性變數
  void displayName() { // displayName() 是內部函式,一個閉包
    print(name); // 使用了父函式中宣告的變數
  }
  name = 'Dart'; //重新賦值
  return displayName;
}
複製程式碼

結果輸出是Dart,可以看到內部函式訪問外部函式的變數時,是在同一個詞法環境中的。

返回值

在Dart中,所有的函式都必須有返回值,如果沒有的話,那將自動返回null

foo() {}

assert(foo() == null);
複製程式碼

流程控制

這部分和大部分語言都一樣,在這裡簡單過一下就行。

if-else

if(hasHause && hasCar){
    marry();
}else if(isHandsome){
    date();
}else{
    pass();
}
複製程式碼

迴圈

各種for:

var list = [1,2,3];

for(var i = 0; i != list.length; i++){}

for(var i in list){}
複製程式碼

while和迴圈中斷(中斷也是在for中適用的):

  var i = 0;
  while(i != list.length){
    if(i % 2 == 0){
       continue;
    }
     print(list[i]);
  }

  i = 0;
  do{
    print(list[i]);
    if(i == 5){
      break;
    }
  }while(i != list.length);
複製程式碼

如果物件是Iterable型別,你還可以像Java的 lambada 表示式一樣:

list.forEach((i) => print(i));
  
list.where((i) =>i % 2 == 0).forEach((i) => print(i));
複製程式碼

switch

switch可以用於intdoubleStringenum等型別,switch 只能在同型別物件中進行比較,進行比較的類不要覆蓋==運算子。

var color = '';
  switch(color){
    case "RED":
      break;
    case "BLUE":
      break;
    default:
      
  }
複製程式碼

assert

在Dart中,assert語句經常用來檢查引數,它的完整表示是:assert(condition, optionalMessage),如果conditionfalse,那麼將會丟擲[AssertionError]異常,停止執行程式。

assert(text != null);

assert(urlString.startsWith('https'), 'URL ($urlString) should start with "https".');
複製程式碼

assert 通常只用於開發階段,它在產品執行環境中通常會被忽略。在下面的場景中會開啟assert

  1. Flutter 的 debug mode
  2. 一些開發工具比如dartdevc預設會開啟。
  3. 一些工具,像dartdart2js ,可以通過引數--enable-asserts 開啟。

異常處理

Dart 的異常處理和Java很像,但是Dart中所有的異常都是非檢查型異常(unchecked exception),也就是說,你不必像 Java 一樣,被強制需要處理異常。

Dart 提供了ExceptionError 兩種型別的異常。 一般情況下,你不應該對Error型別錯誤進行捕獲處理,而是儘量避免出現這類錯誤。

比如OutOfMemoryErrorStackOverflowErrorNoSuchMethodError等都屬於Error型別錯誤。

前面提到,因為 Dart 不像 Java 那樣可以宣告編譯期異常,這種做法可以讓程式碼變得更簡潔,但是容易忽略掉異常的處理,所以我們在編碼的時候,在可能會有異常的地方要注意閱讀API文件,另外自己寫的方法,如果有異常丟擲,要在註釋處進行宣告。比如類庫中的File類其中一個方法註釋:

  /**
   * Synchronously read the entire file contents as a list of bytes.
   *
   * Throws a [FileSystemException] if the operation fails.
   */
  Uint8List readAsBytesSync();
複製程式碼

丟擲異常

throw FormatException('Expected at least 1 section');
複製程式碼

throw除了可以丟擲異常物件,它還可以丟擲任意型別物件,但建議還是使用標準的異常類作為最佳實踐。

throw 'Out of llamas!';
複製程式碼

捕獲異常

可以通過on 關鍵詞來指定異常型別:

 var file = File("1.txt");
  try{
    file.readAsStringSync();
  } on FileSystemException {
     //do something
  }
複製程式碼

使用catch關鍵詞獲取異常物件,catch有兩個引數,第一個是異常物件,第二個是錯誤堆疊。

try{
    file.readAsStringSync();
} on FileSystemException catch (e){
    print('exception: $e');
} catch(e, s){ //其餘型別
   print('Exception details:\n $e');
   print('Stack trace:\n $s');
}
複製程式碼

使用rethrow 拋給上一級處理:

 try{
    file.readAsStringSync();
  } on FileSystemException catch (e){
    print('exception: $e');
  } catch(e){
     rethrow;
  }
複製程式碼

finally

finally一般用於釋放資源等一些操作,它表示最後一定會執行的意思,即便try...catch中有return,它裡面的程式碼也會承諾執行。

try{
     print('hello');
     return;
  } catch(e){
     rethrow;
  } finally{
     print('finally');
}
複製程式碼

輸出:

hello
finally
複製程式碼

Dart 是一門物件導向的程式語言,所有物件都是某個類的例項,所有類繼承了Object類。

一個簡單的類:

class Point {
  num x, 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);
  }
}
複製程式碼

類成員

Dart 通過. 來呼叫類成員變數和方法的。

//建立物件,new 關鍵字可以省略
var p = Point(2, 2);

// Set the value of the instance variable y.
p.y = 3;

// Get the value of y.
assert(p.y == 3);

// Invoke distanceTo() on p.
num distance = p.distanceTo(Point(4, 4));
複製程式碼

你還可以通過.?來避免null物件。在Java 裡面,經常需要大量的空判斷來避免NullPonterException,這是讓人詬病Java的其中一個地方。而在Dart中,可以很方便地避免這個問題:

// If p is non-null, set its y value to 4.
p?.y = 4;
複製程式碼

在 Dart 中,沒有privateprotectedpublic這些關鍵詞,如果要宣告一個變數是私有的,則在變數名前新增下劃線_,宣告瞭私有的變數,只在本類庫中可見。

class Point{
  num _x;
  num _y;
}
複製程式碼

構造器(Constructor)

如果沒有宣告構造器,Dart 會給類生成一個預設的無參構造器,宣告一個帶引數的構造器,你可以像 Java這樣:

class Person{
  String name;
  int sex;
  
  Person(String name, int sex){
    this.name = name;
    this.sex = sex;
  }
}
複製程式碼

也可以使用簡化版:

Person(this.name, this.sex);
複製程式碼

或者命名式構造器:

Person.badGirl(){
    this.name = 'Bad Girl';
    this.sex = 1;
}
複製程式碼

你還可以通過factory關鍵詞來建立例項:

Person.goodGirl(){
	this.name = 'good Girl';
	this.sex = 1;
}

factory Person(int type){
	return type == 1 ? Person.badGirl(): Person.goodGirl();
}
複製程式碼

factory對應到設計模式中工廠模式的語言級實現,在 Flutter 的類庫中有大量的應用,比如Map

// 部分程式碼
abstract class Map<K, V> {  
	factory Map.from(Map other) = LinkedHashMap<K, V>.from;
}
複製程式碼

如果一個物件的建立過程比較複雜,比如需要選擇不同的子類實現或則需要快取例項等,你就可以考慮通過這種方法。在上面Map例子中,通過宣告 factory來選擇了建立子類LinkedHashMapLinkedHashMap.from也是一個factory,裡面是具體的建立過程)。

如果你想在物件建立之前的時候還想做點什麼,比如引數校驗,你可以通過下面的方法:

 Person(this.name, this.sex): assert(sex == 1)
複製程式碼

在構造器後面新增的一些簡單操作叫做initializer list

在Dart中,初始化的順序如下:

  1. 執行initializer list;
  2. 執行父類的構造器;
  3. 執行子類的構造器。
class Person{
  String name;
  int sex;

  Person(this.sex): name = 'a', assert(sex == 1){
    this.name = 'b';
    print('Person');
  }

}

class Man extends Person{
   Man(): super(1){
     this.name = 'c';
     print('Man');
   }
}

void main(){
  Person person = Man();
  print('name : ${person.name}');
}
複製程式碼

上面的程式碼輸出為:

Person
Man
name : c
複製程式碼

如果子類構造器沒有顯式呼叫父類構造器,那麼預設會呼叫父類的預設無參構造器。顯式呼叫父類的構造器:

Man(height): this.height = height, super(1);
複製程式碼

重定向構造器:

Man(this.height, this.age): assert(height > 0), assert(age > 0);

Man.old(): this(12, 60); //呼叫上面的構造器
複製程式碼

Getter 和 Setter

在 Dart 中,對 GetterSetter 方法有專門的優化。即便沒有宣告,每個類變數也會預設有一個get方法,在隱含介面章節會有體現。

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;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}
複製程式碼

抽象類

Dart 的抽象類和Java差不多,除了不可以例項化,可以宣告抽象方法之外,和一般類沒有區別。

abstract class AbstractContainer {
  num _width;

  void updateChildren(); // 抽象方法,強制繼承子類實現該方法。

  get width => this._width;
  
  int sqrt(){
    return _width * _width;
  }
}
複製程式碼

隱含介面

Dart 中的每個類都隱含了定義了一個介面,這個介面包含了這個類的所有成員變數和方法,你可以通過implements關鍵詞來重新實現相關的介面方法:

class Person {
  //隱含了 get 方法
  final _name;

  Person(this._name);

  String greet(String who) => 'Hello, $who. I am $_name.';
}

class Impostor implements Person {
  // 需要重新實現
  get _name => '';

  // 需要重新實現
  String greet(String who) => 'Hi $who. Do you know who I am?';
}
複製程式碼

實現多個介面:

class Point implements Comparable, Location {...}
複製程式碼

繼承

和Java基本一致,繼承使用extends關鍵詞:

class Television {
  void turnOn() {
    doSomthing();
  }
}

class SmartTelevision extends Television {

  @override
  void turnOn() {
    super.turnOn(); //呼叫父類方法
    doMore();
  }
}
複製程式碼

過載操作符

比較特別的是,Dart 還允許過載操作符,比如List類支援的下標訪問元素,就定義了相關的介面:

 E operator [](int index);
複製程式碼

我們通過下面的例項來進一步說明過載操作符:

class MyList{

  var list = [1,2,3];

  operator [](int index){
    return list[index];
  }
}

void main() {
  var list = MyList();
  print(list[1]); //輸出 2
}
複製程式碼

擴充套件方法

這個特性也是Dart讓人眼前一亮的地方(Dart2.7之後才支援),可以對標到 JavaScript 中的 prototype。通過這個特性,你甚至可以給類庫新增新的方法:

//通過關鍵詞 extension 給 String 類新增新方法
extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }
}
複製程式碼

後面String物件就可以呼叫該方法了:

print('42'.parseInt()); 
複製程式碼

列舉型別

列舉型別和保持和Java的關鍵詞一致:

enum Color { red, green, blue }
複製程式碼

switch中使用:

// color 是 enmu Color 型別
switch(color){
    case Color.red:
      break;
    case Color.blue:
      break;
    case Color.green:
      break;
    default:
      break;
}
複製程式碼

列舉型別還有一個index的getter,它是個連續的數字序列,從0開始:

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
複製程式碼

新特性:Mixins

這個特性進一步增強了程式碼複用的能力,如果你有寫過Android的佈局XML程式碼或者Freemaker模板的話,那這個特性就可以理解為其中inlclude 的功能。

宣告一個mixin類:

mixin 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');
    }
  }
}
複製程式碼

通過with關鍵詞進行復用:

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}
複製程式碼

mixin類甚至可以通過on關鍵詞實現繼承的功能:

mixin MusicalPerformer on Musician {
  // ···
}
複製程式碼

類變數和類方法

class Queue {
  //類變數
  static int maxLength = 1000;
  // 類常量
  static const initialCapacity = 16;
  // 類方法
  static void modifyMax(int max){
    _maxLength = max;
  }
}

void main() {
  print(Queue.initialCapacity);
  Queue.modifyMax(2);
  print(Queue._maxLength);
}
複製程式碼

泛型

在物件導向的語言中,泛型主要的作用有兩點:

1、型別安全檢查,把錯誤扼殺在編譯期:

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
//編譯錯誤
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);
}
複製程式碼

你可以通過泛型把它們合併成一個類:

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}
複製程式碼

在Java中,泛型是通過型別擦除來實現的,但在Dart中實打實的泛型:

 var names = <String>[];
 names.addAll(['Tom',"Cat"]);
 // is 可以用於型別判斷
 print(names is List<String>); // true
 print(names is List); // true
 print(names is List<int>); //false
複製程式碼

你可以通過extends關鍵詞來限制泛型型別,這點和Java一樣:

abstract class Animal{}
class Cat extends Animal{}
class Ext<T extends Animal>{
  T data;
}

void main() {
  var e = Ext(); // ok
  var e1 = Ext<Animal>(); // ok
  var e2 = Ext<Cat>(); // ok
  var e3 = Ext<int>(); // compile error
}
複製程式碼

使用類庫

有生命力的程式語言,它背後都有一個強大的類庫,它們可以讓我們站在巨人的肩膀上,又免於重新造輪子。

匯入類庫

在Dart裡面,通過import關鍵詞來匯入類庫。

內建的類庫使用dart:開頭引入:

import 'dart:io';
複製程式碼

瞭解更多內建的類庫可以檢視這裡

第三方類庫或者本地的dart檔案用package:開頭:

比如匯入用於網路請求的dio庫:

import 'package:dio/dio.dart';
複製程式碼

Dart 應用本身就是一個庫,比如我的應用名是ccsys,匯入其他資料夾的類:

import 'package:ccsys/common/net_utils.dart';
import 'package:ccsys/model/user.dart';
複製程式碼

如果你使用IDE來開發,一般這個事情不用你來操心,它會自動幫你匯入的。

Dart 通過pub.dev來管理類庫,類似Java世界的Maven 或者Node.js的npm一樣,你可以在裡面找到非常多實用的庫。

解決類名衝突

如果匯入的類庫有類名衝突,可以通過as使用別名來避免這個問題:

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

// 使用來自 lib1 的 Element
Element element1 = Element();

// 使用來自 lib2 的 Element
lib2.Element element2 = lib2.Element();

複製程式碼

匯入部分類

在一個dart檔案中,可能會存在很多個類,如果你只想引用其中幾個,你可以增加show或者hide來處理:

//檔案:my_lib.dart
class One {}

class Two{}

class Three{}
複製程式碼

使用show匯入OneTwo類:

//檔案:test.dart
import 'my_lib.dart' show One, Two;

void main() {
  var one = One();
  var two = Two();
  //compile error
  var three = Three();
}
複製程式碼

也可以使用hide排除Three,和上面是等價的:

//檔案:test.dart
import 'my_lib.dart' hide Three;

void main() {
  var one = One();
  var two = Two();
}
複製程式碼

延遲載入庫

目前只有在web app(dart2js)中才支援延遲載入,Flutter、Dart VM是不支援的,我們這裡僅做一下簡單介紹。

你需要通過deferred as來宣告延遲載入該類庫:

import 'package:greetings/hello.dart' deferred as hello;
複製程式碼

當你需要使用的時候,通過loadLibrary()載入:

Future greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}
複製程式碼

你可以多次呼叫loadLibrary,它不會被重複載入。

非同步支援

這個話題稍微複雜,我們將用另外一篇文章獨立討論這個問題,請留意下一篇內容。

參考資料

  1. 學習Dart的十大理由

  2. A tour of the Dart language

  3. Dart 常量構造器的理解

  4. JavaScript 閉包

關於AgileStudio

我們是一支由資深獨立開發者和設計師組成的團隊,成員均有紮實的技術實力和多年的產品設計開發經驗,提供可信賴的軟體定製服務。

相關文章