语言简介

Dart 语言是为客户端开发而设计,优先考虑多平台的开发,它的主要特点:

  • 编译型:为了支持多平台而开发的各种编译器
  • 强类型:使用静态类型检查,也支持 dynamic 类型及运行时类型检查
  • GC :由 Dart 运行时环境负责分配和管理内存
  • 面向对象:基于 mixin 继承机制

Dart 平台

Dart 通过编译技术来支持各种不同的平台:

  • 原生平台:对于移动和桌面平台,Dart 拥有具有实时编译功能(JIT)的 Dart VM , 和用于生成机器代码的 AOT 编译器。 开发的时候,Dart VM 通过 JIT 提供增量重编译、热重载、运行时数据收集及各种调试能力。 部署到生产环境的时候通过 AOT 编译器将代码编译成对应平台的机器码。
  • Web 平台:针对 Web 应用程序,Dart 有开发时编译器(dartdevc)和生产时编译器(dart2js), 两者都可以将 dart 代码编译成 js 代码。

Hello World

1
2
3
4
// hello.dart
void main() {
  print("Hello World!");
}

通过 dart run hello.dart 就可以运行上面的代码。

在 Windows 上,我们可以通过 dart compile exe hello.dart 将程序编译成 exe 文件。

编译后生成的是可执行的 hello.exe,我们用 dumpbin.exe /dependents hello.exe 查看它的依赖可以发现它只依赖操作系统的库,其它的全部编译到了 exe 里:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

Image has the following dependencies:

  IPHLPAPI.DLL
  PSAPI.DLL
  WS2_32.dll
  RPCRT4.dll
  SHLWAPI.dll
  ADVAPI32.dll
  SHELL32.dll
  dbghelp.dll
  bcrypt.dll
  CRYPT32.dll
  KERNEL32.dll

我们可以用 dart compile js hello.dart 将 dart 编译成 js 代码, 用 node 执行生成的 js 文件可以得到同样的输出,把生成的代码放到浏览器里也是可以执行的。

变量

和大多数 GC 语言一样,Dart 中的变量都只是引用到一个对象,每个对象都是一个类的实例。 数字、函数以及 null 都是对象。除了 null 外,所有的类都继承于 Object 类。

Dart 的变量具有词法作用域

Dart 是强类型的,没有隐式类型转换,下面的代码会报错,无法从 double 转成 int 。

1
int number = 5.5; // error A value of type 'double' can't be assigned to a variable of type 'int'.

只能用 double 类提供的 toInt 方法进行转换

1
2
int number = 5.5.toInt();
print(number);
1
5

Dart 有自动类型推导,在声明变量时指定类型是可选的,下面的 number 自动推导成 int 类型。

1
2
var number = 10;
print(number.runtimeType);
1
int

Dart 支持声明接收任意类型的变量,在运行时动态改变。

1
2
3
4
Object any = "ANY";
print(any.runtimeType);
any = 2;
print(any.runtimeType);
1
2
String
int

空安全

Dart 中的类型默认是非空的,除非声明它们可为空。

1
int number = null;// Error: The value 'null' can't be assigned to a variable of type 'int' because 'int' is not nullable.

要想声明一个变量可为空,就在类型声明后面加上 ?

1
2
int? number = null;
print(number);
1
null

Dart 的空安全据说是完全可靠的,如果一个变量不可为空,那么它永远不为空。

默认值

对于不可空类型,在使用之前必须初始化

1
2
int number;
print(number);// Error: Non-nullable variable 'number' must be assigned before it can be used.
1
2
3
int number;
number = 5;
print(number);
1
5

对于可空类型,不初始化默认为 null

1
2
int? number;
print(number);
1
null

Late 变量

Dart 2.12 添加了 late 变量修饰符,它的作用有两个:

  1. 延迟初始化不可空变量
  2. 延迟初始化变量

有时 Dart 推导非空类型初始化时会失败,如:

1
2
3
4
5
int number;
void main() {
  number = 10;
  print(number); // Error: Field 'number' should be initialized because its type 'int' doesn't allow null.
}

这个时候可以加上 late 修复这个问题

1
2
3
4
5
late int number;
void main() {
  number = 10;
  print(number);
}
1
10

