Dart

概述

简介

  • Dart 是谷歌开发的、类型安全的、面向对象的编程语言,被应用与 Web、服务器、移动应用和物联网等领域
  • Dart 诞生于 2011 年
  • Dart 是类似 TypeScript 的强类型语言
  • Dart 的运行方式
    • 原生虚拟机(类似 JVM,Dart 代码可以运行在 Windows、Mac、Linux上)
    • JavaScrpit 引擎(Dart 代码可以转成 JS 代码,然后运行在浏览器上)

Dart 与 JavaScript

以下是 Dart 各方面与 JavaScript 的比较

Dart 基础语法

引例

以下入门程序告诉了我们 Dart 的基本语法内容

  • // 来注释语句

  • 声明函数不需要关键字

  • 函数和参数前面都有类型声明

  • 打印使用 print

  • 每行代码结束时,必须以分号结束

  • 字符串通过引号包起来,支持模板字符串($+变量名)

  • main 是入口函数,Dart 应用程序总是从 main 函数开始执行

    // 声明函数
    void printInteger(int aNumber) {
    print('The number is $aNumber');
    }

    // 入口文件-应用从这里开始执行
    void main() {
    var number = 42;
    printInteger(number);
    }

运行

在命令行中输入 dart xxx.dart 来运行 Dart 程序

注释

  • 单行注释 —— // xxx
  • 多行注释 —— /*xxx*/
  • 文档注释 —— ///
    • 可通过 dartdoc 命令将文档注释转成文档(文档注释支持 markdown 语法)

变量

  • 变量是一个引用,Dart 中万物皆对象,变量存储的是对象的引用
  • 声明变量的方式
    • 明确指定类型声明
    • 自动类型推断 var
    • 动态类型 dynamic
  • 变量名大小写敏感
  • 变量默认值是 null
  • Dart 变量的值不会进行隐式转换(null 不会转成 false,0 不会转成 false 等等)

常量

  • 常量就是值不可变的变量(一旦声明,其值不能修改)

  • 声明常量方式

    • 使用 const 关键字
    • 使用 final 关键字
  • const 和 final 的区别

    • const 是编译期不可变

    • final 是运行时不可变

    const time = DateTime.now(); // 错误
    final time = DateTime.now()l // 正确

数据类型

Number

Dart 中的数字由三个关键字描述

  • num 数字类型(既可以是整数,也可以是小数)
    • int 整数
    • double 浮点数
  • num 可以在赋值的时候进行类型推断,推断为 intdouble
  • intdouble 必须在赋值时给定正确的值
  • 当不确定赋值的值是整数还是小数时,就可以用 num

常用 API:在官方文档中查阅

常用:

  • 类型转换:toxxx()
  • 四舍五入:round()
  • 返回余数:remainder()
  • 数字比较:compareTo()
  • 返回最大公因数:gcd()
  • 科学计数法:toStringAsExponential()

String

使用 String 声明字符串

  • 单引号,双引号均可
  • 三个引号可以声明包含换行符的字符串(多行字符串)

常用 API:官方文档查阅

常用:

  • 字符串分割:split()
  • 字符串拼接: + 运算符
  • 字符串裁切:trim()
  • 判断字符串是否为空:isEmpty(是属性,不要括号)
  • 字符串替换:replaceAll() (支持正则替换)
  • 字符串匹配:hasMatch()
  • 查找子串:contains()
  • 定位子串:indexOf()

正则表达式

  • 语法:RegExp(r'正则表达式')
  • 例子:匹配一到多个数字:RegExp(r'\d+')

Boolean

  • Dart 通过 bool 关键字来表示布尔类型
  • 布尔类型只有两个值:truefalse
  • 对变量进行判断时,要显式地检查布尔值,即写表达式来判断
  • 一些特殊的判断
    • 字符串:isEmpty, isNotEmpty
    • 数字:isNaN, isFinite …

List

Dart 中的数组,由 List 对象表示。List 有两种声明方式

  • 字面量方式

    List list = []; // 不限定元素的数据类型
    List list = <int>[]; // 限定元素的类型为int,<>内的称为泛型
  • 构造函数方式

    List list = new List.empty(growable: true); // 不限制长度的空列表
    List list = new List.filled(3, 0); // 声明指定长度的填充列表

扩展操作符(…)

var list1 = [1, 2, 3];
var list2 = [0, ...list1]; // [0, 1, 2, 3]

var list3;
var list4 = [0, ...?list3]; // 空安全的扩展运算符,list3为null时不会报错,list4为[0]

常用 API:查阅官方文档

