Dart

SRIGT發表於2024-11-22

0x01 瞭解 Dart

(1)概述

  • Dart 是一門標準的計算機程式語言,由 Google 於 2011 年推出,專注於改善構建客戶端應用程式的體驗
    • Dart 屬於應用層程式語言,一般執行在 Dart VM 上,特定情況會編譯成 Native Code 執行在硬體上
    • Dart 相比 Java 更加易於理解;相比 JavaScript 更加規範和工程化
  • Dart 特性:
    • 語法便捷
    • 物件導向程式設計,資料型別均由 Object 派生
    • 強型別,可以型別推斷
    • 單程序非同步事件模型
    • 可以實現多執行緒,透過獨特的隔離區(Isolate)
    • 支援泛型運算子過載
    • 可以簡單實現高效程式碼,透過採用強大的 Future 和 Stream 模型
    • 支援 混入(Mixin) 特性,可以更好的實現方法複用
    • 支援多平臺
  • Dart 應用方向:
    • 移動端開發:透過基於 Dart + CPP + Skia 開發的 Flutter 框架實現
    • 服務端開發:即 DartVM 命令列程式
    • Web 端開發:透過 dart2js 可以將 Dart 編譯為 JavaScript

(2)環境配置

以 Windows 為例

  1. 安裝 Chocolatey,訪問連結,選中 Individual,複製命令到以管理員身份執行的 PowerShell 中執行

    使用命令 choco -v 驗證是否安裝成功

  2. 使用命令 choco install dart-sdk 安裝 Dart SDK

    新版本更新後,可以使用命令 choco upgrade dart-sdk 更新

  3. 如果使用 VSCode 進行開發,可以安裝外掛 Dart

  4. 建立 main.dart 檔案,在其中編寫簡單的 Hello World 程式

    void main() {
      print("Hello World!");
    }
    

    main() 是 Dart 程式的入口方法

0x02 基本語法

(1)變數與常量

  • 使用 vardynamic 宣告一個變數,並可以賦予不同型別的值(類似 JavaScript),使用 final 宣告只能賦值一次的變數

    void main() {
      var a = 10;
      print(a);	// 10
      // a = "Hello"; // 報錯:A value of type 'String' can't be assigned to a variable of type 'int'.
    
      var b;
      b = 10;
      print(b); // 10
      b = "Hello";
      print(b); // Hello
    
      dynamic c;
      print(c); // null
    
      final d = 10;
      print(d);
      // d = 20; // 報錯:The final variable 'd' can only be set once.
    }
    
    • 宣告時賦值則固定了變數的型別,之後重新賦值時必須為該型別的值
    • 未賦值則預設為 null
  • 使用 const 宣告一個常量,且使用 const 宣告的必須是編譯器常量

    void main() {
      const a = 10;
      print(a);
    }
    

    constfinal 的超集,const 宣告的常量是在非執行時就能獲取的值

    int getNumber() {
      return 10;
    }
    
    void main() {
      final a = getNumber();
      const b = getNumber(); // 報錯:Methods can't be invoked in constant expressions.
      print(a);
      print(b);
    }
    

(2)內建型別

a. 數字型別

  • Dart 支援兩種數字型別:

    • int:整數型,範圍為 \(-2^{53}\)\(2^{53}\) 之間,如:

      var number = 10;
      var hex = 0xffffff;
      var bigInt = 1234567890123456789;
      
    • double:雙精度浮點數,符合 IEEE 754 標準

      var float = 3.14;
      var exponents = 1.42e5;
      
  • intdouble 都是 num 的子類

  • num 類還包括:

    • 基本運算子(如 +- 等)
    • 數學方法(如 abs()floor() 等)
    • 位運算子(如 >>& 等)

b. 字元型別

  • Dart 字串採用 UTF-16 編碼,可以透過單引號 '' 或雙引號 "" 來建立字串

    var string = "SRIGT";
    
  • 字串中透過 $ 使用變數、透過 ${} 使用表示式

    var string = "SRIGT";
    var output = "My name is $string, I am ${2024 - 2000} years old.";
    // My name is SRIGT, I am 24 years old.
    
  • 可以透過運算子 + 或將多個字串放在一起,實現字串的連線

    void main() {
      var string1 = 'Hello' ' ' 'World';
      var string2 = 'Hello' + ' ' + 'World';
      assert(string1 == string2);
    }
    
  • 可以透過三個單引號或三個雙引號實現多行字串

    void main() {
      var string1 = "Hello\n" + "World";
      var string2 = """Hello
    World""";
      assert(string1 == string2);
    }
    
  • 可以透過字首 r 實現一個原始 raw 字串

    var string = r"Hello\nWorld";
    

