简介
Dart是编写Flutter的官方语言,主要用于编写跨平台的UI页面,但不止于此,Dart还可以写命令行和服务端Server代码。以下是常用的一些地址
Dart中的你需要谨记的概念
- Dart中万物皆对象,无论是numbers、functions还是null,都是对象。除去null外所有类的基类都是Object。
- Dart可以自动推断变量类型
- 要使用任意类型,编译时可以使用
Object或者Object?,运行时可以使用dynamic- Dart支持泛型类型,支持内嵌函数,支持高级函数
- Dart中
_下划线开头的变量是私有的,其余的是公开访问的
Dart简介
- 顶层的main函数是程序入口,函数的默认返回值是void
void main() {
print("Oh Ha You");
}
- var声明可变变量,final声明不可变变量,const声明编译时常量
var age = 23;
final name = "unravel";
const school = false;
import导入语句
// 导入dart中的库,使用 dart:库名
import 'dart:math';
// 导入三方库,使用 package:库名/dart文件名
import 'package:test/test.dart';
// 导入本地库,使用相对路径或绝对路径
import 'path/to/my_other_file.dart';
class声明类
class Spacecraft {
// 非可选属性
String name;
// 可选属性
DateTime? launchDate;
// 只读可选属性
int? get launchYear => launchDate?.year;
// this语法糖的构造函数
Spacecraft(this.name, this.launchDate) {
// Initialization code goes here.
}
// 重定向构造函数
Spacecraft.unlaunched(String name) : this(name, null);
// 方法
void describe() {
print('Spacecraft: $name');
// Type promotion doesn't work on getters.
var launchDate = this.launchDate;
if (launchDate != null) {
int years = DateTime.now().difference(launchDate).inDays ~/ 365;
print('Launched: $launchYear ($years years ago)');
} else {
print('Unlaunched');
}
}
}
enum枚举
enum PlanetType { terrestrial, gas, ice }
enum Planet {
mercury(planetType: PlanetType.terrestrial, moons: 0, hasRings: false),
venus(planetType: PlanetType.terrestrial, moons: 0, hasRings: false),
// ···
uranus(planetType: PlanetType.ice, moons: 27, hasRings: true),
neptune(planetType: PlanetType.ice, moons: 14, hasRings: true);
// 增强型枚举的所有属性必须是final的,构造方法必须是const的。才能保证是一个编译时常量
const Planet(
{required this.planetType, required this.moons, required this.hasRings});
// 所有属性必须是final的
final PlanetType planetType;
final int moons;
final bool hasRings;
// 增强型的枚举可以定义方法
bool get isGiant =>
planetType == PlanetType.gas || planetType == PlanetType.ice;
}
// 使用示例
final yourPlanet = Planet.earth;
if (!yourPlanet.isGiant) {
print('Your planet is not a "giant planet".');
}
extends类继承
class Orbiter extends Spacecraft {
double altitude;
Orbiter(super.name, DateTime super.launchDate, this.altitude);
}
mixin混入
- 类似Swift中协议的默认实现,主要用于代码共享
- 使用
mixin进行定义,定义时基本和class一样 - 其他类使用
with将它混入到类中
mixin Piloted {
int astronauts = 1;
void describeCrew() {
print('Number of astronauts: $astronauts');
}
}
class PilotedCraft extends Spacecraft with Piloted {
// ···
}
- 所有类都隐式成为一个接口
class MockSpaceship implements Spacecraft {
// ···
}
abstract class Describable {
void describe();
void describeWithEmphasis() {
print('=========');
describe();
print('=========');
}
}
- 异步
- 使用
async定义一个异步函数,函数返回值类型必须是一个Future - 使用
await调用异步函数,await必须在异步环境下调用async函数
// 使用Future泛型将async函数的返回值进行封装
Future<void> createDescriptions(Iterable<String> objects) async {
// for ... in 遍历对象
for (final object in objects) {
try {
var file = File('$object.txt');
if (await file.exists()) {
var modified = await file.lastModified();
print(
'File for $object already exists. It was modified on $modified.');
continue;
}
// await调用函数
await file.create();
await file.writeAsString('Start describing $object in this file.');
} on IOException catch (e) {
print('Cannot create description for $object: $e');
}
}
}
async* + yield定义异步迭代器
- 迭代器可以不断的迭代来产生值
- 迭代器通过
next()方法进行迭代
Stream<String> report(Spacecraft craft, Iterable<String> objects) async* {
for (final object in objects) {
await Future.delayed(oneSecond);
yield '${craft.name} flies by $object';
}
}
Dart速查表
- 字符串插值
使用 ${expression}将表达式的值放在字符串中,若表达式为单个标识符,可以省略 {}
String a = '${3 + 2}';
// 如果是一个对象,会调用对象的toString方法
String myObj = '$myObject';
- 可选类型
类型默认不为空,在类型后加?号表示可选类型,默认值为null
int? a = null;
// 可选类型默认为null,上面的代码和下面的代码一样
int? a;
- 避空运算符
??=赋值运算符,仅当该变量为空值时才为其赋值??如果该运算符左边的表达式返回的是空值,则会计算并返回右边的表达式
// 避空赋值
int? a; // = null
// 赋值前a为null,可以赋值
a ??= 3; // a = 3;
// 赋值前a不为null,不会重新赋值,保持之前的值
a ??= 5; // a = 3
// 避空取值
// 1 不为 null,输出 左边表达式的值1
print(1 ?? 3); // <-- Prints 1.
// null为null,输出右边表达式的值
print(null ?? 12); // <-- Prints 12.
- 可选链式访问
在点(.)之前加一个问号(?),可以在一个表达式中连续使用多个 ?.
// 使用?.调用之后,会把返回类型包装成一个可选类型。即使它之前不是可选的
myObject?.someProperty?.someMethod()
- 集合字面量初始化和指定类型初始化
// List<String>
final aListOfStrings = ['one', 'two', 'three'];
// 指定类型
final aListOfStrings2 = <String>[];
// Set<String>
final aSetOfStrings = {'one', 'two', 'three'};
// 指定类型
final aSetOfStrings2 = <String>{};
// Map<String, int>
final aMapOfStringsToInts = {
'one': 1,
'two': 2,
'three': 3,
};
// 指定类型
final aMapOfStringsToInts2 = <String, int>{};
- 箭头函数
bool hasEmpty = aListOfStrings.any((s) => s.isEmpty);
- 级联操作符
..
- 使用
..操作符对同一对象进行一系列操作 ..操作符会修改方法的返回值为对象本身。即 return this;
querySelector('#confirm')
?..text = 'Confirm'
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'))
..scrollIntoView();
- 属性读取器
- 自定义 getter 和 setter 对属性进行更多控制
- 如果只写 getter 表示是一个只读属性
class MyClass {
int _aProperty = 0;
int get aProperty => _aProperty;
set aProperty(int value) {
if (value >= 0) {
_aProperty = value;
}
}
}
// 使用的时候
final myCls = MyClass();
myCls.aProperty;
myCls.aPropgery = 23;
- 可选位置参数与可选命名参数
- Dart 有两种传参方法:位置参数和命名参数
- 一个方法不能同时使用可选位置参数和可选命名参数
- 将参数包裹在方括号中,就变成了可选位置参数
- 可选位置参数永远放在方法参数列表的最后,它的类型要么是可选类型,要么有默认值
int sumUpToFive(int a, [int? b, int? c, int? d, int? e]) {
int sum = a;
if (b != null) sum += b;
if (c != null) sum += c;
if (d != null) sum += d;
if (e != null) sum += e;
return sum;
}
// ···
int total = sumUpToFive(1, 2);
int otherTotal = sumUpToFive(1, 2, 3, 4, 5);
- 在参数列表的靠后位置使用花括号 (
{}) 来定义命名参数 - 参数使用
required标记,它不能有默认值,类型可以是可选类型也可以是非可选类型 - 参数未使用
required标记,它的类型要么是可选类型,要么有默认值
void printName(String firstName, String lastName, {String? middleName}) {
print('$firstName ${middleName ?? ''} $lastName');
}
printName('Dash', 'Dartisan');
printName('John', 'Smith', middleName: 'Who');
// 命名参数调用时,可以在任意位置。不一定和声明时的位置相同
printName('John', middleName: 'Who', 'Smith');
- 使用
try、on、catch、rethrow、finally关键字来处理异常
try {
breedMoreLlamas();
} on OutOfLlamasException {
// 捕获特定类型的异常
buyMoreLlamas();
} on Exception catch (e) {
// 捕获特定类型异常到e变量
print('Unknown exception: $e');
} catch (e) {
// 捕获其他异常到e
print('Something really unknown: $e');
// 如果无法完全处理这个异常,使用rethrow再次抛出
rethrow;
} finally {
// 最终必走的代码
}
- 快捷构造方法
在声明构造方法时使用 this.propertyName
class MyColor {
int red;
int green;
int blue;
MyColor(this.red, this.green, this.blue);
// 相当于 下面代码
// MyColor(int red, int green, int blue) {
// this.red = red;
// this.green = green;
// this.blue = blue;
// }
// 也适用于 命名参数
MyColor2({required this.red, required this.green, required this.blue});
}
final color = MyColor(80, 80, 128);
final color2 = MyColors(red: 80, green: 80, blue: 80);
- 初始化列表
初始化列表位于构造函数的签名与其函数体之间,在构造函数体执行之前
Point.fromJson(Map<String, double> json)
: x = json['x']!,
y = json['y']! {
print('In Point.fromJson(): ($x, $y)');
}
NonNegativePoint(this.x, this.y)
: assert(x >= 0),
assert(y >= 0) {
print('I just made a NonNegativePoint: ($x, $y)');
}
- 命名构造方法
class Point {
double x, y;
Point(this.x, this.y);
Point.origin()
: x = 0,
y = 0;
}
final myPoint = Point.origin();
- 工厂构造方法
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();
throw ArgumentError('Unrecognized $typeName');
}
}
- 重定向构造方法
即一个构造方法调用另一个构造方法,重定向方法没有主体,它在冒号(:)之后调用另一个构造方法
class Automobile {
String make;
String model;
int mpg;
// 主要构造方法,相当于Swift中的定制构造方法
Automobile(this.make, this.model, this.mpg);
// 重定向到主要构造方法。相当于Swift中的便利构造器
Automobile.hybrid(String make, String model): this(make, model, 60);
// 重定向到另一个重定向构造方法。相当于Swift中的便利构造器调用便利构造器
Automobile.fancyHybrid() : this.hybrid('Futurecar', 'Mark 2');
}
- const 构造方法
- 拥有
const构造方法的类的所有属性都是 final 的 - const构造出来的类是一个编译时常量,常用于做注解
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final int x;
final int y;
const ImmutablePoint(this.x, this.y);
}
详细知识点
var、Object、String
// 使用var,让编译器自己推断
var name = 'Bob';
// 使用基类Object
Object name = 'Bob';
// 使用明确的子类String
String name = 'Bob';
可选类型、非可选类型
// 可选类型,默认值为null
String? name;
// 非可选类型,使用前必须赋值
String name;
懒加载属性
// 懒加载属性,首次使用时才初始化
late String desc;
final、const
// final常量,赋值后不可重新赋值
final name = 'Bob';
// const 编译时常量
const Object i = 3; // Where i is a const Object with an int value...
// 类型转换
const list = [i as int];
// is 判断类型 collection if 语法
const map = {if (i is int) i: 'int'};
// 展开list
const set = {if (list is List<int>) ...list};
操作符
浮点除法和整除除法
// 浮点计算的除法
assert(5 / 2 == 2.5); // Result is a double
// 整型计算的除法
assert(5 ~/ 2 == 2); // Result is an int
as、is、is!
// 类型转换
(employee as Person).firstName = 'Bob';
// 类型判断
if (employee is Person) {
// Type check
employee.firstName = 'Bob';
}
is! 是 is的对立值
??空赋值 和 级联操作
// 空取值运算符,只有左边是 null,才取右边的值
String playerName(String? name) => name ?? 'Guest';
// 级联运算符,会修改方法返回值返回对象本身
var paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
导入
导入核心库和三方库
// 导入 dart 库
import 'dart:html';
// 导入三方包
import 'package:test/test.dart';
给导入的库命名
// 给导入的库命名
import 'package:lib2/lib2.dart' as lib2;
导入库的一部分
// 导入库的一部分
// 只导入 foo
import 'package:lib1/lib1.dart' show foo;
// 导入 foo 外的所有
import 'package:lib2/lib2.dart' hide foo;
懒加载导入的库
// 使用 deferred 表示懒加载导入
import 'package:greetings/hello.dart' deferred as hello;
// 使用的时候,在异步函数里调用loadLibrary加载
Future<void> greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
library 声明一个库
/// A really great test library.
@TestOn('browser')
library;
基础类型
// 数字类型 int 和 double 都是 num的子类型
int a = 1;
double a = 2.3;
num a = 3;
// 字符串类型,使用单引号或双引号
String s1 = 'string';
String s1 = "string";
// 多行字符创
String s1 = '''
line 1
line 2
''';
// 布尔值
bool a = true;
bool b = false;
Records 记录类型(元组类型)
记录类型由它的成员数量、成员类型以及成员字段决定
记录类型使用字面量初始化
// 括号里面直接写名字值
var record = ('first', a: 2, b: true, 'last');
记录类型作为参数和返回值
(int, int) swap((int, int) record) {
var (a, b) = record;
return (b, a);
}
// 通过记录类型返回多个参数
(String, int) userInfo(Map<String, dynamic> json) {
return (json['name'] as String, json['age'] as int);
}
不带字段的记录类型
// 声明位置类型
(String, int) record;
// 初始化
record = ('A string', 123);
带字段的记录类型
// 声明命名类型
({int a, bool b}) record;
record = (a: 123, b: true);
记录类型的取值
// 记录类型通过名字或者位置取值。位置从左到右,从1递增,遇到命名字段略过
var record = ('first', a: 2, b: true, 'last');
print(record.$1); // Prints 'first'
print(record.a); // Prints 2
print(record.b); // Prints true
print(record.$2); // Prints 'last'
集合类型List、Set、Map
// 数组类型,使用[]把成员括起来
var list = [1, 2, 3];
// 集合类型,使用{}把成员括起来
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
// Map类型,使用{}把键值对括起来
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
展开操作符 ... 和 ...?
// ... 展开操作符 把list里的成员都放入到list2中
var list = [1, 2, 3];
var list2 = [0, ...list];
// ...? 在...基础上判断了list是否为null,如果为null,啥都不做
var list2 = [0, ...?list];
assert(list2.length == 1);
集合for 和 集合if 操作符
// 如果 promoActive 为true 才添加 'Outlet'
var nav = ['Home', 'Furniture', 'Plants', if (promoActive) 'Outlet'];
// 如果 login是 Manager 才添加 Inventory
var nav = ['Home', 'Furniture', 'Plants', if (login case 'Manager') 'Inventory'];
// 遍历 listOfInts 的成员,拼成 '#$i'插入到listOfStrings中
var listOfInts = [1, 2, 3];
var listOfStrings = ['#0', for (var i in listOfInts) '#$i'];
assert(listOfStrings[1] == '#1');
泛型
泛型集合字面量
var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
明确标注泛型类型
var nameSet = Set<String>.from(names);
var views = Map<int, View>();
使用 extends 限制泛型类型
class Foo<T extends SomeBaseClass> {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}
typedef 定义类型别名
// 定义整型数组
typedef IntList = List<int>;
IntList il = [1, 2, 3];
// 定义复杂的类型
typedef ListMapper<X> = Map<X, List<X>>;
Map<String, List<String>> m1 = {}; // Verbose.
ListMapper<String> m2 = {}; // Same thing but shorter and clearer.
// 定义函数类型
typedef Compare<T> = int Function(T a, T b);
int sort(int a, int b) => a - b;
void main() {
assert(sort is Compare<int>); // True!
}
模式
匹配模式
// 匹配
const a = 'a';
const b = 'b';
const obj = ['a', 'b'];
switch (obj) {
case [a, b]:
print('$a, $b');
}
// 针对单个元素,逻辑或 匹配
switch (list) {
case ['a' || 'b', var c]:
print(c);
}
解构模式
// 解构
var numList = [1, 2, 3];
var [a, b, c] = numList;
print(a + b + c);
匹配、解构模式可以出现的地方
变量声明时
var (a, [b, c]) = ('str', [1, 2]);
变量赋值时
var (a, b) = ('left', 'right');
(b, a) = (a, b); // Swap.
print('$a $b'); // Prints "right left".
Switch 语句中
switch (obj) {
// obj 是 1 时匹配
case 1:
print('one');
// obj 位于 fist和last之间时匹配
case >= first && <= last:
print('in range');
// obj 是 有两个成员的记录类型时匹配
case (var a, var b):
print('a = $a, b = $b');
default:
}
Switch 表达式中 逻辑或匹配
var isPrimary = switch (color) {
Color.red || Color.yellow || Color.blue => true,
_ => false
};
Switch 表达式中 逻辑或匹配 且 需要满足类型守卫条件(s>0)
switch (shape) {
case Square(size: var s) || Circle(size: var s) when s > 0:
print('Non-empty symmetric shape');
}
for in 中 解构匹配MapEntry
Map<String, int> hist = {
'a': 23,
'b': 100,
};
// for in 中 解构匹配MapEntry
for (var MapEntry(key: key, value: count) in hist.entries) {
print('$key occurred $count times');
}
// 如果匹配的字段相同,可以去掉前面相同的key
for (var MapEntry(:key, value: count) in hist.entries) {
print('$key occurred $count times');
}
解构 多个返回值
var (name, age) = userInfo(json);
解构对象
final Foo myFoo = Foo(one: 'one', two: 2);
var Foo(:one, :two) = myFoo;
print('one $one, two $two');
模式匹配类型
逻辑或条件 满足一个即可
var isPrimary = switch (color) {
Color.red || Color.yellow || Color.blue => true,
_ => false
};
逻辑与条件,都满足才可以。多个条件不能包含相同的解构变量
switch ((1, 2)) {
// Error, both subpatterns attempt to bind 'b'.
case (var a, var b) && (var b, var c): // ...
}
比较关系 匹配
String asciiCharType(int char) {
const space = 32;
const zero = 48;
const nine = 57;
return switch (char) {
< space => 'control',
== space => 'space',
> space && < zero => 'punctuation',
>= zero && <= nine => 'digit',
_ => ''
};
}
类型转换匹配
// 类型转换匹配
(num, Object) record = (1, 's');
var (i as int, s as String) = record;
可选类型匹配
String? maybeString = 'nullable with base type String';
switch (maybeString) {
case var s?:
// 's' has type non-nullable String here.
}
强制解包匹配
// 强制解包匹配
List<String?> row = ['user', null];
switch (row) {
case ['user', var name!]: // ...
// 'name' is a non-nullable string here.
}
常量匹配
switch (number) {
// Matches if 1 == number.
case 1: // ...
}
变量匹配
switch ((1, 2)) {
// Does not match.
case (int a, String b): // ...
}
标识符匹配
const c = 1;
switch (2) {
case c:
print('match $c');
default:
print('no match'); // Prints "no match".
}
List匹配
const a = 'a';
const b = 'b';
const obj = ['a', 'b'];
switch (obj) {
// List pattern [a, b] matches obj first if obj is a list with two fields,
// then if its fields match the constant subpatterns 'a' and 'b'.
case [a, b]:
print('$a, $b');
}
剩余元素匹配
var [a, b, ..., c, d] = [1, 2, 3, 4, 5, 6, 7];
// Prints "1 2 6 7".
print('$a $b $c $d');
Map、Record匹配
// Map 匹配需要key值匹配上
{"key": subpattern1, someConst: subpattern2}
// Record pattern with variable subpatterns:
var (untyped: untyped, typed: int typed) = record;
var (:untyped, :int typed) = record;
switch (record) {
case (untyped: var untyped, typed: int typed): // ...
case (:var untyped, :int typed): // ...
}
// Record pattern wih null-check and null-assert subpatterns:
switch (record) {
case (checked: var checked?, asserted: var asserted!): // ...
case (:var checked?, :var asserted!): // ...
}
// Record pattern wih cast subpattern:
var (untyped: untyped as int, typed: typed as String) = record;
var (:untyped as int, :typed as String) = record;
类轮廓匹配
switch (shape) {
// Matches if shape is of type Rect, and then against the properties of Rect.
case Rect(width: var w, height: var h): // ...
}
// Binds new variables x and y to the values of Point's x and y properties.
var Point(:x, :y) = Point(1, 2);
通配符 _ 匹配
var list = [1, 2, 3];
var [_, two, _] = list;
switch (record) {
case (int _, String _):
print('First field is int and second is String.');
}
函数
Dart中函数也是对象,函数的基类类型为Function
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
单行表达式的函数,可以修改为箭头函数
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
命名参数
命名参数是在大括号内声明的一些参数
命名参数默认是可选类型,除非使用required修饰
// 没有写required 使用了可选类型的参数
void enableFlags({bool? bold, bool? hidden}) {...}
enableFlags(bold: true, hidden: false);
// 命名参数的默认值
void enableFlags({bool bold = false, bool hidden = false}) {...}
// 有默认值的参数,就可以不用传
enableFlags(bold: true);
命名参数传递时可以在任意位置,不一定和定义时的位置一致
repeat(times: 2, () {
...
});
可选位置参数
可选位置参数是在中括号内声明的一些参数
String say(String from, String msg, [String? device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
可选位置参数也可以指定默认值
String say(String from, String msg, [String device = 'carrier pigeon']) {
var result = '$from says $msg with a $device';
return result;
}
assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');
函数是一等公民,可以使用普通变量的地方也可以使用函数
函数作为另一个函数的参数
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
// Pass printElement as a parameter.
list.forEach(printElement);
函数赋值给一个变量
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');
匿名函数
([[Type] param1[, …]]) {
codeBlock;
};
const list = ['apples', 'bananas', 'oranges'];
list.map((item) {
return item.toUpperCase();
}).forEach((item) {
print('$item: ${item.length}');
});
函数可以嵌套定义
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);
}
}
}
闭包是可以捕获外部变量的函数
// 返回一个函数,这个函数捕获了外部变量addBy
Function makeAdder(int addBy) {
return (int i) => addBy + i;
}
void main() {
// Create a function that adds 2.
var add2 = makeAdder(2);
// Create a function that adds 4.
var add4 = makeAdder(4);
assert(add2(3) == 5);
assert(add4(3) == 7);
}
返回值,没有指定返回值类型时返回null,将多个返回值放到Record中返回
foo() {}
assert(foo() == null);
(String, int) foo() {
return ('something', 42);
}
生成器
- 生成器分为同步生成器和异步生成器,函数内部使用yield生成值
- 生成器使用时必须调用
next()方法 - 如果生成器是递归语法写的,使用yield* 可以提高效率
- 同步生成器的返回值是Iterable,函数体要使用sync*修饰
- 异步生成器的返回值是Stream,函数体要使用async*修饰
// 同步生成器
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++;
}
// 递归语法的生成器
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
循环语法
普通的for 和 for in(for in实际上遍历的迭代器)
var callbacks = [];
for (var i = 0; i < 2; i++) {
callbacks.add(() => print(i));
}
for (final c in callbacks) {
c();
}
for in 中可以使用模式匹配
for (final Candidate(:name, :yearsExperience) in candidates) {
print('$name has $yearsExperience of experience.');
}
while、do while以及流程中断的continue、break
while (!isDone()) {
doSomething();
}
do {
printLine();
} while (!atEndOfPage());
while (true) {
if (shutDownRequested()) break;
processIncomingRequests();
}
for (int i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
if (candidate.yearsExperience < 5) {
continue;
}
candidate.interview();
}
分支语句
if、else
if (isRaining()) {
you.bringRainCoat();
} else if (isSnowing()) {
you.wearJacket();
} else {
car.putTopDown();
}
if case 语句中使用模式匹配
// 如果 pair 匹配上了 [int x, int y] 就创建一个Point
if (pair case [int x, int y]) return Point(x, y);
switch 语句
- switch会检查是否穷举了所有的case
- 空的case会往下掉,非空的case不会往下掉。非空的case可以通过continue+label语法掉到其他某个case
switch (command) {
case 'OPEN':
executeOpen();
// 掉到 newCase 这个case继续执行
continue newCase;
// 这个空 case 'DENIED'直接往下掉
case 'DENIED':
case 'CLOSED':
executeClosed(); // Runs for both DENIED and CLOSED,
newCase:
case 'PENDING':
executeNowClosed(); // Runs for both OPEN and PENDING.
}
switch表达式,有返回值
// 下面是一个switch语句
// Where slash, star, comma, semicolon, etc., are constant variables...
switch (charCode) {
case slash || star || plus || minus: // Logical-or pattern
token = operator(charCode);
case comma || semicolon: // Logical-or pattern
token = punctuation(charCode);
case >= digit0 && <= digit9: // Relational and logical-and patterns
token = number();
default:
throw FormatException('Invalid');
}
// 可以修改成这个switch 表达式
token = switch (charCode) {
slash || star || plus || minus => operator(charCode),
comma || semicolon => punctuation(charCode),
>= digit0 && <= digit9 => number(),
_ => throw FormatException('Invalid')
};
在if case、switch表达式、switch语句中使用条件守卫
条件守卫紧跟在case关键词之后并且使用when 限制条件
// Switch statement:
switch (something) {
case somePattern when some || boolean || expression:
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Guard clause.
body;
}
// Switch expression:
var value = switch (something) {
somePattern when some || boolean || expression => body,
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Guard clause.
}
// If-case statement:
if (something case somePattern when some || boolean || expression) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Guard clause.
body;
}
错误处理
使用throw、try、catch、on、rethrow、finally来抛出、捕获或重新抛出来处理错误
void breedMoreLlamas() {
throw FormatException('Expected at least 1 section');
}
try {
breedMoreLlamas();
} on OutOfLlamasException {
// A specific exception
buyMoreLlamas();
} on Exception catch (e) {
// Anything else that is an exception
print('Unknown exception: $e');
} catch (e) {
// No specified type, handles all
print('Something really unknown: $e');
rethrow;
} finally {
// Always clean up, even if an exception is thrown.
cleanLlamaStalls();
}
类
使用 . 或 ?. 访问成员变量、方法
var p = Point(2, 2);
// Get the value of y.
assert(p.y == 2);
// Invoke distanceTo() on p.
double distance = p.distanceTo(Point(4, 4));
var a = p?.y;
const构建编译时常量类、const会建立常量上下文
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // They are the same instance!
// 只需要一个const建立常量上下文就可以。里面的构建类不需要再写const
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
通过 runtimeType 可以在运行时获取对象的类型
print('The type of a is ${a.runtimeType}');
类成员属性的声明
class Point {
// 可选类型,默认为null
double? x;
// 非可选类型,设置默认值
double z = 0;
// 常量属性
final String name;
// 常量属性的默认值
final DateTime start = DateTime.now();
// 构造函数只需要传入没有默认值的非可选类型
Point(this.name);
// 也可以使用初始化列表
Point.unnamed(): name = '';
}
类属性和类方法的声明
class Point {
// 类属性
static cosnt initialCapacity = 16;
double x, y;
Point(this.x, this.y);
// 类方法
static double distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}
构造方法
- 构造方法就是一个函数,只不过名字必须是类名
- 命名构造方法也是一个函数,名字是类名加上可选的命名标识
正规构造器和命名构造器
const double xOrigin = 0;
const double yOrigin = 0;
class Point {
final double x;
final double y;
Point(this.x, this.y);
// Named constructor
Point.origin()
: x = xOrigin,
y = yOrigin;
}
调用父类非默认构造器的顺序
- 初始化列表
- 父类非默认构造器
- 子类非默认构造器
class Person {
String? firstName;
// 父类,命名构造器
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {
// 父类没有默认构造器,必须调用父类的fromJson构造器
Employee.fromJson(super.data) : super.fromJson() {
print('in Employee');
}
}
void main() {
var employee = Employee.fromJson({});
print(employee);
// Prints:
// in Person
// in Employee
// Instance of 'Employee'
}
super参数可以让我们减少每次向父类传递属性的次数
class Vector2d {
final double x;
final double y;
Vector2d(this.x, this.y);
}
class Vector3d extends Vector2d {
final double z;
// Forward the x and y parameters to the default super constructor like:
// Vector3d(final double x, final double y, this.z) : super(x, y);
Vector3d(super.x, super.y, this.z);
}
初始化列表在构造函数提执行前运行,可以在开发阶段进行断言
// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, double> json)
: x = json['x']!,
y = json['y']! {
print('In Point.fromJson(): ($x, $y)');
}
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
重定向构造器调用同类的其他构造器。类似Swift中的遍历构造器
class Point {
double x, y;
// The main constructor for this class.
Point(this.x, this.y);
// Delegates to the main constructor.
Point.alongXAxis(double x) : this(x, 0);
}
const 构造器用于创建常量对象,该类的所有属性必须是final的
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
工厂构造器用于实现工厂模式
class Logger {
final String name;
bool mute = false;
// _cache is library-private, thanks to
// the _ in front of its name.
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);
}
}
var logger = Logger('UI');
logger.log('Button clicked');
var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);
方法
实例方法可以访问对象属性和this
import 'dart:math';
class Point {
final double x;
final double y;
Point(this.x, this.y);
double distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
可以通过operator重写运算符,实现同类型或该类型与另一个类型的自定义运算操作
class Vector {
final int x, y;
Vector(this.x, this.y);
// 重写 +、- 运算符,支持 Vector 同类型的运算
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
@override
bool operator ==(Object other) =>
other is Vector && x == other.x && y == other.y;
@override
int get hashCode => Object.hash(x, y);
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
getter、setter可以实现计算属性或属性赋值条件的控制
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 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...
}
}
类继承
使用extends继承类,使用@override重写方法、属性等
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
set contrast(int value) {...}
}
class SmartTelevision extends Television {
// 继承下来的方法
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// 重写父类的方法,实现自己的逻辑
@override
set contrast(num value) {...}
}
重写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}');
}
}
混入
使用mixin定义一个混入类
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');
}
}
}
定义时使用 on 限制可以混入哪些类
class Musician {
// ...
}
mixin MusicalPerformer on Musician {
// ...
}
class SingerDancer extends Musician with MusicalPerformer {
// ...
}
使用 with 让一些混入类混入
class Maestro extends Person with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
mixin class 即可以当 mixin 用,也可以当 class 用、
abstract mixin class Musician {
// No 'on' clause, but an abstract method that other types must define if
// they want to use (mix in or extend) Musician:
void playInstrument(String instrumentName);
void playPiano() {
playInstrument('Piano');
}
void playFlute() {
playInstrument('Flute');
}
}
class Virtuoso with Musician { // Use Musician as a mixin
void playInstrument(String instrumentName) {
print('Plays the $instrumentName beautifully');
}
}
class Novice extends Musician { // Use Musician as a class
void playInstrument(String instrumentName) {
print('Plays the $instrumentName poorly');
}
}
枚举
简单枚举和增强型枚举
// 简单枚举 可以通过index获取下标,通过name获取枚举字符串值
enum Color { red, green, blue }
// 增强型枚举,必须是const构造函数,属性必须是final
enum Vehicle implements Comparable<Vehicle> {
car(tires: 4, passengers: 5, carbonPerKilometer: 400),
bus(tires: 6, passengers: 50, carbonPerKilometer: 800),
bicycle(tires: 2, passengers: 1, carbonPerKilometer: 0);
const Vehicle({
required this.tires,
required this.passengers,
required this.carbonPerKilometer,
});
final int tires;
final int passengers;
final int carbonPerKilometer;
int get carbonFootprint => (carbonPerKilometer / passengers).round();
bool get isTwoWheeled => this == Vehicle.bicycle;
@override
int compareTo(Vehicle other) => carbonFootprint - other.carbonFootprint;
}
扩展方法
使用 extension 扩展方法、存取器、操作符等
extension是静态生效的,无法作用于dynamic类型
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
// ···
}
使用 as 解决不同extension有相同名称方法的情况
// Both libraries define extensions named NumberParsing
// that contain the extension method parseInt(). One NumberParsing
// extension (in 'string_apis_3.dart') also defines parseNum().
import 'string_apis.dart';
import 'string_apis_3.dart' as rad;
// ···
// print('42'.parseInt()); // Doesn't work.
// Use the ParseNumbers extension from string_apis.dart.
print(NumberParsing('42').parseInt());
// Use the ParseNumbers extension from string_apis_3.dart.
print(rad.NumberParsing('42').parseInt());
// Only string_apis_3.dart has parseNum().
print('42'.parseNum());
定义extension 即可以有名字,也可以没有名字
extension <extension name>? on <type> {
(<member definition>)*
}
// 有名称扩展
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
double parseDouble() {
return double.parse(this);
}
}
// 无名称 扩展
extension on String {
bool get isBlank => trim().isEmpty;
}
泛型扩展
extension MyFancyList<T> on List<T> {
int get doubleLength => length * 2;
List<T> operator -() => reversed.toList();
List<List<T>> split(int at) => [sublist(0, at), sublist(at)];
}
可调用的对象
定义 call 方法,把对象当函数调用
// 只能定义一个call方法,无法重载。和Swift还是差点意思
class WannabeFunction {
String call(String a, String b, String c) => '$a $b $c!';
}
var wf = WannabeFunction();
var out = wf('Hi', 'there,', 'gang');
void main() => print(out);
类修饰符
abstract 抽象类必须被 子类继承 或 其他类实现它隐式的接口
// Library a.dart
abstract class Vehicle {
void moveForward(int meters);
}
// Library b.dart
import 'a.dart';
// Error: Cannot be constructed
Vehicle myVehicle = Vehicle();
// Can be extended
class Car extends Vehicle {
int passengers = 4;
// ···
}
// Can be implemented
class MockVehicle implements Vehicle {
@override
void moveForward(int meters) {
// ...
}
}
base base类必须被 直接使用它本身 或 子类继承。无法实现它的隐式接口
// Library a.dart
base class Vehicle {
void moveForward(int meters) {
// ...
}
}
// Library b.dart
import 'a.dart';
// Can be constructed
Vehicle myVehicle = Vehicle();
// Can be extended
base class Car extends Vehicle {
int passengers = 4;
// ...
}
// ERROR: Cannot be implemented
base class MockVehicle implements Vehicle {
@override
void moveForward() {
// ...
}
}
interface 接口必须被 直接使用它本身 或 其他类实现它的接口
// Library a.dart
interface class Vehicle {
void moveForward(int meters) {
// ...
}
}
// Library b.dart
import 'a.dart';
// Can be constructed
Vehicle myVehicle = Vehicle();
// ERROR: Cannot be inherited
class Car extends Vehicle {
int passengers = 4;
// ...
}
// Can be implemented
class MockVehicle implements Vehicle {
@override
void moveForward(int meters) {
// ...
}
}
final final类必须被 直接使用它本身
// Library a.dart
final class Vehicle {
void moveForward(int meters) {
// ...
}
}
// Library b.dart
import 'a.dart';
// Can be constructed
Vehicle myVehicle = Vehicle();
// ERROR: Cannot be inherited
class Car extends Vehicle {
int passengers = 4;
// ...
}
class MockVehicle implements Vehicle {
// ERROR: Cannot be implemented
@override
void moveForward(int meters) {
// ...
}
}
sealed sealed类必须被 子类继承且可枚举,它无法被直接使用
sealed class Vehicle {}
class Car extends Vehicle {}
class Truck implements Vehicle {}
class Bicycle extends Vehicle {}
// ERROR: Cannot be instantiated
Vehicle myVehicle = Vehicle();
// Subclasses can be instantiated
Vehicle myCar = Car();
String getVehicleSound(Vehicle vehicle) {
// ERROR: The switch is missing the Bicycle subtype or a default case.
return switch (vehicle) {
Car() => 'vroom',
Truck() => 'VROOOOMM',
};
}
异步支持
await的调用必须在异步上下文中
使用async、await定义和使用异步函数
Future<String> lookUpVersion() async => '1.0.0';
Future<void> checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
使用try、catch、finally处理异步函数的异常
try {
version = await lookUpVersion();
} catch (e) {
// React to inability to look up the version
}
使用 await for处理 Stream类型的异步函数
void main() async {
// ...
await for (final request in requestServer) {
handleRequest(request);
}
// ...
}