Flutter學(xué)習(xí)筆記 - Dart語(yǔ)言

官方文檔。
在線體驗(yàn)。

@[toc]

Dart是谷歌開(kāi)發(fā)并在2011年亮相,2015推出了基于Dart語(yǔ)言的移動(dòng)應(yīng)用程序的開(kāi)發(fā)框架 Sky,后更名為 Flutter。經(jīng)過(guò)多年的發(fā)展和完善,F(xiàn)lutter 逐漸成為公司開(kāi)發(fā)應(yīng)用程序的新寵兒。
Dart是面向?qū)ο蟮摹㈩惗x的、單繼承的語(yǔ)言。它的語(yǔ)法類似C語(yǔ)言,可以轉(zhuǎn)譯為JavaScript,支持接口(interfaces)、混入(mixins)、抽象類(abstract classes)、具體化泛型(reified generics)、可選類型(optional typing)和sound type system。

體驗(yàn) Dart 程序

// 定義一個(gè)函數(shù)
printInteger(int aNumber) {
  print('The number is $aNumber.'); // 打印輸出到控制臺(tái)。
}

// Dart 程序從 main() 函數(shù)開(kāi)始執(zhí)行。
main() {
  var number = 42; // 聲明并初始化一個(gè)變量。
  printInteger(number); // 調(diào)用一個(gè)函數(shù)。
}

Dart 應(yīng)用程序總是會(huì)從頂級(jí)函數(shù) main() 開(kāi)始執(zhí)行;
Dart 語(yǔ)言需要 ; 符號(hào)作為每一條語(yǔ)句的結(jié)束符號(hào);
$variableName (或 ${expression}) 表示字符串插值。

簡(jiǎn)介

Dart 在語(yǔ)法上和一些熱門的語(yǔ)言非常相似,例如C、JavaScript、Python、Swift。語(yǔ)法的設(shè)計(jì)上有著非常多的相似之處,如果你使用或者了解多門語(yǔ)言,你能很輕松的掌握 Dart 語(yǔ)言。

學(xué)習(xí)不同的編程語(yǔ)言,通過(guò)相互之間的類比,是了解和掌握語(yǔ)言之間的差異性必要條件,筆者這里只記錄一些值得玩味的點(diǎn),讓你能快速的一名 Dart 語(yǔ)言的開(kāi)發(fā)者。想要更深入的了解 Dart 語(yǔ)言特性,請(qǐng)查閱 官方開(kāi)發(fā)文檔 以及相關(guān)的社區(qū)。

變量

String name = 'Bob'; // 指定類型,不可接收其他類型
var name = 'Bob'; // 推斷出類型,不可接收其他類型
Object name = 'Bob'; // 可接收其他類型,只能使用Object中定義的方法和變量,類似NSObject
dynamic name = 'Bob'; // 可接收其他類型,可以使用所有可能的方法和變量,類似id

final 和 const

如果你從未打算更改一個(gè)變量,那么使用 finalconst ,而不是 var 或者某一指定類型。 finalcons 修飾的變量只能被設(shè)置一次,區(qū)別在于, final 變量在第一次使用時(shí)被初始化,const 變量是一個(gè)編譯時(shí)期的常量。

//可以省略String這個(gè)類型聲明
final str = "hi world";
//final String str = "hi world"; 
const str1 = "hi world";
//const String str1 = "hi world";

內(nèi)置類型

Dart 語(yǔ)言內(nèi)置如下的類型:numbers、strings、booleans、list(數(shù)組)、sets(集合)maps(字典)、runes(Unicode字符)、symbols。

