Dart语言一览

一.简介

Dart is a client-optimized language for fast apps on any platform.

Dart是谷歌开发的计算机编程语言,可用于移动应用、网页、服务器等领域。它是一门面向对象的语言,也是强类型的、类定义的、单继承的语言,支持接口、混入(Mixins)、抽象类、泛型、可选类型等。它融合了许多现代编程语言的优秀特性,从中能看到JavaScript、Swift等的影子。

二.语法、特性

本篇主要用于记录接触Dart以来我个人比较感兴趣的一些特性和语法,部分内容会与Swift做比较,以便更好的理解其语法特性。Dart提供了一个基于浏览器的DartPad工具以便调试代码,本文所有代码均可在线调试。开讲~

1.数据类型

Dart 语言支持以下内建类型:

  • num
  • String
  • Boolean
  • List
  • Map
  • Set
  • Rune(表示字符串中的 UTF-32 编码字符)
  • Symbol(符号,如根号√ ̄)

num表示数值型,包括整型int和浮点型double,没有float。其中 int 可转为 double 类型,反之则不行。

1
2
3
4
5
6
7
8
9
10
void main() {
var a = 1.0;
a = 1;

num b = 1; // 直接使用num
b = 1.0;

var c = 1;
c = 1.0; //报错:A value of type 'double' can't be assigned to a variable of type 'int'
}

num类型与String类型可相互转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void main() {
var a = 1.0;
a = double.parse('2.0'); //字符转浮点
print(a);
var b = a.toString(); //浮点转String
print('+++$b, type:${b.runtimeType}'); //输出“+++2, type:String”

int c;
c = int.parse("3");
var d = c.toString();
print('+++c:$c,d:$d, type:${d.runtimeType}'); //输出"+++c:3,d:3, type:String"

num e = 4;
final f = e.toString(); //num转字符
e = double.parse('5.0'); //字符转num
print('+++f:$f,e:$e'); //输出"+++f:4,e:5"
}

Dart 中的集合与 Swift 相似,具备自动推断类型的能力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//数组
var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);
//这里 Dart 会推断 list 的类型为 List<int> 。
//如果将非整数对象添加到此 List 中, 则会报错。
list[1] = 1;
assert(list[1] == 1);


//字典
//key可以是任何类型-与Swift类似
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
}; //gifts 的类型会被推断为 Map<String, String>

var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
}; //nobleGases 的类型推断为 Map<int, String>

2.万物皆对象

Dart中万物皆对象,所有对象都直接或间接继承自Object类,无论是数字,函数还是 null 都是对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
enum Color {
red,
green,
blue
}

void main() {
var a = 1;
print(a is Object); //true

a = null;
print('${a.runtimeType}, ${a is Object}'); //Null, true

var x = Color.blue;
print(x is Object); //true

//未初始化时默认值是null
int aInt;
print(aInt); //null
}

3.动态类型

一般声明变量时都会明确申明变量的类型,或者通过类型推断来自动识别变量的类型:

1
2
int a = 1; //指定为int
var b = 2; //推断为int

但如果对象不限定为单个类型,或者暂时不知道是什么类型,则可以指定为对象类型动态类型

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
26
enum Color {
red,
green,
blue
}

void main() {

//指定为Object,类似于Swift中的Any或者OC中的id
List<Object> arr;
arr = [1,"x",Color.red];
var x = arr[2];

if (x is int){
print('$x is int');
}else if (x is String) {
print('$x is String');
}else if (x is Color) {
print('$x is enum');
}

// 动态类型
dynamic name = 'Bob';
name = 1; // 可以更改类型
print('name is:$name'); //输出”name is:1“
}

4.常量

1.赋值时机

Dart中声明常量时有两种标识符constfinal

  • const修饰编译时常量;
  • final可修饰编译时常量或运行时常量;

编译时常量是指常量的值在编译时就已经确定,而运行时常量的值在运行时才确定:

1
2
3
4
5
6
7
8
9
10
class A {  
}
void main() {
num a = 1;
final b = a.toString();
const c = a.toString(); //报错:Const variables must be initialized with a constant value

final c = A();
const d = A(); // 报错,因为创建A的实例时使用的构造函数在运行时才执行。
}
2.集合可变性

