Dart
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 为例
-
安装 Chocolatey,访问链接,选中 Individual,复制命令到以管理员身份运行的 PowerShell 中执行
使用命令
choco -v
验证是否安装成功 -
使用命令
choco install dart-sdk
安装 Dart SDK新版本更新后,可以使用命令
choco upgrade dart-sdk
更新 -
如果使用 VSCode 进行开发,可以安装插件 Dart 等
-
创建 main.dart 文件,在其中编写简单的 Hello World 程序
void main() { print("Hello World!"); }
main()
是 Dart 程序的入口方法
0x02 基本语法
(1)变量与常量
-
使用
var
或dynamic
声明一个变量,并可以赋予不同类型的值(类似 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); }
const
是final
的超集,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;
-
-
int
和double
都是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 的布尔类型包括
true
和false
-
特别的,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)流程控制
-
条件控制
if
、else if
、else
switch
、case
、default
条件语句中必须明确地使表达式的计算结果为布尔值
-
循环控制
for
while
do
、while
break
、continue
-
断言:
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 中不存在
public
、private
关键字 -
类的成员默认都是公开的(
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 中的异步通过 async
和 await
实现,与 JavaScript 的 async
和 await
类似
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)); }
-
详细内容参考官方文档:
0x05 外部依赖
\(包 > 库\)
(1)自定义库
-
新建 module.dart
class Person { String name; int age; Person(this.name, this.age); void printInfo() { print('Name: $name - Age: $age'); } }
-
在 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 包管理系统,官网链接
-
引用第三方库步骤如下:
-
创建 pubspec.yaml
name: dart_app dependencies: js: ^0.6.0 intl: ^0.17.0
也可以使用命令
dart pub add
添加包信息到 pubspec.yaml -
使用命令
dart pub get
获取包 -
使用前缀
package:
导入包中的库,如import 'package:js/js.dart' as js;
-
使用命令
dart pub upgrade
升级包
-
-End-