上周,我们发布了 Dart 2.7 SDK 的稳定版本,它可以为开发者提供多项新功能。Dart 语言经过了充实的一年,它是一种针对客户端优化的语言,适用于在任何平台上开发高效运行的应用。我们今年发布了 6 个新版本,数十项新功能。我们很欣喜地看到这些功能已经被 Dart 社区广泛使用。最近的 GitHub Octoverse 显示,根据多个参与方的评估结果,Dart 被认定为增长速度最快的编程语言 (排名第一),这一消息让我们备受鼓舞。
Dart 2.7 增加了对扩展方法的支持,此外还添加了一个新的代码包,用来处理带有特殊字符的字符串。我们更新了空安全 (已经实现类型安全的可空和非空类型),还通过 DartPad 带来了全新的代码体验环境 (而且支持空安全)。在生态系统层级,pub.dev 现在加入了新的点赞 (Like) 功能,用户们喜欢代码包如今更加一目了然。Dart 2.7 现在就可以从 dart.dev 下载并作为 SDK 使用,并且它也包含在发布的 Flutter 1.12 中。
- GitHub Octoverse https://octoverse.github.com/
- 增长速度最快的编程语言 https://octoverse.github.com/#top-languages
- 下载 Dart 2.7 http://dart.dev/
扩展方法
Dart 2.7 加入了一个长期以来备受期待的强大新语言功能: 扩展方法。扩展方法可以让您给任何类型 (包括您无法控制的类型) 添加新功能,并依然享受和常规方法一样的简洁输入体验以及代码自动补全功能。
我们来看一个简单的例子: 如何从为 String 添加解析 int 和 double 的方法。作为应用开发者,我们无法更改 String 类,因为这个类是在 dart:core 代码库中定义的,但是在扩展方法的帮助下,我们就可以亲手扩展它!在定义了扩展方法之后,我们就可以在 String 上调用新的 parseInt 方法,就如同这个方法是在 String 类中被原生定义的那样:
代码语言:javascript复制extension ParseNumbers on String {
int parseInt() {
return int.parse(this);
}
double parseDouble() {
return double.parse(this);
}
}
main() {
int i = '42'.parseInt();
print(i);
}
扩展方法是静态的
扩展方法是静态解析、静态配置的,也就是说,您无法通过动态值来调用它们。如下所示,该调用在运行时会抛出异常:
代码语言:javascript复制 dynamic d = '2';
d.parseInt();
→ Runtime exception: NoSuchMethodError
扩展方法和 Dart 的类型推断可以很好地协作,所以在下面这个例子中,变量 "v" 被推断为 String 类,自然 String 上的扩展方法是可用的:
代码语言:javascript复制 var v = '1';
v.parseInt(); // Works!
因为扩展方法是静态解析的,所以它们的速度就和调用静态方法或 helper 方法一样快,但调用语法则要友好很多。
- 类型推断 https://dart.dev/guides/language/sound-dart#type-inference
扩展可以拥有类型变量
因为扩展方法是静态解析的,所以它们的速度就和调用静态方法或 helper 方法一样假如我们想在 List 上定义一个扩展,用来获取序号为偶数的内容列表。那么我们就会希望让这个扩展运行在任何类型的列表上,返回和输入列表相同类型的新列表。为了做到这一点,我们可以把扩展泛型化,并将它的类型参数应用到它扩展的类型和方法里:
代码语言:javascript复制extension FancyList<T> on List<T> {
List<T> get evenElements {
return <T>[for (int i = 0; i < this.length; i = 2) this[i]];
}
}
扩展方法是扩展成员
我们把这个功能称作 "扩展方法" 是因为,如果您在其他编程语言中使用过相应的语言功能,就会对这个术语感到熟悉。不过在 Dart 中,这个功能更加宽泛: 它还支持使用新的 getter、setter 以及运算符来扩展类。在上面那个 FancyList 的例子中,evenElements 就是一个 getter。下面则是一个例子,用来展示如何为 String 添加一个用于字符串移位的运算符:
代码语言:javascript复制extension ShiftString on String {
String operator <<(int shift) {
return this.substring(shift, this.length) this.substring(0, shift);
}
}
来自社区的优秀范例
我们已经看到 Dart 社区的很多开发者们开始试用扩展方法。下面列出我们见过的几个优秀范例。
Jeremiah Ogbomo 创建了 time 代码包,它在 num (int 和 double 的基类) 上使用扩展,从而简化了 Duration 的创建过程。
代码语言:javascript复制// Create a Duration via a `minutes` extension on num.
Duration tenMinutes = 10.minutes;
// Create a Duration via an `hours` extension on num.
Duration oneHourThirtyMinutes = 1.5.hours;
// Create a DateTime using a ` ` operator extension on DateTime.
final DateTime afterTenMinutes = DateTime.now() 10.minutes;
Marcelo Glasberg 创建了 i18n (国际化) 代码包,它使用扩展方法来简化字符串的本地化操作:
代码语言:javascript复制Text('Hello'.i18n) // Displays Hello in English, Hola in Spanish, etc.
Simon Leier 创建了 dartx 代码包,其中包含了多个核心 Dart 类型的扩展,如:
代码语言:javascript复制var allButFirstAndLast = list.slice(1, -2); // [1, 2, 3, 4]
var notBlank = ' .'.isBlank; // false
var file = File('some/path/testFile.dart');
print(file.name); // testFile.dart
print(file.nameWithoutExtension); // testFile
Brian Egan 正在更新广受欢迎的 RxDart 代码包,使用扩展方法重新定义 API,以便更好地操作流。
- time 代码包 https://pub.dev/packages/time
- i18n (国际化) 代码包 https://www.reddit.com/r/FlutterDev/comments/dm288s/dart_extensions_applied_to_i18n_you_have/
- dartx 代码包 https://pub.dev/packages/dartx
更安全的字符串截取操作
Dart 的标准 String 类使用 UTF-16 编码。这是编程语言的常见选择,特别是那些需要同时支持设备本地运行和 Web 端运行的编程语言。
- UTF-16 编码 https://en.wikipedia.org/wiki/UTF-16
UTF-16 字符串通常运作良好,编码过程对于开发者来说是透明的。然而,在操作字符串时,特别是操作那些由用户输入的字符串时,您可能会发现,某些被用户认为是字符的东西,和相应的被 UTF-16 编码系统认为是字符单元的东西,其实并不一致。下面我们来看一个例子,从用户输入的字符串中截取前三个字符:
代码语言:javascript复制var input = ['Resume'];
input.forEach((s) => print(s.substring(0, 3)));
$ dart main.dart
Res
目前看来没有问题;我们打印出了输入列表中的字符串上的前三个字母,结果是 Res。现在我们来想想,假如用户来自世界上不同的地区,他们输入的字符中可能包含自己语言特有的符号,比如韩语,他们甚至还会创造性地用表情符号组合来表达出 "简历" 的含义:
代码语言:javascript复制// New longer input list:
var input = ['Resume', 'Résumé', '이력서', '??', 'Currículo'];
$ dart main.dart
Res
Ré
이력서
?�
Cur
那问题来了。有些字符串处理正常,但是 Résumé 和 ?? 这些 "特殊" 字符串呢?先来看 Résumé,为什么我们的结果字符串里只有两个字符?再看看 ??,这个奇怪的问号又是怎么回事?这里的问题涉及到 Unicode 中的一些不为人知的秘密。Résumé 中的字符 é 其实会占据两个编码位: 一个 e,还有一个 "重音组合符" 。而纸卷图标 ?,确实只占据一个编码位,但这个编码却是用 U d83d 和 U dcc3 代理对 (surrogate pair) 来编码的。是不是被搞迷糊了?
- Unicode 中的一些不为人知的秘密 https://eev.ee/blog/2015/09/12/dark-corners-of-unicode/
- 重音组合符 https://unicode.org/cldr/utility/character.jsp?a=0301
- 纸卷图标 emoji
https://emojipedia.org/emoji/