Dart 所有變量引用的都是對(duì)象類型,每個(gè)對(duì)象都是一個(gè)類型的實(shí)例。數(shù)字、函數(shù)或者 null 都是對(duì)象。所有的類都繼承自 Object 類。

  • numbers

    Dart 有兩種類型的 number:int 和 double,兩個(gè)都是 num 的子類。

    var x = 1;
    var hex = 0xDEADBEEF;
    var y = 1.1;
    double z = 1; // double z = 1.0.
    

    Dart 盡管是強(qiáng)類型語(yǔ)言,但是在聲明變量的時(shí)候,類型是可選的,因此你可以指定變量類型,如 double,也可以使用 var 聲明,由 Dart 自動(dòng)進(jìn)行類型推斷。

  • Strings

    var s1 = '使用單引號(hào)創(chuàng)建字符串字面量。';
    var s2 = "雙引號(hào)也可以用于創(chuàng)建字符串字面量。";
    var s3 = '使用單引號(hào)創(chuàng)建字符串時(shí)可以使用斜杠來(lái)轉(zhuǎn)義那些與單引號(hào)沖突的字符串:\'。';
    var s4 = "而在雙引號(hào)中則不需要使用轉(zhuǎn)義與單引號(hào)沖突的字符串:'";
    var s5 = r'在 raw 字符串中,轉(zhuǎn)義字符串 \n 會(huì)直接輸出 “\n” 而不是轉(zhuǎn)義為換行。';
    var s6 = '可以拼接'
    '字符串'
    "即便它們不在同一行。";
    var s7 = '使用加號(hào)+運(yùn)算符' + '也可以達(dá)到相同的效果。';
    var s8 = """
    你可以像這樣創(chuàng)建多行字符串
    """;
    var s9 = '字符串插值';
    print ('Dart 有$s9,使用起來(lái)非常方便');
    print ('使用${s9.substring(3,5)}表達(dá)式也非常方便');
    
  • Booleans

    布爾類型只有兩個(gè)對(duì)象 true 和 false,兩者都是編譯時(shí)常量。Dart 需要顯示地檢查布爾值,不允許使用其他類型作為條件,例如 int 類型。

    var fullName = '';
    assert(fullName.isEmpty);
    bool hited = false;
    assert(hited);
    
  • Lists

    Dart 中的數(shù)組類型的字面量和 JavaScript 中的數(shù)組字面量一樣。

    var list1 = [1,2,3];
    var count = list1.length; // 3
    // 替換
    list1[0] = 0; 
    var list2 = [4,5,6];
    // 拼接
    var list3 = list1 + list2; // [0, 2, 3, 4, 5, 6]
    // 擴(kuò)展符號(hào) ... 和 ...?
    var list4 = [1, ...list2]; // [1, 4, 5, 6]
    var list5; // null
    var list6 = [1,...?list5]; // [1],數(shù)組可能為空,使用 ...? 避免異常
    // 2.3版本的 Collection If
    bool promoActive = true;
    var list7 = [
      'Home',
      'Furniture',
      'Plants',
      if (promoActive) 'OutLet'
    ];
    // 2.3版本的 Collection For
    var listOfInts = [1,2,3];
    var list8 = [
      '0',
      for (var i in listOfInts) '$i'
    ];
    var list9 = [
      for (var i =1; i<4; i++) i,
      for (var i in [4,5,6]) i
    ];
    
  • Sets

    一組特定元素的無(wú)序集合。

    // Dart 2.2 版本之后才加入 Set 字面量的創(chuàng)建方式
    var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
    // 創(chuàng)建空 set 類型,添加非指定類型時(shí)異常,不指定類型的{}是空Map類型
    var names = <String>{};
    // 添加一個(gè)或一組
    names.add('luo');
    names.addAll(['liang','guo']);
    

    從 Dart2.3版本開(kāi)始,Set 和 List 一樣支持使用擴(kuò)展操作符 ... 和 ...? 以及 Collection If 和 Collection For 的方式創(chuàng)建實(shí)例。

  • Maps

    Maps是一組 key 和 value 的集合對(duì)象,在 OC 中,我們稱之為字典類型。Dart 中的 key 和 value 可以是任何類型的對(duì)象。

    // 字面量
    var fruits = {
        "first":"Apple",
        "second":"pear"
    };
    print(fruits);
    // 構(gòu)造函數(shù)
    var nobleGases = Map();
    nobleGases['1'] = 'helium';
    nobleGases['2'] = 'neon';
    // 所有的key
    var keys = nobleGases.keys;
    // 所有的值 
    var values = nobleGases.values;
    

    從 Dart2.3版本開(kāi)始,Map 和 List 一樣支持使用擴(kuò)展操作符 ... 和 ...? 以及 Collection If 和 Collection For 的方式創(chuàng)建實(shí)例。

  • Runes

    Unicode 編碼為每一個(gè)字母、數(shù)字和符號(hào)都定義了一個(gè)唯一的數(shù)值。因?yàn)?Dart 中的字符串是一個(gè) UTF-16 的字符序列,所以如果想要表示 32 位的 Unicode 數(shù)值則需要一種特殊的語(yǔ)法。

    通常使用 \uXXXX 來(lái)表示 Unicode 字符, XXXX 是一個(gè)四位數(shù)的 16 進(jìn)制數(shù)字。例如心形字符(?)的 Unicode 為 \u2665。對(duì)于不是四位數(shù)的 16 進(jìn)制數(shù)字,需要使用大括號(hào)將其括起來(lái)。例如大笑的 emoji 表情(??)的 Unicode 為 \u{1f600}。

    import 'package:characters/characters.dart';
    ...
    var hi = 'Hi ????';
    print(hi);
    print('The last character: ${hi.characters.last}\n');
    

