Dart常用库的使用
最后更新于:2022-04-02 04:59:46
本教程展示了如何使用下列库的主要功能,这些库包含在所有Dart平台中:
[TOC]
[dart:core](#)
内置类型、集合和其他核心功能。这个库被自动导入到每个Dart程序中。
[dart:async](#)
支持异步编程,使用诸如Future和Stream之类的类。
[dart:math](#)
数学常数和函数,和随机数发生器。
[dart:convert](#)
用于在不同数据表示之间转换的编码器和解码器,包括JSON和UTF-8。
这个页面只是一个概述;它只覆盖了几个dart:*库,没有第三方库。特定于平台的dart:io和dart:html库都包含在dart:io tour和dart:html tour中。
其他可以找到库信息的地方是[pub.darlang.org](https://pub.dartlang.org/)和[Dart web developer library guide](https://webdev.dartlang.org/guides/web-programming)。您可以在dart API引用中找到所有dart:* [库的API文档](https://api.dartlang.org/stable),如果您正在使用Flutter,则可以找到[Flutter API文档](https://docs.flutter.io/)。
>DartPad提示:你可以把本页的代码复制到[DartPad](https://dartpad.dartlang.org/)上运行。
>
# dart:core - numbers, collections, strings, 等
dart:core 库 ([API文档](https://api.dartlang.org/stable/dart-core/dart-core-library.html))提供了一组小型但关键的内置功能。这个库被自动导入到每个Dart程序中。
## 向控制台打印
顶级print()方法接受单个参数(任何对象),并在控制台中显示该对象的字符串值(由toString()返回)。
~~~
print(anObject);
print('I drink $tea.');
~~~
有关基本字符串和toString()的更多信息,请参阅语言教程中的[Strings](https://www.dartlang.org/guides/language/language-tour#strings)。
## Numbers
dart:core库定义了num、int和double类,它们有一些处理数字的基本工具。
您可以使用int和double的parse()方法将字符串转换为整数或双精度浮点数:
~~~
assert(int.parse('42') == 42);
assert(int.parse('0x42') == 66);
assert(double.parse('0.50') == 0.5);
~~~
或者使用num的parse()方法,它在可能的情况下创建一个整数,否则创建一个double:
~~~
assert(num.parse('42') is int);
assert(num.parse('0x42') is int);
assert(num.parse('0.50') is double);
~~~
要指定整数的基数,添加一个基数参数:
~~~
assert(int.parse('42', radix: 16) == 66);
~~~
使用toString()方法将int或double转换为字符串。要指定小数点右边的位数,使用[toStringAsFixed()](https://api.dartlang.org/stable/dart-core/num/toStringAsFixed.html)。要指定字符串中的有效位数,使用[toStringAsPrecision()](https://api.dartlang.org/stable/dart-core/num/toStringAsPrecision.html):
~~~
// Convert an int to a string.
assert(42.toString() == '42');
// Convert a double to a string.
assert(123.456.toString() == '123.456');
// Specify the number of digits after the decimal.
assert(123.456.toStringAsFixed(2) == '123.46');
// Specify the number of significant figures.
assert(123.456.toStringAsPrecision(2) == '1.2e+2');
assert(double.parse('1.2e+2') == 120.0);
~~~
有关更多信息,请参阅[int](https://api.dartlang.org/stable/dart-core/int-class.html)、[double](https://api.dartlang.org/stable/dart-core/double-class.html)和[num](https://api.dartlang.org/stable/dart-core/num-class.html)的API文档。也可以看[dart:math](https://www.dartlang.org/guides/libraries/library-tour#dartmath---math-and-random)部分
## 字符串和正则表达式
Dart中的字符串是UTF-16代码单元的不可变序列。 本文档的其他章节有关于字符串的更多信息。 您可以使用正则表达式(RegExp对象)在字符串中搜索并替换部分字符串。
String类定义了诸如split()、contains()、startsWith()、endsWith()等方法。
### 在字符串中搜索
您可以在字符串中找到特定的位置,并检查字符串是以特定模式开始还是以特定模式结束。例如:
~~~
// Check whether a string contains another string.
assert('Never odd or even'.contains('odd'));
// Does a string start with another string?
assert('Never odd or even'.startsWith('Never'));
// Does a string end with another string?
assert('Never odd or even'.endsWith('even'));
// Find the location of a string inside a string.
assert('Never odd or even'.indexOf('odd') == 6);
~~~
### 从字符串中提取数据
您可以分别从字符串中获取字符串中的单个字符作为字符串或整数。 确切地说,您实际上获得了单独的UTF-16代码单元; 诸如高音谱号符号('\ u {1D11E}')之类的高编号字符分别是两个代码单元。
您还可以提取子字符串或将字符串分割为一列子字符串:
~~~
// Grab a substring.
assert('Never odd or even'.substring(6, 9) == 'odd');
// Split a string using a string pattern.
var parts = 'structured web apps'.split(' ');
assert(parts.length == 3);
assert(parts[0] == 'structured');
// Get a UTF-16 code unit (as a string) by index.
assert('Never odd or even'[0] == 'N');
// Use split() with an empty string parameter to get
// a list of all characters (as Strings); good for
// iterating.
for (var char in 'hello'.split('')) {
print(char);
}
// Get all the UTF-16 code units in the string.
var codeUnitList =
'Never odd or even'.codeUnits.toList();
assert(codeUnitList[0] == 78);
~~~
### 转换为大写或小写
您可以轻松地将字符串转换为其大写和小写变体:
~~~
// Convert to uppercase.
assert('structured web apps'.toUpperCase() ==
'STRUCTURED WEB APPS');
// Convert to lowercase.
assert('STRUCTURED WEB APPS'.toLowerCase() ==
'structured web apps');
~~~
>注意:这些方法并不适用于所有语言。例如,土耳其字母表的dotless I被错误地转换。
>
### 修剪和空字符串
使用trim()删除所有前、后空白。要检查字符串是否为空(长度为零),使用isEmpty。
~~~
// Trim a string.
assert(' hello '.trim() == 'hello');
// Check whether a string is empty.
assert(''.isEmpty);
// Strings with only white space are not empty.
assert(' '.isNotEmpty);
~~~
### 构建一个字符串
要以编程方式生成字符串,可以使用StringBuffer。在调用toString()之前,StringBuffer不会生成新的字符串对象。writeAll()方法有一个可选的第二个参数,它允许您指定分隔符(在本例中是空格)。
~~~
var sb = StringBuffer();
sb
..write('Use a StringBuffer for ')
..writeAll(['efficient', 'string', 'creation'], ' ')
..write('.');
var fullString = sb.toString();
assert(fullString ==
'Use a StringBuffer for efficient string creation.');
~~~
### 正则表达式
RegExp类提供了与JavaScript正则表达式相同的功能。使用正则表达式进行字符串的高效搜索和模式匹配。
~~~
// Here's a regular expression for one or more digits.
var numbers = RegExp(r'\d+');
var allCharacters = 'llamas live fifteen to twenty years';
var someDigits = 'llamas live 15 to 20 years';
// contains() can use a regular expression.
assert(!allCharacters.contains(numbers));
assert(someDigits.contains(numbers));
// Replace every match with another string.
var exedOut = someDigits.replaceAll(numbers, 'XX');
assert(exedOut == 'llamas live XX to XX years');
~~~
您也可以直接使用RegExp类。Match类提供对正则表达式匹配的访问。
~~~
var numbers = RegExp(r'\d+');
var someDigits = 'llamas live 15 to 20 years';
// Check whether the reg exp has a match in a string.
assert(numbers.hasMatch(someDigits));
// Loop through all matches.
for (var match in numbers.allMatches(someDigits)) {
print(match.group(0)); // 15, then 20
}
~~~
### 更多信息
有关字符串操作方法的完整列表,请参阅[字符串API文档](https://api.dartlang.org/stable/dart-core/String-class.html)。还可以查看关于[StringBuffer](https://api.dartlang.org/stable/dart-core/StringBuffer-class.html)、[Pattern](https://api.dartlang.org/stable/dart-core/Pattern-class.html)、[RegExp](https://api.dartlang.org/stable/dart-core/RegExp-class.html)和[Match](https://api.dartlang.org/stable/dart-core/Match-class.html)的API文档。
## 集合(Collections)
Dart带有一个核心集合API,其中包括列表(list)、集合(sets)和映射(maps)的类。
### Lists(列表)
正如本文档list部分所示,您可以使用文字来创建和初始化列表。或者,使用列表构造函数之一。List类还定义了几种向列表中添加项和从列表中删除项的方法。
~~~
// Use a List constructor.
var vegetables = List();
// Or simply use a list literal.
var fruits = ['apples', 'oranges'];
// Add to a list.
fruits.add('kiwis');
// Add multiple items to a list.
fruits.addAll(['grapes', 'bananas']);
// Get the list length.
assert(fruits.length == 5);
// Remove a single item.
var appleIndex = fruits.indexOf('apples');
fruits.removeAt(appleIndex);
assert(fruits.length == 4);
// Remove all elements from a list.
fruits.clear();
assert(fruits.length == 0);
~~~
使用indexOf()查找列表中对象的索引:
~~~
var fruits = ['apples', 'oranges'];
// Access a list item by index.
assert(fruits[0] == 'apples');
// Find an item in a list.
assert(fruits.indexOf('apples') == 0);
~~~
使用sort()方法对列表进行排序。您可以提供一个比较两个对象的排序函数。这个排序函数必须返回< 0表示较小,0表示相同,>0表示较大。下面的示例使用compareTo(),它由Comparable定义,由String实现。
~~~
var fruits = ['bananas', 'apples', 'oranges'];
// Sort a list.
fruits.sort((a, b) => a.compareTo(b));
assert(fruits[0] == 'apples');
~~~
列表是参数化的类型,所以您可以指定列表应该包含的类型:
~~~
// This list should contain only strings.
var fruits = List();
fruits.add('apples');
var fruit = fruits[0];
assert(fruit is String);
~~~
以下是错误示例:
~~~
fruits.add(5); // Error: 'int' can't be assigned to 'String'
~~~
有关方法的完整列表,请参阅[List API文档](https://api.dartlang.org/stable/dart-core/List-class.html)。
### 集合(Sets)
Dart中的集合是一组无序的独特物品集合。因为集合是无序的,所以不能通过索引(位置)获得集合的项。
~~~
var ingredients = Set();
ingredients.addAll(['gold', 'titanium', 'xenon']);
assert(ingredients.length == 3);
// Adding a duplicate item has no effect.
ingredients.add('gold');
assert(ingredients.length == 3);
// Remove an item from a set.
ingredients.remove('gold');
assert(ingredients.length == 2);
~~~
使用contains()和containsAll()来检查集合中是否有一个或多个对象:
~~~
var ingredients = Set();
ingredients.addAll(['gold', 'titanium', 'xenon']);
// Check whether an item is in the set.
assert(ingredients.contains('titanium'));
// Check whether all the items are in the set.
assert(ingredients.containsAll(['titanium', 'xenon']));
~~~
交集是一个集合,其项在另外两个集合中。
~~~
var ingredients = Set();
ingredients.addAll(['gold', 'titanium', 'xenon']);
// Create the intersection of two sets.
var nobleGases = Set.from(['xenon', 'argon']);
var intersection = ingredients.intersection(nobleGases);
assert(intersection.length == 1);
assert(intersection.contains('xenon'));
~~~
有关方法的完整列表请查看[Set API文档](https://api.dartlang.org/stable/dart-core/Set-class.html)
### 映射(Maps)
通常称为字典或散列的映射是键-值对的无序集合。映射将一个键关联到某个值,以便于检索。与JavaScript不同,Dart对象不是映射。
您可以使用简单的文字语法声明映射,也可以使用传统的构造函数:
~~~
// Maps often use strings as keys.
var hawaiianBeaches = {
'Oahu': ['Waikiki', 'Kailua', 'Waimanalo'],
'Big Island': ['Wailea Bay', 'Pololu Beach'],
'Kauai': ['Hanalei', 'Poipu']
};
// Maps can be built from a constructor.
var searchTerms = Map();
// Maps are parameterized types; you can specify what
// types the key and value should be.
var nobleGases = Map();
~~~
使用方括号语法add、get和set映射项。使用remove()从映射中删除键及其值。
~~~
var nobleGases = {54: 'xenon'};
// Retrieve a value with a key.
assert(nobleGases[54] == 'xenon');
// Check whether a map contains a key.
assert(nobleGases.containsKey(54));
// Remove a key and its value.
nobleGases.remove(54);
assert(!nobleGases.containsKey(54));
~~~
你可以从一个map检索所有的值或所有的键:
~~~
var hawaiianBeaches = {
'Oahu': ['Waikiki', 'Kailua', 'Waimanalo'],
'Big Island': ['Wailea Bay', 'Pololu Beach'],
'Kauai': ['Hanalei', 'Poipu']
};
// Get all the keys as an unordered collection
// (an Iterable).
var keys = hawaiianBeaches.keys;
assert(keys.length == 3);
assert(Set.from(keys).contains('Oahu'));
// Get all the values as an unordered collection
// (an Iterable of Lists).
var values = hawaiianBeaches.values;
assert(values.length == 3);
assert(values.any((v) => v.contains('Waikiki')));
~~~
要检查映射是否包含某个key,请使用containsKey()。因为映射值可以为空,所以不能简单地依赖于获取键的值并检查是否为空来确定键的存在。
~~~
var hawaiianBeaches = {
'Oahu': ['Waikiki', 'Kailua', 'Waimanalo'],
'Big Island': ['Wailea Bay', 'Pololu Beach'],
'Kauai': ['Hanalei', 'Poipu']
};
assert(hawaiianBeaches.containsKey('Oahu'));
assert(!hawaiianBeaches.containsKey('Florida'));
~~~
有关方法的完整列表请查看 [Map API文档](https://api.dartlang.org/stable/dart-core/Map-class.html)
### 集合类型的共同方法
列表、集合和映射共享许多集合中的公共功能。其中一些常见功能是由Iterable类定义的,该类列出并设置实现。
>注意:尽管Map没有实现Iterable,但是您可以使用Map键和值属性从它获得迭代。
>
使用isEmpty或isNotEmpty检查列表、集合或映射是否有项:
~~~
var coffees = [];
var teas = ['green', 'black', 'chamomile', 'earl grey'];
assert(coffees.isEmpty);
assert(teas.isNotEmpty);
~~~
要对列表、集合或映射中的每个项执行函数,可以使用forEach():
~~~
var teas = ['green', 'black', 'chamomile', 'earl grey'];
teas.forEach((tea) => print('I drink $tea'));
~~~
在映射上调用forEach()时,函数必须接受两个参数(键和值):
~~~
hawaiianBeaches.forEach((k, v) {
print('I want to visit $k and swim at $v');
// I want to visit Oahu and swim at
// [Waikiki, Kailua, Waimanalo], etc.
});
~~~
Iterables提供map()方法,它可以在一个对象中提供所有结果:
~~~
var teas = ['green', 'black', 'chamomile', 'earl grey'];
var loudTeas = teas.map((tea) => tea.toUpperCase());
loudTeas.forEach(print);
~~~
>注意:map()返回的对象是一个可迭代的、延迟求值的对象:直到从返回的对象请求项时才调用函数。
>
强制在每个项上立即调用函数,使用map().toList()或map().toSet():
~~~
var loudTeas =
teas.map((tea) => tea.toUpperCase()).toList();
~~~
使用迭代器的where()方法获得所有与条件匹配的项。使用迭代器的any()和every()方法检查某些或所有项是否匹配一个条件。
~~~
var teas = ['green', 'black', 'chamomile', 'earl grey'];
// Chamomile is not caffeinated.
bool isDecaffeinated(String teaName) =>
teaName == 'chamomile';
// Use where() to find only the items that return true
// from the provided function.
var decaffeinatedTeas =
teas.where((tea) => isDecaffeinated(tea));
// or teas.where(isDecaffeinated)
// Use any() to check whether at least one item in the
// collection satisfies a condition.
assert(teas.any(isDecaffeinated));
// Use every() to check whether all the items in a
// collection satisfy a condition.
assert(!teas.every(isDecaffeinated));
~~~
要获得完整的方法列表,请参考[Iterable API文档](https://api.dartlang.org/stable/dart-core/Iterable-class.html)以及[List](https://api.dartlang.org/stable/dart-core/List-class.html)、[Set](https://api.dartlang.org/stable/dart-core/Set-class.html)和[Map](https://api.dartlang.org/stable/dart-core/Map-class.html)文档。
## URIs
Uri类提供了对字符串进行编码和解码的函数,以便在Uri中使用(您可能知道url)。这些函数处理uri特有的字符,例如&和=。Uri类还解析并公开Uri主机、端口、 协议等的组件。
### 编码和解码完整的uri
要对URI中具有特殊含义的字符(例如/、:、&、#)进行编码和解码,请使用encodeFull()和decodeFull()方法。这些方法适用于编码或解码完整的URI,保留完整的特殊URI字符。
~~~
var uri = 'http://example.org/api?foo=some message';
var encoded = Uri.encodeFull(uri);
assert(encoded ==
'http://example.org/api?foo=some%20message');
var decoded = Uri.decodeFull(encoded);
assert(uri == decoded);
~~~
注意,只有some和message中间的空格被编码
### 对URI组件进行编码和解码
要对URI中具有特殊含义的所有字符串字符进行编码和解码,包括(但不限于)/、&和:,请使用encodeComponent()和decodeComponent()方法。
~~~
var uri = 'http://example.org/api?foo=some message';
var encoded = Uri.encodeComponent(uri);
assert(encoded ==
'http%3A%2F%2Fexample.org%2Fapi%3Ffoo%3Dsome%20message');
var decoded = Uri.decodeComponent(encoded);
assert(uri == decoded);
~~~
注意每个特殊字符是如何编码的。例如,/被编码为%2F。
### 解析uri
如果您有一个Uri对象或一个Uri字符串,您可以使用Uri字段(如path)获得它的部分。要从字符串创建Uri,使用parse()静态方法:
~~~
var uri =
Uri.parse('http://example.org:8080/foo/bar#frag');
assert(uri.scheme == 'http');
assert(uri.host == 'example.org');
assert(uri.path == '/foo/bar');
assert(uri.fragment == 'frag');
assert(uri.origin == 'http://example.org:8080');
~~~
查看[Uri API文档](https://api.dartlang.org/stable/dart-core/Uri-class.html)可以获得更多的Uri组件。
### 构建uri
您可以使用Uri()构造函数从各个部分构建一个URI:
~~~
var uri = Uri(
scheme: 'http',
host: 'example.org',
path: '/foo/bar',
fragment: 'frag');
assert(
uri.toString() == 'http://example.org/foo/bar#frag');
~~~
## 日期和时间
DateTime对象是一个时间点。时区不是UTC就是当地时区。
你可以使用几个构造函数来创建DateTime对象:
~~~
// Get the current date and time.
var now = DateTime.now();
// Create a new DateTime with the local time zone.
var y2k = DateTime(2000); // January 1, 2000
// Specify the month and day.
y2k = DateTime(2000, 1, 2); // January 2, 2000
// Specify the date as a UTC time.
y2k = DateTime.utc(2000); // 1/1/2000, UTC
// Specify a date and time in ms since the Unix epoch.
y2k = DateTime.fromMillisecondsSinceEpoch(946684800000,
isUtc: true);
// Parse an ISO 8601 date.
y2k = DateTime.parse('2000-01-01T00:00:00Z');
~~~
日期的millisecondsSinceEpoch属性返回自“Unix epoch”(1970年1月1日,UTC)以来的毫秒数:
~~~
// 1/1/2000, UTC
var y2k = DateTime.utc(2000);
assert(y2k.millisecondsSinceEpoch == 946684800000);
// 1/1/1970, UTC
var unixEpoch = DateTime.utc(1970);
assert(unixEpoch.millisecondsSinceEpoch == 0);
~~~
使用Duration类来计算两个日期之间的差值,并将一个日期向前或向后移动:
~~~
var y2k = DateTime.utc(2000);
// Add one year.
var y2001 = y2k.add(Duration(days: 366));
assert(y2001.year == 2001);
// Subtract 30 days.
var december2000 = y2001.subtract(Duration(days: 30));
assert(december2000.year == 2000);
assert(december2000.month == 12);
// Calculate the difference between two dates.
// Returns a Duration object.
var duration = y2001.difference(y2k);
assert(duration.inDays == 366); // y2k was a leap year.
~~~
>警告:使用持续时间以天为单位来移动日期时间是有问题的,因为时钟会改变(例如,夏令时)。如果你必须换班,请使用UTC日期。
>
有关[DateTime](https://api.dartlang.org/stable/dart-core/DateTime-class.html) 和[Duration](https://api.dartlang.org/stable/dart-core/Duration-class.html)的完整方法列表,请参阅API文档。
## 实用程序类
核心库包含各种实用程序类,用于排序、映射值和迭代。
### 比较对象
实现Comparable接口,以指示一个对象可以与另一个对象进行比较,通常用于排序。compareTo()方法返回< 0对于小的,0对于相等的,大的返回>0。
~~~
class Line implements Comparable {
final int length;
const Line(this.length);
@override
int compareTo(Line other) => length - other.length;
}
void main() {
var short = const Line(1);
var long = const Line(100);
assert(short.compareTo(long) < 0);
}
~~~
### 实现map键
Dart中的每个对象自动地提供了一个整数哈希码,因此可以作为映射中的键。但是,您可以重写hashCode getter来生成自定义哈希代码。如果这样做,您可能还想重写==操作符。相等的对象(via ==)必须具有相同的哈希码。哈希代码不一定是唯一的,但它应该是分布良好的。
~~~
class Person {
final String firstName, lastName;
Person(this.firstName, this.lastName);
// Override hashCode using strategy from Effective Java,
// Chapter 11.
@override
int get hashCode {
int result = 17;
result = 37 * result + firstName.hashCode;
result = 37 * result + lastName.hashCode;
return result;
}
// You should generally implement operator == if you
// override hashCode.
@override
bool operator ==(dynamic other) {
if (other is! Person) return false;
Person person = other;
return (person.firstName == firstName &&
person.lastName == lastName);
}
}
void main() {
var p1 = Person('Bob', 'Smith');
var p2 = Person('Bob', 'Smith');
var p3 = 'not a person';
assert(p1.hashCode == p2.hashCode);
assert(p1 == p2);
assert(p1 != p3);
}
~~~
### 迭代
Iterable类和Iterator类支持for-in循环。无论何时,只要您创建了一个类,可以为for-in循环提供迭代器,就可以扩展(如果可能的话)或实现Iterable。实现迭代器来定义实际的迭代能力。
~~~
class Process {
// Represents a process...
}
class ProcessIterator implements Iterator {
@override
Process get current => ...
@override
bool moveNext() => ...
}
// A mythical class that lets you iterate through all
// processes. Extends a subclass of [Iterable].
class Processes extends IterableBase {
@override
final Iterator iterator = ProcessIterator();
}
void main() {
// Iterable objects can be used with for-in.
for (var process in Processes()) {
// Do something with the process.
}
}
~~~
## 异常
Dart核心库定义了许多常见的异常和错误。异常被认为是可以提前计划和捕捉的条件。错误是您没有预料到或计划的情况。
一些最常见的错误是:
[NoSuchMethodError](https://api.dartlang.org/stable/dart-core/NoSuchMethodError-class.html)
当接收对象(可能为空)没有实现方法时抛出。
[ArgumentError](https://api.dartlang.org/stable/dart-core/ArgumentError-class.html)
可以由遇到意外参数的方法抛出。
抛出特定于应用程序的异常是表示发生了错误的常见方法。您可以通过实现异常接口来定义自定义异常:
~~~
class FooException implements Exception {
final String msg;
const FooException([this.msg]);
@override
String toString() => msg ?? 'FooException';
}
~~~
更多信息请查看 [Exceptions](https://www.dartlang.org/guides/libraries/library-tour#exceptions) 和 [Exception API 文档](https://api.dartlang.org/stable/dart-core/Exception-class.html)。
# dart:async - 异步编程
异步编程通常使用回调函数,但是Dart提供了其他方法:[Future](https://api.dartlang.org/stable/dart-async/Future-class.html)对象和[Stream](https://api.dartlang.org/stable/dart-async/Stream-class.html)对象。Future就像对未来某个时刻的结果的承诺。Stream是一种获取值序列(如事件)的方法。dart:async库([API参考](https://api.dartlang.org/stable/dart-async/dart-async-library.html))中包含了Future, Stream和更多内容。
>注意:您并不总是需要直接使用Future api或Stream api。Dart语言支持使用诸如async和await等关键字进行异步编码。有关详细信息,请参阅语本文档中的[异步支持](https://www.kancloud.cn/marswill/dark2_document/709109)。
>
dart:async库在web应用程序和命令行应用程序中都能工作。要使用它,导入dart:async:
~~~
import 'dart:async';
~~~
## Future
Future对象出现在Dart库中,通常是异步方法返回的对象。当Future完成时,它的值就可以使用了。
### 使用await
在直接使用Future API之前,请考虑使用await。使用await表达式的代码比使用Future API的代码更容易理解。
考虑下面的函数。它使用Future的then()方法在一行中执行三个异步函数,等待每个函数完成后再执行下一个。
~~~
runUsingFuture() {
// ...
findEntryPoint().then((entryPoint) {
return runExecutable(entryPoint, args);
}).then(flushThenExit);
}
~~~
具有await表达式的等效代码看起来更像同步代码:
~~~
runUsingAsyncAwait() async {
// ...
var entryPoint = await findEntryPoint();
var exitCode = await runExecutable(entryPoint, args);
await flushThenExit(exitCode);
}
~~~
异步函数可以捕获Futures中的异常。例如:
~~~
var entryPoint = await findEntryPoint();
try {
var exitCode = await runExecutable(entryPoint, args);
await flushThenExit(exitCode);
} catch (e) {
// Handle the error...
}
~~~
>重要提示:异步函数返回Future。如果您不希望函数返回Future,那么使用不同的解决方案。例如,您可以从您的函数调用一个异步函数。
>
有关使用await和相关Dart语言特性的更多信息,请参阅[异步支持](https://www.dartlang.org/guides/language/language-tour#asynchrony-support)。
### 基本使用
您可以使用then()来调度Future完成时运行的代码。例如,HttpRequest.getString()返回一个Future,因为HTTP请求可能需要一段时间。使用then()可以让您在Future完成并且承诺的字符串值可用时运行一些代码:
~~~
HttpRequest.getString(url).then((String result) {
print(result);
});
~~~
使用catchError()来处理Future对象可能抛出的任何错误或异常。
~~~
HttpRequest.getString(url).then((String result) {
print(result);
}).catchError((e) {
// Handle or ignore the error.
});
~~~
then().catchError()模式是try-catch的异步版本。
>重要提示:一定要在then()的结果上调用catchError()——而不是在原始Future的结果上。否则,catchError()只能从原始Future的计算中处理错误,而不能从then()注册的处理程序处理错误。
>
### 链接多个异步方法
then()方法返回一个Future,提供了一种有用的方式来以特定的顺序运行多个异步函数。如果用then()注册的回调返回一个Future,那么then()返回一个等效的Future。如果回调返回任何其他类型的值,那么then()将创建一个新的Future,并使用该值完成操作。
~~~
Future result = costlyQuery(url);
result
.then((value) => expensiveWork(value))
.then((_) => lengthyComputation())
.then((_) => print('Done!'))
.catchError((exception) {
/* Handle exception... */
});
~~~
在前面的例子中,方法按照以下顺序运行:
1. costlyQuery()
2. expensiveWork()
3. lengthyComputation()
下面是使用await编写的相同代码:
~~~
try {
final value = await costlyQuery(url);
await expensiveWork(value);
await lengthyComputation();
print('Done!');
} catch (e) {
/* Handle exception... */
}
~~~
### 等待多个Future
有时您的算法需要调用许多异步函数,并等待它们全部完成后再继续。使用Future.wait()静态方法管理多个期货,并等待它们完成:
~~~
Future deleteLotsOfFiles() async => ...
Future copyLotsOfFiles() async => ...
Future checksumLotsOfOtherFiles() async => ...
await Future.wait([
deleteLotsOfFiles(),
copyLotsOfFiles(),
checksumLotsOfOtherFiles(),
]);
print('Done with all the long steps!');
~~~
## Stream
流对象出现在Dart api中,表示数据序列。例如,HTML事件(如按钮单击)是使用流传递的。您还可以将文件读取为流。
### 使用异步for循环
有时候,您可以使用异步for循环(wait for),而不是使用流API。
考虑下面的函数。它使用Stream的listen()方法订阅一个文件列表,传入一个搜索每个文件或目录的函数文字。
~~~
void main(List arguments) {
// ...
FileSystemEntity.isDirectory(searchPath).then((isDir) {
if (isDir) {
final startingDir = Directory(searchPath);
startingDir
.list(
recursive: argResults[recursive],
followLinks: argResults[followLinks])
.listen((entity) {
if (entity is File) {
searchFile(entity, searchTerms);
}
});
} else {
searchFile(File(searchPath), searchTerms);
}
});
}
~~~
带有await表达式的等价代码,包括异步for循环(await for),看起来更像同步代码:
~~~
Future main(List arguments) async {
// ...
if (await FileSystemEntity.isDirectory(searchPath)) {
final startingDir = Directory(searchPath);
await for (var entity in startingDir.list(
recursive: argResults[recursive],
followLinks: argResults[followLinks])) {
if (entity is File) {
searchFile(entity, searchTerms);
}
}
} else {
searchFile(File(searchPath), searchTerms);
}
}
~~~
>重要提示:在使用await for之前,确保它使代码更清晰,并且您确实希望等待流的所有结果。例如,对于DOM事件侦听器,通常不应该使用await For,因为DOM发送无穷无尽的事件流。如果您使用await for在一行中注册两个DOM事件侦听器,那么第二类事件永远不会被处理。
>
有关使用await和相关Dart语言特性的更多信息,请参阅[异步支持](https://www.dartlang.org/guides/language/language-tour#asynchrony-support)。
### 监听流数据
要在每个值到达时获得它,可以使用await()方法对流使用或使用listen()方法订阅:
~~~
// Find a button by ID and add an event handler.
querySelector('#submitInfo').onClick.listen((e) {
// When the button is clicked, it runs this code.
submitData();
});
~~~
在本例中,onClick属性是“submitInfo”按钮提供的流对象。
如果只关心一个事件,那么可以使用属性first、last或single来获得它。要在处理事件之前测试它,可以使用诸如firstWhere()、lastWhere()或singleWhere()之类的方法。
如果关心事件的子集,可以使用诸如skip()、skipWhile()、take()、takeWhile()和where()等方法。
### 改变流数据
通常,您需要在使用流数据之前更改其格式。使用transform()方法生成具有不同类型数据的流:
~~~
var lines = inputStream
.transform(utf8.decoder)
.transform(LineSplitter());
~~~
这个例子使用了两个转换器。首先,它使用utf8.decoder将整数流转换为字符串流。然后,它使用linesp才将字符串流转换为单独的行流。这些转换器来自dart:convert库(参见[dart:convert部分](https://www.dartlang.org/guides/libraries/library-tour#dartconvert---decoding-and-encoding-json-utf-8-and-more))。
### 处理错误和完成
如何指定错误和完成处理代码取决于是使用异步for循环(wait for)还是流API。
如果使用异步for循环,则使用try-catch处理错误。在流关闭后执行的代码在异步for循环之后执行。
~~~
Future readFileAwaitFor() async {
var config = File('config.txt');
Stream
';
- > inputStream = config.openRead();
var lines = inputStream
.transform(utf8.decoder)
.transform(LineSplitter());
try {
await for (var line in lines) {
print('Got ${line.length} characters from stream');
}
print('file is now closed');
} catch (e) {
print(e);
}
}
~~~
如果使用流API,则通过注册onError侦听器来处理错误。通过注册onDone侦听器,在流关闭后运行代码。
~~~
var config = File('config.txt');
Stream
- > inputStream = config.openRead();
inputStream
.transform(utf8.decoder)
.transform(LineSplitter())
.listen((String line) {
print('Got ${line.length} characters from stream');
}, onDone: () {
print('file is now closed');
}, onError: (e) {
print(e);
});
~~~
### 更多信息
有关在命令行应用程序中使用Future和Stream的一些示例,请参阅[dart:io的引导](https://www.dartlang.org/dart-vm/io-library-tour)。也看这些文章和教程:
* [Asynchronous Programming: Futures](https://www.dartlang.org/tutorials/language/futures)
* [Futures and Error Handling](https://www.dartlang.org/guides/libraries/futures-error-handling)
* [The Event Loop and Dart](https://webdev.dartlang.org/articles/performance/event-loop)
* [Asynchronous Programming: Streams](https://www.dartlang.org/tutorials/language/streams)
* [Creating Streams in Dart](https://www.dartlang.org/articles/libraries/creating-streams)
# dart:math - math and random
dart:math 库 ([API 参考](https://api.dartlang.org/stable/dart-math/dart-math-library.html))提供了常见的功能,如正弦和余弦,最大值和最小值,以及常量,如pi和e。Math 库中的大多数功能是作为顶级函数实现的。
要在应用程序中使用这个库,导入dart:math。
~~~
import 'dart:math';
~~~
## 三角函数
数学库提供基本三角函数:
~~~
// Cosine
assert(cos(pi) == -1.0);
// Sine
var degrees = 30;
var radians = degrees * (pi / 180);
// radians is now 0.52359.
var sinOf30degrees = sin(radians);
// sin 30° = 0.5
assert((sinOf30degrees - 0.5).abs() < 0.01);
~~~
>注意:这些函数使用弧度,而不是角度!
>
## 最大和最小值
数学库提供max()和min()方法:
~~~
assert(max(1, 1000) == 1000);
assert(min(1, -1000) == -1000);
~~~
## 数学常量
在数学库中找到你最喜欢的常量- pi, e,以及更多:
~~~
// See the Math library for additional constants.
print(e); // 2.718281828459045
print(pi); // 3.141592653589793
print(sqrt2); // 1.4142135623730951
~~~
## 随机数
用Random类生成随机数。您可以选择向随机构造函数提供种子。
~~~
var random = Random();
random.nextDouble(); // Between 0.0 and 1.0: [0, 1)
random.nextInt(10); // Between 0 and 9.
~~~
你甚至可以产生随机布尔:
~~~
var random = Random();
random.nextBool(); // true or false
~~~
## 更多信息
有关方法的完整列表,请参阅[Math API文档](https://api.dartlang.org/stable/dart-math/dart-math-library.html)。还可以查看有关[num](https://api.dartlang.org/stable/dart-core/num-class.html)、[int](https://api.dartlang.org/stable/dart-core/int-class.html)和[double](https://api.dartlang.org/stable/dart-core/double-class.html)的API文档。
# dart:convert - 解码和编码JSON、UTF-8等等
dart:convert库([API reference](https://api.dartlang.org/stable/dart-convert/dart-convert-library.html))为JSON和UTF-8提供了转换器,并且支持创建额外的转换器。[JSON](https://www.json.org/)是一种表示结构化对象和集合的简单文本格式。[UTF-8](https://en.wikipedia.org/wiki/UTF-8)是一种常见的变宽编码,可以表示Unicode字符集中的每个字符。
dart:convert 库可以在web应用程序和命令行应用程序中工作。要使用它,导入dart:convert。
~~~
import 'dart:convert';
~~~
## 编码接吗JSON
用jsonDecode()将json编码的字符串解码为Dart对象:
~~~
// NOTE: Be sure to use double quotes ("),
// not single quotes ('), inside the JSON string.
// This string is JSON, not Dart.
var jsonString = '''
[
{"score": 40},
{"score": 80}
]
''';
var scores = jsonDecode(jsonString);
assert(scores is List);
var firstScore = scores[0];
assert(firstScore is Map);
assert(firstScore['score'] == 40);
~~~
用jsonEncode()将受支持的Dart对象编码为json格式的字符串:
~~~
var scores = [
{'score': 40},
{'score': 80},
{'score': 100, 'overtime': true, 'special_guest': null}
];
var jsonText = jsonEncode(scores);
assert(jsonText ==
'[{"score":40},{"score":80},'
'{"score":100,"overtime":true,'
'"special_guest":null}]');
~~~
只有int、double、String、bool、null、List或Map(带字符串键)类型的对象可以直接编码为JSON。列表和映射对象是递归编码的。
对于不能直接编码的对象,有两个编码选项。第一个是使用第二个参数调用encode():一个返回可直接编码对象的函数。第二个选项是省略第二个参数,在这种情况下,编码器调用对象的toJson()方法。
## 解码和编码UTF-8字符
使用utf8.decode()将utf8编码的字节解码到Dart字符串:
~~~
List
代码风格
最后更新于:2022-04-02 04:59:43
### 这部分内容已经转到[编写高质量Dart程序(Dart2)](https://www.kancloud.cn/marswill/effective_dart/728173)这个文档中。
';
附件
最后更新于:2022-04-02 04:59:41
[代码风格](addition/effect_style.md)
';
注释
最后更新于:2022-04-02 04:59:39
[TOC]
Dart支持单行注释、多行注释和文档注释。
## 单行注释
单行注释以//开头。在//和行尾之间的所有内容都被Dart编译器忽略。
~~~
void main() {
// TODO: refactor into an AbstractLlamaGreetingFactory?
print('Welcome to my Llama farm!');
}
~~~
## 多行注释
多行注释以/*开头,以*/结尾。在/*和*/之间的所有内容都被Dart编译器忽略(除非注释是文档注释;见下一节)。多行注释可以嵌套
~~~
void main() {
/*
* This is a lot of work. Consider raising chickens.
Llama larry = Llama();
larry.feed();
larry.exercise();
larry.clean();
*/
}
~~~
## 文档注释
文档注释是以///或/*开头的多行或单行注释。在连续的行上使用///和多行文档注释具有相同的效果。
在文档注释中,Dart编译器会忽略所有文本,除非它被括在括号中。使用括号,您可以引用类、方法、字段、顶级变量、函数和参数。括号中的名称在文档化程序元素的词法范围内解析。
这里有一个引用其他类和参数的文档注释示例:
~~~
/// A domesticated South American camelid (Lama glama).
///
/// Andean cultures have used llamas as meat and pack
/// animals since pre-Hispanic times.
class Llama {
String name;
/// Feeds your llama [Food].
///
/// The typical llama eats one bale of hay per week.
void feed(Food food) {
// ...
}
/// Exercises your llama with an [activity] for
/// [timeLimit] minutes.
void exercise(Activity activity, int timeLimit) {
// ...
}
}
~~~
在生成的文档中,[Food]成为了与Food类的API文档的链接。
要解析Dart代码并生成HTML文档,可以使用[SDK的文档生成工具]。有关生成文档的示例,请参阅[Dart API文档]。有关如何构造注释的建议,请参阅[Dart文档注释的指南]。
';
元数据
最后更新于:2022-04-02 04:59:37
使用元数据提供关于代码的附加信息。元数据注释以字符@开头,后跟对编译时常量(如deprecated)的引用或对常量构造函数的调用。
所有Dart代码都可以使用两个注释:@deprecated和@override。有关使用@override的示例,请参见扩展类。这里有一个使用@deprecated注释的例子:
~~~
class Television {
/// _Deprecated: Use [turnOn] instead._
@deprecated
void activate() {
turnOn();
}
/// Turns the TV's power on.
void turnOn() {...}
}
~~~
您可以定义自己的元数据注释。这里有一个定义带有两个参数的@todo注释的示例:
~~~
library todo;
class Todo {
final String who;
final String what;
const Todo(this.who, this.what);
}
~~~
这里有一个使用@todo注释的例子:
~~~
import 'todo.dart';
@Todo('seth', 'make this do something')
void doSomething() {
print('do something');
}
~~~
元数据可以出现在库、类、类型定义、类型参数、构造函数、工厂、函数、字段、参数或变量声明之前,也可以出现在导入或导出指令之前。您可以使用反射在运行时检索元数据。
';
类型定义
最后更新于:2022-04-02 04:59:34
在Dart中,函数是对象,就像字符串和数字是对象一样。typedef或function-type为函数提供一个类型别名,你可以在声明字段和返回类型时使用这个名称。当函数类型被分配给变量时,typedef保留类型信息。
以下代码不使用typedef:
~~~
class SortedCollection {
Function compare;
SortedCollection(int f(Object a, Object b)) {
compare = f;
}
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
// All we know is that compare is a function,
// but what type of function?
assert(coll.compare is Function);
}
~~~
Type information is lost when assigning f to compare. The type of f is (Object, Object) → int (where → means returns), yet the type of compare is Function. If we change the code to use explicit names and retain type information, both developers and tools can use that information.
当给compare分配f时类型信息会丢失。f的类型是(Object, Object)->int(int表示返回值类型),当然compare的类型是Function。如果我们更改代码以使用显式名称和保留类型信息,开发人员和工具都可以使用这些信息。
~~~
typedef Compare = int Function(Object a, Object b);
class SortedCollection {
Compare compare;
SortedCollection(this.compare);
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
assert(coll.compare is Function);
assert(coll.compare is Compare);
}
~~~
>注意:目前,typedefs仅限于函数类型。我们期望这种情况会改变。
>
因为typedef仅仅是别名,所以它们提供了一种检查任何函数类型的方法。例如:
~~~
typedef Compare = int Function(T a, T b);
int sort(int a, int b) => a - b;
void main() {
assert(sort is Compare); // True!
}
~~~
';
隔离器
最后更新于:2022-04-02 04:59:32
大多数计算机,甚至在移动平台上,都有多核cpu。为了利用所有这些核心,开发人员通常使用同时运行的共享内存线程。但是,共享状态并发容易出错并且可能增加代码的复杂度。
不同于线程,所有Dart代码都运行在隔离器内部,而不是线程。每个隔离都有它自己的内存堆,确保任何其他隔离器都不能访问隔离状态。
有关更多信息,请参见[dart:isolate库文档]。
';
可调用的类
最后更新于:2022-04-02 04:59:30
实现call()方法可以让你的Dart类像函数一样被调用。
在下面的示例中,WannabeFunction类定义了一个call()函数,该函数接受三个字符串并将它们连接起来,每个字符串用空格分隔,并在结尾加一个感叹号。
~~~
class WannabeFunction {
call(String a, String b, String c) => '$a $b $c!';
}
main() {
var wf = new WannabeFunction();
var out = wf("Hi", "there,", "gang");
print('$out');
}
///执行结果
Hi there, gang!
~~~
有关类的更多信息,请参见[Dart中的模拟函数]。
';
生成器
最后更新于:2022-04-02 04:59:28
当您需要延迟地生成一个值序列时,请考虑使用生成器函数。Dart内置支持两种生成器函数:
* 同步生成器:返回Iterable对象
* 异步生成器:返回Stream对象
要实现同步生成器函数,将函数体标记为sync*,并使用yield语句传递值:
~~~
Iterable naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
~~~
要实现异步生成器函数,将函数体标记为async*,并使用yield语句传递值:
~~~
Stream asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}
~~~
如果您的生成器是递归的,您可以使用yield*来改进它的性能:
~~~
Iterable naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
~~~
有关生成器的更多信息,请参阅文章[Dart语言异步支持:Phase 2]。
';
异步支持
最后更新于:2022-04-02 04:59:25
[TOC]
Dart库中有非常多的函数返回Future对象或Stream对象。这些函数是异步的:在可能耗时的操作(例如I/O)操作的语句之后不等到操作执行完成就返回。
async和await关键字支持异步编程,允许您编写类似于同步代码的异步代码。
## 处理Futures
当你需要一个完整的Futures的结果时,你有两个选择:
* 使用async和await
* 使用Future的API,如[库的引导]中描述的一样
使用async和await的代码虽然是异步的,但是看起来很像同步代码。例如,以下代码使用await来等待异步函数执行的结果:
~~~
await lookUpVersion();
~~~
要使用await必须是对一个使用async标注的异步函数:
~~~
Future checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
~~~
>注意:尽管异步函数可能执行耗时的操作,但它不会等待这些操作。相反,异步函数一直执行直到遇到第一个await表达式([查看详细信息])。然后它返回一个Futures的对象,只有在await表达式完成之后才恢复执行。
>
Use try, catch, and finally to handle errors and cleanup in code that uses await:
使用try,catch和finally来处理使用await的代码中的错误:
~~~
try {
version = await lookUpVersion();
} catch (e) {
// React to inability to look up the version
}
~~~
你可以在异步函数中多次使用await。例如,以下代码执行了三次await来获取函数的结果。
~~~
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
~~~
在await表达式中,表达式的值通常是一个Future对象。如果不是,那么这个值将被自动包装成Future。Futrue对象指示返回结果一定是一个对象。表达式的值就是被返回的对象。await表达式会让程序执行挂起,直到返回的对象可用。
***如果在使用await时出现编译时错误,请确保await在异步函数中*** 。例如,要在应用程序的main()函数中使用wait, main()的主体必须标记为async:
~~~
Future main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}
~~~
## 声明异步函数
异步函数是函数体被用async修饰符标记的函数。
向函数中添加async关键字将使其返回一个Future。例如,请思考下例中返回一个字符串的同步函数:
~~~
String lookUpVersion() => '1.0.0';
~~~
如果你将他改变成一个异步函数,他的返回值将是一个Future:
~~~
Future lookUpVersion() async => '1.0.0';
~~~
注意,函数的主体不需要使用Future的API。如果需要,Dart将创建Future的对象。
如果您的函数没有返回一个有用的值,那么将其返回Future\类型。
## 处理流(Stream)
当您需要从Stream获取值时,您有两个选择:
* 使用async和异步的for循环(await for)
* 使用Stream API,如[库的引导]中的描述
>注意:在使用await for之前,请确保它使代码更清晰,并且您确实希望等待流(Stream)的所有结果。例如,您通常不应该为UI事件侦听器使用await,因为UI框架会发送无穷无尽的事件流。
>
异步for循环有以下形式:
~~~
await for (varOrType identifier in expression) {
// Executes each time the stream emits a value.
}
~~~
表达式的值必须具有Stream类型。执行过程如下:
1. 等待流发出值。
2. 执行for循环的主体,并将变量设置为发出的值。
3. 重复1和2,直到流关闭。
要停止侦听流,您可以使用break或return语句,该语句将跳出for循环,并从流中取消订阅。
如果在实现异步for循环时出现编译时错误,请确保await在异步函数中。例如,要在应用程序的main()函数中使用异步For循环,main()的主体必须标记为async:
~~~
Future main() async {
// ...
await for (var request in requestServer) {
handleRequest(request);
}
// ...
}
~~~
有关异步编程的更多信息,请参阅[库引导文档]的dart:async部分。还可以参阅文章[Dart语言异步支持:阶段1]和[Dart语言异步支持:阶段2]和[Dart语言规范]。
';
库和可见性
最后更新于:2022-04-02 04:59:23
[TOC]
import和library指令可以帮助您创建模块化和可共享的代码库。库不仅提供api,而且包含隐私部分:以下划线(_)开头的标识符仅在库中可见。每个Dart应用程序都是一个库,即使它不使用库指令。
可以使用包来分发库。有关Pub Package和Asset Manager的信息,请参见[Pub Package和Asset Manager], Pub是SDK中包含的包管理器。
## 使用库
使用import来指定如何在另一个库的范围中使用来自一个库的命名空间。
例如,Dart web应用程序通常使用Dart:html库,它们可以这样导入:
~~~
import 'dart:html';
~~~
导入一个库仅仅需要提供库的URI。对于内置库,URI具有特定的形式(dart:scheme)。对于其他库,可以使用文件路径或者包:scheme的形式。包:scheme形式指定包管理器(如pub工具)提供的库。例如:
~~~
import 'package:test/test.dart';
~~~
>注意:URI表示统一资源标识符。url(统一资源定位器)是一种常见的URI。
>
## 指定一个库前缀
如果您导入两个具有冲突标识符的库,那么您可以为一个或两个库指定一个前缀。例如,如果library1和library2都有一个Element类,那么你可以用以下的方法:
~~~
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// Uses Element from lib1.
Element element1 = Element();
// Uses Element from lib2.
lib2.Element element2 = lib2.Element();
~~~
## 只导入库的一部分
如果您只想使用库的一部分,您可以有选择地导入库。例如:
~~~
// Import only foo.
import 'package:lib1/lib1.dart' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
~~~
## 懒加载库
延迟加载(也称为懒加载)允许应用程序在需要时按需加载库。以下是一些您可能使用延迟加载的情况:
* 减少应用程序的初始启动时间。
* 例如,要执行A/B测试——尝试算法的其他实现。
* 加载很少使用的功能,如可选屏幕和对话框。
要延迟加载库,必须首先使用deferred as进行导入。
~~~
import 'package:greetings/hello.dart' deferred as hello;
~~~
当您需要库时,使用库的标识符调用loadLibrary()。
~~~
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
~~~
在前面的代码中,wait关键字暂停执行,直到加载库。有关async和waiting的更多信息,请参见[异步支持]。
您可以在库上多次调用loadLibrary(),没有问题。该库只加载一次。
在使用延迟加载时,请记住以下几点:
* 在导入文件中,递延库的常量不是常量。记住,这些常量在延迟库加载之前是不存在的。
* 您不能在导入文件中使用来自延迟库的类型。相反,考虑将接口类型移动到由延迟库和导入文件导入的库。
* Dart隐式地将loadLibrary()插入到您定义使用deferred作为名称空间的名称空间中。函数的作用是:返回一个未来。
>Dart VM差异:由于问题#33118,Dart VM甚至在调用loadLibrary()之前就允许访问递延库的成员。我们希望这个bug能够很快得到修复,所以不要依赖于当前的VM行为。
>
## 库的实现
有关如何实现库包的建议,请参阅[创建库包],包括:
* 如何组织库的代码。
* 如何使用export指令。
* 何时使用part指令。
';
泛型
最后更新于:2022-04-02 04:59:21
[TOC]
如果您查看基本数组类型List的API文档,您将看到该类型实际上是List\。<...>符号标记列表为泛型(或参数化)类型——具有形式类型参数的类型。根据约定,类型变量具有单字母名称,如E、T、S、K和V。
## 为什么使用泛型
泛型通常是类型安全所必需的,他们对于写出严谨高质量的代码是很有用的:
* 适当地指定泛型类型可以生成更好的代码。
* 您可以使用泛型来减少代码重复。
如果您想要一个列表只包含字符串,您可以将它声明为list (读作“String of String”)。这样,您和其他程序员,以及您的工具就可以检测到将一个非字符串分配到列表中可能是一个错误。这里有一个例子:
~~~
var names = List();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error
~~~
使用泛型的另一个原因是减少代码重复。泛型允许您在许多类型之间共享一个接口和实现,同时仍然利用静态分析。例如,假设您创建了一个用于缓存对象的接口:
~~~
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
~~~
您发现您想要这个接口的特定字符串版本,所以您创建了另一个接口:
~~~
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
~~~
如果稍后你想要获取这个接口的一个数字特征的版本。
泛型类型可以省去创建所有这些接口的麻烦。相反,您可以创建一个具有类型参数的接口:
~~~
abstract class Cache {
T getByKey(String key);
void setByKey(String key, T value);
}
~~~
在这段代码中,T是替代类型。它是一个占位符,您可以将其视为开发人员稍后将定义的类型。
## 使用集合字面量
List和map字面量可以被参数化。参数化字面量和你已经认识的所有字面量一样,仅仅是在字面量的开始括号之前添加\(对于list类型来说)或者添加(对于map类型来说)。
~~~
var names = ['Seth', 'Kathy', 'Lars'];
var pages = {
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
~~~
## 构造函数的参数化类型
要在使用构造函数时指定一个或多个类型,请将类型放在类名后面的尖括号(<…>)中。例如:
~~~
var names = List();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = Set.from(names);
~~~
下面的代码创建了一个具有整数键和视图类型值的map映射:
~~~
var views = Map();
~~~
## 泛型集合及其包含的类型
Dart通用类型被具体化,这意味着它们在运行时携带它们的类型信息。例如,您可以测试集合的类型:
~~~
var names = List();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List); // true
~~~
>注意:相反,Java中的泛型使用擦除,这意味着泛型类型参数在运行时被删除。在Java中,您可以测试一个对象是否是一个列表,但不能测试它是否是List。
>
## 限制参数化类型
在实现泛型类型时,您可能希望限制其参数的类型。你可以使用extends。
~~~
class Foo {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
~~~
可以使用SomeBaseClass 或它的任何子类作为泛型参数:
~~~
var someBaseClassFoo = Foo();
var extenderFoo = Foo();
~~~
也可以不指定泛型参数:
~~~
var foo = Foo();
print(foo); // Instance of 'Foo'
~~~
指定任何非somebaseclass类型都会导致错误:
~~~
var foo = Foo
';
类变量和方法
最后更新于:2022-04-02 04:59:19
[TOC]
使用static关键字实现类范围的变量和方法。
## 静态变量
静态变量(类变量)对于类范围内的状态和常量是有用的:
~~~
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
~~~
静态变量在使用之前不会初始化。
>注意:此页面遵循代码样式规范,对常量名使用小驼峰命名法。
>
## 静态方法
静态方法(类方法)不对实例进行操作,因此无法访问该实例。例如:
~~~
import 'dart:math';
class Point {
num x, y;
Point(this.x, this.y);
static num distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}
void main() {
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(2.8 < distance && distance < 2.9);
print(distance);
}
~~~
>注意:对于通用或广泛使用的实用程序和功能,考虑使用顶级函数,而不是静态方法。
>
可以使用静态方法作为编译时常量。例如,可以将静态方法作为参数传递给常量构造函数。
';
为类添加mixins特性
最后更新于:2022-04-02 04:59:16
mixin是在多个类层次结构中重用类代码的一种方式。
要使用mixin,请在with关键字后面加上一个或多个mixin名称。下面的例子显示了两个使用mixin的类:
~~~
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
~~~
要实现mixin,创建一个Object的子类,不声明构造函数,也不调用super。例如:
~~~
abstract class Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}
~~~
>注意:一些对mixin的限制要被删除。有关详细信息,请参见建议的[mixin规范]。
>
有关Dart中mixin的演化的理论介绍,请参阅[Dart中mixin的简史]。
';
枚举类型
最后更新于:2022-04-02 04:59:14
[TOC]
枚举类型,通常称为枚举或枚举类型,是一种特殊类型的类,用于表示固定数量的常量值。
## 使用枚举
使用enum关键字声明一个枚举类型:
~~~
enum Color { red, green, blue }
~~~
枚举中的每个值都有一个索引getter,它返回enum声明中值的从0开始的位置。例如,第一个值有索引0,第二个值有索引1。
~~~
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
~~~
要获取枚举中所有值的列表,请使用enum的values 常量。
~~~
List colors = Color.values;
assert(colors[2] == Color.blue);
~~~
您可以在switch语句中使用enum,如果switch的case不处理enum的所有值,将会报一个警告消息:
~~~
var aColor = Color.blue;
switch (aColor) {
case Color.red:
print('Red as roses!');
break;
case Color.green:
print('Green as grass!');
break;
default: // Without this, you see a WARNING.
print(aColor); // 'Color.blue'
}
~~~
枚举类型有以下限制:
* 您不能子类化、混合或实现枚举。
* 不能显式实例化一个枚举
更多信息参见[Dart语言的特性]
';
重写类的成员
最后更新于:2022-04-02 04:59:12
[TOC]
## 重写类的成员
子类可以覆盖实例方法、getter和setter。您可以使用@override注释来指示你重写了某个成员方法:
~~~
class SmartTelevision extends Television {
@override
void turnOn() {...}
// ···
}
~~~
要在类型安全的代码中缩小方法参数或实例变量的类型,可以使用covariant关键字。
## 重写操作符
您可以重写下表中显示的操作符。例如,如果定义一个Vector类,可以定义一个+方法来让两个向量相加。
| < | + | \| | [] |
| --- | --- | --- | --- |
| > | / | ^ | []= |
| <= | ~/ | & | ~ |
| >= | * | << | == |
| - | % | >> | |
下例在类中重写了+和-操作符:
~~~
class Vector {
final int x, y;
Vector(this.x, this.y);
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
// Operator == and hashCode not shown. For details, see note below.
// ···
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
~~~
如果重写==,还应该重写对象的hashCode getter。有关override == 和hashCode的示例,请参见[ Implementing map keys]。
有关重写的更多信息,请参见[扩展类]。
## noSuchMethod()
可以重写noSuchMethod()方法来处理程序访问一个不存在的方法或者成员变量:
~~~
class A {
// Unless you override noSuchMethod, using a
// non-existent member results in a NoSuchMethodError.
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: ' +
'${invocation.memberName}');
}
}
~~~
您不能调用未实现的方法,除非下列任何一个是正确的:
* 被调用者有静态方法dynamic
* 被调用者有一个静态类型来定义未实现的方法(抽象也可以OK),而接收者的动态类型有一个noSuchMethod()的实现,它与类对象中的方法不同。
更多信息,请参见[noSuchMethod转发规范]
';
扩展一个类(继承)
最后更新于:2022-04-02 04:59:10
使用extend创建子类,使用super引用超类:
~~~
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
~~~
';
隐式接口
最后更新于:2022-04-02 04:59:07
每个类都隐式地定义一个接口,该接口包含类的所有实例成员及其实现的任何接口。如果您想创建一个类A,它支持类B的API而不继承B的实现,那么类A应该实现B接口。
~~~
// 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 {
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()));
}
~~~
这里有一个例子,说明一个类实现多个接口:
~~~
class Point implements Comparable, Location {...}
~~~
>译者注:仔细阅读本章的示例代码去理解继承和接口实现的差别
>
';
抽象类
最后更新于:2022-04-02 04:59:05
使用abstract修饰符定义不能实例化的抽象类。抽象类对于定义接口非常有用。如果您希望抽象类看起来是可实例化的,请定义一个工厂构造函数。
抽象类通常有抽象方法。这里有一个声明抽象类的例子,它有一个抽象的方法:
~~~
// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
// Define constructors, fields, methods...
void updateChildren(); // Abstract method.
}
~~~
';
方法
最后更新于:2022-04-02 04:59:03
[TOC]
方法是为对象提供行为的函数。
## 实例方法
对象上的实例方法可以访问实例变量。下面示例中的distanceTo()方法是一个实例方法的示例:
~~~
import 'dart:math';
class Point {
num x, y;
Point(this.x, this.y);
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
~~~
## Getters 和 setters 方法
getter和setter是对对象属性的读写访问的特殊方法。回想一下,每个实例变量都有一个隐式的getter,如果需要的话还可以加上一个setter。使用get和set关键字来实现getter和setter方法可以来读写其他属性:
~~~
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// Define two calculated properties: right and bottom.
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 = 12;
assert(rect.left == -8);
}
~~~
使用getter和setter,你可以使用方法包装实例变量,而无需改动业务代码。
>注意:诸如increment(++)之类的操作符以预期的方式工作,无论getter是否被显式定义。为了避免任何意外的副作用,操作符只调用getter一次,将其值保存在一个临时变量中。
>
## 抽象方法
实例方法、getter和setter方法可以是抽象方法,之定义一个接口但是将具体实现留给其他类。抽象方法只能存在于抽象类中,抽象方法是没有方法体的。
~~~
abstract class Doer {
// Define instance variables and methods...
void doSomething(); // Define an abstract method.
}
class EffectiveDoer extends Doer {
void doSomething() {
// Provide an implementation, so the method is not abstract here...
}
}
~~~
调用抽象方法会导致运行时错误。
';