阅读 language-tour 时做的一些笔记。
Important concepts
- Dart 中一切皆对象,除了 null 之外,所有对象都继承自 Object
- Dart 是强类型语言,但是类型标注是可选的,因为 Dart 中存在类型推断的机制,使用
var
关键字后编译器可以推断出具体的类型 - 当开启了空安全(dart ≧ v2.12)之后,对于可为空的值必须明确指出(用
?
),可为空的值转换成不为空的值时使用!
- 当需要接收任意对象时使用
Object?
- Dart 支持泛型,比如
List<int>
- Dart 支持顶层函数和局部函数,同样也支持顶层变量
- 标识符可以以字母或者
_
开头,Dart 中_
开头的方法表示私有方法 - Dart 中也分为表达式 expressions 和声明 statements,表达式有返回值而声明无返回值
- Dart 工具提供了两种类型的问题:警告和错误。警告表示代码可能无法正确执行,错误分为编译期错误和运行时错误,编译期错误会导致程序无法执行,而运行时错误是代码执行过程中抛出的异常。
Variables
var name = 'Bob';
变量保存了引用,上面的例子中,name 变量包含了一个引用,该引用指向了一个值为 Bob 的 String 对象。
当使用 var
关键字时,变量的类型可以自动被推断出来,当然你也可以显式指出对象的类型。除此之外,如果你不想限定变量的类型,则可以使用 Obejct
或者 dynamic
。
默认值
如果没有开启空安全 (dart ≧ v2.12),则所有未被初始化的变量都会被初始化为 null,哪怕是数字型的变量,因为 Dart 中一切都是对象。但是,如果开启了空安全,则所有不可为空的变量会被要求先初始化才能使用。
Late 变量
Dart 2.12 之后添加了 late
修饰符,主要有两种用途:
- 声明不可为空的顶层变量或者成员变量而不直接初始化值
- 延迟初始化变量(使用到时才初始化)
第一种情况很好理解,因为如果开启了空安全,则顶层变量和成员变量必须要在声明的同时进行初始化,否则编译期无法保证空安全,所以引进 late
之后表明我们不想立马初始化该变量,但是它会在稍后被初始化。
第二种情况则适用于,一个变量对于程序来说是非必须的,或者性能消耗比较大,则可以用 late
修饰,这样,只有在使用到该变量时程序才会初始化它。比如:
// 如果该变量未被使用,则 _readThermometer() 不会被调用
late String temperature = _readThermometer(); // 延迟初始化
final 和 const
final
修饰的变量只能被赋值一次,并且 final
修饰的顶层变量会在第一次被使用时初始化。
final name = 'Bob'; // final 修饰的变量可以没有类型标注
final String nickname = 'Bobby';
name = 'Alice'; // Error: a final variable can only be set once.
const
变量是编译期常量(同时也是 final 的)。如果 const
修饰的是成员变量,则需要用 static const
。
const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere
final
和 const
的不同之处在于,final
修饰的对象不能被修改,但是其属性可以被修改;而 const
修饰的对象和属性都不能被修改,它们是不可变的。
Build-in types
Numbers
int 和 double 都是 num 的子类,num 类型的数据包含了基本的操作符,比如加减乘以及 abs(), ceil(), floor(), 位运算等。
Strings
- Dart 中使用 String 可以用单引号或者双引号。不过,在单引号中的 String 对象中需要对
'
进行转义。 - 我们可以使用
${expression}
在 String 中引用变量或者表达式。 - 我们可以用
==
比较两个 String 的值是否一致。 - 我们可以使用三个单引号或者双引号来创建多行 String。
- 我们可以在 String 值之前加
r
来表示原始类型的 String。
Booleans
Dart 中使用 bool
表示布尔类型的值。
Lists
Dart 中数组是用 List
表示的。可以用如下的方式创建数组:
var list = [1, 2, 3];
var list = [
'Car',
'Boat',
'Plane', // Dart 中允许添加 trailing comma,这样可以防止复制粘贴出错
];
Dart 2.3 之后添加了扩展运算符 (...
) 和空值敏感的扩展运算符 (...?
):
var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);
Dart 中还有提供了集合 if 和集合 for 的操作:
var nav = [
'Home',
'Mall',
'Mine',
if (promoActive) 'Outlet'
];
var listOfInts = [1, 2, 3];
var listOfStrings = [
'#0',
for (var i in listOfInts) '#$i'
];
关于其它常见的 API 见 collections。
Sets
Dart 中同样用 Set
类型表示无序且唯一的集合。
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
Set 和 List 一样,支持扩展运算符、集合 if 和集合 for 的操作。
Maps
Map 是包含了 key 和 value 集合,其中 key 必须唯一。Dar 中的 Map 同样使用花括号 {}
表示,而且优先级比集合 Set 更高:
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
}; // 自动推断出 map 的类型为 Map<String, String>
// 也可以通过这种方式创建
var gifts = Map<String, String>();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
var names = {}; // 这种方式创建的是 map 而不是 set
// 访问 map 的语法 [],其实类似调用方法,具体获取到的值是不确定的
// 报错:Const variables must be initialized with a constant value
const lightRed = Colors.red[200];
Runes and grapheme clusters
Dart 中,使用 Runes 表示字符串的 Unicode 字符集。我们可以使用 characters 包下的类来操作字符。
import 'package:characters/characters.dart';
...
var hi = 'Hi ✡️';
print(hi);
print('The end of the string: ${hi.substring(hi.length - 1)}');
print('The last character: ${hi.characters.last}\n'); // 使用 characters 才能正常打印 emoji
Dart 中的字符串使用 UTF-16 编码,如果要用字符串表示 Unicode 字符则需要使用 \uXXXX
的语法。
print('\u{1f606}'); // 😆
Symbols
Dart 中使用 Symbol 对象表示操作符或者标识符,用 #
+ 修饰符表示。
Functions
Dart 中一切都是对象,函数的类型用 Function 表示,而且函数也可以作为变量或者作为方法的参数进行传递。
void say(String content) {
print(content);
}
// Dart 中只有单个表达式的函数可以使用缩写,用 => 代替 { }
bool isEvenNumber(int num) => num > 0 && num % 2 == 0;
参数
Dart 中的函数除了可以有普通的参数,还可以是具名参数或者可选位置参数。如果使用了 Sound null safety (sdk ≧ 2.12),且没有指明默认值,则这两种类型参数都必须是可为空的,即参数后跟 ?
。
具名参数
具名参数 (Named parameters) 是可选的参数。
// 定义函数
void enableFlags({bool? bold, int? index}) {}
// 也可以给具名函数的参数添加默认值
void enableFlags({bool? bold = true}) {}
// 用 required 标记具名参数中必须有的参数
void enableFlags({required bool? bold}) {}
// 调用函数,无参数
enableFlags()
// 调用时如果有参数名则必须写参数名称
enableFlags(
bold: true,
hidden: false
);
可选位置参数
可选位置参数 (Optional positional parameters) 和普通的参数唯一的不同之处是它是可选的😅。用 []
圈起来的参数即可选位置参数。比如:
// title 是必须的位置参数,而 subtitle 是可选的
void say(String title, [String? subtitle]) {
print(title);
if (subtitle != null) print(subtitle);
}
// 可选位置参数也可以有默认值
void sing(String song, [String? instrument = 'Piano']) { }
main() 函数
每个应用都有一个 main() 函数作为应用的入口。main() 函数通常返回空,并且可以有数组作为参数。
void main(List<String> arguments) {
print(arguments);
}
匿名函数
Dart 中的匿名函数和其它语言中类似,可以有多个参数或者没有参数,后跟方法体,形式如下:
([[Type] param1[, …]]) {
codeBlock;
};
如果只有单个的表达式或者只有返回值,可以使用箭头表达式:
([[Type] param1[, …]]) => expression; // 如果是赋值表达式,则该值就是返回值
Lexical scope
Dart 同样是具有词法作用域或静态作用域的语言,即只要还在代码作用域内的值,都能访问到。
bool topLevel = true;
void main() {
var insideMain = true;
void myFunction() {
var insideFunction = true;
void nestedFunction() {
var insideNestedFunction = true;
assert(topLevel);
assert(insideMain);
assert(insideFunction);
assert(insideNestedFunction);
}
}
}
Lexical closures
词法闭包也叫函数闭包,和 JS 中的闭包概念类似,即定义在函数中的函数,同时使得该函数能够访问其它函数作用域中的变量。
Function makeAdder(int addBy) {
return (int i) => addBy + i;
}
void main() {
var add2 = makeAdder(2);
print(add2(3)); // 这里 add2 访问了 makeAdder 中定义的值,也即把 2 保存了下来
print(makeAdder(10)(3));
}
Operators
Dart 中支持的操作符表,见 Operators。
Cascade notation
// cascade notation,使用级联表达式使得属性赋值变得更简单
var paint = Paint()
..color = 'Black'
..strokeCap = 'Round'
..strokeWidth = 5.0;
print(paint);
Control flow statements
switch and case
// Dart 中的 case 语法有 fall through 机制
// 但是如果定义了 case 内容而没有 break 则会报错
var command = 'CLOSED';
switch (command) {
case 'EARLY_CLOSE': // fall through to 'CLOSED'
case 'CLOSED':
var youCantAccess = 1; // Dart 中的 case 自带作用域
print('bye bye');
continue afterClosed; // 可以使用 label 绕过无 break 的限制
afterClosed:
case 'CLOSED_AFTER':
// print(youCantAccess); // undefined
print('see you tomorrow');
break;
}
Exceptions
Dart 中所有异常都是 unchecked exceptions,意味着 Dart 不会强制你去捕捉异常。
Catch
Dart 中的 try catch 语法:
try {
breedMoreLlamas();
} on OutOfLlamasException {
// 特定类型的异常
buyMoreLlamas();
} on Exception catch (e, s) { // 第二个参数为错误栈
// 其它类型的异常
print('Unknown exception: $e');
} catch (e) {
// 所有其它未指定的错误
print('Something really unknown: $e');
}
Classes
Dart 中一切皆对象,除了 null 之外,所有对象都是某个类的实例。除了可以继承类之外,Dart 还提供了一种基于 mixin 的继承,也就是说可以通过 with 关键字继承某个没有构造器的类来扩展功能,还可以使用扩展函数。
Using class members
所有对象都由成员变量和方法构成,使用 .
的语法访问对象的属性或者方法,使用 ?.
访问可为空对象的属性和方法。
Using constructors
Dart 中创建构造器除了可以使用类名,也可以使用 类名.构造器名()
的形式。
class DummyClass {
late int i;
// 默认构造函数
DummyClass(this.i);
// 我们可以为构造函数定义名称,这种构造器被称为「具名构造器」
DummyClass.create(int i, String message) {
print(message);
this.i = i;
}
// 在「具名构造器」后为成员变量赋值
DummyClass.ten() : i = 10 { // :后的部分称为 initializer list
print('Ten ${this.i}'); // print: Ten 10
}
}
Getting an object’s type
我们可以使用 runtimeType
获得对象的类型 Type。
var dummy = DummyClass.create(1);
print('The type of a is ${dummy.runtimeType}');
Instance variables
- 所有未初始化可为空的实例变量都会被初始化为 null。
- 所有实例变量都会默认自带 getter 函数,非 final 的实例变量和不带初始化器的 late final 实例变量会自动生成 setter 函数。
- 非延迟初始化的实例变量的值会在对象创建之后,构造器和初始化器列表执行之前,就被赋值。可为空的赋值为 null,不可为空的赋值为初始值。
- 实例变量可以是 final 的,但是必须在构造器或者初始化列表中进行赋值。
Constructors
Dart 中类的构造器和其它语言类似:
class Point {
double x = 0;
double y = 0;
Point(double x, double y) {
this.x = x;
this.y = y;
}
}
不过,Dart 提供了一种语法糖的写法:
class Point {
double x = 0;
double y = 0;
Point(this.x, this.y);
}
具名构造器
我们可以通过具名构造器 (Named constructors) 为一个类实现多个构造器。不过具名构造器不能被继承,如果想在子类中使用和父类相同的构造器,只能为子类单独实现。
class Weather {
var humidity = '';
Weather.display(this.humidity);
}
class Shower extends Weather{
Shower.display(String humidity) : super.display(humidity);
}
构造器初始化列表
除了调用父类构造器之外,我们还可以在构造器方法体执行之前初始化成员变量,称为 Initializer list。
Point.fromJson(Map<String, double> json)
: x = json['x']!,
y = json['y']! {
print('In Point.fromJson(): ($x, $y)');
}
构造器初始化顺序
默认情况下,子类会先调用父类中的默认构造函数(未定义的话会生成一个无参构造器),如果存在构造器初始化列表 (Initializer list) 则会先调用它们。所以,当使用默认构造函数创建对象时,构造器的初始化顺序为:
- 构造器初始化列表
- 父类无参构造器
- 子类无参构造器
另外,如果父类未定义默认构造器,则子类实现构造器时必须先调用父类的某个具名构造器。
重定向构造器
class Point {
double x, y;
Point(this.x, this.y);
// 使用 this 关键字重定向到默认构造器
Point.alongXAxis(double x) : this(x, 0);
}
常量构造器
当你想要创建的对象是编译期常量时,可以使用 const
修饰构造器(比如用于创建注解):
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y; // 记得所有的变量都要是 final 的
const ImmutablePoint(this.x, this.y);
}
使用常量构造器 (Constant Constructors) 初始化对象时,使用相同的值的对象只会在第一次创建时被初始化,也就是说相同值的对象只会初始化一次。
工厂构造器
使用工厂模式创建对象。
class Logger {
final String name;
bool mute = false;
static final Map<String, Logger> _cache = <String, Logger>{};
factory Logger(String name) {
return _cache.putIfAbsent(name, () => Logger._internal(name));
}
factory Logger.fromJson(Map<String, Object> json) {
return Logger(json['name'].toString());
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
Methods
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;
}
Abstract classes
Dart 中的抽象类同样无法被直接实例化,如果需要实例化可以通过工厂构造器。
abstract class AbstractContainer {
// Define constructors, fields, methods...
void updateChildren(); // Abstract method.
}
Implicit interfaces
Dart 中同样使用 implement
关键字实现一个或多个接口。
class Point implements Comparable, Location {...}
Extending a class
使用 extends
继承父类,使用 super
引用父类。
Extension methods
和 Kotlin 类似,在 Dart 中我们同样可以通过扩展函数扩充函数库。
Enumerated types
Dart 中的枚举类和 Java 中类似。
enum Color { red, green, blue }
枚举类有以下限制:
- 无法继承、实现或者混用 (mixin) 枚举类
- 无法直接实例化
Adding features to a class: mixins
Dart 中还提供了另外一种复用代码的方式 Mixins。我们可以把可复用的代码放到 mixin 类中。子类使用 with
关键字进行关联。
mixin 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');
}
}
}
class Musician with Musical {
// ···
}
class Maestro extends Person with Musical {
Maestro(String maestroName) : super(maestroName) {
canPlayPiano = true;
}
}
除此之外,我们还可以限制 mixin 的使用范围 (on
),以及像接口一样使用逗号 (,
) 分割继承多个 mixin:
class Musician {
// ...
}
mixin MusicalPerformer on Musician {
// ...
}
class SingerDancer extends Musician with MusicalPerformer, DancerPerformer {
// ...
}
Class variables and methods
Dart 中的类同样可以使用静态变量和常量,以及静态方法。
class DummyClass {
static const a = 1;
static var b = 2;
static void f() { }
}
Generics
Dart 中的泛型(参数化类型)和 Java 以及 Kotlin 等语言非常相似,使用 <>
表示泛型。
为什么使用泛型
使用泛型一般有两个目的:
- 指明泛型类型之后,减少代码出错。比如在集合中加入不该加入的值。
- 使用泛型减少复制粘贴的代码。比如持有某个对象的类,当需要改变持有的对象而其它部分不发生变化时,如果使用了泛型就不用重新复制一个新的类。
Using collection literals
即在使用集合时定义集合类型,形式如 <type>[]
or <type>{}
or <keyType, valueType>{}
。
var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
泛型集合及其类型
Java 中,由于泛型存在类型擦除,所以是无法确定集合的确切类型的。但是,Dart 中的泛型是 reified 的,也就是说在运行时也能得到集合的类型信息。
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
泛型参数类型的限制
Dart 中限制泛型参数边界同样使用 extends
关键字。
class Foo<T extends SomeBaseClass> {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
Libraries and visibility
Dart 中,我们可以通过 import
和 library
指示符创建模块化的、可共享的代码库。另外,即使不使用 library
指示符,每个 dart app 都是一个 library。
导入一个 library 的语法:
import 'dart:html'; // html 是 dart 内建的库,所以使用 dart 作为命名空间
其它库可以使用文件路径或者 package:
命名空间:
import 'package:test/test.dart'; // 导入一些三方库,比如通过 pub 包管理工具发布的三方库
指定库的前缀
如果两个库有相同的名称,可以通过 as
指定前缀解决冲突。
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();
部分导入
如果只使用到库的一部分,可以使用部分导入的语法,通过 show
和 hide
做到。
// 只导入 foo
import 'package:lib1/lib1.dart' show foo;
// 导入除了 foo 意外的部分
import 'package:lib2/lib2.dart' hide foo;
实现 libraries
Asynchrony support
Dart 中有很多返回 Future 和 Stream 对象的函数,我们可以使用它们编写出异步的代码。除此之外,Dart 还提供了 async
和 await
关键字,让你可以像 JS 一样,更方便地写出一些更具易读性的异步代码。
常见的用法如下:
Future<String> lookUpVersion() async => '1.0.0';
// 在方法末尾添加 async 关键字,说明方法执行耗时操作,返回值通常是 Future
Future checkVersion() async {
var version = '';
try {
version = await lookUpVersion();
} catch (e) {
// React to inability to look up the version
}
}
如果 async 方法无有意义的返回值,可以返回 Future<void>
。
Generators
Dart 中的 Generator 函数和 ES6 中相似,如果你想要延迟生成一系列值,可以考虑使用生成器。Dart 中的 Generator 函数分为两种:
// 同步生成器
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
// 异步生成器
Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}
// 递归的生成器,使用 yield* 提高性能
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
Callable classes
我们可以通过给某个类实现 call()
方法,使得该类可以像方法一样被调用。
class WannabeFunction {
String call(String a, String b, String c) {
print('WannabeFunction.call()');
return '$a $b $c!';
}
}
var wf = WannabeFunction(); // 初始化该类对象,然后可以像方法一样调用它们
wf('Hi', 'there,', 'gang') // 实际调用的是 call() 方法
Isolates
与其它语言的并发机制不同,Dart 中并没有采用共享状态的并发机制 (shared-state concurrency),而是使用了 isolates。所有的代码都运行在自己的 isolate
中,每个 isolate
都有自己的内存堆,保证了相互独立性。
更多资料见:Isolates。
Typedefs
Dart 中,一切皆是对象,函数也是对象,但是函数对象的信息在运行时往往会被丢失,比如下面这个例子:
class SortedCollection {
Function compare;
SortedCollection(int f(Object a, Object b)) : compare = f;
}
int sort(Object a, Object b) => 0;
SortedCollection sc = SortedCollection(sort);
// 只知道 compare 是 Function,但是其具体类型被丢失了
assert(sc.compare is Function);
typedef
就是为了解决上面这个问题而出现的,我们可以给方法类型一个别名,这样,当方法被赋值到一个变量上时就能保留其类型信息:
// 函数的信息被保存在 Compare 中
typedef Compare = int Function(Object a, Object b);
class SortedCollection {
Compare compare; // 用于接收我们定义的函数
SortedCollection(this.compare);
}
int sort(Object a, Object b) => 0;
SortedCollection sc = SortedCollection(sort);
assert(sc.compare is Compare); // 现在可以确定函数的具体类型了
typedef
也可以使用泛型:
typedef Compare<T> = int Function(T a, T b);
int sort(int a, int b) => a - b;
assert(sort is Compare<int>); // True!
Metadata
通过元信息注解,我们可以为类和方法提供额外的信息。Dart 中自带的注解有 @deprecated
和 @override
,除此之外,你也可以自定义注解,只要在类的构造器上使用 const
关键字就可以了:
class Todo {
final String who;
final String what;
const Todo(this.who, this.what);
}
Comments
Dart 支持三种类型的注释:单行注释、多行注释和文档注释。
/// 文档注释,使用 [] 给成员属性或者方法添加链接,比如查看 [main] 方法
/// 虽然 Dart 中也能用 /** */ 作为文档注释,但是编辑器会提醒你用 /// 代替
void main() {
// 单行注释
print('Howdy!');
/*
* 多行注释
*/
print('Hola!');
}