集合的可变性是由【其本身的修饰符】和【其字面量的修饰符】共同决定的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 基本类型
final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';
name = 'Alice'; // Error: 一个 final 变量只能被设置一次。

// 集合
var constantList = const [1, 2, 3];
// constantList[1] = 1; // 取消注释会引起编译时异常。

const constantList2 = [1, 2, 3];
constantList[1] = 1; // 取消注释会引起编译时异常。

final constantList2 = [1, 2, 3];
constantList2[1] = 1; // 取消注释不会引起错误。

var constantList3 = final [1, 2, 3]; //报错

可以这么总结:

  • final不能修饰字面量;
  • const可定义编译时常量,也可以用来定义不可变字面量;
  • final和const常量都只能赋值一次;
  • final集合变量不能重新赋值,但集合内的元素可修改;
  • const集合或字面量不能重新赋值,集合内元素也不可修改。
3.用const优化性能

const修饰的两个常量的值相同,则二者指向同一片内存空间:

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
// 测试非const
var a = Object();
var b = Object();
//检查两个引用是否指向同一个对象
print(identical(a,b)); //输出false

//测试const
const a1 = Object();
const b1 = Object();
print(identical(a1,b1)); //输出true
}

const创建多个相同的对象时,内存中只会保留一个对象。

因此,在优化性能时,可以考虑合理使用const关键字这个方法。

5.函数&可选参数

  • 胖箭头语法

如果函数体中只有一行语句或一个表达式时,可以使用=> expr简写语法:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 原写法
void printInt(int i) {
print(i); // 函数体中只有一行语句
}
// 胖箭头写法
void printInt1(int i) => print(i);

// 示例0:没返回值的函数
add0(int a, int b){
a+b; //表达式
}

// 示例1:带返回值的函数
int add1(int a, int b) {
return a+b; //返回值
}

// 示例2:add1的=>写法
int add2(int a, int b) => a+b; //=>后为返回值

// 示例3:add1的=>写法 注意函数前没带返回值类型"int"
add3(int a, int b) => a+b; //=>后为返回值

// 示例4:{}函数体内只有一个print表达式
add4() => ((){
print("++++this is A block+++");
});

// 示例4的胖箭头写法,=>后为print表达式
add5() => (() => print("++++this is B block+++"));

void main() {
print(add0(0,1)); //null
print(add1(0,1)); //1
print(add2(0,1)); //1
print(add3(0,1)); //1
add4()(); //++++this is A block+++
add5()(); //++++this is B block+++
}

注意:add0不能改成胖箭头写法,否则=>会把其后的数值变成返回值,从而改变函数类型。

也就是说:=> expr != {return expr;}

在箭头=>和分号;之间只能使用一个表达式 ,不能是多语句。

  • 命名可选参数

定义函数时,使用花括号 {param1, param2, …} 来指定命名参数;

  • 位置可选参数

将参数放到 [] 中来标记参数是可选的;

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
26
class Bird {
void tweet(int a, int b){ //未命名参数
print("tweet: JIO JIO JIO~");
}

int tweet2({int a, int b, int c}){ //命名可选参数,调用时类似于 swift 的外部参数
if(b is Null){
b = 0;
}
print("tweet2: $a + $b + $c JIO JIO JIO~");
return a+b;
}

int tweet3(int a, [int b, int c]){ //位置可选参数
print('tweet3: b:$b,c:$c');
}
}

void main() {
var b = Bird();
b.tweet(1, 2); // 输出:tweet: JIO JIO JIO~
b.tweet2(a:1, b:2); // 输出:tweet2: 1 + 2 + null JIO JIO JIO~
b.tweet2(a:1); // 输出:tweet2: 1 + 0 + null JIO JIO JIO~
b.tweet3(1); // 输出:tweet3: b:null,c:null
b.tweet3(1, 2); // 输出:tweet3: b:2,c:null
}

一个参数只能选择【命名可选参数】和【位置可选参数】其中一种方式修饰。

  • 默认参数值

和Swift一样,定义方法时使用 = 来定义可选参数的默认值,默认值只能是编译时常量。

1
2
3
4
5
int add(int a, [int b = 0]) => a+b;
void main() {
print(add(1, 2)); // 输出3
print(add(1)); // 输出1
}

6.函数是一等公民

与Swift一样,Dart中函数也是一等公民,这意味着一个函数可以作为另一个函数的参数。示例1:

1
2
3
4
5
6
7
8
void printElement(int element) {
print(element);
}
void main() {
var list = [1, 2, 3];
// 将 printElement 函数名作为参数传递
list.forEach(printElement);
}

同样可以将一个函数赋值给一个变量,例如:

1
2
3
4
5
6
7
8
9
String printElement(String element) {
var x = '$element'.toUpperCase();
print(x);
return x;
}
void main() {
var loudify = printElement;
print(loudify('hello') == 'HELLO');
}

但是,Dart中函数作为参数时,会有个小困扰,示例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 定义方法
int mathAdd(int a, int b) { //相加
return a + b;
}
int mathMultiple(int a, int b) { //相乘
return a * b;
}
int mathIncrease(int a) { //自增1
return ++a;
}
// 方法作为参数
int math1(int a, int b, Function mathOperation) { // 接收2个参数
return mathOperation(a, b); //调用具体做事的函数
}
int math2(int a, Function mathOperation) { //接收1个参数
return mathOperation(a);
}
//调用
main() {
// 向外层函数传递内层函数作为参数时,直接使用内层函数名
print(math1(1, 2, mathAdd));
print(math1(1, 2, mathMultiple));
print(math2(1, mathIncrease));
}

示例2中,当方法作为参数(mathOperation)时,Dart只是将mathOperation标注为Function类型:

1
2
3
int math1(int a, int b, Function mathOperation) {
return mathOperation(a, b);
}

但从Function关键字上并看不出mathOperation代表的函数有几个参数,参数分别是什么类型,返回值是啥,这有时很容易让人困惑。相较而言,Swift中将函数作为参数时,会明确标明此函数的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义作为参数的函数
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
// 定义作为参数的函数
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
return a * b
}
// 定义使用函数作为参数的函数(明确知道作为参数的函数是什么类型的)
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
// 调用并传递函数作为参数
printMathResult(addTwoInts, 3, 5) // Prints "Result: 8"

这里(Int, Int) -> Int即明确指明了mathFunction参数的函数签名,清楚明了。

为了解决这个问题,Dart也提供了自己的解决方案:使用Typedefs为函数起一个别名, 别名可以用来声明字段及返回值类型。 当函数类型分配给变量时,typedef会保留类型信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef aliasType = int Function(int a, int b); //给函数定义别名

// 定义方法
int mathAdd(int a, int b) { //相加
return a + b;
}
int mathMultiple(int a, int b) { //相乘
return a * b;
}
// 方法作为参数
int math1(int a, int b, aliasType mathOperation) { //使用函数别名
return mathOperation(a, b); //调用具体做事的函数
}

//调用
main() {
print(math1(1, 2, mathAdd));
print(math1(1, 2, mathMultiple));
}

7.闭包

大部分方法都带有名字,你也可以创建没名字的方法,称之为匿名函数或者闭包

匿名函数和命名函数看起来类似— 在括号之间可以定义一些参数或可选参数,参数使用逗号分割。

后面大括号中的代码为函数体:

1
2
3
([[Type] param1[, …]]) { 
codeBlock;
};

示例1:

1
2
3
4
void main() {
var loudify = (msg) => '${msg.toUpperCase()}'; // 匿名函数/闭包,赋值给变量
print(loudify('hello') == 'HELLO'); //调用闭包,输出true
}

示例2:

1
2
3
4
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});

8.链式/级联

级联运算符 ..可以像RAC一样,实现对同一个对像的一系列链式操作。 除了调用函数,还可以访问同一对象上的字段属性。

1
2
3
4
5
// 一般情况下的操作
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

使用级联操作符:

1
2
3
4
querySelector('#confirm') // 获取对象。
..text = 'Confirm' // 调用成员变量。
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));

9.switch的值

switch可比较整数、字符串、枚举。比较的对象必须都是同一个类的实例且类没有重写==

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
26
27
28
29
30
31
32
33
34
35
// 定义枚举
enum Color{
red,
green,
blue
}
// 比较
void main() {
var a = Color.red;
switch (a){
case Color.red:
print("red");
break;
case Color.green:
print("green");
break;
case Color.blue:
print('blue');
break;
default:
print("unknown");
}
// 试错
var x = 0.1;
switch (x){ //报错 The switch case expression type 'double' can't override the == operator
case 0.0:
print('0.0');
break;
case 0.1:
print('0.1');
break;
default:
print('default');
}
}