函數(shù)

Dart 中函數(shù)也是一種對(duì)象類型:Function,這就意味著函數(shù)可以作為變量、參數(shù)和返回值使用。

// 返回類型可選,可不寫
返回值類型 函數(shù)名(參數(shù)列表) {
    函數(shù)體;
};
例如:

bool isNoble(int atomicNumber) {
    return _nobleGases[atomicNumber] != nil;
}

當(dāng)函數(shù)體只包含一個(gè)表達(dá)式,可以簡(jiǎn)寫成下面這樣,你可以經(jīng)常看到這種寫法:

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != nil;

可選參數(shù)

函數(shù)的參數(shù)有兩種:必須要參數(shù)和可選參數(shù)。必要參數(shù)定義在最前面,可選參數(shù)接在后面,可選參數(shù)又分為兩種:命名參數(shù)和位置參數(shù)。這兩種選擇其中之一,不能同時(shí)出現(xiàn)。

必要參數(shù)沒(méi)什么可說(shuō)的,我們來(lái)了解一下可選參數(shù)。

命名參數(shù)

通過(guò)名稱來(lái)傳遞參數(shù),名稱的位置可變動(dòng)。

通過(guò) {parm1, param2, ...} 的形式來(lái)傳遞參數(shù)。

// 定義可選參數(shù)
void enableFlags(String str, {bool blod, bool hidden}) {
  print("$str, $blod,$hidden");
}
// 調(diào)用
void main() {
  enableFlags("xiaoming",hidden: false, blod: true);
}

位置參數(shù)

使用 [] 將一系列參數(shù)包裹起來(lái)作為位置參數(shù)。

String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

提供默認(rèn)參數(shù)值

你可以為可選參數(shù)提供默認(rèn)值,默認(rèn)值必須為編譯時(shí)常量,沒(méi)有指定默認(rèn)值的情況下值為 null。

// 提供默認(rèn)參數(shù)
void enableFlags({bool bold = false, bool hidden = false}) {...}
String say(String from, String msg,
    [String device = 'carrier pigeon', String mood]) {...}

函數(shù)對(duì)象

函數(shù)可以看作是 Function 類,可以賦值給變量,也可以作為參數(shù)使用。

void main() {
  var method = printElement; // 賦值給變量
  [1,2,3].forEach(method); // 作為參數(shù)
}
printElement(int e) => print(e);

匿名函數(shù)

匿名函數(shù)是編程語(yǔ)言中關(guān)于函數(shù)的一種重要的變體:沒(méi)有名字的函數(shù),也叫 Lambda 表達(dá)式 或者 Closure 閉包。你可以在各種編程語(yǔ)言中找到它們,可以將匿名函數(shù)直接賦值給變量去使用,或者直接放在參數(shù)列表中使用(通常是最后一個(gè)參數(shù),部分語(yǔ)言稱之為尾隨閉包,有其他變體)。