当在声明变量同时初始化时,加上 late 可以延迟初始化,主要有两种场景用到:

  1. 这个变量可能不会被用到,或者初始化它花的时间比较长
  2. 需要初始化一个实例变量,但是在构造函数里需要用到 this
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int delayInit() {
  print("delay init");
  return 10;
}

void main() {
  late int number = delayInit();
  print("do some things");
  print(number);
}
1
2
3
do some things
delay init
10

Final 和 Const

final 修饰的变量只可以被赋值一次,const 修饰的变量是一个编译期常量,必须在编译期计算出来。

1
2
final int number = 5;
number = 10; // Error: Can't assign to the final variable 'number'.

final 可以赋值一次

1
2
3
4
final int? number;
number = 10;
print(number); // 10
number = 20;// Error: Final variable 'number' might already be assigned at this point.

const 修饰的变量必须在编译期就确定

1
2
3
4
5
6
7
8
int f() {
  return 10;
}

void main() {
  const int number = f(); // Error: Method invocation is not a constant expression.
  print(number);
}

右边的值必须在编译器确定

1
2
3
4
void main() {
  const int number = 10;
  print(number);
}
1
10

const 也可以创建常量值,对应的变量值是可以改变的,即使它引用过 const 值

1
2
3
4
var number = const [];
print(number);
number = [1, 2, 3];
print(number);
1
2
[]
[1, 2, 3]

内置类型

Numbers

Dart 支持两种内置类型:intdouble ,如果一个数字包括了小数点,它就是 double ,否者就是 int

1
2
3
4
var n = 1;
print(n.runtimeType);
var m = 1.1;
print(m.runtimeType);
1
2
int
double

整型字面量可以自动转换成 double 类型,反之就不行。

1
2
double m = 1;
print(m);
1
1.0

intdouble 的父类型是 num

1
2
3
num n = 1;
n += 1.5;
print(n);
1
2.5

Number 和 String 之间的转换如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// String => int
var one = int.parse("1");
print(one);
// String => double
var onePointOne = double.parse("1.1");
print(onePointOne);
// int => String
print(1.toString());
// double => String
print(3.14159.toStringAsFixed(2));
1
2
3
4
1
1.1
1
3.14

Strings

Dart 字符串是 UTF-16 编码的字符序列,可以使用单引号和双引号创建字符串, 或者使用三引号创建多行字符串。

1
2
3
4
5
6
7
8
9
var s1 = "first";
var s2 = 'second';
var s3 = """first
second
""";
var s4 = '''first
second
''';
print(s4);
1
2
first
second

使用 ${表达式} 在字符串中插入表达式,如果表达式是一个标识符,可以省略 {} ,如果是一个对象, 会自动调用对象的 toString 方法。当然,加上 \ 就可以当成原本的字符。

1
2
3
4
String s = "Jack";
print("My name is $s");
print("My name is \$s");

1
2
My name is Jack
My name is $s

字符串前面加 r 创建 raw 字符串

1
2
String s = r"This is \";
print(s);
1
This is \

字符串操作

使用 contains startsWith endsWith indexOf 在字符串内搜索

1
2
3
4
5
String s = "Hello, my name is Jack";
print(s.contains("name"));
print(s.startsWith("Hello"));
print(s.endsWith("Jack"));
print(s.indexOf("my"));
1
2
3
4
true
true
true
7

使用 substring 提取子串,split 分割字符串,toUpperCase toLowerCase 转换大小写,trim 移除首尾空格

1
2
3
4
5
6
String s = "Hello World";
print(s.substring(0, 5));
print(s.split(" "));
print(s.toUpperCase());
print(s.toLowerCase());
print("   hello   ".trim());
1
2
3
4
5
Hello
[Hello, World]
HELLO WORLD
hello world
hello

使用 replaceAll 替换部分字符串

1
2
String s  = "Hello World";
print(s.replaceAll(RegExp("World"), "Jack"));
1
Hello Jack

构造字符串可以使用 StringBuffer , writeAll 第二个可选参数为分割字符

1
2
3
4
5
6
var sb = StringBuffer();
sb
..write("Use StringBuffer")
..writeAll(["A", "B", "C"], "/")
..write("!");
print(sb.toString());
1
Use StringBufferA/B/C!

更多的 API: String

布尔类型