字串與數字型別轉換

  • 字串與整型

    void main() {
      // 字串轉換為整型
      var number = int.parse("123");
      assert(number == 123);
    
      // 整型轉換為字串
      var string = number.toString();
      assert(string == "123");
    }
    
  • 字串與浮點型

    void main() {
      // 字串轉換為浮點型
      var number = double.parse("1.23");
      assert(number == 1.23);
    
      // 浮點型轉換為字串
      var string = number.toStringAsFixed(2);
      assert(string == "1.23");
    }
    

c. 布林型別

  • Dart 的布林型別包括 truefalse

  • 特別的,Dart 中只有 true 為真,其他均為假

    if (1) print("true");		// 報錯:Conditions must have a static type of 'bool'.
    if ("") print("true");		// 報錯:Conditions must have a static type of 'bool'.
    if (null) print("true");	// 報錯:Conditions must have a static type of 'bool'.
    

    相應的,需要明確比較內容獲取布林值

    void main() {
      assert(1 >= 0);
      assert("".isEmpty);
      assert(null == null);
    }
    

d. 列表(陣列)型別

  • Dart 中的列表型別 List 和其他程式語言的陣列型別相似

    void main() {
      var list = [1, 2, 3];
      assert(list[0] == 1);
      assert(list.length == 3);
    }
    
  • 透過在列表字面量前新增 const 可以定義以不變的列表常量

    var list = const [1, 2, 3];
    

e. 對映型別

  • Dart 中的對映型別 Map 是一個鍵值對相關的物件,鍵值必須唯一

    var teachers = {
      "Alex": "Math",
      "Bob": "English",
      "Charlie": "History"
    };
    
    var students = new Map();
    students["David"] = 101;
    students["Edward"] = 202;
    students["Fred"] = 501;
    

f. Runes 型別

  • Dart 中 Runes 用於在字串中表示 Unicode 字元,採用 UTF-32 編碼

    void main() {
      var runes1 = "\u2665"; // ♥
      var runes2 = new Runes("\u2665");
      assert(runes1 == String.fromCharCodes(runes2));
    }
    
  • 對於 \u 的數字大於 4 位則需要使用大括號 {}

    var runes = "\u{1f600}"; // 😀
    

g. Symbol 型別

  • Dart 中的 Symbol 物件代表 Dart 程式中宣告的運算子或識別符號

    void main() {
      var symbol1 = #value;
      var symbol2 = Symbol("value");
      assert(symbol1 == symbol2);
    }
    
  • 常用於反射,混淆後的程式碼中,Symbol 的名字不會被混淆

(3)運算子