(參數(shù)列表) {
    函數(shù)體;
};

// 使用匿名函數(shù)實(shí)現(xiàn)上個(gè)列子中的效果
[1,2,3].forEach((e) {
    print(e);
});
// 或者
[1,2,3].forEach( (e) => print(e) );

注意事項(xiàng)

  1. 同類的同一個(gè)方法在不同實(shí)例對(duì)象中不相等。
  2. 所有函數(shù)都有返回值,即使沒(méi)有顯示的返回語(yǔ)句(返回nil)。

運(yùn)算符

運(yùn)算符在編程語(yǔ)言中同樣占據(jù)著重要的地位,Dart 中同樣擁有并遵循的絕大部分的運(yùn)算符效果,例如算術(shù)運(yùn)算符、關(guān)系運(yùn)算符、類型判斷運(yùn)算符、邏輯運(yùn)算符等等。我們主要關(guān)注一些特別的運(yùn)算符的定義和使用場(chǎng)景。

  • 算術(shù)運(yùn)算符

    ~/ : 除并且取整 5 ~/ 2 的結(jié)果為 2。

  • 類型判斷運(yùn)算符

    Dart 作為一門面向?qū)ο笳Z(yǔ)言,類型判斷運(yùn)算符必不可免。常見(jiàn)的 as 類型轉(zhuǎn)換和 is 類型判斷。Dart 中有一個(gè)和 is 相反的類型判斷運(yùn)算符 is!,當(dāng)對(duì)象是指定類型則返回 false。

  • 賦值運(yùn)算符

    =、+=、-= 等等這樣的復(fù)合賦值運(yùn)算符在編程語(yǔ)言中非常常見(jiàn),Dart 中有 ??= 這樣的復(fù)合運(yùn)算符來(lái)為 null 的變量進(jìn)行默認(rèn)值的賦值。

    a ??= "xiaoming";
    // a = a ?? "xiaoming";
    
  • 級(jí)聯(lián)運(yùn)算符

    .. 可以讓你在同一個(gè)對(duì)象上連續(xù)調(diào)用多個(gè)對(duì)象的變量或者方法。

    querySelector('#confirm') // 獲取對(duì)象 (Get an object).
    ..text = 'Confirm' // 使用對(duì)象的成員 (Use its members).
    ..classes.add('important')
    ..onClick.listen((e) => window.alert('Confirmed!'));
    
  • 訪問(wèn)運(yùn)算符

    .?. ,Dart 通過(guò)點(diǎn)語(yǔ)法訪問(wèn)成員變量或者方法,?. 則會(huì)加上一層判斷,如果操作對(duì)象為 null,則停止訪問(wèn)并返回 null。

異常

Dart中處理異常沒(méi)有太多的差別,只是拋出的對(duì)象可以是任何非 null 對(duì)象而不局限于 Exception 或 Error 類型。

  • 拋出異常
throw FormatException('Expected at least 1 section');
  • 捕獲異常
try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // 指定異常
  buyMoreLlamas();
} on Exception catch (e) {
  // 其它類型的異常
  print('Unknown exception: $e');
} catch (e) {
  // 不指定類型,處理其它全部
  print('Something really unknown: $e');
} finally {
  // 總是清理,即便拋出了異常。
  cleanLlamaStalls();
}

Dart 是支持基于 mixin 繼承機(jī)制的面向?qū)ο笳Z(yǔ)言,所有對(duì)象都是一個(gè)類的實(shí)例,所有的類都繼承自 Object 類。基于 mixin 的繼承意味著除 Object 類之外都只能單繼承。Extension 方法是一種在不更改類型或創(chuàng)建子類的情況下向類添加功能的方法,類似于 Swift 中的 Extension,OC 中的分類。

構(gòu)造函數(shù)

聲明一個(gè)與類名一樣的函數(shù)即可聲明一個(gè)構(gòu)造函數(shù)。

