在 flutter
稳定版出来的那段时间因为工作原因有在 Android
开发的需求,所以自己空余时间也有去了解过这个跨平台 UI 开发框架,学了一段时间后个人感觉确实比较好用,可以比较容易的写出漂亮的界面,加上官方宣传的高性能表现,自己业余学习一下也是比较推荐的。之所以前面扯了一下 flutter
,是因为若没有 flutter
的话,Dart
应该没什么人用吧?
语言基础
基本内置类型,申明
和 C/C++
一样,Dart
程序的也有一个叫做 main()
函数的入口,比如 Dart
的入门程序:
void main() {
print("hello world");
}
Dart
也是静态类型语言,每个变量都有自己的类型,但是 Dart
支持类型推断,所以大多数可以推断的情况下都不需要显示指定变量类型。
没有指定类型的变量申明使用关键字 var
。
var name = "Dart Program Language"; // 自动推断 name 的类型为 String 类型。
var year = 1995; // 自动推断 year 的类型为 int 类型。
var pi = 3.1415926; // 自动推断 pi 的类型为 double 类型。
var arr = [
"c",
"c++",
"rust",
"dart",
"javascript",
"haskell"
]; // 自动推断 arr 的 类型为 List<String> 类型。
var image = {
'tags': 'Saturn',
'url': '//path/to/saturn.jpg'
}; // 自动推断 image 的类型为 Map<String,String>。
bool right = true; // 显示指定 right 的类型为 bool 类型。
但是对于函数来说,一般都是建议显示指定返回值以及参数的类型。更明确的说,函数返回值类型不能指定 var
,但你可以省略,而函数参数则可以使用 var
申明。
var fibonacci(var n) {
if (n == 0 || n == 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
报错:
test.dart:1:1: Error: The return type can't be 'var'.
Try removing the keyword 'var', or replacing it with the name of the return type.
但是下面的代码是可以正常运行的:
// 明确指定函数返回值,使用 var 申明函数参数
int fibonacci(var n) {
if (n == 0 || n == 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 直接省略函数的返回值
fibonacciii(var n) {
if (n == 0 || n == 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
流程控制语句
条件控制语句 if
,else
,else if
等这些和大多数语言都是一样的,但它还有两个条件表达式可以替代 if-else
语句。condition ? expr1 : expr2
如果 condition
为真
,则执行表达式 expr1
,否则执行表达式 expr2
。
expr1 ?? expr2
如果 expr1
不为 null
,则返回 expr1
,否则返回 expr2
。
// 一般 if-else 的方式
String playerName() {
if (name != null) {
return name;
} else {
return "Guest";
}
}
// 下面是更简单的方法
String playerName(String name) => name != null ? name : 'Guest';
// 下面是更更简单的方法
String playerName(String name) => name ?? 'Guest';
需要注意的是,Dart
的 if
语句中的条件判断必须是一个布尔值。
int a = 10;
if (a) {
print("a 不等 0");
}
报错:
test.dart:3:7: Error: A value of type 'int' can't be assigned to a variable of type 'bool'.
if (a) {
但是写成下面那样一个布尔表达式就没问题。
if( a != 0 )
对于循环结构,它支持 c-style
的 for
循环。
var arr = [0, 1,2,3,4,5,6,7,8,9];
for(int i=0; i<10; ++i) {
print(i);
}
并且对于遍历的对象若实现了 Iterable
接口,还支持 for-in
形式的迭代以及 forEach
迭代方法。
// List 实现了 Iterable 接口
var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
// 使用 for-in 形式迭代方法
for (var item in arr) {
print(item);
}
// 还可使用 forEach() 迭代方法
arr.forEach((i) => print(i * i));
另外,while
循环和 do-while
循环和 c/c++ 都是一样的。
函数
Dart
函数的定义方式如下:
[ReturnType] FunctionName(Type1 var1, Type2 var2, Type3 var3...) {
...
}
可以看到返回值是可以省略的,虽然 Dart 具有类型推断功能,但还是建议函数返回值,参数类型等都做明确说明。
int add(int a, int b) {
return a + b;
}
对于像上面那种比较简单的函数,Dart
还有一种更简单的申明方式。
int add(int a, int b) => a + b;
这种方式称之为胖箭头语法,需要注意的是,在 =>
与 ;
之间只能是表达式而不能是语句。
Dart
函数的参数和其他语言有特别之处,它有两种形式的参数:必要参数,可选参数。必要参数 一定要在 可选参数 之前。可选参数可是是 命名的 或 位置的。
命名参数的定义方式是使用大括号括起来,调用的时候则使用 参数名: 参数值
的方式。如下:
// 申明
void enableFlags({bool bold, bool hidden}) {...}
// 调用
enableFlags(hold: true, hidden: false);
命名参数虽然是一种可选参数,但是你仍然可以使用 @required
注解来标识一个命名参数是必须参数,此时调用者就必须为该参数提供一个值,否则就会出错。
const Scrollbar({Key key, @required Widget child}) {...}
位置参数的定义是使用中括号括起来,调用的时候如果有值的话和必要参数一样的方式传参。
// 声明带有位置参数的函数
String say(String from, String msg, [String device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
// 不给位置参数传值
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
// 给位置参数传值
assert(say('Bob', 'Howdy', 'smoke signal') ==
'Bob says Howdy with a smoke signal');
此外,还可以使用 =
为函数的可选参数定义一个默认值,默认是必须是编译期常量,没有指定默认值的情况下默认值为 null,这是合理的,因为可选参数在不传值的情况下有一个默认值相比不确定的值是更加安全的做法。
void enableFlags({bool bold = false, bool hidden = false}) {...}
Dart 是一种面向对象的语言,所以即使是函数,也是一种对象且类型为 Function
。
面向对象
类的声明以及构造
Dart 的类和 c++ 也有很多相似的地方,比如没有显示提供的时候都会默认提供一个无参构造函数,以及初始化列表等。但也有不一样的地方,比如除了支持 c++ 样式的构造函数以外,Dart 自己还有其他不同的构造函数语法。
class Point {
double x;
double y;
// 生成构造函数,一种语法糖
Point(this.x, this.y);
// 命名构造函数,转发到默认构造函数
Point.fromArr(List<double> arr) : this(arr[0], arr[1]);
// 命令构造函数
Point.fromStr(String x, String y) {
this.x = double.parse(x);
this.y = double.parse(y);
}
}
你可以像下面那样使用:
Point p = new Point(1, 2);
print("x=${p.x}; y=${p.y}");
Point p2 = new Point.fromArr([12, 3]);
print("x1=${p2.x}; y2=${p2.y}");
p2 = Point.fromStr("12.4", "11.01"); // 等价 p2 = new Point.fromStr("12.4", "11.01");
print("x1=${p2.x}; y2=${p2.y}");
可以看到,使用构造函数,包括命名构造函数的时候,可以加 new
关键字,也可以不加 new
关键字。对于常量构造函数,调用的时候加 const
关键字就可以创建编译期常量,例如:
class Point {
final double x;
final double y;
// 生成构造函数,
const Point(this.x, this.y);
}
void main() {
// 创建编译期常量 p
Point p = const Point(1, 2);
print("x1=${p.x}; y2=${p.y}");
}
在构造函数之前加上 factory
关键字就会令该构造函数为工厂构造函数,工厂构造函数意味着它构造的实例并非总是会返回该类新的实例对象,有可能返回一个子类型的实例。工厂构造函数中无法使用 this
。
另外,Dart 还会为类的所有成员隐式声明一个 Getter 方法,对于非 final
类型的成员还会隐式申明一个 Setter 方法,同样你也可以使用 get
和 set
关键字为额外的属性添加 Getter 和 Setter 方法。
class Rectangle {
double left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// 定义两个计算产生的属性:right 和 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;
}
使用 abstract
关键字可以让该类成为一个抽象类,类似于 c++ 的虚基类,抽象类无法被实例化,抽象类常用于声明接口方法,也可以有具体的实现。
abstract class AbstractContainer {
// 定义构造函数、字段、方法等……
void updateChildren(); // 直接使用分号(;)替代方法体声明一个抽象方法。
}
隐式接口实现,类的扩展
每个类的方法都相当于隐式的定义了一个接口并给出了默认的实现。如果一个类 A 想要支持并调用类 B 的 API 而又不想继承类 B,则可通过 implements
关键字实现类 B 的接口。
// Person 类的隐式接口中包含 greet() 方法。
class Person {
// _name 变量同样包含在接口中,但它只是库内可见的。
final _name;
// 构造函数不在接口中。
Person(this._name);
// greet() 方法在接口中。
String greet(String who) => '你好,$who。我是$_name。';
}
// Person 接口的一个实现。
class Impostor implements Person {
get _name => '';
String greet(String who) => '你好$who。你知道我是谁吗?';
}
String greetBob(Person person) => person.greet('小芳');
void main() {
print(greetBob(Person('小芸')));
print(greetBob(Impostor()));
}
如果有多个类接口需要实现,可以使用逗号分隔每个接口类。
class Point implements Comparable, Location {...}
使用 extends
关键字来创建一个类的子类,并可使用 super
关键字来引用其父类,子类可重写父类的方法,包括 Getter 以及 Setter 方法,可使用 @override
注解来表示你重写了一个成员方法。
class Person {
final String name;
final String year;
final String address;
Person(this.name, this.year, this.address);
void hello() {
print("hello ${this.name}!");
}
}
class Student extends Person {
final String gradel;
final String major;
Student(String name, String year, String address, this.gradel, this.major)
: super(name, year, address) {}
@override
void hello() {
print("Hello, I'm a student, my marjor is $major");
}
}
void main() {
Student student = Student(
"zmant", "25", "changsha", "senior", "computer science andtechnology");
student.hello();
}
对某些少数情况下,我们重写了基类的方法,但是可能希望限定重写的子类方法的参数类型,这个需求可以使用 covariant
协变关键字实现,如下:
class Animal {
void chase(Animal a) {}
}
class Mouse extends Animal {}
class Cat extends Animal {
@override
void chase(covariant Mouse a) { // 重写了父类的 chase 方法,并限定类型为 Mouse
print("Cat Chase ${a.runtimeType} ");
}
}
使用 Mixin 为类添加功能
mixin
是将一些共同特性的类抽象出来共用,把具体的特性封装成一个 mixin 类
供其他类使用,目的是想要减少代码冗余,虽然 implement
也能实现这种功能,但是 implement
的子类需要完全重写父类的所有属性与方法,这在某些时候是不必要的。
可以使用关键字 mixin
替代 class
让其成为一个单纯的 Mixin 类:
mixin Flyer {
void fly() {
print("I'm flying");
}
}
在定义 mixin
类的时候,可以使用 on
关键字来指定该 mixin
类的使用范围:
// 定义了 mixin 类 Flyer 只能被 Bird 类(或其子类)使用
mixin Flyer on Bird {
...
}
使用 with
关键字后跟 mixin
类名的方式使用 mixin
模式,当有多个 mixin
类的函数名一样时,后一个 mixin
类的函数实现会覆盖前一个(线性特征):
class S {
fun()=>print('A');
}
mixin MixinA {
fun()=>print('Mixin-A');
}
mixin MixinB {
fun()=>print('Mixin-B');
}
class A extends S with MixinA, MixinB {}
class B extends S with MixinB, MixinA {}
void main() {
A a = A();
a.fun();
B b = B();
b.fun();
}
========
Mixin-B
Mixin-A
扩展方法
Dart 2.7 引入的 extension
方法是为了方便向现有的库添加功能的一种方式,这是非常有用的一个特性,现代发布的语言几乎都会支持这个特性,比如 kotlin,rust(通过 triat 系统可做到)等。
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
}
比如上面的代码就是为 String
类型扩展了一个 parseInt()
的方法可以方便我们做 String
到 int
的转换。
void main() {
int a = "1024".parseInt();
print(a);
}
异步支持
在 Dart 中,对于返回 Future
或 Stream
对象的函数通常都是异步的,它们会在耗时操作执行完之前直接返回而不会等待耗时操作执行完毕。
async
和 await
关键字用于实现异步编程,并让你的代码看起来就像是同步的一样。await
只能用在带有 async
关键字的异步函数之中。
定义异步函数只需要在普通函数方法之后加上 async
关键字并将其返回值用 Future
包裹即可。假设有如下放回 String 对象的方法:
String lookUpVersion() => "1.0.1";
将其改为异步函数:
Future<String> lookUpVersion() async => "1.0.1";
当一个 Future
完成之后,Future
中的值就已经可用了,除了使用 await
这种更贴近同步代码的方式之外,还可使用 Future
的 then()
方法来执行,then()
中的代码会在 Future
完成之后执行。
HttpRequest.getString(url).then((String result) {
print(result);
});
上面的代码使用 await
表达式的等价代码如下:
String result = await HttpRequest.getString(url);
print(result);
可以明显看到使用 await
表达式的可读性更高,这也会推荐的方式。
await
表达式的返回值通常是一个 Future
对象,就算不是也会自动将其包裹在一个 Future
对象中。Future
对象代表一个现在无法实现,但未来某个时间点会实现的“承诺”,await
表达式就是等待这个“承诺”实现的,它会阻塞直到需要的对象返回。
有时需要等待多个异步函数全部执行完之后才可以继续往后执行,这种情况可使用 Future.wait()
静态方法管理多个 Future
以及等待它们完成。
Future deleteLotsOfFiles() async => ...
Future copyLotsOfFiles() async => ...
Future checksumLotsOfOtherFiles() async => ...
await Future.wait([
deleteLotsOfFiles(),
copyLotsOfFiles(),
checksumLotsOfOtherFiles(),
]);
print('Done with all the long steps!');
Stream
也是用于接收异步事件数据的,和 Future
不同的是,它可以接收多个异步操作的结果。
总结
这篇文章只是简单的对 Dart 语言的的一个概览,了解这些知识后对一个有经验的程序员来说平常写业余写点 flutter 程序是足够了的,对于不了解的地方也可以通过平常用的时候查漏补缺来解决,但对于一个专业的 Dart 开发者来说还差了很多。
不管怎么说,希望本文可以对你小有帮助,另外您的鼓励是我输出更好文章的动力。