常用:

  • 列表长度:length(属性)
  • 列表翻转:reversed(属性,且得到的是一个可迭代对象,不是List)
  • 转为列表:toList()
  • 添加元素:add(), addAll()
  • 删除元素:remove(), removeAt()
  • 在指定位置添加元素:insert()
  • 清空:clear()
  • 合并列表中的元素:join()(返回字符串)

遍历列表

方法 功能
forEach() 遍历列表
map() 遍历并处理元素,然后生成新的列表
where() 返回满足条件的数据,传布尔函数
any() 只要有一项满足条件,即返回 true,传布尔函数
every() 判断是否每一项都满足条件,都满足才返回 true,传布尔函数
expand() 将二维数组降维为一维数组
fold() 对列表中的每一个元素,做一个累计操作(类似于归约)

同样可以用 for 循环来遍历

for (var i = 0; i < arr.length; i++) {
...
}

for (var item in arr) {
...
}

Set

简介

  • Set 是一个无序的、元素唯一的集合
  • Set 有字面量和构造函数两种声明方式(字面量用大括号包裹)
    • var set1 = <int>{1, 2, 3};
    • var set2 = new Set();
  • 无法通过下标取值
  • 具有集合特有的操作
    • 如求交并差等

常用 API 查阅官方文档

常用:

  • 列表转换成集合:toSet()(可以过滤掉列表中重复的元素)
  • 批量添加:addAll(),参数可以是列表,也可以是集合
  • 求交集:intersection()
  • 求并集:union()
  • 求差集:difference()
  • 返回第一个元素:first(属性)
  • 返回最后一个元素:last(属性)

Map

简介

  • Map 是一个无序的键值对映射,通常被称作哈希字典

  • 声明方式

    • var map = {key1:value1, key2:value2};
    var map = new Map();
    map['key'] = value;

常用 API 查阅官方文档

常用:

  • 访问属性:map[‘key’]
  • 判断 Map 中的 key 是否存在:containsKey(‘key’)
  • 如果 key 不存在,才赋值:putIfAbsent()
  • 获取 Map 中所有的 key:keys(属性)
  • 获取 Map 中所有的 value:values(属性)
  • 根据条件进行删除:removeWhere()

其他

Runes

  • Runes 对象是一个 32 位字符对象。它可以把文字转换成符号表情或特定的文字(使用 UTF32编码)
  • 参考网站:https://copychar.cc/

Symbol

  • 在 Dart 中符号用 # 开头来表示的标识符
  • 用于反射

dynamic

  • 动态数据类型,即弱类型,此类型的变量可以被赋值为任意数据类型

运算符

地板除(~/)

用于得到除法结果的向下取整的整数

7 ~/ 4; // 1

类型判断运算符(is 和 is!)

判断类型,类似 Java 中的 instanceOf()

var list = [];
list is List; // true

避空运算符(?? 和 ??=)

?? 是三元运算符的语法糖,??= 用于赋值语句

// ??前面的不为空,则值为前面的,为空则值为后面的
1 ?? 3; // 1
null ?? 12; // 12

// 变量为空,则赋值,不为空则不赋值
var a;
a ??= 3; // a为3
a ??= 5; // 赋值失败

条件属性访问(?.)

判断属性是否存在,存在才访问,否则不访问,返回 null,避免报错

级联运算符(…)

用于连续调用对象的方法,链式调用,每次方法返回的不是方法返回值,而是当前对象的引用

函数

声明函数

直接声明

  • Dart 中声明函数不需要 function 关键字

    void printInfo() {
    print("hello world");
    }

箭头函数

  • Dart 中的箭头函数中,函数体只能写一行且不能带有结束的分号

  • Dart 中的箭头函数,只是函数的一种简写形式

    list.forEach((element) => print(element));

匿名函数

var func = (value) {
print(value);
}

立即执行函数

// 当语句执行到此时,该函数会立即执行,打印17
((int n) {
print(n);
}) (17);

函数参数

必填参数

用法:数据类型 参数名称

String userInfo(String name) {
return name;
}

可选参数

语法:[数据类型 参数名称, …]

// 可选参数如果不给dynamic类型的话,必须赋初值
String userInfo(String name, [int age = 0]) {
return '$name, $age';
}
// 调用,可选参数按顺序赋值
userInfo('tom', 20);

命名参数

语法:{数据类型 参数名称, …}

// 命名参数调用时,需要与声明时的形参名一致
// 同理,命名参数如果不给dynamic类型的话,必须赋初值
String userInfo(String name, {int age = 0}) {
return '$name, $age';
}
// 调用
userInfo('tom', age: 20);

函数参数

将函数当作参数传递,传参的函数也叫回调函数

作用域与闭包

