dart基础2

1.类

Dart是一种基于类和mixin继承机制的面向对象的语言。所有的类都继承于Object。基于Mixin机制意味着每个类都只有一个超类(除Object外),一个类中的代码可以在其它多个继承类中反复使用。

在Dart2中,new关键字是可选的。

在dart中,存在像下面这样实例化对象的方式:

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

2.常量构造函数

在构造函数名之前加const关键字,来创建编译时常量,构造两个相同的编译时常量会产生一个唯一的,标准的实例:

1
2
3
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b));

3.一个不太明白的特性:在dart2中,一个常量上下文中的const关键字可以被省略。如下所示:

1
2
3
4
5
6
7
8
// 首先是一个不省略的版本
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)]
};
// 接下来是一个省略的版本,仅有一个const,由该const建立上下文
const pointAndLine = {
'point': [ImmutablePoint(0, 0)]
};

4.获取对象的类型

使用对象的runtimeType属性可以在运行的时候获取对象的类型。他返回的是一个Type对象。

5.实例变量:就是不是类对象所拥有的,而是类所实例化出来的对象所拥有的

所有未初始化变量的默认值都是null,所有实例变量都会生成一个隐式的getter方法,非final的实例变量同样会生成一个隐式的setter方法。也就是说对一个实例变量进行赋值的时候,实际都是通过调用setter方法来赋值的。

1
2
3
4
5
class Point {
num x; // 初始值是null
num y; // 初始值是null
num z = 0; // 初始值是0
}

6.构造函数,通过创建一个和类同名的函数来声明构造函数,可以使用this关键字来获取当前实例。

1
2
3
4
5
6
7
8
9
10
// 很常见的生成构造函数
class Point {
num x, y;

Point(num x, num y) {
// 还有更好的方式实现下面代码,在dart里面最佳实践应该减少使用this
this.x = x;
this.y = y;
}
}

下面是精简模式:

1
2
3
4
5
class Point {
num x, y;
// 一颗语法题
Point (this.x, this.y);
}

在没有声明构造函数的情况下,Dart会提供一个默认的构造函数,默认构造函数没有参数并会调用父类的无参构造函数。子类不会继承父类的构造函数,子类不声明构造函数,那么它就只有默认构造函数。

7.命名构造函数,使用命名构造函数可以为一个类实现多个构造函数,可以使用命名构造函数来更清晰的表明函数意图:

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

Point(this.x, this.y);

// 命名构造函数
Point.origin() {
x = 0;
y = 0;
}
}

8.什么鬼super,如下所示

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

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

class Employee extends Person {
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee')
}
}

main() {
var emp = new Employee.fromJson({}); // 输出 => in Person in Employee

if (emp is Person) {
emp.firstName = 'Bob';
}
(emp as Person).firstName = 'Bob';
}

9.初始化列表概念,很难看

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

Point(x, y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
var p = new Point(2, 3);
print(p.distanceFromOrigin);
}

上面这种初始化列表的写法起的作用对应了js里面的参数默认值,在写法上后者简直不要太简单优雅;在dart中,我们可以在构造函数后面加上一个:后面跟上一些初始化列表语句,同时也能够对其进行开发期assert判断处理。

10.重定向构造函数,在某些情况下,某些命名构造函数只需要使用其它构造函数的逻辑即可,那么此时使用重定向构造函数便很合适:

1
2
3
4
5
class Point {
num x, y;
Point(this.x, this.y);
Point.which(num x): this(x, 0);
}

11.常量构造函数,常量构造函数所创建出来的实例并不是常量(??),如果希望实例出来的变量是固定不变的话,那么首先构造函数得是const的,并且所有实例变量也都得是final。如下所示:

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

final num x, y;

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

12.工厂构造函数,第一次接触到。如果类中的某个构造函数并不总是返回这个类的实例(而是有可能返回其它类的实例)的话,那么这个构造函数也被叫做工厂构造函数,使用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
25
class Logger {
final String name;
bool mute = false;

static final Map<String, Logger> _cache = <String, Logger>{};

// 工厂构造函数
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = Logger._internal(name);
_cache[name] = logger;
return logger;
}
}

// 命名构造函数
Logger._internal(this.name);

// 实例方法
void log(String msg) {
if (!mute) print(msg);
}
}