class Point {
    double x, y;
    Point(double x, double y) {
        this.x = x;
        this.y = y;
    }
    // 或者使用 Dart 提供的語(yǔ)法糖
    Point(this.x, this.y);
}
  • 如果你沒(méi)有聲明構(gòu)造函數(shù),那么Dart 會(huì)自動(dòng)生成一個(gè)無(wú)參的構(gòu)造函數(shù)并且調(diào)用父類的無(wú)參構(gòu)造方法。
  • 構(gòu)造函數(shù)不被繼承,子類不繼承父類的構(gòu)造函數(shù),如果子類不聲明自己的構(gòu)造函數(shù),那么只會(huì)有一個(gè)默認(rèn)無(wú)參的構(gòu)造函數(shù)。

調(diào)用父類非默認(rèn)構(gòu)造函數(shù)

默認(rèn)情況下,本類的構(gòu)造函數(shù)會(huì)調(diào)用父類的無(wú)參構(gòu)造方法,并且先于本類的構(gòu)造函數(shù)之前,如果本類構(gòu)造函數(shù)存在一個(gè)初始化列表,那么調(diào)用父類的無(wú)參構(gòu)造方法會(huì)在該列表的初始化之后,本類的構(gòu)造函數(shù)之前,即下面的順序。

  1. 本類的初始化列表
  2. 父類的無(wú)參構(gòu)造函數(shù)
  3. 本類的構(gòu)造函數(shù)

如果父類沒(méi)有匿名無(wú)參構(gòu)造函數(shù),那么子類就必須在函數(shù)體前使用 : 指定調(diào)用父類其中一個(gè)構(gòu)造函數(shù)。

class Person {
  String firstName;
  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // 指定構(gòu)造函數(shù)
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});
  // in Person
  // in Employee
}

初始化列表

在構(gòu)造函數(shù)體之前,我們還可以初始化實(shí)例變量。

Point.fromJson(Map<String, double> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

重定向構(gòu)造函數(shù)

類似于便利構(gòu)造器,我們可以將提供一些默認(rèn)值后,調(diào)用類的其他構(gòu)造器。

class Point {
  double x, y;

  // 該類的主構(gòu)造函數(shù)。
  Point(this.x, this.y);

  // 委托實(shí)現(xiàn)給主構(gòu)造函數(shù)。
  Point.alongXAxis(double x) : this(x, 0);
}

Getter 和 Setter

Dart 中實(shí)例對(duì)象的每個(gè)屬性都有一個(gè)隱式的 Getter 方法,如果非 final 屬性的話還會(huì)有一個(gè) Setter 方法,你可以使用 get 和 set 關(guān)鍵字為額外的屬性添加 Getter 和 Setter 方法。

class Rectangle {
  double left, top, width, height;
  Rectangle(this.left, this.top, this.width, this.height);

  // 定義兩個(gè)計(jì)算產(chǎn)生的屬性:right 和 bottom。
  double get right => left + width;
  set right(double value) => left = value - width;
  double get bottom => top + height;
  set bottom(double value) => top = value - height;
}

抽象類

使用關(guān)鍵字 abstract 標(biāo)識(shí)符可以讓該類成為抽象類,抽象類一般無(wú)法被實(shí)例化,常用于聲明接口方法,有時(shí)也會(huì)有具體的方法實(shí)現(xiàn)。

abstract class AbstractContainer {
  // 定義構(gòu)造函數(shù)、字段、方法等……
  void updateChildren(); // 抽象方法。
}

隱式接口

每一個(gè)類都隱式地定義并實(shí)現(xiàn)了該接口,這個(gè)接口包含所有這個(gè)類的實(shí)例成員以及這個(gè)類所有實(shí)現(xiàn)的其他接口。如果你想要?jiǎng)?chuàng)建一個(gè) A 類,它支持所有 B 類中的 API,但是不想繼承 B,那么就可以通過(guò)關(guān)鍵字 implements 實(shí)現(xiàn) B 類中的隱式接口。