作用域

内层作用域可以访问外层的变量内容,但外层不能访问内层的变量内容,即 Java 中作用域的内容。

闭包

  • Dart 中闭包的实现方式与 JavaScript 中完全一致

  • 使用时机:既能重用变量,又保护变量不被全局污染

  • 实现原理:外层函数被调用后,外层函数的作用域对象(AO)被内层函数引用着,导致外层函数的作用域对象无法释放,从而形成闭包

    // 闭包可以持久化外层函数的变量,避免函数调用后内部变量被清除
    parent() {
    var money = 1000;
    return () {
    money -= 100;
    print(money);
    };
    }

    var p = parent();
    p(); // 900
    p(); // 800
    p(); // 700

异步函数

简介

  • Dart 中,异步调用通过 Future 来实现
  • async 函数返回一个 Future,await 用于等待 Future
  • 关于 Future 的详情可到官网文档查看

接收处理异步结果

使用 then
Future getIpAddress() {
final url = 'https://httpbin.org/ip';
// then中的函数需要返回某些值,不然外部调用getIPAddress函数得到的返回值是null
// then中的函数返回的值会被包装成Future类型
return http.get(url).then( (response) {
print(response.body);
return response.body
});
}
使用 async 和 await
Future getIpAddress() async {
final url = 'https://httpbin.org/ip';
final response = await http.get(url);
print(response.body);
return response.body;
}

类与对象

简介

  • 类是通过 class 声明的代码段,包含属性和方法
  • 对象是类的实例化结果

构造器

普通构造函数

默认构造函数,与类同名,在实例化时自动被调用

class Point {
num x, y;
Point (num x, num y) {
this.x = x;
this.y = y;
}

// 上面的构造器可以简写为下面这样
Point(this.x, this.y);
}
命名构造函数

在类中使用命名构造器(类型.函数名)可以实现多个构造器,可以提供额外的清晰度

class Point {
num x, y;
// 默认构造函数
Point(this.x, this.y);
// 命名构造函数
Point.origin() {
x = 0;
y = 0;
}
// 命名构造函数
Point.fromJson({num x = 0, num y = 0}) {
this.x = x;
this.y = y;
}
}
常量构造函数

如果类生成的对象不会改变(类中的属性都为常量),则可以通过常量构造函数使这些对象称为编译时常量,可以提高性能。

class Point() {
final num x;
final num y;
// 常量构造函数不能有函数体
const Point(this.x, this.y);
}

void main() {
// 使用const关键字来通过常量构造函数构造不可变对象
var p1 = const Point(1, 2);
var p2 = const Point(1, 2);
print(p1 == p2); // true
// 不用const关键字,则使用的是普通构造函数
// new可省略
var p3 = Point(1, 2);
var p4 = Point(1, 2);
print(p3 == p4); // false
}
工厂构造函数

工厂构造函数形式上是普通构造函数,但是通过 factory 声明,调用工厂构造函数不会返回自动实例对象,而是通过代码来决定返回的实例,主要用于配合实现类的单例模式,避免多次创建对象造成资源浪费,提高性能。

class Person {
String name;
static Person instance; // 单例
// 工厂构造函数
factory Person([String name = 'tom']) {
// 工厂构造函数中,不能使用this关键字
// print(this.name);
// 第一次实例化
if (Person.name == null) {
Person.instance = new Person.newSelf(name);
}

// 非第二次实例化
return Person.instance;
}

// 命名构造函数
Person.newSelf(this.name);
}

访问修饰

  • Dart 与 TypeScript 不同,没有访问修饰符
  • Dart 类中,默认的访问修饰符(即不加任何内容)是公开的(public)
  • 如果属性或方法以 _ 开头,则表示为私有(private)
  • 只有把类单独抽离出去,私有属性和方法才起作用(即如果类写在主函数所在的文件中,私有属性和方法是不起作用的)(方法为写在另一个文件中,然后在主函数所在的文件中引用)

Getter 和 Setter