Dart 使用 bool 关键字表示布尔类型,布尔类型的只有 truefalse 两个对象。 Dart 的类型安全不允许其它类型隐式转换成布尔类型,比如数字 0 在 if 里并不是 fasle

Lists

Dart 中的 List 对象表示的就是数组类型,下标从 0 开始,可以用 [] 访问

1
2
3
4
5
var list = [1, 2, 3];
print(list.length);
print(list[1]);
print(list.runtimeType);

1
2
3
3
2
List<int>

上面的声明会被推导成 List<int> 类型

扩展操作符(...) 和空扩展操作符(...?)提供了一种将多个元素插入集合的简洁方法。

1
2
3
4
5
6
7
var list1 = [1, 2, 3];
var list2 = [0, ...list1];
var list3 = [...list1, 4];
var list4 = [0, ...list1, 4];
print(list2);
print(list3);
print(list4);
1
2
3
[0, 1, 2, 3]
[1, 2, 3, 4]
[0, 1, 2, 3, 4]

如果扩展操作符右边可能为 null ,就使用空扩展操作符避免异常。

1
2
3
var list;
var list1 = [1, ...?list];
print(list1);
1
[1]

Dart 还可以在构建集合时使用 if 或者 for

1
2
3
4
5
6
7
8
var n = 3;
var list = [
  1,
  2,
  if (n == 0) 3
  else 4
];
print(list);
1
[1, 2, 4]
1
2
3
var list = [1, 2, 3];
var list1 = [for (var i in list) i];
print(list1);
1
[1, 2, 3]

List 常用操作

add addAll 添加元素,removeAt 删除单个元素,clear 删除所有元素

1
2
3
4
5
6
7
8
var list = [1];
list.add(2);
list.addAll([3, 4]);
print(list);
list.removeAt(0);
print(list);
list.clear();
print(list);
1
2
3
[1, 2, 3, 4]
[2, 3, 4]
[]

indexOf 查找一个对象对应的下标值

1
2
var list = ["Apple", "Orange"];
print(list.indexOf("Orange"));
1
1

sort 排序

1
2
3
var list = [3, 1, 2, 5, 4];
list.sort();
print(list);
1
[1, 2, 3, 4, 5]

Sets

Dart 中的 Set 是无序元素的集合

1
2
3
4
5
6
var set = {"Apple", "Orange"};
print(set.runtimeType);
var set1 = <String>{};
print(set1.runtimeType);
Set<int> set2 = {};
print(set2.runtimeType);
1
2
3
_CompactLinkedHashSet<String>
_CompactLinkedHashSet<String>
_CompactLinkedHashSet<int>

Set 和 Map 的声明类似,如果声明时不指定元素,默认会推导成 Map

1
2
var itIsMap = {};
print(itIsMap.runtimeType);
1
_InternalLinkedHashMap<dynamic, dynamic>

Set 同样支持 ......? 操作符,以及集合内的 iffor

Set 常用操作

使用 from 从 List 构造 Set

1
print(Set.from([1, 2, 3]));
1
{1, 2, 3}

add addAll 添加元素,remove 移除一个元素,clear 移除所有元素

1
2
3
4
5
6
7
8
Set<int> set = {};
set.add(1);
set.addAll([2, 3]);
print(set);
set.remove(2);
print(set);
set.clear();
print(set);
1
2
3
{1, 2, 3}
{1, 3}
{}

containscontainsAll 检查一个或多个元素是否在 Set 中

1
2
3
var set = {1, 2, 3, 4};
print(set.contains(3));
print(set.containsAll([1, 4]));
1
2
true
true

intersection 求交集 ,

1
2
3
4
var set1 = {1, 2, 3};
var set2 = {2, 3, 4};
print(set1.intersection(set2));
print(set1.difference(set2));
1
2
{2, 3}
{1}

更多操作看相关的 API : https://api.dart.cn/stable/dart-core/Set-class.html

Maps

Map 就是 key-value 对象,key value 可以为任意类型

1
2
3
4
var map = {
  "first" : 1,
  "second" : 2
};

可以使用 [] 操作 Map

1
2
3
4
5
6
7
var map = {
  "first" : 1
};
print(map["first"]);
map["second"] = 2;
print(map);
print(map["third"]);
1
2
3
1
{first: 1, second: 2}
null

Map 同样支持 ......? 操作符以及 if for

