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}.'); } }