class Person {
  // _name 變量同樣包含在接口中,但它只是庫(kù)內(nèi)可見(jiàn)的。
  final _name;
  // 構(gòu)造函數(shù)不在接口中。
  Person(this._name);
  // greet() 方法在接口中。
  String greet(String who) => '你好,$who。我是$_name。';
}

// Person 接口的一個(gè)實(shí)現(xiàn)。
class Impostor implements Person {
  get _name => '';
  String greet(String who) => '你好$who。你知道我是誰(shuí)嗎?';
}

如果需要實(shí)現(xiàn)多個(gè)類接口,可以使用逗號(hào)分割每個(gè)接口類:

class Point implements Comparable, Location {...}

創(chuàng)建子類

使用 extends 關(guān)鍵字來(lái)創(chuàng)建一個(gè)子類,并可使用 super 關(guān)鍵字引用一個(gè)父類:

class Television {
  void turnOn() {
    ...
  }
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    ...
  }
}

重寫

你可以使用 @override 注解來(lái)表示你重寫來(lái)一個(gè)成員。

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
}

noSuchMethod()

如果調(diào)用了對(duì)象上不存在的方法或?qū)嵗兞繉?huì)觸發(fā) noSuchMethod 方法,可以通過(guò)重寫該方法來(lái)追蹤和記錄這一行為。

class A {
  // 除非你重寫 noSuchMethod,否則調(diào)用一個(gè)不存在的成員會(huì)導(dǎo)致 NoSuchMethodError。
  @override
  void noSuchMethod(Invocation invocation) {
  print('你嘗試使用一個(gè)不存在的成員:' +
  '${invocation.memberName}');
  }
}

你不能調(diào)用一個(gè)未實(shí)現(xiàn)的方法除非下面其中的一個(gè)條件成立:

  1. 接收方是靜態(tài)的 dynamic 類型
  2. 接收方具有靜態(tài)類型,定義了未實(shí)現(xiàn)的方法/抽象方法,并且實(shí)現(xiàn)了 noSuchMethod 方法且實(shí)現(xiàn)與 Object 中的不同

Extension 擴(kuò)展類

extension 是 Dart2.7 引入的,向現(xiàn)有庫(kù)添加功能的一種方式。

// 給 String 添加轉(zhuǎn)換為 Int 的功能
extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }
}
main() {
  // 使用新的方法
  print('123'.parseInt());
}

枚舉類型

一些固定數(shù)量的常量值,Dart 的枚舉功能沒(méi)有特別之處,我們看下常用的方法。

// 定義枚舉
enum Color { red, green, blue }
// 獲取索引
var idx = Color.red.index;
// 獲取所有的類型
List<Color> colors = Color.values;

Dart 中的枚舉類型還有兩個(gè)限制:

  1. 枚舉不能繼承,也不可以實(shí)現(xiàn)一個(gè)枚舉。
  2. 不能顯示地實(shí)例化一個(gè)枚舉類。

Mixin 模式

Mixin 是一種在多重繼承中復(fù)用某個(gè)類中代碼的方法模式。Dart 通過(guò)關(guān)鍵字 with 來(lái)使用 Mixin 模式。

mixin Athlete {
  run() {
    print("run.");
  }
}
mixin Singer {
  sing() {
    print("Sing.");
  }
}

class A with Athlete, Singer {
  // ...
}

void main() {
  var a = A();
  a.run();
  a.sing();
}
  • Minix 類:定一個(gè)類,繼承自 Object 但不為該類定義構(gòu)造函數(shù)
  • 單純的 Mixin 類:可以使用關(guān)鍵字 mixin 代替 class

類變量和類方法

類變量和類方法也成為靜態(tài)變量和靜態(tài)方法,它們屬于類而不是某個(gè)實(shí)例變量。

在變量和方法前通過(guò)關(guān)鍵字 static 可以聲明類變量和類方法。

類方法不能被實(shí)例訪問(wèn),因此內(nèi)部也不能使用 this。

泛型