Map 的 API 直接看相关文档:Map api docs

函数

Dart 的函数也是对象,它的类型是 Function ,它是一级对象,可以复制给变量,也可以作为参数

1
2
3
4
5
void f() {
}
void main() {
  print(f.runtimeType);
}
1
() => void

Dart 的函数都有返回值,如果不写,默认返回 null , 定义时可以没有返回类型,但是推荐在公开的 API 上加上返回类型

1
2
3
4
5
6
7
f() {
  return 10;
}

void main() {
  print(f());
}
1
10

如果函数体只有一个表达式,可以使用 =>

1
2
f() =>  10;
print(f());
1
10

参数

Dart 函数有两种参数,必要参数和可选参数,必要参数放到前面。可选参数可以是命名的或位置的, 可选参数如果不传就为 null ,如果可选参数类型为不可空类型,就需要给可选参数提供默认值。

命名参数

命名参数默认为可选参数,使用 {} 包起来,除非被标记为 required

1
2
3
4
5
6
f({required String? first, String? second}) {
  print(first);
  print(second);
}
f(first: "FIRST");
f(first: "FIRST", second: "SECOND");
1
2
3
4
FIRST
null
FIRST
SECOND

位置参数

位置参数使用 [] 包起来

1
2
3
4
5
6
7
f(String must, [String? first]) {
  print(must);
  print(first);
}

f("must");
f("MUST", "FIRST");
1
2
3
4
must
null
MUST
FIRST

main 函数

main 函数作为入口,返回值为 void ,有一个 List<String> 的可选参数。

1
2
3
void main() {
  print("Hello World");
}
1
Hello World
1
2
3
void main(List<String> args) {
  print(args);
}
1
[]

匿名函数

格式与命名函数类似

1
2
3
4
var f = () {
  return 10;
};
print(f());
1
10

词法闭包

函数可以封闭定义到它作用域的变量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(int addBy) {
  return (int i) => addBy + i;
}

void main() {
  // Create a function that adds 2.
  var add2 = makeAdder(2);

  // Create a function that adds 4.
  var add4 = makeAdder(4);

  print(add2(3) == 5);
  print(add4(3) == 7);
}

1
2
true
true

运算符

算术运算符:+ - -表达式 * / ~/(除并向下取整) % ++ --

关系运算符:== != > < >= <= 判断两个对象是否相同一般用 == 就可以了,及少数情况需要 identical

类型判断符:

  • as ,类型转换
  • is ,为指定类型返回 true
  • is! ,为指定类型返回 flase

赋值运算符:= ??=

1
2
3
4
5
// Assign value to a
a = value;
// 如果 b 是 null ,b 为 value ,否则 b 保持原值
b ??= value;

算数运算符同样可以和赋值运算符结合,如:+= -=

逻辑运算符:

  • ! ,取反
  • || ,逻辑或
  • && ,逻辑与

按位和移位运算符:& | ^ ~ << >> >>>

条件表达式:?: ?? 根据布尔表达式赋值时用 ?:

1
var a = isPub ? "A" : "B";

根据 null 赋值时用 ??

1
String playerName(String? name) => name ?? "Default";

级联运算符:.. ?.. 可以在同一对象上连续调用多个对象的变量或方法

1
2
3
4
var paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;

等价于:

1
2
3
4
var paint = Paint();
paint.color = Colors.black;
paint.strokeCap = StrokeCap.round;
paint.strokeWidth = 5.0;

控制流

条件

if-else 进行条件判断,else 是可选的,if 语句中的条件必须是布尔值

1
2
3
4
5
6
7
8
var n = 1;
if (n <= 0) {
  print("<=0");
} else if (n <= 1) {
  print("<=1");
} else {
  print("else");
}
1
<=1

Dart 支持 switch-case ,非空的 case 子句必须有 break;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var command = 'OPEN';
switch (command) {
  case 'OPEN':
    print("Open");
    // ERROR: Missing break

  case 'CLOSED':
    print("Close");
    break;
}

Dart 支持空的 case

循环

普通的 for 循环

1
2
3
for (var i = 0; i < 2; i++) {
  print(i);
}
1
2
0
1

for-in 遍历可迭代对象,如 List

1
2
3
4
var list = [1, 2];
for (final e in list) {
  print(e);
}
1
2
1
2