Getter 是通过 get 关键字修饰的方法

  • 该函数不能有括号,访问时也不用加括号(即像访问属性一样访问方法

Setter 是通过 set 关键字修饰的方法

  • 访问时,像设置属性一样给函数传参

    class Circle {
    final double PI = 3.1415;
    num r;

    Circle(this.r);
    // 使用get声明的方法不能有括号
    // Getter
    num get area {
    return PI * r * r;
    }
    // Setter
    set setR(value) {
    this.r = value;
    }
    }

    void main() {
    var c = Circle(10);
    // getter调用
    print(c.area);
    // setter调用
    c.setR = 20;
    }

初始化列表

  • 作用:在构造函数中设置属性的默认值

  • 时机:在构造函数体执行之前执行

  • 语法:使用逗号分隔初始化表达式

  • 场景:常用于设置 final 常量的值

    class Rect {
    int height;
    int width;
    // 初始化列表
    Rect() : height = 4, width = 12 {
    ...
    }
    }

    // 初始化列表的特殊用法
    class Point {
    num x, y, z;
    Point(this.x, this.y, this.z);

    // 初始化列表特殊用法:重定向构造函数
    Point.twoDimentionPoint(num x, num y) : this.(x, y, 0);
    }

static

与 Java 一致

static 关键字用来指定静态成员

  • 通过 static 修饰的属性是静态属性
  • 通过 static 修饰的方法是静态方法

静态成员可以通过类名直接访问(不需要实例化)

  • 实例化是比较消耗资源的,声明静态成员,可以提高程序性能

静态方法不能访问非静态成员,非静态方法可以访问静态成员

  • 静态方法中不能使用 this 关键字
  • 不能使用 this 关键字来访问静态属性

元数据

类似 Java 中的注解

元数据以 @ 开头,可以给代码标记一些额外的信息

  • 元数据可以用在库、类、构造器、函数、字段、参数或变量声明的前面

一些元数据

  • @override:表示方法重写
  • @required:指示某个命名参数是必填的
  • @deprecated:表示方法或类不建议使用

继承

根据类的先后顺序,可以将类分成父类和子类

子类通过 extends 关键字继承父类

  • 继承后,子类可以使用父类中可见的属性和方法

子类中,可以通过 @override 元数据来标记 ”重写“ 的方法

  • 重写方法:子类中与父类中同名的方法

子类中,可以通过 super 关键字来引用父类中可见的属性和方法(包括各种构造器)

抽象类

抽象类是用 abstract 关键字修饰的类

抽象类的作用是充当普通类的模板,约定一些必要的属性和方法

抽象方法是指没有方法体的方法

  • 抽象类中一般都有抽象方法,也可以没有抽象方法,还可以有普通方法
  • 普通类中,不能有抽象方法

抽象类不能被实例化

抽象类可以被普通类继承

  • 如果普通类继承抽象类,必须实现抽象类中所有的抽象方法

抽象类还可以充当接口被实现

  • 如果把抽象类当作接口实现的话,普通类必须得实现抽象类里面定义的所有属性和方法

接口

接口在 Dart 中就是一个类(只是用法不同)

  • 与 Java 不同,Java 中的接口需要用 interface 关键字声明,Dart 中不需要
  • 接口可以是任意类,但一般使用抽象类做接口

一个类可以实现(implements)多个接口,多个接口用逗号分隔

  • 接口可以看成一个个小零件。类实现接口就相当于组装零件

普通类实现接口后,必须重写接口中所有的属性和方法

混入

混入(Mixin)是一段公共代码。混入的声明方式:

  • 使用 mixin 关键字声明 mixin MixinB {…}

混入(Mixin)可以提高代码复用的效率,普通类可以通过 with 来使用混入

使用多个混入时,后引入的混入会覆盖之前混入中的重复的内容

混入后,可以使用混入的类中的属性和方法(Dart 单继承的补充)

泛型

泛型是在函数、类、接口中指定宽泛数据类型的语法

  • 泛型函数
  • 泛型类
  • 泛型接口

通常,在尖括号中,使用一个字母来代表类型

作用:提高代码的复用性,还可以限制集合类的元素数据类型

除了可以指定单一数据类型外,泛型还可以限定传入的数据类型必须是某个类的子类或其本身,语法:class name<T extends xxx> {}

枚举

枚举是数量固定的常量值,通过 enum 关键字声明

  • enum Color { red, green, blue }

枚举的 values 常量,可以获取所有枚举值列表

  • List<Color> colors = Color.values;

可以通过 index 获取值的索引(索引的值从 0 开始,与枚举常量声明的顺序有关)

  • assert(Color.green.index == 1);

扩展(extension)

  • extension 关键字在 Dart 2.7 及其以上才支持
  • extension 可以扩展对象的内容
    • 如:extension StringExtension on String {// 扩展内容}
    • 扩展不仅可以定义方法,还可以定义 setter, getter, operator
  • 使用
    • 声明扩展,即上面的 extension ... on ... 语法
    • 引入扩展,即 dart 引入包的语法,可以用相对路径,也可以用绝对路径
    • 使用扩展(String.扩展内容)

call 方法

在类中可以声明 call 方法(方法名不能变)

当我们将类的实例,当作函数来调用时,会自动调用 call 方法

class IOSPhone {
call(String num) {
print('phone number is $num');
}
}

void main() {
var phone = IOSPhone();
// 将类的实例当作函数一样调用
phone('911'); // phone number is 911
// 上面的可以简写为下面这样
IOSPhone()('911'); // phone number is 911
}

noSuchMethod 方法

当我们调用了一个类的,未定义的方法时,Dart 会自动调用 noSuchMethod 方法

使用前提

  • 类中声明了 noSuchMethod (否则会调用默认的 noSuchMethod)
  • 实例化对象必须用 dynamic 来修饰
    • dynamic p = Person();
  • 调用的是未定义的方法(p.undefinedMethod()

作用

  • 防止调用不存在的方法时报错

hashCode 属性

同 Java 中的 hashCode 方法

hashCode 是 Dart 对象的唯一标识

hashCode 表现为一串数字

Dart 中每个对象都有 hashCode

我们可以通过 hashCode 来判断两个对象是否相等

typedef

typedef 可以用来自定义类型(别名),目的是让程序的可读性更强

  • 我们可以在声明泛型时,使用自定义的类型

语法

  • typedef function_name(parameters);
  • typedef variable_name = List<int>;

版本要求

  • Dart 版本 2.13 之前,typedef 仅限于函数类型

  • 2.13 之后,typedef 可以定义非函数类型

    typedef MathOperation(int a, int b);

    // 加法运算
    add(int a, int b) {
    print('加法运算:' + (a+b).toString());
    return a+b;
    }

    // 减法运算
    sub(int a, int b) {
    print('减法运算:' + (a-b).toString());
    return a-b;
    }

    // 三个数加法
    add3(int a, int b, int c) {
    print('加法运算:' + (a+b+c).toString());
    return a+b+c;
    }

    void main() {
    print(add is MathOperation); // true
    print(sub is MathOperation); // true
    print(add3 is MathOperation); // false

    print(add is Function); // true
    print(sub is Function); // true
    print(add3 is Function); // true

    MathOperation op = add;
    op(10, 20); // 30
    }
    typedef MathOperation(int a, int b);

    calculator(int a, int b, MathOperation op) {
    print('计算器');
    return op(a, b);
    }

    void main() {
    calculator(8, 5, add); // 13
    calculator(9 ,6, sub); // 3
    calculator(a, b, (a, b) => null); // null
    }

异步编程

单线程(EventLoop)

Dart 单线程的核心包括

  • 主线程
  • 微任务
  • 宏任务

Dart 中的同步操作都在主线程中完成,遇到异步任务时,会将异步任务放到两个地方来完成

  • 微任务队列
    • 微任务队列包含微任务,主要通过 scheduleMicrotask 来调度
  • 事件队列
    • 事件队列包含外部事件,例如 I/O、Timer、绘制事件等,对应宏任务

同步与异步

  • 同步是指任务之间有强依赖关系,后一个任务依赖于前一个任务的结果,例子:4x100 米
  • 异步是同步的反义词,任务之间没有明确的依赖关系,例子:100 米中有 8 个跑道

Dart 单线程执行流程图示:

要点:

  1. 异步任务会在同步任务全部执行完(主线程结束)之后再执行
  2. 微任务先于宏任务(事件任务)执行
  3. 宏任务(事件任务)中可能含有微任务(加入到微任务队列中)

Dart 中的事件轮询机制

  1. 同步任务全部执行完毕之后,事件轮询机制开始起作用

  2. 事件轮询机制每次会先询问微任务队列是否有任务,有的话就执行队头的微任务,没有的话就询问事件队列

  3. 事件队列有任务,则执行队头的事件任务,没有的话表明异步任务全部已经执行完毕,此时程序就结束了

  4. 可以自己对着下图推导一遍任务的执行顺序:

Isolate 多线程

介绍

Isolate 是 Dart 中的现成

  • Dart 中的线程是以**隔离(Isolate)**的方式存在的
  • 每个 Isolate 都有自己独立的私有内存块(即多个线程不共享内存
  • 没有共享内存,就不需要竞争资源,就不需要锁(不用担心死锁问题)

所有的 Dart 代码,都运行在 Isolate 中

  • Isolate 提供了 Dart | Flutter 的运行环境
  • 微任务队列、事件队列、事件轮询(EventLoop)都在 Isolate 中进行

多线程经常用来解决耗时较长的异步任务

Isolate 相对于整体的位置图示:

Isolate 中的内容图示:

内容包括:

  1. 各种对象
  2. 微任务队列
  3. 事件队列
  4. 事件轮询

创建多线程

Isolate 类用来管理(创建、暂停、杀死)线程,常用函数有:

  • Isolate.spawn()
  • Isolate.spawnUri()
  • Isolate.pause()
  • Isolate.kill()

Isolate.spawn()

  • 函数签名:Future<Isolate> Isolate.spawn(entryPoint, message)

  • 使用:import 'dart:isolate';

  • entryPoint:进入点,是一个回调函数,必须是一个顶层方法或静态方法,必须能被访问到

  • message

    1. Dart 原始数据类型,如 null、bool、int、double、String 等
    2. SendPort 实例 - ReceivePort().sendPort
    3. 包含 1 和 2 的 list 和 map,也可以嵌套
  • 示例程序:

    import 'dart:isolate';

    void main() {
    MultipleThread();
    }

    void MultipleThread() {
    print("MultiThread start");
    print("当前线程名:" + Isolate.current.debugName);

    Isolate.spawn(newThread, "hello");

    print("MultiThread end");
    }

    void newThread(String message) {
    print("当前线程名:" + Isolate.current.debugName);
    print(massage);
    }

    /* 输出
    MultiThread start
    当前线程名:main
    MultiThread end
    当前线程名:newThread
    hello
    */

    // 线程的创建和执行也是一个异步过程,会在同步任务结束后再执行

Isolate.spawnUri()

  • 参数:
    • uri:创建的新的线程的代码逻辑所在的 uri 路径
    • args:创建新的线程时要传递的参数
    • msg:创建新的线程时要传递的消息,可以是 SendPort

Isolate.compute()

  • 是对 Isolate.spawn() 的一个封装
  • 使用:import 'package:flutter/foundation.dart';

多线程之间的通信机制

Isolate 多线程之间,通信的唯一方式是 Port,有两个类可以用来实现线程间的通信

  • ReceivePort 类
    • 初始化接收端口,创建发送端口,接收消息,监听消息,关闭端口
  • SendPort 类
    • 将消息发送给 ReceivePort

线程之间的通信方式

  • 单向通信 (A -> B)

    1. 发送端口是通过接收端口的 sendPort getter 方法创建的
    2. 创建新线程时传递的消息为发送端口实例,这样新线程就可以向根线程发送消息了
  • 双向通信(A <-> B)

    1. 创建新线程时传递的消息为发送端口实例,这样新线程就可以向根线程发送消息了
    2. 新线程可以自己实例化一个接收端口,然后创建这个接收端口的发送端口实例,通过根线程提供的发送端口实例将自己的发送端口实例发送给根线程,这样就建立了线程之间的双向通信了

Future

什么是 Future

Future 是 Dart 中的类,我们可以通过 Future 实例,封装一些异步任务

Future 的含义是未来,未来要执行的一些任务,我们可以放到 Future 中

Future 有三种状态

  • 未完成(Uncompleted)

  • 已完成,并返回数据(Completed with data)

  • 已完成,但返回报错(Completed with error)

  • 图示:

    Future 相当于是一个黑盒,打开前状态不确定,打开后才确定

获取 Future 实例

自动返回,一些 API 会返回 Future 实例

  • final myFuture = http.get('https://my.image.url');
  • final myFuture = SharedPreferences.getInstance;

手动创建,调用 Future 的 API

  • final myFuture = Future( () { return 123; });
  • final myFuture = Future.error(Exception());
  • final myFuture = Future.delayed(Duration(seconds: 5), () => 123); 延迟一段时间后才开始执行该异步任务

与 Future 的方法相关的状态

  • 创建:Uncompleted

  • then():Completed with data,该方法拿返回数据

  • catchError():Completed with error,该方法捕获错误

  • whenCompleted():Completed with data + Completed with error,该方法用于做一些收尾工作

    void main() {
    // 创建 Future 实例
    final f = Future( () {
    // 创建该异步任务时会执行这个函数
    print("Create the future");
    return 123;
    });

    print(f); // instance of 'Future<int>'

    // 接收异步任务返回的数据
    f.then( (value) => print(value)); // 123
    }

Future 执行顺序

Future 默认是异步任务,会被丢到事件队列(Event queue)中

Future.sync()

  • 同步任务,同步执行(不会被丢到异步队列中)

Future.microtask()

  • 异步任务中的微任务,会被丢到微任务队列中,优先级比事件任务高

Future.value(val)

  • 适用于可以直接拿到结果的异步操作
  • val 是常量,则创建微任务
  • val 是异步任务(如一个 Future),如果异步任务是微任务,则丢到微任务队列,如果异步任务是宏任务,则丢到事件队列

Future 多任务

Future.any(futures)

  • 返回最先完成的 Future 结果,futures 是一组 Future 任务

Future.wait(futures)

  • 等待所有 Future 任务执行完成,并收集所有 Future 任务的返回结果

Future.doWhile(action)

  • 按照条件遍历执行多个 Future 任务

Future.forEach(elements, action)

  • 遍历一个给定的集合,根据集合元素执行多个 Future

    void main() {
    var i = 0;
    // doWhile方法将根据传入回调函数的返回值是ture还是false来决定是否要继续下一次循环
    Future.doWhile( () {
    i++;
    return Future.delayed(Duration(seconds: 2), () {
    print("Future.doWhile() $i");
    return i < 6;
    }).then( (value) {
    // 返回延时Future任务的布尔值,这样doWhile才可以拿到这个值判断是否还要继续
    return value;
    });
    });
    }
    void main() {
    Future.forEach([1, 2, 3], (element) {
    return Future.delayed(Duration(seconds: 2), () {
    print(element);
    return element;
    }).then( (value) => print(element));
    });
    }

FutureBuilder

FutureBuilder 是 Flutter SDK 中提供的异步组件

  • FutureBuilder 是一个类,可以接收 Future 任务的数据,并将数据渲染成界面
  • 使用:import 'package:flutter/material.dart';

FutureBuilder 中,有三个属性

  • future:要完成的任务
  • initialData:初始数据
  • builder(context, snapshot):构造器
    • snapshot 三种状态:
      • snapshot.connectionState
        • ConnectionState.none(未连接异步任务)
        • ConnectionState.waiting(连接异步任务,等待交互)
        • ConnectionState.active(正在交互)
        • ConnectionState.done(异步任务完成)
      • snapshot.hasData (Completed with data)
        • snapshot.data 获取数据
      • snapshot.hasError (Completed with error)

Stream

什么是 Stream

Stream 是 Dart 中的异步数据流,可以连续不断地返回多个数据

  • Future 是异步,但只能返回一个值
  • Stream 也是异步,但可以返回多个值(数据流)

Stream 相关的 API

  • 通过 listen 进行数据监听(最常用)
  • 通过 error 接收失败状态
  • 通过 done 接收结束状态

Stream 类型

  • Single-Subscription(单一订阅)

    • 数据流只能被 listen 一次(listen 多次会报错)
    • StreamController().stream
    • Stream stream = Stream.fromIterable(data)
  • Broadcast(广播)

    • 数据流可以被 listen 多次
    • StreamController<int>.broadcast()
    • stream.asBroadcastStream()
    void main() {
    // 单一订阅数据流
    final StreamController controller = StreamController();

    // 第一次监听
    controller.stream.listen( (event) {
    print("Data is $event");
    });

    /*
    // 第二次监听 —— 会报错
    controller.stream.listen( (event) {
    print("Data is $event");
    });
    */

    // 发送数据
    controller.sink.add("123");
    controller.sink.add("abc");
    }

    /* 输出
    Data is 123
    Data is abc
    */
    void main() {
    // 广播数据流
    final StreamController controller = StreamController.broadcast();

    // 第一次监听
    controller.stream.listen( (event) {
    print("Data1 is $event");
    });

    // 发送数据
    controller.sink.add("123");

    // 第二次监听 —— 不会报错
    controller.stream.listen( (event) {
    print("Data2 is $event");
    });

    // 发送数据
    controller.sink.add("abc");
    }

    /* 输出
    Data1 is 123
    Data2 is abc
    Data1 is abc
    */

创建 Stream

StreamController 类

  • sink:槽,用于添加数据
  • stream:流,用于取出数据

Stream 类

  • Stream.fromFuture(),从单个 Future 任务中获取数据并可以读取
  • Stream.fromFutures(),从多个 Future 任务中获取数据并可以读取
  • Stream.fromIterable(),从一个可迭代的对象中获取数据并可以读取
  • Stream.periodic(Duration, computation),周期性地获取数据并可以读取

操作 Stream

take() | takeWhile()

  • 限制获取的数据的数量,或当满足条件时才获取数据

where()

  • 只获取满足条件的数据

distinct()

  • 去掉数据流中连续重复的值

skip() | skipWhile()

  • 跳过数据中的前几个数据,或跳过满足条件的数据

map()

  • 对数据流中的每一个数据加工

expand()

  • 对数据流中的每一个数据扩展为自定义的数组

toSet() | toList() | toString()

length | first | last | isEmpty

firstWhere() | lastWhere()

StreamBuilder

StreamBuilder 是 Flutter SDK 中提供的异步组件

  • StreamBuilder 是一个类,接收 Stream 数据,并将数据渲染成界面
  • 引入:import 'package:flutter/material.dart';

StreamBuilder 中,有三个属性,类似于 FutureBuilder

  • stream
  • initialData
  • builder(context, snapshot)

作用图示:

总结

Async / Await

async:标记函数是一个异步函数,其返回值类型是 Future

await:等待某个异步方法执行完毕

  • 用来等待耗时操作的返回结果,这个操作会阻塞到后面的代码

作用

  • await 会等待异步任务执行(相当于将异步转成同步
  • async-await 简化代码,防止回调地狱的产生

Generator(生成器)

什么是生成器

  • 生成器是是一种特殊的函数,返回值通过 yield 关键字来指定

生成器的分类

  • 同步生成器(sync + yield)

    • 使用 sync*,返回的是 Iterable 对象

    • yield 会返回 moveNext 为 true,并等待 moveNext 指令

    • void main() {
      var res = getNumber(5).iterator;
      // 最终moveNext的指针指向-1位置
      while (res.moveNext()) {
      print(res.current);
      }
      }
      // 同步生成器
      Iterable<int> getNumber(int n) sync* {
      print("start");
      int i = 0;
      while (i < n) {
      yield i++;
      }
      print("end");
      }

      /* 输出
      start
      0
      1
      2
      3
      4
      end
      */
  • 异步生成器(async + yield)

    • 使用 async*,返回的是 Stream 对象

    • yield 不用暂停,数据以流的方式一次性推送

      void main() {
      final Stream<int> s = asyncCountDown(5);

      print("start");
      s.listen( (event) => print(event))
      .onDone( () => print("Done"));
      print("end");
      }
      // 异步生成器
      Stream<int> asyncCountDown(int n) async* {
      while (n > 0) {
      yield n--;
      }
      }

      /* 输出
      start
      end
      5
      4
      3
      2
      1
      Done
      */
  • 递归生成器(yield*)

    • yield* 是指针,指向递归函数

      void main() {
      final Iterable<int> s = getRange(1, 6);

      print("start");
      s.forEach( (element) => print(element));
      print("end");
      }

      // 同步递归生成器
      Iterable<int> getRange(int start, int end) sync* {
      if (start <= end) {
      yield start;

      /*
      // 递归调用
      for (final val in getRange(start + 1, end)) {
      yield val;
      }
      */

      // 上面的递归调用可以简写为以下形式
      yield* getRange(start + 1, end);
      }
      }

      /* 输出
      start
      1
      2
      3
      4
      5
      6
      end
      */

库与生态

简介

Dart 中的库就是具有特定功能的模块

  • 可能包含单个文件,也可能包含多个文件

按照库的作者进行划分,库可以分成三类

  • 自定义库(工程师自己写的)
  • 系统库(Dart 中自带的)
  • 第三方库(Dart 生态中的)

Dart 生态

自定义库

通过 library 来声明库

每个 Dart 文件默认都是一个库,只是没有使用 library 来显式声明

Dart 使用 _ 开头的标识符来表示库内访问可见(私有)

library 关键字声明的库名建议使用:小写字母 + 下划线

通过 import 来引入库

不同类型的库,引入方式不同

  • 自定义库:import '库所处目录/库名称.dart'
  • 系统库:import 'dart:库名称'
  • 第三方库:之后单独阐述

引入部分库(仅引入需要的内容)

  • 包含引入(show):跟在 show 后面的是仅引入的内容
  • 排除引入(hide):跟在 hide 后面的是不引入的内容

通过指定库的前缀来避免命名冲突

  • 当库内的属性、函数名等冲突时,可以通过 as 关键字,给库声明一个前缀

延迟引入(懒加载)

  • 使用 deferred as 关键字来标识需要延时加载的库

    import 'lib/function.dart' deferred as func;

    void main() {
    print(1);
    greet();
    print(2);
    }

    Future greet() async {
    await func.loadLibrary()
    func.hello();
    }

    /* 输出
    1
    hello world
    2
    */

通过 part 和 part of 来组装库

图示:

系统库

介绍

系统库(也叫核心库)是 Dart 提供的常用内置库

  • 不需要单独下载,就可以直接使用

引入

  • import 'dart:库名'
  • dart:core 会自动引入,无需手动引入

系统库列表

  • async
  • collection
  • convert
  • core
  • developer
  • math

第三方库

来源

使用

  • 在项目目录下创建 pubspec.yaml
  • pubspec.yaml 中声明第三方库(依赖)
  • 在命令行中进入 pubspec.yaml 所在目录,执行 pub get 进行安装
  • 在项目中引入第三方库 import 'package:xxx/xxx.dart'

第三方库的结构

  • 一个第三方库,必须包含一个 pubspec.yaml