描述 運算子
一元后綴 ++ -- () [] . ?.
一元字首 - ! ~ ++ --
乘法 * / % ~/
加法 + -
移動位運算 << >>
與位運算 &
異或位運算 ^
或位運算 `
關係和型別測試 <= >= < > as is is!
等式 == !=
邏輯與 &&
邏輯或 `
三目運算子 ?:
級聯 ..
賦值 = *= /= ~/= %= += -= <<= >>= &= ^= `

(4)流程控制

  • 條件控制

    • ifelse ifelse
    • switchcasedefault

    條件語句中必須明確地使表示式的計算結果為布林值

  • 迴圈控制

    • for
    • while
    • dowhile
    • breakcontinue
  • 斷言:assert

(5)異常處理

Dart 中採用 try 測試異常,throw 丟擲異常,catch 捕獲異常,finally 執行清理

void main() {
  try {
    throw Exception('MyError');
  } catch (e) {
    print(e); // Exception: MyError
  } finally {
    print('finally'); // finally
  }
}

(6)方法

a. 定義方法

  • Dart 推薦在公開的 APIs 上使用靜態型別定義,也支援忽略型別定義(即使用 dynamic

    bool isOpen() {
      return true;
    }
    
    isClose() {
      return false;
    }
    
  • Dart 支援胖箭頭語法(類似 JavaScript 的箭頭函式)

    int increase(int i) => i + 1;
    

b. 必要引數和可選引數

  • 在方法的引數列表中,預設引數都是必要的,且必要引數在前,可選引數在後

  • 可選引數包括:

    • 可選命名引數,使用 {},如:

      getValue({a, b}) {
        return a ?? b;
      }
      
      void main() {
        print(getValue(a: 1)); // 1
        print(getValue(b: 2)); // 2
        print(getValue(a: 1, b: 2)); // 1
      }
      
    • 可選位置引數,使用 []

      getValue([a, b]) {
        return "$a ${b ?? ''}";
      }
      
      void main() {
        print(getValue(1)); // 1
        print(getValue(2)); // 2
        print(getValue(1, 2)); // 1 2
      }
      

c. 引數預設值

當可選引數是指定的靜態型別時,必須提供預設值,其他引數可以提供預設值

String getTrafficLight({bool red = false, bool green = false}) => green
    ? "green"
    : red
        ? "red"
        : "unknown";

void main() {
  print(getTrafficLight()); // unknown
  print(getTrafficLight(red: true)); // red
  print(getTrafficLight(green: true)); // green
}

d. 匿名方法

  • 匿名方法指沒有方法名的方法,又稱 lambda 表示式

  • 匿名方法也可以採用胖箭頭語法

    void main() {
      var words = const ["hello", "world"];
      words.forEach((item) => print(item.toUpperCase()));
    }
    

e. 函式閉包

閉包是一個方法物件,可以隨時隨地訪問自己作用域內的變數

Function closure() {
  int number = 0;
  return () => number++;
}

void main() {
  var counter = closure();
  print(counter());	// 0
  print(counter());	// 1
  print(counter());	// 2
}

0x03 物件導向

(1)定義類

  • Dart 的類是一種開發者自定義的型別,類中包含資料、資料的操作方法等

    class Person {
      var name;
    
      Person(this.name);
    
      void sayHello() {
        print("Hello $name.");
      }
    }
    
    void main() {
      var student = Person("Alex");
      print(student.name); // Alex
      print(student.runtimeType); // Person
      student.sayHello(); // Hello Alex.
    }
    
  • Dart 的類可以有無數個建構函式,可以過載類中的運算子(詳細檢視(5)構造方法

  • Dart 的類也是介面,介面是透過抽象類來實現的(詳細檢視(9)介面

(2)訪問控制

  • Dart 中不存在 publicprivate 關鍵字

  • 類的成員預設都是公開的(public

  • 對於需要私有化的成員, 需要在其名稱前加下劃線

    class Person {
      var _name;
    
      Person(this._name);
    
      void sayHello() {
        print("Hello $_name.");
      }
    }
    
    void main() {
      var student = Person("Alex");
      student.sayHello(); // Hello Alex.
    }
    

(3)setter 與 getter

用於設定和訪問類私有成員

class Person {
  var _name;

  Person();

  // setter
  set name(String name) => _name = name;

  // getter
  String get name => _name;
}

void main() {
  var student = Person();

  student.name = "Alex";
  print(student.name);
}

(4)靜態成員

類靜態成員屬於類本身,不屬於例項物件

class Person {
  var name;

  Person(this.name);

  static String type = "Human";
}

void main() {
  var student = Person("Alex");
  // print(student.type); // 報錯:The static getter 'type' can't be accessed through an instance.
  print(Person.type); // Human
  Person.type = "Student";
  print(Person.type); // Student
}

(5)構造方法

  • 匿名構造方法只能有一個,語法為:類名稱()
  • 命名構造方法語法為:類名稱.命名()
class Person {
  var attr;

  // 匿名建構函式
  Person(String name) {
    this.attr = {'name': name};
  }

  // 命名建構函式
  Person.withId(int id) {
    this.attr = {'id': id};
  }
}

void main() {
  var p1 = new Person('Bob');
  var p2 = new Person.withId(1);
  print(p1.attr); // {name: Bob}
  print(p2.attr); // {id: 1}
  print(p1.runtimeType); // Person
  print(p2.runtimeType); // Person
}
  • 常量構造方法語法為:const 類名稱()

    • 常用於生成不可改變的物件,此時類中的所有例項屬性必須是 final
    class Person {
      final String name;
      final int age;
    
      Person(this.name, this.age);
    }
    
    void main() {
      final student = Person('Bob', 20);
      print(student.name); // Bob
      print(student.age); // 20
    }
    
  • 工廠構造方法語法為:factory 類名稱()

    • 基於工廠構造方法建立時,不一定需要建立一個類的新例項
    class Point {
      num x, y;
    
      Point(this.x, this.y);
    
      factory Point.fromJson(Map<String, dynamic> json) =>
          Point(json['x'], json['y']);
    }
    
    void main() {
      Point point = Point.fromJson({'x': 1, 'y': 2});
      print("${point.x}, ${point.y}");
    }
    

(6)模擬函式

  • 模擬函式指將類的物件作為方法使用

  • 類的模擬函式透過特殊方法 call() 實現

    class Person {
      var name;
    
      Person(this.name);
    
      call() {
        print("Hello, my name is $name.");
      }
    }
    
    void main() {
      var student = Person("Alex");
      student(); // Hello, my name is Alex.
    }
    

(7)繼承

  • 宣告類時,透過 extends 指定被繼承的父類
  • 透過 super 繼承父類的構造方法
  • 透過 @override 覆寫父類的某個方法
class Person {
  var name;

  Person(this.name);

  void sayHello() {
    print("Hello, my name is $name.");
  }
}

class Student extends Person {
  var school;

  Student(String name, this.school) : super(name);

  @override
  void sayHello() {
    print("Hello, my name is $name, and I go to $school.");
  }
}

void main() {
  var student = Student("Alex", "UCLA");
  student.sayHello(); // Hello, my name is Alex, and I go to UCLA.
}

上述繼承屬於單繼承,多繼承需要透過(10)混入實現

(8)抽象類

  • 使用 abstract 宣告抽象類,透過 implements 實現抽象類
  • 抽象類不能例項化、不能有物件
abstract class Person {
  void sayHello();
}

class Chinese implements Person {
  @override
  void sayHello() {
    print("你好");
  }
}

class American implements Person {
  @override
  void sayHello() {
    print("Hello");
  }
}

void main() {
  var p;
  p = new Chinese();
  p.sayHello(); // 你好
  p = new American();
  p.sayHello(); // Hello
}

(9)介面

介面要求子類實現介面中的所有屬性或方法

class Person {
  var name;

  Person(this.name);

  void sayHello() {}
}

class Student implements Person {
  var name;
  var school;

  Student(this.name, this.school);

  void sayHello() {
    print("Hello, my name is $name, and I go to $school.");
  }
}

void main() {
  var student = Student("Alex", "UCLA");
  student.sayHello(); // Hello, my name is Alex, and I go to UCLA.
}

(10)混入(Mixin)

  • 混入用於實現類的多繼承

  • 透過 with 實現混入,有以下兩種方法:

    • class C extends A with B

      class A {
        getA() => print("a");
      }
      
      mixin B {
        getB() => print("b");
      }
      
      class C extends A with B {
        getC() => print("c");
      }
      
      void main() {
        C().getA();
        C().getB();
        C().getC();
      }
      
    • class A with B, C

      mixin A {
        getA() => print("a");
      }
      
      mixin B {
        getB() => print("b");
      }
      
      class C with A, B {
        getC() => print("c");
      }
      
      void main() {
        C().getA();
        C().getB();
        C().getC();
      }
      

(11)列舉

透過 enum 宣告一個列舉

enum Day { monday, tuesday, wednesday, thursday, friday, saturday, sunday }

void main() {
  Day today = Day.monday;
  print('Today is $today.');
}

(12)泛型

泛型用於在例項化物件後指定類成員的靜態型別

class Attribute<T> {
  var value;

  Attribute(this.value);

  void getType() {
    print(value.runtimeType);
  }
}

void main() {
  Attribute<String> name = Attribute("");
  Attribute<int> age = Attribute(0);

  name.getType(); // String
  age.getType(); // int
}

0x04 非同步程式設計

(1)async / await

Dart 中的非同步透過 asyncawait 實現,與 JavaScript 的 asyncawait 類似

void request() async {
  await Future.delayed(Duration(seconds: 1)); // 等待 1s
  print("Message from request");
}

void main() {
  print("Start");
  request();
  print("End");
}

(2)Future

  • Dart 中的非同步回撥問題透過 Future 實現,與 JavaScript 的 Promise 類似

    Future<String> request() async {
      await Future.delayed(Duration(seconds: 1)); // 等待 1s
      // throw "Error";
      return "Message from request";
    }
    
    void main() {
      request().then((value) => print(value), onError: (e) => print(e));
    }
    
  • 當同時執行多個請求時,透過 wait 實現等待所有請求結果都返回後,再執行後續操作,類似 JavaScript 的 Promise.all(當佇列中某個非同步任務丟擲異常時,會中斷執行並直接進入異常處理階段)

    Future<String> request1() async {
      await Future.delayed(Duration(seconds: 1)); // 等待 1s
      return "Message from request1";
    }
    
    Future<String> request2() async {
      await Future.delayed(Duration(seconds: 2)); // 等待 2s
      throw "Error2";
      // return "Message from request2";
    }
    
    Future<String> request3() async {
      await Future.delayed(Duration(seconds: 3)); // 等待 3s
      return "Message from request3";
    }
    
    void main() {
      Future.wait([request1(), request2(), request3()]).then((value) {
        print(value);
      }).catchError((error) {
        print(error); // Error2
      });
    }
    
  • Future.then 返回的依然是 Future 型別,可以採用鏈式呼叫的方法

    Future<int> request1() async {
      await Future.delayed(Duration(seconds: 1)); // 等待 1s
      return 1;
    }
    
    Future<int> request2(int value) async {
      await Future.delayed(Duration(seconds: 2)); // 等待 2s
      return value + 2;
    }
    
    void main() {
      request1()
          .then((value) => request2(value))
          .then((value) => print(value))
          .catchError((error) => print(error));
    }
    

(3)Stream

  • Stream 是一系列非同步事件的序列,類似於一個非同步的 Iterable

  • 舉例:讀取文字檔案

    import 'dart:io';
    import 'dart:convert';
    
    void main() {
      Stream<List<int>> stream = new File("./text.txt").openRead();
      stream.transform(utf8.decoder).listen((data) => print(data));
    }
    
  • 詳細內容參考官方文件:

    • 使用 stream
    • 建立 stream

0x05 外部依賴

\(包 > 庫\)

(1)自定義庫

  1. 新建 module.dart

    class Person {
      String name;
      int age;
    
      Person(this.name, this.age);
    
      void printInfo() {
        print('Name: $name - Age: $age');
      }
    }
    
  2. 在 main.dart 中匯入並使用 Person

    import 'module.dart';
    
    void main() {
      var student = Person("SRIGT", 18);
      student.printInfo();
    }
    

對於重複命名的變數或方法,可以使用 as 對匯入的庫設定別名解決

(2)核心庫(內建庫)

  • 核心庫統一採用字首 dart:庫名稱,如 import 'dart:core';
  • 常用核心庫包括:
    • core:小而關鍵的內建功能集
    • async:非同步程式設計
    • math:數學
    • convert:具有用於 JSON 和 UTF-8的轉換器,以及對建立其他轉換器的支援
    • io:處理檔案、目錄、程序、套接字、 WebSocket 以及 HTTP 客戶端和伺服器
    • html:編寫瀏覽器程式,操作 DOM 中的物件和元素,並訪問 HTML5 API
      • 該庫正在被 web 庫取代,web 庫具有可以相容 Wasm 等新特性
    • ……

(3)第三方庫引用

  • Dart 採用 Pub 包管理系統,官網連結

  • 引用第三方庫步驟如下:

    1. 建立 pubspec.yaml

      name: dart_app
      
      dependencies:
        js: ^0.6.0
        intl: ^0.17.0
      

      也可以使用命令 dart pub add 新增包資訊到 pubspec.yaml

    2. 使用命令 dart pub get 獲取包

    3. 使用字首 package: 匯入包中的庫,如 import 'package:js/js.dart' as js;

    4. 使用命令 dart pub upgrade 升級包

-End-

相關文章