语言简介 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 Copy 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 里:
Copy 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 。
Copy 1
int number = 5.5 ; // error A value of type 'double' can't be assigned to a variable of type ' int '.
只能用 double
类提供的 toInt
方法进行转换
Copy 1
2
int number = 5.5 .toInt();
print(number);
Dart 有自动类型推导,在声明变量时指定类型是可选的,下面的 number
自动推导成 int
类型。
Copy 1
2
var number = 10 ;
print(number.runtimeType);
Dart 支持声明接收任意类型的变量,在运行时动态改变。
Copy 1
2
3
4
Object any = "ANY" ;
print(any.runtimeType);
any = 2 ;
print(any.runtimeType);
空安全 Dart 中的类型默认是非空的,除非声明它们可为空。
Copy 1
int number = null ;// Error: The value 'null' can't be assigned to a variable of type ' int ' because ' int ' is not nullable.
要想声明一个变量可为空,就在类型声明后面加上 ?
Copy 1
2
int ? number = null ;
print(number);
Dart 的空安全据说是完全可靠的,如果一个变量不可为空,那么它永远不为空。
默认值 对于不可空类型,在使用之前必须初始化
Copy 1
2
int number;
print(number);// Error: Non- nullable variable 'number' must be assigned before it can be used.
Copy 1
2
3
int number;
number = 5 ;
print(number);
对于可空类型,不初始化默认为 null
Copy 1
2
int ? number;
print(number);
Late 变量 Dart 2.12 添加了 late
变量修饰符,它的作用有两个:
延迟初始化不可空变量 延迟初始化变量 有时 Dart 推导非空类型初始化时会失败,如:
Copy 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
修复这个问题
Copy 1
2
3
4
5
late int number;
void main() {
number = 10 ;
print(number);
}
当在声明变量同时初始化时,加上 late
可以延迟初始化,主要有两种场景用到:
这个变量可能不会被用到,或者初始化它花的时间比较长 需要初始化一个实例变量,但是在构造函数里需要用到 this
Copy 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);
}
Copy 1
2
3
do some things
delay init
10
Final 和 Const final
修饰的变量只可以被赋值一次,=const= 修饰的变量是一个编译期常量,必须在编译期计算出来。
Copy 1
2
final int number = 5 ;
number = 10 ; // Error: Can't assign to the final variable ' number'.
final
可以赋值一次
Copy 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
修饰的变量必须在编译期就确定
Copy 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);
}
右边的值必须在编译器确定
Copy 1
2
3
4
void main() {
const int number = 10 ;
print(number);
}
const
也可以创建常量值,对应的变量值是可以改变的,即使它引用过 const 值
Copy 1
2
3
4
var number = const [];
print(number);
number = [1 , 2 , 3 ];
print(number);
内置类型 Numbers Dart 支持两种内置类型:=int= 和 double
,如果一个数字包括了小数点,它就是 double
,否者就是 int
Copy 1
2
3
4
var n = 1 ;
print(n.runtimeType);
var m = 1.1 ;
print(m.runtimeType);
整型字面量可以自动转换成 double
类型,反之就不行。
Copy 1
2
double m = 1 ;
print(m);
int
和 double
的父类型是 num
Copy 1
2
3
num n = 1 ;
n += 1.5 ;
print(n);
Number 和 String 之间的转换如下:
Copy 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 ));
Strings Dart 字符串是 UTF-16 编码的字符序列,可以使用单引号和双引号创建字符串,或者使用三引号创建多行字符串。
Copy 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);
使用 ${表达式}
在字符串中插入表达式,如果表达式是一个标识符,可以省略 {}
,如果是一个对象,会自动调用对象的 toString
方法。当然,加上 \
就可以当成原本的字符。
Copy 1
2
3
String s = "Jack" ;
print("My name is $ s" );
print("My name is \$ s" );
Copy 1
2
My name is Jack
My name is $s
字符串前面加 r
创建 raw 字符串
Copy 1
2
String s = r"This is \" ;
print(s);
字符串操作 使用 contains
startsWith
endsWith
indexOf
在字符串内搜索
Copy 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" ));
使用 substring
提取子串,=split= 分割字符串,=toUpperCase= toLowerCase
转换大小写,=trim= 移除首尾空格
Copy 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());
Copy 1
2
3
4
5
Hello
[Hello, World]
HELLO WORLD
hello world
hello
使用 replaceAll
替换部分字符串
Copy 1
2
String s = "Hello World" ;
print(s.replaceAll(RegExp("World" ), "Jack" ));
构造字符串可以使用 StringBuffer
, writeAll
第二个可选参数为分割字符
Copy 1
2
3
4
5
6
var sb = StringBuffer();
sb
..write("Use StringBuffer" )
..writeAll(["A" , "B" , "C" ], "/" )
..write("!" );
print(sb.toString());
更多的 API: String
布尔类型 Dart 使用 bool
关键字表示布尔类型,布尔类型的只有 true
和 false
两个对象。
Dart 的类型安全不允许其它类型隐式转换成布尔类型,比如数字 0 在 if
里并不是 fasle
。
Lists Dart 中的 List 对象表示的就是数组类型,下标从 0 开始,可以用 []
访问
Copy 1
2
3
4
var list = [1 , 2 , 3 ];
print(list.length);
print(list[1 ]);
print(list.runtimeType);
上面的声明会被推导成 List<int>
类型
扩展操作符(=…=) 和空扩展操作符(=…?=)提供了一种将多个元素插入集合的简洁方法。
Copy 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);
Copy 1
2
3
[0, 1, 2, 3]
[1, 2, 3, 4]
[0, 1, 2, 3, 4]
如果扩展操作符右边可能为 null
,就使用空扩展操作符避免异常。
Copy 1
2
3
var list;
var list1 = [1 , ...? list];
print(list1);
Dart 还可以在构建集合时使用 if
或者 for
。
Copy 1
2
3
4
5
6
7
8
var n = 3 ;
var list = [
1 ,
2 ,
if (n == 0 ) 3
else 4
];
print(list);
Copy 1
2
3
var list = [1 , 2 , 3 ];
var list1 = [for (var i in list) i];
print(list1);
List 常用操作 add
addAll
添加元素,=removeAt= 删除单个元素,=clear= 删除所有元素
Copy 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);
Copy 1
2
3
[1, 2, 3, 4]
[2, 3, 4]
[]
indexOf
查找一个对象对应的下标值
Copy 1
2
var list = ["Apple" , "Orange" ];
print(list.indexOf("Orange" ));
sort
排序
Copy 1
2
3
var list = [3 , 1 , 2 , 5 , 4 ];
list.sort();
print(list);
Sets Dart 中的 Set 是无序元素的集合
Copy 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);
Copy 1
2
3
_CompactLinkedHashSet<String>
_CompactLinkedHashSet<String>
_CompactLinkedHashSet<int>
Set 和 Map 的声明类似,如果声明时不指定元素,默认会推导成 Map
Copy 1
2
var itIsMap = {};
print(itIsMap.runtimeType);
Copy 1
_InternalLinkedHashMap<dynamic, dynamic>
Set 同样支持 ...
和 ...?
操作符,以及集合内的 if
和 for
Set 常用操作 使用 from
从 List 构造 Set
Copy 1
print(Set.from([1 , 2 , 3 ]));
add
addAll
添加元素,=remove= 移除一个元素,=clear= 移除所有元素
Copy 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 );
Copy 1
2
3
{1, 2, 3}
{1, 3}
{}
contains
和 containsAll
检查一个或多个元素是否在 Set 中
Copy 1
2
3
var set = {1 , 2 , 3 , 4 };
print(set .contains(3 ));
print(set .containsAll([1 , 4 ]));
intersection
求交集 ,
Copy 1
2
3
4
var set1 = {1 , 2 , 3 };
var set2 = {2 , 3 , 4 };
print(set1.intersection(set2));
print(set1.difference(set2));
更多操作看相关的 API : https://api.dart.cn/stable/dart-core/Set-class.html
Maps Map 就是 key-value 对象,key value 可以为任意类型
Copy 1
2
3
4
var map = {
"first" : 1 ,
"second" : 2
};
可以使用 []
操作 Map
Copy 1
2
3
4
5
6
7
var map = {
"first" : 1
};
print(map["first" ]);
map["second" ] = 2 ;
print(map);
print(map["third" ]);
Copy 1
2
3
1
{first: 1, second: 2}
null
Map 同样支持 ...
和 ...?
操作符以及 if
for
Map 的 API 直接看相关文档:Map api docs
函数 Dart 的函数也是对象,它的类型是 Function ,它是一级对象,可以复制给变量,也可以作为参数
Copy 1
2
3
4
5
void f() {
}
void main() {
print(f.runtimeType);
}
Dart 的函数都有返回值,如果不写,默认返回 null
,定义时可以没有返回类型,但是推荐在公开的 API 上加上返回类型
Copy 1
2
3
4
5
6
7
f() {
return 10 ;
}
void main() {
print(f());
}
如果函数体只有一个表达式,可以使用 =>
Copy 1
2
f() => 10 ;
print(f());
参数 Dart 函数有两种参数,必要参数和可选参数,必要参数放到前面。可选参数可以是命名的或位置的,可选参数如果不传就为 null
,如果可选参数类型为不可空类型,就需要给可选参数提供默认值。
命名参数 命名参数默认为可选参数,使用 {}
包起来,除非被标记为 required
Copy 1
2
3
4
5
6
f({required String ? first, String ? second}) {
print(first);
print(second);
}
f(first: "FIRST" );
f(first: "FIRST" , second: "SECOND" );
Copy 1
2
3
4
FIRST
null
FIRST
SECOND
位置参数 位置参数使用 []
包起来
Copy 1
2
3
4
5
6
7
f(String must, [String ? first]) {
print(must);
print(first);
}
f("must" );
f("MUST" , "FIRST" );
Copy 1
2
3
4
must
null
MUST
FIRST
main 函数 main 函数作为入口,返回值为 void ,有一个 List<String>
的可选参数。
Copy 1
2
3
void main() {
print("Hello World" );
}
Copy 1
2
3
void main(List< String > args) {
print(args);
}
匿名函数 格式与命名函数类似
Copy 1
2
3
4
var f = () {
return 10 ;
};
print(f());
词法闭包 函数可以封闭定义到它作用域的变量。
Copy 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// 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 );
}
运算符 算术运算符:=+= -
-表达式
*
/
~/(除并向下取整)
%
++
--
关系运算符:==== !=
>
<
>=
<=
判断两个对象是否相同一般用 ==
就可以了,及少数情况需要 identical
类型判断符:
as ,类型转换 is ,为指定类型返回 true is! ,为指定类型返回 flase 赋值运算符:=== ??=
Copy 1
2
3
4
// Assign value to a
a = value;
// 如果 b 是 null ,b 为 value ,否则 b 保持原值
b ??= value;
算数运算符同样可以和赋值运算符结合,如:=+== -=
等
逻辑运算符:
按位和移位运算符:=&= |
^
~
<<
>>
>>>
条件表达式:=?:= ??
根据布尔表达式赋值时用 ?:
Copy 1
var a = isPub ? "A" : "B" ;
根据 null
赋值时用 ??
Copy 1
String playerName(String ? name) => name ?? "Default" ;
级联运算符:=..= ?..
可以在同一对象上连续调用多个对象的变量或方法
Copy 1
2
3
4
var paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0 ;
等价于:
Copy 1
2
3
4
var paint = Paint();
paint.color = Colors.black;
paint.strokeCap = StrokeCap.round;
paint.strokeWidth = 5.0 ;
控制流 条件 if-else 进行条件判断,else 是可选的,if 语句中的条件必须是布尔值
Copy 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" );
}
Dart 支持 switch-case
,非空的 case
子句必须有 break
;
Copy 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
循环
Copy 1
2
3
for (var i = 0 ; i < 2 ; i++ ) {
print(i);
}
for-in 遍历可迭代对象,如 List
Copy 1
2
3
4
var list = [1 , 2 ];
for (final e in list) {
print(e);
}
forEach
也可以作用于可迭代对象
Copy 1
2
var list = [1 , 2 ];
list.forEach(print);
while
, do-while
和其它语言一样
break
中断循环, continue
继续循环。
断言 Dart 的 assert
失败会抛出 AssertionError
异常,一般在生产环境会忽略
异常 Dart 可以抛出和捕获异常,推荐抛出 Error 和 Exception 类型的异常
抛出异常:
Copy 1
throw "Any type exception" ;
使用 try
on
catch
来捕获异常
Copy 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 的第二参数可以为堆栈信息。
Copy 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
产生问题。
Copy 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;
构造函数 可以使用构造函数创建一个对象,构造函数可以为类名或者类名.标识符的方式:
Copy 1
2
var p1 = Point(2 , 2 );
var p2 = Point.fromJson({'x' : 1 , 'y' : 2 });
类可以提供常量构造函数,来提供编译时常量
Copy 1
var p = const ImmutablePoint(2 , 2 );
实现类 实例变量 Copy 1
2
3
4
5
class Point {
double ? x;
double ? y;
double z = 0 ;
};
未初始化的实例变量被初始化成 null
所有实例变量都会有一个 get 方法,非 final
的实例变量和 later final
声明但是为初始化的实例变量还会有一个 set 方法
Copy 1
2
3
4
5
6
7
8
9
10
11
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.
}
构造函数 声明一个和类名一样的函数即为构造函数
Copy 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;
}
}
一种简化的方法:
Copy 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);
}
构造函数可以有初始化列表
Copy 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
如果没有声明构造函数,类会自动生成一个无参数构造函数,并会调用父类的无参数构造函数。
子类不会继承父类的构造函数。
可以为一个类声明多个命名式构造函数,让语义更加清晰
Copy 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;
}
子类和父类构造函数调用顺序:
子类构造函数的初始化列表 父类构造函数 当前类的构造函数 默认会调用父类的无参数构造函数,除非显示调用父类构造函数。
Copy 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'
}
Copy 1
2
3
in Person
in Employee
Instance of 'Employee'
传递给父类的参数不能有 this ,因为子类构造函数还没有执行
构造函数可以重定向
Copy 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 );
}
如果类生成的对象是不可变的,可以定义常量构造函数
Copy 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
标记的构造函数会让该构造函数称为工厂构造函数,意味着使用工厂构造函数不一定总是返回新的实例
Copy 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
Copy 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 可以重写部分操作符
Copy 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 ));
}
Getter 和 Setter 是读写对象属性的特殊方法,可以用 get
set
关键字为额外属性定义 Getter 和 Setter 方法
Copy 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 );
}
抽象方法是定义接口而没有具体的实现,只会存在于抽象类中,直接用 ;
代替方法体可以声明一个抽象方法
Copy 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
标识的类可以让该类称为抽象类,抽象类无法实例化。如果想让抽象类可以实例化,可以定义一个工厂构造函数
Copy 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
Copy 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()));
}
Copy 1
2
Hello, Bob. I am Kathy.
Hi Bob. Do you know who I am?
要实现多个接口,可以用逗号分割每个接口类
Copy 1
class Point implements Comparable, Location {...}
继承 使用 extends
来创建一个子类,使用 super
关键字在子类中引用父类
Copy 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
注解表示重写了一个成员
Copy 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) {...}
// ···
}
重写的方法需要满足下面几点:
返回值类型必须是父类方法返回值的相同类型或父类型 参数类型同返回值类型 父类方法接收 n 个位置参数,重写方法也要接收 n 个位置参数 泛型方法不能重写非泛型方法,非泛型方法不能重写泛型方法 noSuchMethod 如果调用了对象上不存在的方法或实例变量,会触发 noSuchMethod
方法,可以重写这个方法
只有下面一个条件成立时,才能调用到 noSuchMethod
:
接收方是静态的 dynamic 类型 接收方具有静态类型,定义了未实现的方法,并且接收方的动态类型实现了 noSuchMethod
方法。 Copy 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();
}
Copy 1
2
A: noSuchMethod
B: noSuchMethod
扩展方法 扩展方法用来向一些已有的库添加额外的方法,比如下面向 String 添加一个 addPrefix 方法
Copy 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" ));
}
枚举 使用 enum
关键字定义枚举类型 ,每一个枚举值都有一个 index ,=values= 可以获取枚举值列表
Copy 1
2
3
4
5
enum Color { red, green, blue }
void main() {
print(Color.red.index);
print(Color.values);
}
Copy 1
2
0
[Color.red, Color.green, Color.blue]
枚举的限制:
枚举不能称为子类,不可以 mixin ,也不可以实现枚举 不能实例化一个枚举 Mixin 使用 mixin
关键字定义 mixin ,使用 with
关键字使用 mixin ,普通类也可以作为 mixin 。
Copy 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();
}
Copy 1
2
Readable: read
Writable: write
类变量和方法 使用 static
关键字声明类的变量和方法
Copy 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());
}
泛型 Dart 的方法和类都支持泛型,通过 extends
限制泛型类型
Copy 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 ));
}
库 使用 import
和 library
关键字可以创建一个模块化和可共享的库以 _
开头的成员只在库内部可见。
使用库 使用 import
导入库,它的参数是代码库的 URI ,对于内置库,使用 dart:xxxx
形式,对于其它库,可以使用 package:xxxxx
或者直接是文件路径
Copy 1
2
import "dart:html"
import "package:test/test.dart"
如果库有冲突的标识符,可以指定前缀
Copy 1
2
3
4
5
6
7
import "package:lib1" ;
import "package:lib2" as lib2;
// lib1
A a = A();
// lib2
lib2.A lib2a = lib2.A();
可以只导入库的一部分
Copy 1
2
3
4
// 只导入 foo
import "package:lib1" show foo
// 导入所有,除了 foo
import "packaeg:lib2" hide foo
实现库 这一部分会单独写一篇文章
异步 Dart 中有两种异步对象: Future
和 Stream
,返回这两种对象的函数都是异步函数。
Future 一个 Future<T>
表示一个异步操作的结果,它有两种状态:
当调用一个异步函数时,返回一个未完成态的 Future ,这个 Future 正在等待异步函数的结果 T
,如果异步函数完成,Future 会到完成状态并拿到结果,如果异步函数出现错误,
Future 同样会到达完成状态并获取到对应的错误。
Future<T>
的 then
方法会注册一个回调,当 Future 到达完成状态时会调用这个回调函数
Copy 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));
}
Copy 1
2
Received: Hello
Error: Delay Error
async 和 await async
可以让一个函数称为异步函数,在异步函数里可以用 await
等待 Future 结果,用 try-catch
捕获 Future 的错误。main 函数也可以成为一个异步函数
Copy 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" );
}
}
Copy 1
2
Use Await
Error: Delay Error
Stream Stream 提供了一种异步的数据序列,包括用户生成的事件或从文件中读取的数据。
可以使用 async
关键字和 异步循环 await for
从 Stream 中获取值
Copy 1
2
3
4
5
void main() async {
await for (final request in requestServer) {
handleRequest(request);
}
}
生成器 当需要延迟生成一连串的值时,可以使用生成器函数:
同步生成器返回一个 Iterable
对象 异步生成器返回一个 Stream
对象 通过 sync*
关键字实现一个同步生成器函数,使用 yield
语句来传递值通过 async*
关键字实现一个异步生成器函数,使用 yield
语句来传递值
Copy 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
}
}
元数据 使用元数据可以为代码增加额外的信息。元数据注解以 @
开头,后面跟一个编译时常量,或者调用一个常量构造函数。
自定义元数据注解
Copy 1
2
3
4
5
6
7
8
9
10
class Todo {
final String who;
final String what;
const Todo(this .who, this .what);
}
@ Todo("Jack" , "change function name" )
void f() {
}