10.可选链

与Swift一样,调用方法时使用?.来代替., 可以避免因为左边对象可能为 null 导致的异常:

1
2
3
var p = Point(2, 2);
// 如果 p 为 non-null,设置它变量 y 的值为 4。
p?.y = 4;

11.构造函数

  • 构造函数

创建一个与类同名的函数来声明构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Point {
num x, y, z;

Point(num x, num y) { //构造函数
this.x = x;
this.y = y;
//不要求所有成员变量都完成初始化
print('x:$x,y:$y,z:$z'); //x:0,y:1,z:null
}
}
// 创建实例
void main() {
var p = Point(0,1);
}

利用语法糖对上面的构造函数进行简化:

1
2
3
4
5
6
7
class Point {
num x, y, z;
Point(this.x, this.y); // 将传入的参数的值赋值给对应的实例变量
}
void main() {
var p = Point(0,1);
}
  • 默认构造函数

在【没有声明构造函数的情况下】, Dart 会提供一个默认的构造函数。

1
2
3
4
5
6
class Point {
num x, y, z;
}
void main() {
var p = Point(); //默认构造函数
}

【子类不会自动继承父类的构造函数】。如果子类不声明构造函数,那么它就只有默认构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
class Point { // 父类
num x, y;
Point(this.x, this.y); //自定义构造函数
}
class SubPoint extends Point { // 子类
num z;
// 没有构造函数,所以只有默认构造函数SubPoint()
}
void main() {
var p = SubPoint(12); // 报错1,因为子类不会自动继承父类的构造函数,所以这里调用无效
var p2 = SubPoint(); // 报错2,因为子类只有默认构造函数,而子类的默认构造函数会调用父类的无参构造函数
}

示例中会出现两处报错:

报错1是因为子类不会自动继承父类的构造函数,所以也就无法调用父类的构造函数;

报错2是因为子类的默认构造函数会自动调用父类的无参构造函数,而此时父类中因为提供了自定义构造函数,所以就没有无参构造函数,因此调用失败,所以子类想要调用默认构造参数。父类就不能自定义构造函数:

1
2
3
4
5
6
7
8
9
class Point { // 父类
num x, y;
}
class SubPoint extends Point { // 子类
num z;
}
void main() {
var p = SubPoint();
}
  • 命名构造函数

给构造函数命名,以便更清晰的表明函数的意图,格式为【类名.函数名(参数..)】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Point { // 父类
num x, y;
Point.initWithXY(this.x,this.y){
print('called super init,x:$x,y:$y');
}
}
class SubPoint extends Point { // 子类
num z;
SubPoint.initWithXYZ(num x, y, this.z): super.initWithXY(x,y){ //注意这里调用父类构造函数的语法
print('called sub init,x:$x,y:$y,z:$z');
}
}
void main() {
var p = SubPoint.initWithXYZ(1,2,3);
}

注意这里调用父类构造函数的语法:【在当前构造函数冒号 (:) 之后,函数体之前,声明调用父类构造函数】。

  • 常量构造函数

常量构造函数以const修饰,用于成员变量都是final的的类。

用常量构造函数实例化多个对象时,若参数相同,则内存中只会保留一个对象,节省内存开销。

反例1:

1
2
3
4
5
6
7
8
9
10
11
12
//使用非常量构造函数
class Box {
double width;
double height;
Box({required this.width,required this.height});
}

void main() {
var a = Box(width:1,height:1);
var b = Box(width:1,height:1);
print(identical(a,b)); //检查两个引用是否指向同一个对象,输出false
}

正例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//使用常量构造函数
class Box {
final double width; //运行时才传入,所以用final修饰,而非const
final double height;
const Box({required this.width,required this.height}); // 这就是常量构造函数
}

void main() {
//写法1
Box a1 = const Box(width:1,height:1);
Box b1 = const Box(width:1,height:1);
print(identical(a1,b1)); //输出true

//写法2
const a2 = Box(width:1,height:1);
const b2 = Box(width:1,height:1);
print(identical(a2,b2)); //输出true

//反例
var a3 = Box(width:1,height:1);
var b3 = Box(width:1,height:1);
print(identical(a3,b3)); //输出false
}

