Dart基础知识

345 阅读11分钟

简介

Dart是编写Flutter的官方语言,主要用于编写跨平台的UI页面,但不止于此,Dart还可以写命令行和服务端Server代码。以下是常用的一些地址

  1. Dart 三方库地址
  2. 编写命令行
  3. 编写服务器软件
  4. 编写Web应用
  5. 可迭代集合
  6. Dart异步编程
  7. Dart中所有的关键字

Dart中的你需要谨记的概念

  1. Dart中万物皆对象,无论是numbers、functions还是null,都是对象。除去null外所有类的基类都是Object。
  2. Dart可以自动推断变量类型
  3. 要使用任意类型,编译时可以使用Object或者Object?,运行时可以使用dynamic
  4. Dart支持泛型类型,支持内嵌函数,支持高级函数
  5. Dart中 _下划线开头的变量是私有的,其余的是公开访问的

Dart简介

  1. 顶层的main函数是程序入口,函数的默认返回值是void
void main() {
    print("Oh Ha You");
}
  1. var声明可变变量,final声明不可变变量,const声明编译时常量
var age = 23;
final name = "unravel";
const school = false;
  1. import 导入语句
// 导入dart中的库,使用 dart:库名
import 'dart:math';

// 导入三方库,使用 package:库名/dart文件名
import 'package:test/test.dart';

// 导入本地库,使用相对路径或绝对路径
import 'path/to/my_other_file.dart';
  1. 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');
    }
  }
}
  1. 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".');
}
  1. extends 类继承
class Orbiter extends Spacecraft {
  double altitude;

  Orbiter(super.name, DateTime super.launchDate, this.altitude);
}
  1. mixin 混入
  • 类似Swift中协议的默认实现,主要用于代码共享
  • 使用 mixin 进行定义,定义时基本和 class 一样
  • 其他类使用 with 将它混入到类中
mixin Piloted {
  int astronauts = 1;

  void describeCrew() {
    print('Number of astronauts: $astronauts');
  }
}

class PilotedCraft extends Spacecraft with Piloted {
  // ···
}
  1. 所有类都隐式成为一个接口
class MockSpaceship implements Spacecraft {
  // ···
}

abstract class Describable {
  void describe();

  void describeWithEmphasis() {
    print('=========');
    describe();
    print('=========');
  }
}
  1. 异步
  • 使用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');
    }
  }
}
  1. 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速查表

  1. 字符串插值

使用 ${expression}将表达式的值放在字符串中,若表达式为单个标识符,可以省略 {}

String a = '${3 + 2}';
// 如果是一个对象,会调用对象的toString方法
String myObj = '$myObject';
  1. 可选类型

类型默认不为空,在类型后加?号表示可选类型,默认值为null

int? a = null;
// 可选类型默认为null,上面的代码和下面的代码一样
int? a;
  1. 避空运算符
  • ??= 赋值运算符,仅当该变量为空值时才为其赋值
  • ?? 如果该运算符左边的表达式返回的是空值,则会计算并返回右边的表达式
// 避空赋值
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.
  1. 可选链式访问

在点(.)之前加一个问号(?),可以在一个表达式中连续使用多个 ?.

// 使用?.调用之后,会把返回类型包装成一个可选类型。即使它之前不是可选的
myObject?.someProperty?.someMethod()
  1. 集合字面量初始化和指定类型初始化
// 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>{};
  1. 箭头函数
bool hasEmpty = aListOfStrings.any((s) => s.isEmpty);
  1. 级联操作符 ..
  • 使用 ..操作符 对同一对象进行一系列操作
  • ..操作符 会修改方法的返回值为对象本身。即 return this;
querySelector('#confirm')
  ?..text = 'Confirm'
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'))
  ..scrollIntoView();
  1. 属性读取器
  • 自定义 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;
  1. 可选位置参数与可选命名参数
  • 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');
  1. 使用 tryoncatchrethrowfinally 关键字来处理异常
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 {
  // 最终必走的代码
}
  1. 快捷构造方法

在声明构造方法时使用 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);
  1. 初始化列表

初始化列表位于构造函数的签名与其函数体之间,在构造函数体执行之前

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)');
}
  1. 命名构造方法
class Point {
  double x, y;

  Point(this.x, this.y);

  Point.origin()
      : x = 0,
        y = 0;
}

final myPoint = Point.origin();
  1. 工厂构造方法
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');
  }
}
  1. 重定向构造方法

即一个构造方法调用另一个构造方法,重定向方法没有主体,它在冒号(:)之后调用另一个构造方法

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

操作符

image.png

浮点除法和整除除法

// 浮点计算的除法
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 !!!');

匿名函数

([[Typeparam1[, …]]) {
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 语句

  1. switch会检查是否穷举了所有的case
  2. 空的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;
}

调用父类非默认构造器的顺序

  1. 初始化列表
  2. 父类非默认构造器
  3. 子类非默认构造器
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重写运算符,实现同类型或该类型与另一个类型的自定义运算操作

image.png

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

资料

dart.cn/tutorials