泛型存在于很多編程語(yǔ)言中,例如 Python,Swfit,OC 等,Dart 語(yǔ)言同樣不例外,在之前 List<E> 這樣的聲明,使用到的就是泛型。<...> 符號(hào)表示一個(gè)泛型類,通常使用一個(gè)字母來(lái)表示類型參數(shù),例如E、T、S、K 和 V 等。

泛型常用于需要類型安全的情況,有了它可以更好地生成代碼,減少代碼量。拿 List 來(lái)舉例,List<String> 讓數(shù)組中的元素都必須是字符串類型,當(dāng)你放入非字符串類就會(huì)提前告知你錯(cuò)誤,另外,通過(guò)泛型,我們可以只編寫一份通用的 List 操作就可以適用于多種類型的操作。

類似的,Set 和 Map 同樣可以使用泛型來(lái)約束元素內(nèi)容。

var names = <String>['小蕓', '小芳', '小民'];
var uniqueNames = <String>{'小蕓', '小芳', '小民'};
var pages = <String, String>{
  'index.html': '主頁(yè)',
  'robots.txt': '網(wǎng)頁(yè)機(jī)器人提示',
  'humans.txt': '我們是人類,不是機(jī)器'
};

var nameSet = Set<String>.from(names);
var views = Map<int, View>();
var names = List<String>();
names.addAll(['小蕓', '小芳', '小民']);

限制參數(shù)化類型

有時(shí)想要泛型只支持一定的類型時(shí),可以通過(guò)關(guān)鍵字 extends 來(lái)限制泛型的范圍。

// 支持泛型,但是有約束范圍
class Foo<T extends SomeBaseClass> {...}
// 某類的子類
class Extender extends SomeBaseClass {...}

這樣你在使用 Foo 類時(shí),就可以使用 SomeBaseClass 或者其子類作為泛型參數(shù)。

var a = Foo<SomeBaseClass>();
var b = Foo<Extender>();
var c = Foo<Object>(); // 非SomeBaseClass類型時(shí),將發(fā)生錯(cuò)誤

泛型方法

Dart 中的泛型還可以用在方法中,我們稱使用泛型的方法為泛型方法。

T first<T>(List<T> ts) {
  // 處理一些初始化工作或錯(cuò)誤檢測(cè)……
  T tmp = ts[0];
  // 處理一些額外的檢查……
  return tmp;
}
  1. 方法的返回值為 T 類型
  2. 參數(shù) ts 為 T 類型的數(shù)組
  3. 臨時(shí)變量的類型為 T 類型

庫(kù)和可見(jiàn)性

代碼庫(kù)不僅只是提供 API 而且還起到了封裝的作用,庫(kù)中的私有成員通過(guò) _ 開(kāi)頭聲明。每個(gè) Dart 程序都是一個(gè)庫(kù)。

導(dǎo)入庫(kù)

對(duì)于 Dart 內(nèi)置庫(kù),使用 dart:xxx 的形式。

import 'dart:html';

而對(duì)于其他的庫(kù),使用系統(tǒng)路徑或者以 package:xxx 的形式 package:xxx 指定的庫(kù)通過(guò)包管理器,如pub工具,來(lái)提供。

import 'package:test/test.dart';

指定前綴

如果導(dǎo)入的庫(kù)有沖突,可以為其中一個(gè)指定前綴。

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

// 使用 lib1 的 Element 類。
Element element1 = Element();

// 使用 lib2 的 Element 類。
lib2.Element element2 = lib2.Element();

部分導(dǎo)入

// 導(dǎo)入 foo
import 'package:lib1/lib1.dart' show foo;
// 導(dǎo)入 除了 foo 外的所有
import 'package:lib2/lib2.dart' hide foo;

延遲加載庫(kù)

允許應(yīng)用在需要的時(shí)候才去加載庫(kù)代碼,延遲加載有幾個(gè)好處:

  1. 減少應(yīng)用的初始化時(shí)間
  2. 處理 A/B 測(cè)試,比如測(cè)試各種算法的不同實(shí)現(xiàn)
  3. 加載很少會(huì)使用到的功能