forEach 也可以作用于可迭代对象

1
2
var list = [1, 2];
list.forEach(print);
1
2
1
2

whiledo-while 和其它语言一样

break 中断循环, continue 继续循环。

断言

Dart 的 assert 失败会抛出 AssertionError 异常,一般在生产环境会忽略

异常

Dart 可以抛出和捕获异常,推荐抛出 ErrorException 类型的异常

抛出异常:

1
throw "Any type exception";

使用 try on catch 来捕获异常

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');
}

on 指定异常类型,catch 捕获异常对象,catch 的第二参数可以为堆栈信息。

1
2
3
4
5
6
7
8
try {
  // ···
} on Exception catch (e) {
  print('Exception details:\n $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

rethrow 可以再次抛出异常

finally 语句会始终执行

Dart 是基于 mixin 继承机制的面向对象语言,除了 null 外,所有的类都继承自 Object 类。

使用类

访问类成员

使用 . 运算符访问类的成员,使用 ?. 同样可以访问类的成员,但是可以避免左边表达式为 null 产生问题。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var p = Point(2, 2);

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

// Invoke distanceTo() on p.
double distance = p.distanceTo(Point(4, 4));

// If p is non-null, set a variable equal to its y value.
var a = p?.y;

构造函数

可以使用构造函数创建一个对象,构造函数可以为类名或者类名.标识符的方式:

1
2
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

类可以提供常量构造函数,来提供编译时常量

1
var p = const ImmutablePoint(2, 2);

实现类

实例变量

1
2
3
4
5
class Point {
  double? x;
  double? y;
  double z = 0;
};

未初始化的实例变量被初始化成 null

所有实例变量都会有一个 get 方法,非 final 的实例变量和 later final 声明但是为初始化的实例 变量还会有一个 set 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Point {
  double? x; // Declare instance variable x, initially null.
  double? y; // Declare y, initially null.
}

void main() {
  var point = Point();
  point.x = 4; // Use the setter method for x.
  print(point.x == 4); // Use the getter method for x.
  print(point.y == null); // Values default to null.
}

1
2
true
true

构造函数

声明一个和类名一样的函数即为构造函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Point {
  double x = 0;
  double y = 0;

  Point(double x, double y) {
    // There's a better way to do this, stay tuned.
    this.x = x;
    this.y = y;
  }
}

一种简化的方法:

1
2
3
4
5
6
7
8
class Point {
  double x = 0;
  double y = 0;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}

构造函数可以有初始化列表

1
2
3
4
5
6
7
8
// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, double> json)
    : x = json['x']!,
      y = json['y']! {
  print('In Point.fromJson(): ($x, $y)');
}
// 等号右边不能用 this

如果没有声明构造函数,类会自动生成一个无参数构造函数,并会调用父类的无参数构造函数。

子类不会继承父类的构造函数。

可以为一个类声明多个命名式构造函数,让语义更加清晰

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  double x = 0;
  double y = 0;

  Point(this.x, this.y);

  // Named constructor
  Point.origin()
      : x = xOrigin,
        y = yOrigin;
}

子类和父类构造函数调用顺序:

  1. 子类构造函数的初始化列表
  2. 父类构造函数
  3. 当前类的构造函数

默认会调用父类的无参数构造函数,除非显示调用父类构造函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  class Person {
  String? firstName;

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

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

void main() {
  var employee = Employee.fromJson({});
  print(employee);
  // Prints:
  // in Person
  // in Employee
  // Instance of 'Employee'
}
1
2
3
in Person
in Employee
Instance of 'Employee'

传递给父类的参数不能有 this ,因为子类构造函数还没有执行

构造函数可以重定向

1
2
3
4
5
6
7
8
9
class Point {
  double x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(double x) : this(x, 0);
}

如果类生成的对象是不可变的,可以定义常量构造函数

1
2
3
4
5
6
7
class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0);

  final double x, y;

  const ImmutablePoint(this.x, this.y);
}

使用 factory 标记的构造函数会让该构造函数称为工厂构造函数,意味着使用工厂构造函数不一定总是返回新的实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

方法

实例方法可以访问实例变量和 this

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import 'dart:math';

class Point {
  double x = 0;
  double y = 0;

  Point(this.x, this.y);

  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}