注意,正例2中最后一条可以看出,调用常量构造函数创建对象时,如果不用const修饰,则这些对象不是常量实例,内存中会存在多份。

常量构造函数在优化Flutter性能时非常有用,因为每次构建组件树时,const修饰的组件只存在一份,并且不参与重新构建。

12.初始化列表

与Swift不一样的是,Dart的构造函数中并不强制所有成员变量都完成初始化,那它们什么时候去初始化呢?Dart的解决方案是初始化列表,即在构造函数的方法体花括号{...}之前初始化实例变量【~~比较奇怪的语法~~】。

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
26
27
28
29
class Point { // 父类
num x, y;
Point.initWithXY(this.x,this.y){
print('called super init,x:$x,y:$y');
}
Point.initFromMap(Map<String,int>dic)
: x = dic['x'],
y = dic['y']{ //初始化列表,初始化当前类的实例变量
print('called super.initFromMap,x:$x,y:$y');
}
}
class SubPoint extends Point { // 子类
num z;
SubPoint.initList(Map<String,int>dic)
: z = dic['z'],
super.initFromMap(dic){ //调用父类构造函数要放在初始化列表的最后
if (x == 1){
x = 0; // 函数体内可以继续修改变量的值
}
print('called sub.initList,x:$x,y:$y');
}
SubPoint.initWithXYZ(x,y,this.z): super.initWithXY(x,y){
print('called sub init,x:$x,y:$y,z:$z');
}
}
void main() {
var dic = {'x':1,'y':2,'z':3};
var p = SubPoint.initList(dic);
}

各参数的初始化用逗号分隔,且调用父类构造函数的语句要放在初始化列表的最后。

13.Getter/Setter

和Swift一样,Dart中属性的Getter/Setter也是为计算属性而存在的:

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

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

// 定义两个计算属性: right 和 bottom。
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}

void main() {
var rect = Rectangle(3, 4, 20, 15);
print(rect.left); //调用Getter 输出“3”
rect.right = 12; //调用Setter
print(rect.right); //12
print(rect.left); //-8
}

通过修改left,从而影响 left + width = right,最终完成对right属性的修改。

14.抽象类&extends

使用abstract来定义抽象类。抽象类不能被实例化,通常用来定义接口,以及部分实现。

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
26
27
28
29
30
31
32
33
34
// 抽象类
abstract class AbstractClass {
// 定义字段...
var name;
var hasValue = 1;

// 抽象方法。
abstractMethod();

// 带实现的方法
funcWithImplements(){
print("~~~~~~~");
}
}
// 子类
class ConcreteClass extends AbstractClass { // 单继承
var name = 1;
var hasValue = 2;

@override
abstractMethod(){ // 提供方法实现,所以这里的方法就不是抽象方法了...
print('++call abstractMethod()');
}

@override
funcWithImplements(){ // 重写
print("~~~call funcWithImplements()");
}
}
void main() {
var a = ConcreteClass();
a.abstractMethod(); //++call abstractMethod()
a.funcWithImplements(); //~~~call funcWithImplements()
}

Dart也和Swift一样,只允许单继承~

15.接口&implements

Dart中移除了interface关键字,可以通过抽象类或implements普通类来实现接口的功能。

每个类都隐式的定义了一个接口,接口包含了该类所有的实例成员及其实现的接口。 如果要创建一个 A 类,A 要支持 B 类的 API ,但是不需要继承 B 的实现, 那么可以通过 A 实现 B 的接口。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 抽象类1
abstract class AbstractClass {
// 定义字段...
var name;
var hasValue = 1;

// 抽象方法。
abstractMethod();

// 带实现的方法
funcWithImplements(){
print("~~~~~~~");
}
}
// 抽象类2
abstract class AbstractClass2 {
abstractMethod2();
}
// 实现类
class ConcreteClass implements AbstractClass, AbstractClass2 { //可以实现多个抽象类
var name = 1;
var hasValue = 2;

@override
abstractMethod(){
print('++call abstractMethod()');
}

@override
abstractMethod2(){
print('++call abstractMethod2()');
}

@override
funcWithImplements(){
print("~~~call funcWithImplements()");
}
}
void main() {
var a = ConcreteClass();
a.abstractMethod(); //++call abstractMethod()
a.abstractMethod2(); //++call abstractMethod2()
a.funcWithImplements(); //~~~call funcWithImplements()
}

