Dart
Table of Contents
1. Dart 简介
Dart is a client-optimized language for fast apps on any platform.
Dart 最开始是 Google 设计出来替代 Javascript 的,但是并没有凑效,后来 Flutter 选择了 Dart, 才使 Dart 活跃起来。
参考:
A tour of the Dart language: https://dart.dev/guides/language/language-tour
Dart cheatsheet codelab: https://dart.dev/codelabs/dart-cheatsheet
Dart style guide: https://dart.dev/guides/language/effective-dart/style
Dart documentation: https://dart.dev/guides
2. 语法简介
下面是一个简单的 dart 程序:
// Define a function.
void printInteger(int aNumber) {
print('The number is $aNumber.'); // Print to console.
}
// This is where the app starts executing.
void main() {
var number = 42; // Declare and initialize a variable.
printInteger(number); // Call a function.
}
2.1. 变量
使用 var 可以定义变量,由于有类型推导,所以不用明确指定类型,如:
var name = 'Voyager I';
var year = 1977;
var antennaDiameter = 3.7;
var flybyObjects = ['Jupiter', 'Saturn', 'Uranus', 'Neptune'];
var image = {
'tags': ['saturn'],
'url': '//path/to/saturn.jpg'
};
也可以明确指定类型,如:
var name = 'Bob'; // 不明确指定类型,类型推导系统可推导出类型 String name = 'Bob'; // 同上
如果不想把变量限定为单一类型,可以使用 Object 或者 dynamic 类型,如:
dynamic name = 'Bob'; Object name = 'Bob';
dynamic 和 Object 的区别可参考:https://dart.dev/guides/language/effective-dart/design#do-annotate-with-object-instead-of-dynamic-to-indicate-any-object-is-allowed
2.1.1. late 变量
在 Dart 2.12 中引入了 late 修饰符,它有两个作用:
- 变量的初始化工作可在声明之后进行;
- Lazily initializing a variable.
下面是一个例子:
late String description;
void main() {
description = 'Feijoada!';
print(description);
}
如果直接使用一个没有初始化的变量,将导致运行时错误。
2.1.2. 常量(final, const)
使用 final 或者 const 可以声明常量,它们不能被重新赋值,如:
final name = 'Bob'; // Without a type annotation final String nickname = 'Bobby'; const bar = 1000000; // Unit of pressure (dynes/cm2) const double atm = 1.01325 * bar; // Standard atmosphere
此外, 用 const 声明常量时,要求这个常量必须是编译时就能确定的常量。
2.2. 内置类型
Dart 中支持下面这些内置类型:
- int 整数
- double 浮点数
- String 字符串
- bool 布尔值
- List 列表(数组)
- Set 集合
- Map 映射
- Runes and grapheme clusters
- Symbol 符号
参考:https://dart.dev/guides/language/language-tour#built-in-types
2.2.1. List
下面是 List 的实例:
var list = [10, 20, 30]; // 创建 List,类型推导得到的类型为 List<int> assert(list.length == 3); assert(list[0] == 10); // 下标从 0 开始,即首元素的下标是 0 assert(list[1] == 20); list.add(40); // 使用 add 往 list 中增加一个元素 list[1] = 10; // 直接修改 List 中数据 assert(list[1] == 10);
使用 spread operator (...) 可以由一个 List 创建另一个 List:
var list = [1, 2, 3]; var list2 = [0, ...list]; assert(list2.length == 4);
上面例子中,如果 list 为 null,则会报错,这时可以使用 null-aware spread operator (...?) 避免这个问题:
var list; var list2 = [0, ...?list]; // list 为 null,也不会报错 assert(list2.length == 1);
在构造 list 时,可以使用 if,如:
var nav = [ 'Home', 'Furniture', 'Plants', if (promoActive) 'Outlet' ];
在构造 list 时,可以使用 for,如:
var listOfInts = [1, 2, 3]; var listOfStrings = [ '#0', for (var i in listOfInts) '#$i' ]; assert(listOfStrings[0] == '#0'); assert(listOfStrings[1] == '#1'); assert(listOfStrings[2] == '#2'); assert(listOfStrings[3] == '#3');
2.2.2. Set
下面是 Set 的例子:
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
下面是使用 Set 构造函数创建 Set 的例子:
var names = <String>{};
// Set<String> names = {}; // This works, too.
// var names = {}; // Creates a map, not a set.
names.add('fluorine'); // 往 set 中增加元素
names.addAll(halogens); // 往 set 中增加多个元素
当不特别指定时,Set 默认为 LinkedHashSet,你也可以显式指定使用 HashSet 或者 SplayTreeSet 。
2.2.3. Map
下面是 Map 的例子:
var gifts = { // 类型推导为 Map<String, String>
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
var nobleGases = { // 类型推导为 Map<int, String>
2: 'helium',
10: 'neon',
18: 'argon',
};
下面是使用 Map 构造函数创建 Map 的例子:
var gifts = Map<String, String>(); gifts['first'] = 'partridge'; // Add a key-value pair gifts['second'] = 'turtledoves'; gifts['fifth'] = 'golden rings'; var nobleGases = Map<int, String>(); nobleGases[2] = 'helium'; nobleGases[10] = 'neon'; nobleGases[18] = 'argon';
当不特别指定时,Map 默认为 LinkedHashMap(维持插入顺序),你也可以显式指定使用 HashMap 或者 SplayTreeMap(一种自平衡树,最近访问的元素可以在再次访问时更快)。
2.2.4. null safety
在 Dart 2.12 中引入了 null safety:
//int a = null; // 从 Dart 2.12 起,这是不合法的。int 不能为 null int? a = null; // 类型后加个 ? 表示,这个变量可能为 null a = 42; int b = a!; // ! 是 Null assertion operator,从 int? 类型的 a 得到了 int 类型的 b
同时引入了一些 Null-aware operators,如表 1 所示。
| 记号 | 说明 |
|---|---|
Null-aware assignment operator ??= |
a ??= 3 是赋值语句,当变量 a 为 null 时赋值为 3;不为 null 就维持原值 |
Conditional expressions ?? |
a ?? 5 是表达式, a 为 null 时表达式为 5;不为 null 时表达式是原值 |
Conditional property access .? |
myObject?.someProperty 相当于 (myObject != null) ? myObject.someProperty: null |
Null-aware cascade operator ?.. |
见下文 |
Null-aware index operator ?[] |
items?[0] 相当于 items != null ? items[0] : null |
下面介绍一下 .? ,假设有代码:
querySelector('#confirm') // Get an object.
?..text = 'Confirm' // Use its members.
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
这相当于:
var button = querySelector('#confirm');
button?.text = 'Confirm';
button?.classes.add('important');
button?.onClick.listen((e) => window.alert('Confirmed!'));
2.3. 函数
下面是函数的例子:
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
可以省略类型,即上面定义还可以写为:
isNoble(atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
如果函数体中只有一个表达式,则还可以进一步使用“箭头语法”简化它,如上面定义还可以写为:
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
箭头语法 => expr 是 { return expr; } 的简写。
2.3.1. 命名参数(大括号)
定义函数时,参数使用 {param1, param2, …} 的形式(即参数用大括号包围起来)就是命名参数,如:
void func1({String arg1, bool arg2}) {
print(arg1);
print(arg2);
}
main() {
func1(arg1: "abc", arg2: true); // 调用时,需要指定参数的名称 arg1,arg2
func1(); // 如果指定参数的话,函数体内会为 null
}
命名参数是可选的,不指定时,函数体内会得到 null 。执行上面程序,会输出:
abc true null null
2.3.2. 可选的位置参数(中括号)
如果把位置参数放到中括号中,则该位置参数是可选的。如下面例子中的参数 device 是可选的:
String say(String from, String msg, [String device]) { // 参数 device 在中括号中,是可选的
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
main() {
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
assert(say('Bob', 'Howdy', 'smoke signal') == 'Bob says Howdy with a smoke signal');
}
2.3.3. 参数默认值
用 = 可以指定参数的默认值。如:
/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold = false, bool hidden = false}) {...}
// bold will be true; hidden will be false.
enableFlags(bold: true);
2.3.4. 匿名函数
匿名函数的语法如下:
([[Type] param1[, …]]) {
codeBlock;
};
下面代码中 list.forEach 的参数是一个匿名函数:
main() {
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
}
如果函数体中只有一个表达式,可以使用箭头语法,如:
list.forEach((item) => print('${list.indexOf(item)}: $item'));
2.3.5. 函数返回值
All functions return a value. If no return value is specified, the statement return null;
2.4. 操作符
Dart 操作符如表 2 所示。
| Description | Operator |
|---|---|
| unary postfix | expr++ expr-- () [] . ?. |
| unary prefix | -expr !expr ~expr ++expr --expr await expr |
| multiplicative | * / % ~/ |
| additive | + - |
| shift | << >> >>> |
| bitwise AND | & |
| bitwise XOR | ^ |
| bitwise OR | | |
| relational and type test | >= > <= < as is is! |
| equality | == != |
| logical AND | && |
| logical OR | || |
| if null | ?? |
| conditional | expr1 ? expr2 : expr3 |
| cascade | .. |
| assignment | = *= /= += -= &= ^= etc. |
2.4.1. Type test operators (as, is, is!)
Dart 中可以使用 as, is, is! 在运行时对对象类型进行检查,如表 3 所示。
| Operator | Meaning |
|---|---|
| as | Typecast |
| is | obj is T is true if obj implements the interface specified by T |
| is! | obj is! T is true if obj doesn’t implement the interface specified by T |
下面是使用 as 把 employee 转换为 Person 的例子:
(employee as Person).firstName = 'Bob';
2.4.2. Cascade notation (..)
操作符 .. 可以方便地让你在一个相同对象上进行一系列操作。如:
querySelector('#confirm') // Get an object.
..text = 'Confirm' // Use its members.
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
上面代码等同于:
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
在这个例子中,操作符 .. 让我们省去了定义临时变量 button 的过程。
2.5. 控制流语句
2.5.1. if, else
下面是 if 语句的例子:
if (isRaining()) {
you.bringRainCoat();
} else if (isSnowing()) {
you.wearJacket();
} else {
car.putTopDown();
}
2.5.2. for
下面是 for 语句的例子:
var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
message.write('!');
}
List 和 Set 还支持 for-in 形式的遍历:
var collection = [0, 1, 2];
for (var x in collection) {
print(x); // 0 1 2
}
2.5.3. while, do-while
下面是 while 语句的例子:
while (!isDone()) {
doSomething();
}
下面是 do-while 语句的例子:
do {
printLine();
} while (!atEndOfPage());
2.5.4. switch, case
下面是 switch 语句的例子:
var command = 'OPEN';
switch (command) {
case 'CLOSED':
executeClosed();
break;
case 'PENDING':
executePending();
break;
case 'APPROVED':
executeApproved();
break;
case 'DENIED':
executeDenied();
break;
case 'OPEN':
executeOpen();
break;
default:
executeUnknown();
}
2.6. 异常
使用 try/catch/finally 可以进行异常处理,使用 on 可以捕获特定的异常。如:
void main() {
try {
var result = 100 ~/ 0; // throws `IntegerDivisionByZeroException` exception
print( 'result: $result' );
} on IntegerDivisionByZeroException { // 这里也可以加上 catch(e)
print("Error: can not divide by 0.");
} on FormatException catch( e ) { // 如果只关心异常类型,而不关心细节,则 catch(e) 可以不写
print("Error: format is not correct, $e");
rethrow; // Allow callers to see the exception.
} catch( e, s ) { // catch 可以指定一个或者两个参数
print('Exception details:\n $e');
print('Stack trace:\n $s');
} finally { // finally 中代码总会执行
print( 'Job Done!' );
}
}
我们可以使用 throw 来抛出异常,如:
void distanceTo(Point other) => throw UnimplementedError();
2.7. 类
Dart 中使用 class 定义类,类中可以包含实例变量和方法,如:
import 'dart:math';
class Point { // 定义一个名为 Point 的类
double x = 0; // Point 实例变量,默认为 0
double y = 0; // Point 实例变量,默认为 0
Point(double x, double y) { // Point 构造函数,可以使用语法糖省写为 Point(this.x, this.y);
this.x = x;
this.y = y;
}
Point.origin() // Point 的另一种形式的构造函数,称为 Named constructor
: x = 0, // 冒号后面的 x = 0, y = 0 是 initializer list
y = 0 {} // 这对空大括号,可以写为分号 ;
Point.fromJson(Map<String, double> json) // Point 的另一个 named constructor
: x = json['x']!,
y = json['y']! {
print('In Point.fromJson(): ($x, $y)');
}
double distanceTo(Point other) { // Point method
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
void main() {
var p1 = Point(2, 2);
// Get the value of y.
assert(p1.y == 2);
// Invoke distanceTo() on p.
double distance = p1.distanceTo(Point(4, 4));
print(distance); // 2.8284271247461903
var p2 = Point.origin();
assert(p2.y == 0);
var p3 = Point.fromJson({'x': 1, 'y': 2});
assert(p3.y == 2);
}
2.7.1. Factory constructors
前面的例子中,介绍了普通构造函数和 Named 构造函数(如 Point.origin 和 Point.fromJson),这里再介绍另一种构造函数:Factory constructor。
Factory constructor 可以返回子类型甚至 null,如:
class Square extends Shape {}
class Circle extends Shape {}
class Shape {
Shape();
factory Shape.fromTypeName(String typeName) { // 这个构造函数返回子类型
if (typeName == 'square') return Square();
if (typeName == 'circle') return Circle();
print('I don\'t recognize $typeName');
throw Error();
}
}
2.7.2. Getters and setters
每个成员变量都隐式地有一个 get 方法和一个可能存在的 set 方法(对于常量就没有 set)。我们也可以自己定义 Getters and setters,下面是一个例子:
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);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
2.7.3. Abstract class
使用关键字 abstract 可以定义一个抽象类,这样的类不能被实例化,抽象类往往用于定义接口。下面是抽象的例子:
// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer { // 这是一个抽象类
// Define constructors, fields, methods...
void updateChildren(); // Abstract method.
}
2.7.4. Implicit interfaces (implements)
每个类都“隐式”定义了一个接口,这个接口包含所有实例成员(方法和变量)。下面是一个例子:
// A person. The implicit interface contains greet().
class Person {
// In the interface, but visible only in this library.
final _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 { // Impostor 实现了 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('Kathy')));
print(greetBob(Impostor()));
}
2.7.5. Extending a class (extends)
使用 extends 可以创建一个子类,下面是一个例子:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television { // SmartTelevision 是 Television 的子类
void turnOn() {
super.turnOn(); // super 关键字表示其父类
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
2.7.6. Private 成员(名字以下划线开始)
Dart 中的 Class 成员,不使用 public/private 等关键字修饰,默认都是外部可访问的。如果想定义不能被外部访问的 Private 成员,则其成员名字以下划线 _ 开始即可。
3. 包管理
Dart 生态中使用 package 来管理可复用的公共代码(可称为包),可以从 https://pub.dev/ 中查找公开可用的 Dart 包。
3.1. Package 使用实例
下面以 http 为例,介绍一下 Dart 中 Package 的使用。
首先,准备 pubspec.yaml 文件,把 http 作为依赖引入进来,内容如下:
name: my_app environment: sdk: '>=2.10.0 <3.0.0' dependencies: http: ^0.13.0
然后,执行 dart pub get 下载依赖。
使用 Package 时,在 import 语句中以 package: 为前缀导入 pubspec.yaml 中指定的包,如:
import 'dart:convert' as convert;
import 'package:http/http.dart' as http; // 引入 http
void main(List<String> arguments) async {
// This example uses the Google Books API to search for books about http.
// https://developers.google.com/books/docs/overview
var url = Uri.https('www.googleapis.com', '/books/v1/volumes', {'q': '{http}'});
// Await the http get response, then decode the json-formatted response.
var response = await http.get(url); // 使用 http
if (response.statusCode == 200) {
var jsonResponse = convert.jsonDecode(response.body) as Map<String, dynamic>;
var itemCount = jsonResponse['totalItems'];
print('Number of books about http: $itemCount.');
} else {
print('Request failed with status: ${response.statusCode}.');
}
}