13.Getters和Setters,每一个实例变量都会有一个隐式的getter,通常也会有一个setter。同时我们也可以使用get和set关键字来显式设置getters和setters。

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

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

// 显示声明带有getter和setter的实例属性
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);
assert(rect.left == 3);
rect.right = 12l
assert(rect.left = -8);
}

14.抽象方法,抽象方法只能够存在于抽象类中。实例方法,getter,setter方法可以是抽象的,他们只定义接口而不实现,实现交给子类去完成。调用抽象方法会带来运行时错误:

1
2
3
4
5
6
7
8
abstract class Doer {
void doStuff();
}
class Stuff extends Doer {
void soStuff() {
...
}
}

15.抽象类:使用abstract修饰符来定义一个抽象类,抽象类不能用来实例化,如果希望抽象类能够被实例化,那么可以通过定义工厂实例函数来实现。在dart中声明抽象方法无需加上abstract修饰符,没有大花括号并且加上返回值就是了。抽象方法只能在抽象类中定义。

1
2
3
abstract class AbstractContainer {
void updateChildren(); // 抽象方法
}

16.接口:每个类都隐式的定义了一个接口,接口包含了该类所有的实例变量以及实例方法,但是,注意并不包含构造函数。如果想要创建一个A类,并且A类要支持B类的API的话,但是又不想继承B类的实现的话,那么可以通过让A类来实现B类的接口。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person {
final _name;
Person(this._name);
String greet(String who) => 'Hello, $who, I am $_name';
}
class Impostor implements Person {
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('kay')));
print(greetBob(Impostor()));
}

同时一个类也能够用来实现多个接口,如下所示:

class Point implements PointA, PointB {…}

17.枚举类型是一种特殊的类,使用enum关键字来定义一个枚举类。枚举中的每一个值都具有一个index getter方法,该方法这个值在枚举类型中的位置;每个枚举类型,都具有一个values实例常量,用来获取所有枚举值列表。

1
2
3
4
enum Color { red, green, blue }
assert(Color.red.index == 0);
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

18.使用mixins,使用类的时候with一个mixin,怎么实现一个mixin?使用mixin关键字进行定义,写法和类写法一样,除了使用mixin代替了class外。

19.静态变量和静态方法,使用static即可,注意,静态变量只有在被使用的时候才会初始化;静态方法因为不可以在实例上访问,所以无法访问this。

20.泛型,挺好的

1
2
3
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // 错误

21.参数化字面量,作用是不用去猜?

1
2
3
4
5
var names = <String>['haha', 'hai'];
var obj = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'all spider'
};

22.在运行时能够测试到泛型类型,泛型类型是固化的

1
2
3
var names = List<String>();
names.addAll(['n1', 'n2', 'n3']);
print(names is List<String>); // true

23.限制泛型类型

1
2
3
class Foo<T extends SomeBaseClass> {
String toString() =>
}

24.泛型方法,这才像样

1
2
3
4
T first<T>(List<T> ts) {
T tmp = ts[0];
return tmp;
}

25.库和可见性,import和library指令用来创建模块化的,可共享的代码库。以下划线_开头的标识符仅在库中可见,每个dart程序都是一个库,尽管没有使用library指令。

利用import命令来使用一个库,import后面加上一个URI;对于内置的库来说,URI拥有自己内置的dart:方案;对于其他的库,使用系统文件路径或者package:方案;其中后者使用package:方案是指定由包管理器(pub工具)所提供的库。如下所示:

1
2
import 'dart:html';
import 'package:test/test.dart';

26.指定库前缀来解决两个库里面定义了同一个类;假设下面两个类中都有Element的声明的话,那么如下所示:

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

Element ele1 = Element();
lib2.Element ele2 = lib2.Element();

27.导入库的一部分,还是挺方便的,如下所示:

1
2
import 'package:lib1/lib1.dart' show foo; // 只导入这个库的foo部分
import 'package:lib2/lib2.dart' hide foo; // 导入这个库除了foo外的其它部分

28.延迟加载库,在某些地方还是能够派上用场的,使用延迟加载库的功能能够优化App启动时间;延迟加载库使用上了dart的异步功能,如下所示:

1
2
3
4
5
6
import 'package:greetings/hello.dart' deferred as hello;

Future greet() async {
await hello.loadLibrary(); // 调用loadLibrary函数来加载库
hello.printGreet();
}