16.Mixin

Mixin就是“混入”的意思,用于给当前类添加【新的】【可选】功能。

通常来说,我们可以通过继承或者实现接口来给现有类扩展新的功能。但这些都是带有侵入性的,你可能会获得这些父类或接口中一些多余的功能,并且你需要自己提供这些功能的实现;使用 Mixin 则你只需要定义可复用的 mixin 类,在其中定义API并提供具体实现,然后就可以将这些mixin类的API通过with关键字组合到当前类中。

继承强调的是is-a,而 Mixin 强调的是I can

Mixin是复用类代码的一种途径,复用的类可以在不同层级,可以不存在继承关系。

通过创建一个继承自Object且没有构造函数的类,来实现一个Mixin。 如果Mixin不希望作为常规类被使用,使用关键字mixin替换class

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Devices {
void run() {
print('++++开机');
}
void stop() {
print('++++关机');
}
}

class iPhone extends Devices {
void run() {
super.run();
print('++++iphone 开机');
}
}

// 定义组件/能力
mixin iOS {
void call() {
print('+++iOS running');
}
}

// 定义组件/能力
// 非mixin声明的类也可以通过with关键字混入
class AppStore {
void download() {
print('++++downloading');
}
}

// 混入、拓展iPhone的功能
class iPhoneX extends iPhone with iOS, AppStore {
void run() {
super.run();
print('iphoneX 开机');
}
}

void main() {
var p = iPhoneX();
p.run();
p.call(); //使用混入的能力
p.download(); //使用混入的能力
}

Dart的这个功能还是挺强大的,根据我的掌握的知识,Swift 中尚没有与之对应的关键字。不过真要说的话,或许可以借道protocol+extension,在不增加某个类本身的代码量的前提下,给此类扩展额外的能力:

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
26
27
28
29
30
31
32
33
// 定义组件/能力
protocol IOS {
func call()
}
// 定义组件/能力
protocol AppStore {
func download()
}
// 扩展协议
extension IOS {
func call(){ //给接口添加默认行为
print("+++iOS running")
}
}
// 扩展协议
extension AppStore{
func download(){ //给接口添加默认行为
print("++++downloading")
}
}

// 在不额外增加iPhone类本身代码的前提下,扩展其能力
class iPhone: IOS, AppStore{
func run(){
print("++++iphone 开机");
}
}

// 使用额外的能力
let iphone = iPhone()
iphone.run()
iphone.call()
iphone.download()

17.泛型

这里的泛型与 Swift 中的类似,都是使用<T>格式来表示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Store<T> {
var map = {};
T getByKey(String key){
T value = map[key];
return value;
}
void setByKey(String key, T value){
map[key] = value;
}
}
void main() {
var aStore = Store();
aStore.setByKey('first', 1);
print(aStore.getByKey('first'));
aStore.setByKey('second', 'ii');
print(aStore.getByKey('second'));
}

18.is&as

is用于进行类型检查,即类是否在某个类的继承树上。或者检查对象是否实现了某个接口:

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
26
27
28
29
class Person {
String name;
}

abstract class Speak {
speak();
}

class Student extends Person implements Speak{
int identifier;
speak(){
print('Hello~');
}
}

void main() {
var p = Person();
var s = Student();
Person x = Student(); //父类指针指向子类对象
//Student y = Person(); //报错,不允许子类指针指向父类实例

print(p is Person); //true,直属类
print(s is Student); //true,直属类
print(s is Person); //true,父类
print(x is Student); //true,x实际就是Student
print(x is Person); //true,x是Student,也是Person的子类
print(p is Speak); //false,父类没实现Speak接口
print(s is Speak); //true,子类实现了Speak接口
}

as用于类型转换:

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
26
27
28
29
30
class Person {
String name;
}

abstract class Speak {
speak();
}

class Student extends Person implements Speak{
int identifier;
speak(){
print('Hello~');
}
}

void main() {
var p = Person();
var s = Student();
Person x = Student(); //父类指针指向子类对象

var y = x as Student;
y.identifier = 101;

print(p as Person); //Instance of 'Person'
print(p as Student); //报错,运行时异常
print(s as Person); //Instance of 'Student'
print(s as Student); //Instance of 'Student'
print(x as Person); //Instance of 'Student'
print(x as Student); //Instance of 'Student'
}