Dart 可以重写部分操作符

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // Operator == and hashCode not shown.
  // ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  print(v + w == Vector(4, 5));
  print(v - w == Vector(0, 1));
}
1
2
false
false

Getter 和 Setter 是读写对象属性的特殊方法,可以用 get set 关键字为额外属性定义 Getter 和 Setter 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Rectangle {
  double left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and 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;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  print(rect.left == 3);
  rect.right = 12;
  print(rect.left == -8);
}
1
2
true
true

抽象方法是定义接口而没有具体的实现,只会存在于抽象类中,直接用 ; 代替方法体可以声明一个抽象方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
abstract class Doer {
  // Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide an implementation, so the method is not abstract here...
  }
}

抽象类

使用关键字 abstract 标识的类可以让该类称为抽象类,抽象类无法实例化。 如果想让抽象类可以实例化,可以定义一个工厂构造函数

1
2
3
4
5
6
7
// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
  // Define constructors, fields, methods...

  void updateChildren(); // Abstract method.
}

隐式接口

如果想要 A 类支持调用 B 类的 API 且不想继承 B 类,则可以实现 B 类的接口

一个类可以通过关键字 implements 来实现一个或多个接口并实现每个接口定义的 API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final String _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

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

// An implementation of the Person interface.
class Impostor implements Person {
  String get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}
1
2
Hello, Bob. I am Kathy.
Hi Bob. Do you know who I am?

要实现多个接口,可以用逗号分割每个接口类

1
class Point implements Comparable, Location {...}

继承

使用 extends 来创建一个子类,使用 super 关键字在子类中引用父类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}

子类可以重写父类的实例方法,可以使用 @override 注解表示重写了一个成员

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Television {
  // ···
  set contrast(int value) {...}
}

class SmartTelevision extends Television {
  @override
  set contrast(num value) {...}
  // ···
}

重写的方法需要满足下面几点:

  1. 返回值类型必须是父类方法返回值的相同类型或父类型
  2. 参数类型同返回值类型
  3. 父类方法接收 n 个位置参数,重写方法也要接收 n 个位置参数
  4. 泛型方法不能重写非泛型方法,非泛型方法不能重写泛型方法

noSuchMethod

如果调用了对象上不存在的方法或实例变量,会触发 noSuchMethod 方法,可以重写这个方法

只有下面一个条件成立时,才能调用到 noSuchMethod

  • 接收方是静态的 dynamic 类型
  • 接收方具有静态类型,定义了未实现的方法,并且接收方的动态类型实现了 noSuchMethod 方法。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class A {
  @override
  void noSuchMethod(Invocation invocation) {
    print("A: noSuchMethod");
  }
}

class B {
  @override
  void noSuchMethod(Invocation invocation) {
    print("B: noSuchMethod");
  }

  void foo();
}

void main() {
  dynamic a = A();
  a.method();

  B b = B();
  b.foo();
}
1
2
A: noSuchMethod
B: noSuchMethod

扩展方法

扩展方法用来向一些已有的库添加额外的方法,比如下面向 String 添加一个 addPrefix 方法

1
2
3
4
5
6
7
8
9
extension MyExt on String {
  String addPrefix(String prefix) {
    return prefix + ": " + this;
  }
}

void main() {
  print("Hello".addPrefix("Jack"));
}
1
Jack: Hello

枚举

使用 enum 关键字定义枚举类型 ,每一个枚举值都有一个 index ,values 可以获取枚举值列表

1
2
3
4
5
enum Color { red, green, blue }
void main() {
  print(Color.red.index);
  print(Color.values);
}
1
2
0
[Color.red, Color.green, Color.blue]

枚举的限制:

  • 枚举不能称为子类,不可以 mixin ,也不可以实现枚举
  • 不能实例化一个枚举

Mixin

使用 mixin 关键字定义 mixin ,使用 with 关键字使用 mixin , 普通类也可以作为 mixin 。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Stream {

}

mixin Readable {
  void read() {
    print("Readable: read");
  }
}

mixin Writable {
  void write() {
    print("Writable: write");
  }
}

class ReadWriteStream extends Stream  with Readable, Writable{

}

void main() {
  var stream = ReadWriteStream();
  stream.read();
  stream.write();
}
1
2
Readable: read
Writable: write

类变量和方法