使用 deferred as 關(guān)鍵字來(lái)標(biāo)識(shí)需要延時(shí)加載的代碼庫(kù)。

import 'package:greetings/hello.dart' deferred as hello;

當(dāng)實(shí)際需要使用到庫(kù)中 API 時(shí)先調(diào)用 loadLibrary 函數(shù)加載庫(kù)。

Future greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}
  1. 延遲加載的代碼庫(kù)中的常量需要在代碼庫(kù)被加載的時(shí)候才會(huì)導(dǎo)入,未加載時(shí)候是不會(huì)導(dǎo)入的。
  2. 導(dǎo)入文件的時(shí)候無(wú)法使用延遲加載庫(kù)中的類型。
  3. Dart會(huì)隱式地將 loadLibrary 方法導(dǎo)入到使用了 deferred as 命名空間 的類中。loadLibrary 函數(shù)返回的是一個(gè) Future 類型。

異步

Dart 通過(guò) asyncawait 關(guān)鍵字實(shí)現(xiàn)異步編程,使用了這些關(guān)鍵字的函數(shù)會(huì)返回 FutureStream 對(duì)象。

  • Future 對(duì)象

必須在帶有 async 關(guān)鍵字的異步函數(shù)中使用 await。

// 異步函數(shù)
Future checkVersion() async {
    var version = await lookUpVersion(); // 耗時(shí)操作
    // 使用 version 繼續(xù)處理
}

你可以在異步函數(shù)中多次使用 await 關(guān)鍵字。

var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);

await 表達(dá)式的返回值通常是一個(gè) Future 對(duì)象,如果不是 Dart 也會(huì)自動(dòng)將其包裹在一個(gè) Future 對(duì)象里。Future 對(duì)象代表一個(gè)“承諾”,await 表達(dá)式會(huì)阻塞到需要的對(duì)象返回。

  • Stream 對(duì)象

想要從 Stream 中獲取值,需要使用 async 關(guān)鍵字或一個(gè)異步循環(huán) await for

Future method() async {
    await for (var request in requestList) {
        // 返回類型為 Stream
        handleRequest(request);
    }
}

生成器

Dart 中的生成器和迭代器和 Python 中的類似,當(dāng)你需要延遲地生成一連串的值時(shí),可以考慮使用生成器函數(shù)。Dart 內(nèi)置支持兩種形式的生成器方法。

  • 同步 生成器:返回一個(gè) Iterable 對(duì)象
  • 異步 生成器:返回一個(gè) Stream 對(duì)象

通過(guò)在函數(shù)上加 sync* 關(guān)鍵字并將返回值類型設(shè)置為 Iterable 來(lái)實(shí)現(xiàn)一個(gè) 同步 生成器函數(shù),在函數(shù)中使用 yield 語(yǔ)句來(lái)傳遞值。

Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

實(shí)現(xiàn) 異步 生成器函數(shù)與同步類似,只不過(guò)關(guān)鍵字為 async* 并且返回值為 Stream。

Stream<int> asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

總結(jié)

  • 所有變量引用都是對(duì)象,是類的實(shí)例,包括數(shù)字、函數(shù)以及 null。
  • Dart 是強(qiáng)類型語(yǔ)言,變量在聲明是可以指定類型,也可以由Dart自行推斷出類型。
  • Dart 支持泛型,List<int> 表示一組由 int 對(duì)象組成的數(shù)組。
  • Dart 支持頂級(jí)函數(shù),類方法,類變量等,你還可以進(jìn)行函數(shù)嵌套和局部函數(shù)。
  • Dart 的私有屬性通過(guò) _ 開(kāi)頭表示
  • Dart 可以顯示警告和錯(cuò)誤兩種類型問(wèn)題。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,283評(píng)論 6 530
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,947評(píng)論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 175,094評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,485評(píng)論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,268評(píng)論 6 405
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 54,817評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,906評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,039評(píng)論 0 285
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,551評(píng)論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,502評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,662評(píng)論 1 366
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,188評(píng)論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,907評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,304評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,563評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,255評(píng)論 3 389
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,637評(píng)論 2 370