19.库

  • 库不仅提供了 API ,而且对代码起到了封装的作用。
  • import 和 library 指令可以用来创建一个模块化的,可共享的代码库。
  • Dart中没有publicprotectedprivate这些访问修饰符,约定以下划线_定义私有类型,表示仅在当前库中可见。
  • 每个 Dart 应用程序都是一个库 ,虽然没有使用 library 指令。
1
2
3
4
5
6
7
8
// 导入核心库
import 'dart:math';

// 从外部包导入库
import 'package:test/test.dart';

// 导入文件
import 'path/to/my_other_file.dart';

如果只使用库的一部分功能,则可以选择需要导入的内容:

1
2
3
4
5
// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;

如果导入两个存在冲突标识符的库, 则可以为这两个库,或者其中一个指定前缀。 例如,如果 library1 和 library2 都有一个 Element 类, 那么可以通过下面的方式处理:

1
2
3
4
5
6
7
8
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

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

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

20.异步

Dart 默认是单线程的,耗时操作很容易造成线程阻塞:

1
2
3
4
5
6
7
loadData(){
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}

网络请求可能会很慢,这时主线程会被挂起,直到请求完成并更新状态。

好在 Dart 提供了异步工具asyncawaitFuture,来实现异步操作。函数体被async标记的函数,即是一个异步函数。将async关键字添加到函数使其返回Future,以便检查结果。

例如,下面的同步函数,它返回一个 String :

1
String lookUpVersion() => '1.0.0';

假如将来的实现将非常耗时,将其更改为异步函数,返回值是Future

1
Future<String> lookUpVersion() async => '1.0.0';
  • awaitasync借鉴自ES7,一般成对出现;
  • 出现await操作的【方法】必须声明为async;
  • await修饰耗时操作,此耗时操作会阻塞当前线程;
  • async修饰的方法会立刻返回,不阻塞当前线程;

示例1:调用时不带await

1
2
3
4
5
6
7
8
9
10
11
12
Future refreshDate() async{
print("++++3333: +${DateTime.now()}");
await Future.delayed(Duration(seconds:5));
print("++++44444: ${DateTime.now()}");
return null;
}

void main() async{
print("++++111: ${DateTime.now()}");
refreshDate(); // 不标记为await 会直接执行下一行
print("++++222: ${DateTime.now()}");
}

日志:

1
2
3
4
++++111: 2020-05-08 23:53:59.366
++++3333: +2020-05-08 23:53:59.366
++++222: 2020-05-08 23:53:59.366
++++44444: 2020-05-08 23:54:04.367

示例2:调用时带await

1
2
3
4
5
6
7
8
9
10
11
12
Future refreshDate() async{
print("++++3333: +${DateTime.now()}");
await Future.delayed(Duration(seconds:5));
print("++++44444: ${DateTime.now()}");
return null;
}

void main() async{
print("++++111: ${DateTime.now()}");
await refreshDate(); // 标记为await 则先执行完refreshDate操作才会执行下一行
print("++++222: ${DateTime.now()}");
}

日志:

1
2
3
4
++++111: 2020-05-08 23:51:24.701
++++3333: +2020-05-08 23:51:24.701
++++44444: 2020-05-08 23:51:29.702
++++222: 2020-05-08 23:51:29.702

Dart 还提供了Isolate来让代码运行在其他线程中,从而实现并发编程,后续有时间继续研究~

三.后记

Dart包含了诸多现代编程语言的特性,特点鲜明:

  • 高效:语法清晰简洁、工具简单而强大、输入检测可尽早识别细微错误、库文件丰富;

  • 快速:提供AOT提前编译优化,在移动设备和web上实现高性能和快速启动;支持热加载

  • 可移植:可编译成ARMx86代码,支持移动、桌面及终端程序;web上会转换为JavaScript

  • 易学:面向对象,语法风格与Swift相似,上手相对容易些。

  • 响应式:支持响应式编程。可通过 Future 和 Stream 的特性和API实现异步编程。

学习Flutter之前有必要研究一下Dart,对比学习从而加深对各语言的理解~


相关参考:

#©Dart中文网

#©DartPad-在线编译调试工具

#©Flutter中文网


Dart语言一览
https://davidlii.cn/2022/01/09/dart.html
作者
Davidli
发布于
2022年1月9日
许可协议