使用 static 关键字声明类的变量和方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class A {
  static String name = "A";

  static String GetName() {
    return name;
  }
}

void main() {
  print(A.name);
  print(A.GetName());
}
1
2
A
A

泛型

Dart 的方法和类都支持泛型,通过 extends 限制泛型类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class A<T extends num> {
  num add(T first, T second) {
    return first + second;
  }
}

num add<T extends num>(T first, T second) {
  return first + second;
}

void main() {
  print(add(2, 2.5));

  var a = A();
  print(a.add(2.5, 3));
}
1
2
4.5
5.5

使用 importlibrary 关键字可以创建一个模块化和可共享的库 以 _ 开头的成员只在库内部可见。

使用库

使用 import 导入库,它的参数是代码库的 URI ,对于内置库,使用 dart:xxxx 形式, 对于其它库,可以使用 package:xxxxx 或者直接是文件路径

1
2
import "dart:html"
import "package:test/test.dart"

如果库有冲突的标识符,可以指定前缀

1
2
3
4
5
6
7
8
import "package:lib1";
import "package:lib2" as lib2;

// lib1
A a = A();
// lib2
lib2.A lib2a = lib2.A();

可以只导入库的一部分

1
2
3
4
// 只导入 foo
import "package:lib1" show foo
// 导入所有,除了 foo
import "packaeg:lib2" hide foo

实现库

这一部分会单独写一篇文章

异步

Dart 中有两种异步对象: FutureStream ,返回这两种对象的函数 都是异步函数。

Future

一个 Future<T> 表示一个异步操作的结果,它有两种状态:

  • 未完成状态
  • 完成状态

当调用一个异步函数时,返回一个未完成态的 Future ,这个 Future 正在等待异步函数 的结果 T ,如果异步函数完成,Future 会到完成状态并拿到结果,如果异步函数出现错误, Future 同样会到达完成状态并获取到对应的错误。

Future<T>then 方法会注册一个回调,当 Future 到达完成状态时会调用这个回调函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Future<String> delayEcho(String msg) {
  return Future<String>.delayed(const Duration(seconds: 1), () => msg);
}

Future<void> delayError() {
  return Future.delayed(const Duration(seconds: 1), () => throw "Delay Error");
}

void main() {
  var result = delayEcho("Hello");
  result.then((msg) => print("Received: " + msg));

  var error = delayError();
  error.then((value) => print("finished"), onError: (error) => print("Error: " + error));
}
1
2
Received: Hello
Error: Delay Error

async 和 await

async 可以让一个函数称为异步函数,在异步函数里可以用 await 等待 Future 结果, 用 try-catch 捕获 Future 的错误。main 函数也可以成为一个异步函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Future<String> delayEcho(String msg) {
  return Future<String>.delayed(const Duration(seconds: 1), () => msg);
  }

Future<void> delayError() {
  return Future.delayed(const Duration(seconds: 1), () => throw "Delay Error");
}

void main() async {
  var msg = await delayEcho("Use Await");
  print(msg);
  try {
    await delayError();
  } catch (e) {
    print("Error: $e");
  }
}
1
2
Use Await
Error: Delay Error

Stream

Stream 提供了一种异步的数据序列,包括用户生成的事件或从文件中读取的数据。

可以使用 async 关键字和 异步循环 await for 从 Stream 中获取值

1
2
3
4
5
void main() async {
  await for (final request in requestServer) {
    handleRequest(request);
  }
}

生成器

当需要延迟生成一连串的值时,可以使用生成器函数:

  • 同步生成器返回一个 Iterable 对象
  • 异步生成器返回一个 Stream 对象

通过 sync* 关键字实现一个同步生成器函数,使用 yield 语句来传递值 通过 async* 关键字实现一个异步生成器函数,使用 yield 语句来传递值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Iterable<int> g(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

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

void main() async {
  for (final n in g(3)) {
    print(n);
  }

  await for (final n in ga(3)) {
    print(n);
  }
}

1
2
3
4
5
6
0
1
2
0
1
2

元数据

使用元数据可以为代码增加额外的信息。元数据注解以 @ 开头,后面跟一个编译时常量, 或者调用一个常量构造函数。

自定义元数据注解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Todo {
  final String who;
  final String what;

  const Todo(this.who, this.what);
}

@Todo("Jack", "change function name")
void f() {
}