从 javascript 角度看 Dart【二】

上篇文章,主要介绍了 Dart 的基本语法(数据类型,函数等),并与 js 进行了简单的对比,方便大家从 js 的角度来快速理解 Dart。本文主要介绍 Dart 中类的使用,总的来看差别不大,在构造函数上我们需要格外注意即可。还有几个 Dart 特有的内容也简单介绍下。

新的运算符

在 Dart 中多出了一个类型测试操作符,具体如下:

运算符 类型转换
as 类型转换
is 如果 对象是该类型 则返回 true
is! 如果 对象是该类型 则返回 false

如果obj 实现了T 所定义的借口,那么obj is T 将返回 true。比如, obj is Object 必然返回 true。使用as 操作符可以把一个对象转换为特定类型。一般来说,如果在is 测试之后还有一些关于对象的表达式。

Classes

首先我们看下 js 简单声明一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Point {
static id = "Point Map";
_prop = "prototype";
constructor(x, y) {
this.x = x;
this.y = y;
};
get prop() {
return this._prop;
}
set prop(prop) {
this._prop = prop + "test";
}
}

而用 Dart 翻译上面的 js 代码为:

1
2
3
4
5
6
7
8
9
class Point {
double y;
double x;
static String id = "Point Map";
String _prop = "prototype";
Point(this.x, this.y);
String get prop => this._prop + "test";
void set prop(String prop) => this._prop = prop;
}

构造函数

Dart 的构造函数的名字和类名一样,其中 this 关键字引用当前实例。和 js 不同的是,Dart 提供了一个语法糖来方便我们赋值:

1
2
3
4
5
6
class Point {
num x;
num y;
// 在构造函数体执行之前设置实例变量的语法糖
Point(this.x, this.y);
}

构造函数规则

这一部分,完全和 js 的规则不一样, Dart 对构造函数有如下限制:

默认构造函数

如果没有定义构造函数,则会生成一个默认构造函数。 默认构造函数没有参数,并调用没有参数的 superclass(父类) 构造函数。

命名构造函数

对于 Dart 语言,同样不支持函数重载。那么如果我想要我的类有多个构造方法该如何呢?Dart 使用命名构造来提供多个构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
class Point {
num x;
num y;
Point(this.x, this.y);
// 命名构造函数
Point.fromJson(Map json) {
x = json['x'];
y = json['y'];
}
}
Point p = new Point.fromJson({});

构造函数不支持继承

任何类型的构造函数都不支持继承。 如果你想让子类也能用父类的构造函数,则你必需在子类中定义并实现该构造函数。默认情况下,子类的构造函数,会调用父类无名无参数的默认构造函数。但是大多数情况下,我们的父类肯定有自己实现的构造函数。这样父类就不会有默认构造函数了,因此我们必须为子类的构造函数手工调用一个父类的构造函数。在冒号 (:) 后面和构造函数体之前指定要调用的父类构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Point {
double x;
double y;
Point(this.x, this.y);
Point.fromJson(Map data) {
this.x = data['x'];
this.y = data['y'];
}
}
class Point3D extends Point {
double z;
Point3D(x, y, this.z): super(x, y);
Point3D.fromJson(Map data): super.fromJson(data) {
// 这里如果 this.z = data.z 就会报错
this.z = data["z"];
}
}

常量构造函数

如果你的类生成从来不改变的对象,则可以把这些对象定义为编译期常量。 用一个 const 构造函数并把实例变量设置为 final 来实现该功能。

1
2
3
4
5
6
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin = const ImmutablePoint(0, 0);
}

工厂构造函数

如果一个构造函数并不总是创建一个新的对象,则可以用 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;
// _cache is library-private, thanks to the _ in front of its name.
static final Map<String, Logger> _cache = <String, Logger>{};
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = new Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) {
print(msg);
}
}
}

工厂构造函数无法访问 this

抽象类

Dart 为我们提供了 abstract 抽象类

1
2
3
4
5
// 该类为 abstract 的,所以无法实例化
abstract class AbstractContainer {
// ...Define constructors, fields, methods...
void updateChildren(); // 抽象函数
}

隐式接口

每个类都隐式的定义了一个包含所有实例变量和所实现所有接口的接口。 如果你想创建一个类 A 支持 类 B 的 API,但是又不想继承类 B 的实现,则类 A 可以实现 类 B 的隐式接口。
类通过 implements 语句来定义其实现的其他类的接口, 并实现需要的 API。 例如:

1
2
3
4
5
6
7
8
9
10
11
12
// 一个 person 类, 隐式接口包含 greet().
class Person {
final _name; // 该变量在隐式接口中,但是是库返回可见的
Person(this._name); // 这是个构造函数,不在隐式接口中
String greet(who) => 'Hello, $who. I am $_name.'; // 在隐式接口中
}
// 实现 Person 的隐式接口。
class Imposter implements Person {
final _name = ""; // We have to define this, but we don't use it.
String greet(who) => 'Hi $who. Do you know who I am?';
}

继承

关键字 extends 和 js 继承没有太大区别,唯一需要注意的就是构造函数,之前已经说过了。另外在 Dart 中提供了注解功能。我们可以用 @override 来表明正在重写的函数或者变量。这一点有点类似 java。

类(静态)变量和函数

关键字 static 和 js 没有太大区别。

Typedefs

在 js 里面,我们很容易的就可以将函数作为参数进行传递。在 Dart 中我们也可以像 js 一样方便的传递函数。但是这样,有时候我们需要指明函数的类型,比如有多少个参数啊,返回值之类的,从而方便类型检查。
在 Dart 中函数也是对象。我们可以用 typedef 来定义一个名称来指明函数名字, 并且定义参数和返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef int CalFunc(int num1, int num2);
main() {
int num1 = 1;
int num2 = 2;
int calculate(CalFunc func) {
return func(num1, num2);
};
int result = calculate((int num1, int num2) {
return num1 - num2;
});
print(result);
}

(思考了下,java 能不能将函数作为参数直接传递?不能直接简单的传递,我们可以利用 interface 继承的方式;在 java8 中我们还可以用 lambada 表达式。)

Metadata(元数据)

用元数据给你的代码提供额外的信息。元数据注解 使用 @ 字符开头,后面跟着一个引用合作 编译期常量(例如 deprecated)或者调用一个 常量构造函数。
下面三个注解,所有的 Dart 代码都可以使用: @deprecated、 @override、 和 @proxy。
Metadata 可以出现在 library、 class、 typedef、 type parameter、 constructor、factory、 function、 field、 parameter、或者 variable declaration 、import 或者 export 之前。 以后,可以通过反射来获取元数据信息。

泛型

泛型,由于 js 是动态类型的语言,因此没有泛型的概念。Dart 中我们可以使用泛型,也可以不适用。个人倾向于应该主动使用泛型,来帮助我们更好的表达意图。也方便 IDE 对代码的错误的检查,提高效率。具体内容比较杂,就不对比了。

总结

Dart 语言的基础到这里就介绍的差不多了,总的来看还算简单。我觉得从 js 的角度 和 java 的角度综合分析下,还是蛮好玩的。下篇文章主要介绍下 Dart 常用库。