# 语法基础

  • 必须有 main 函数作为入口,且不需要主动调用
  • var 声明变量,不存在变量提升
  • 文档 (opens new window)

# 变量

  • 变量只是一个引用,存储的时对象的地址
  • 声明变量
    • 明确指定类型:int a = 1;
    • 不指定类型:var a = 1; 或 dynamic a = 1;(var 一旦赋值,则确定类型,dynamic 一直保持不确定)
  • 变量值默认时 null,且不存在隐式转换

# 常量

  • 一旦声明,不可修改
  • 常量声明
    • const 声明:const a = 1;
    • final 声明:final a = 1;
  • const 和 final 的区别:const 声明的常量是编译时常量,final 声明的常量是运行时常量
const time = DateTime.now(); // 报错
final time = DateTime.now(); // 正常

# 数据类型

  • Number
  • String
  • Boolean
  • List,列表(数组)
  • Map,映射
  • Set,集合
  • 其它

# Number

声明:

  • num,包含 int 和 double
  • int
  • double

常用方法:

// 类型转换
3.8.toString(); // 3.8
int.parse('3'); // 3
double.parse('3.8'); // 3.8

// 数学运算
3.8.toInt(); // 3,向下取整
3.8.floor(); // 3,向下取整
3.8.ceil(); // 4,向上取整
3.8.round(); // 4,四舍五入
3.1415926.toStringAsFixed(2); // 3.14,保留两位小数
10.remainder(3); // 1,求余

// 判断
10.compareTo(3); // 1,比较大小,大于返回1,等于返回0,小于返回-1
0/0.isNaN; // true,判断是否为NaN
-1.isNegative; // true,判断是否为负数

// 随机数
import 'dart:math';
Random().nextInt(10); // 0-9

# String

  • 单双引号:没有区别
  • 三个引号:可以包含换行符

常用方法:

// 判空
''.isEmpty; // true
' '.isNotEmpty; // true

// 查找
'hello world'.contains('world'); // true
'hello world'.startsWith('hello'); // true
'hello world'.endsWith('world'); // true
'hello world'.indexOf('world'); // 6

// 拼接
String str1 = 'hello';
String str2 = 'world';
'$str1 $str2'; // hello world
str1 + str2; // helloworld

// 分隔
String str = 'hello world';
str.split(' '); // ['hello', 'world']

// trim
' hello world '.trim(); // hello world

// 截取
str.substring(0, 5); // hello
str.substring(6); // world

// RegExp(r'正则表达式')
var isPhone = RegExp(r'^1\d{10}$');
isPhone.hasMatch('12345678901'); // true

// 替换
String str = 'hello world';
str.replaceAll('world', 'dart'); // hello dart
'a1b1c1d1'.replaceAll(RegExp(r'\d+'), '_'); // a_b_c_d

# Boolean

对变量判断时,不存在隐式转换。

var a;
if(a){}// 报错
if(a === null){}// 正确

# List

声明:

  • 字面量方式
  • 构造函数方式
// 字面量方式
var list = [1, 2, 3, 'a'];// 不限定类型
List list = <int>[1, 2, 3];// 指定类型

// 构造函数方式
List list = new List.empty(growable: true);// 不限制长度
List list = new List(3);// 限制长度
List list = new List.filled(3, 6);// 限制长度且指定默认值 [6,6,6]

// 扩展运算符
var l1 = [1, 2, 3];
var l2 = [4, 5, 6];
var l3 = [...l1, ...l2]; // [1, 2, 3, 4, 5, 6]

// 列表长度
l1.length; // 3
[].isEmpty; // true
[].isNotEmpty; // false

// 列表反转
l1.reversed; // (3, 2, 1) 可迭代但不是列表

// 转换为列表
l1.reversed.toList(); // [3, 2, 1]

// 添加元素
l1.add(4); // [1, 2, 3, 4]
l1.addAll([4, 5, 6]); // [1, 2, 3, 4, 5, 6]

// 删除元素
l1.remove(1); // [2, 3]
l1.removeAt(1); // [1, 3]

// 修改元素
l1[0] = 0; // [0, 2, 3]

// push pop
l1.add(4); // [1, 2, 3, 4]
l1.removeLast(); // [1, 2, 3]

// unshift shift
l1.insert(0, 0); // [0, 1, 2, 3]
l1.removeAt(0); // [1, 2, 3]

// 清空
l1.clear(); // []

// 合并
['hello', 'world'].join(' '); // hello world

// 遍历
// for、for...in、forEach、map、where、any、every
var nums = [1,2,3];

for(var i = 0; i < nums.length; i++){
  print(nums[i]);
}

for(var i in nums){
  print(i);
}

nums.forEach((item){
  print(item);
});

// 注意 map、where 是返回一个可迭代对象,需要使用 toList 转换为列表
nums.map((item){
  return item * 2;
}).forEach((item){
  print(item);
});

// 相当于 js:[].filter
var l = nums.where((item)=> item % 2 == 1);
print(l.toList()); // [1, 3]

nums.any((item)=> item % 2 == 1); // true
nums.every((item)=> item % 2 == 1); // false

// 相当于 js:[].reduce
nums.reduce((prev, item){
  return prev + item;
}); // 6

nums.fold(10, (previousValue, element) => previousValue + element);// 16

# Set

  • 无序元素唯一的集合
  • 无法通过下标取值
  • 特有操作:交集、并集、差集
// 字面量声明
<int>{1,2,2,3}// {1,2,3}

// 构造函数声明
Set<int> set = new Set();
set.add(1);
set.add(2);
set.add(2);
set.add(3);
set // {1,2,3}

// 与 List 相互转换
set.toList(); // [1,2,3]
[1,2,2,3].toSet(); // {1,2,3}

// 访问
set.contains(1); // true
set.length; // 3
set.isEmpty; // false
set.isNotEmpty; // true

// 集合特有操作
var s1 = {1,2,3};
var s2 = {2,3,4};

// 交集
s1.intersection(s2); // {2,3}

// 并集
s1.union(s2); // {1,2,3,4}

// 差集
s1.difference(s2); // {1}

# Map

  • 无序的键值对(k-v)映射集
// 字面量声明
var map = {'name': '张三', 'age': 18};// {'name': '张三', 'age': 18}

// 构造函数声明
Map map = new Map();
map['name'] = '张三';
map['age'] = 18;
map // {'name': '张三', 'age': 18}

// 访问
map['name']; // 张三
map.length; // 2
map.isEmpty; // false
map.isNotEmpty; // true
map.containsKey('name'); // true
map.containsValue('张三'); // true

// 如果 key 不存在才赋值
map.putIfAbsent('name', () => '李四'); // 张三

// 遍历
map.keys.toList(); // [name, age]

map.values.toList(); // [张三, 18]

map.forEach((key, value){
  print('$key: $value');
});

// 删除
map.remove('name'); // {'age': 18}
map.clear(); // {}
map.removeWhere((key, value) => key == 'name'); // 按条件删除

# 其它类型

  • Runes,符文
  • 是一个 UTF-32 位字符对象,用于把文字转为符号表情或特定的文字
  • 文档 (opens new window)
var str = '👏';
print(str); // 👏
print(str.length)// 2 , dart 默认的字符串编码是 UTF-16,所以一个 emoji 表情占两个字符
print(str.runes.length); // 1

// 将字符串转为 Runes
Runes input = new Runes('\u{1f680}');
print(new String.fromCharCodes(input)); // 🚀
  • Symbols,符号
  • 用 # 开头来表示的标识符
var a = #abc;
print(a); // Symbol("abc")

var b = new Symbol('abc');
print(b); // Symbol("abc")

print(a == b); // true
print(#abc === new Symbol('abc')); // true
  • dynamic,动态类型
dynamic a = 1;
a = 'hello';

# 运算符

  • 加减乘除余,条件逻辑等都跟 js 一样
  • 地板除:~/,返回整数
  • 类型判断运算符:is、is!,用于判断对象是否为指定类型
  • 避空运算符:??、??=,用于判断对象是否为空,为空则返回指定值
  • 条件属性访问:?.,用于判断对象是否为空,为空则不执行后续操作
  • 级联运算符:..,用于对同一个对象进行一系列操作
// 地板除
7 ~/ 4; // 1

// 类型判断运算符
[] is List; // true
[] is! List; // false

// 避空运算符
1 ?? 3; // 1
null ?? 3; // 3

var a;
a ??= 3; // 3

// 条件属性访问
var obj;
obj.length; // 报错
obj?.length; // 不报错,返回 null

// 级联运算符
Set s = new Set();
s..add(1)..add(2)..add(3)..remove(2);// {1,3}
// 等价
s.add(1);
s.add(2);
s.add(3);
s.remove(2);

# 函数

# 声明与参数

// 声明不需要 function 关键字
void main(){
  print('hello world');
}

// 返回值跟函数声明的类型一致
int sum(int a, int b){
  return a + b;
}

// 匿名函数
var sum = (int a, int b){
  return a + b;
};

// 箭头函数,注意它只是一种简写,不像 JS 会改变 this 指向,也不产生其它副作用
var sum = (int a, int b) => a + b;

// 立即执行函数
void main(){
  ((){
    print('hello world');
  })();
}

// 必填参数
String hello(String name){
  return 'hello $name';
}
hello('张三');// hello 张三

// 可选参数,中括号 []
String hhh(String name, [int age = 0]){
  return 'hello $name, age is $age';
}
hhh('张三');// hello 张三, age is 0,如果没有默认值,则为 null

// 命名参数,大括号 {}
String hhh(String name, {int age = 0}){
  return 'hello $name, age is $age';
}
hhh('张三', age: 20);// hello 张三, age is 20

# 作用域与闭包

作用域和闭包跟 JS 基本一致。

  • 区分全局作用域和局部作用域,里层可访问外层。

  • 闭包原理:

    • 函数嵌套
    • 内部函数调用外部函数变量(导致外层作用域对象无法释放)
    • 执行外部函数
    • 实现变量私有化
a(){
  var x = 2;
  b(){
    x++;
    print(x);
  }
  return b;
}

var f = a();
f(); // 3
f(); // 4

# 异步函数

  • js 中,异步调用通过 Promise 来实现

    • async 返回一个 Promise 对象;await 等待一个 Promise 对象
  • dart 中,异步调用通过 Future 来实现

    • async 返回一个 Future 对象;await 等待一个 Future 对象

异步请求获取 Ip 地址

import 'dart:convert';
import 'package:http/http.dart' as http;

final Uri url = Uri.parse('http://ip-api.com/json/?lang=zh-CN');

Future getIp() {
  return http.get(url).then((response) {
    final body = jsonDecode(response.body);
    final String ip = body['query'];
    print(ip);
    return ip;
  }).catchError((onError) {
    print(onError);
  });
}

void main() {
  getIp();
}

async 声明、await 调用

import 'dart:convert';
import 'package:http/http.dart' as http;

final Uri url = Uri.parse('http://ip-api.com/json/?lang=zh-CN');

Future getIp() async {
  final response = await http.get(url);
  final body = jsonDecode(response.body);
  final String ip = body['query'];
  return ip;
}

void main() async {
  try {
    final ip = await getIp();
    print(ip);
  } catch (e) {
    print(e);
  }
}

# 类与对象

  • 声明与访问
class Personal{
  String name = '张三';

  void greet(){
    print('hello $name');
  }
}

void main(){
  var p = new Personal();
  p.name;// 张三
  p.greet(); // hello 张三
}

# 构造函数

# 默认构造函数

class Rect{
  num width;
  num height;

  Rect(num width, num height){
    this.width = width;
    this.height = height;
  }
}

void main(){
  var r = new Rect(10, 20);
  print(r.width); // 10
  print(r.height); // 20
}

简化版

class Rect{
  num width;
  num height;

  Rect(this.width, this.height);
}

# 命名构造函数

使用命名构造函数可以实现多个构造函数,提供额外的清晰度

class Point{
  num x;
  num y;

  Point(this.x, this.y);

  Point.origin(){
    this.x = 0;
    this.y = 0;
  }

  Point.fromJson({x:0, y:0}){
    this.x = x;
    this.y = y;
  }
}

void main(){
  var p1 = new Point(10, 20);
  var p2 = new Point.origin();
  var p3 = new Point.fromJson(x: 10, y: 20);
}

# 常量构造函数

常量构造函数使对象成为编译时常量(提高程序性能)

class ImmutablePoint{
  // 常量构造函数属性必须是 final
  final num x;
  final num y;

  // 常量构造函数,必须通过 const 声明,且不能有函数体
  const ImmutablePoint(this.x, this.y);
}

void main(){
  // 通过 const 实例化(一个不可变对象),而不是 new
  var p1 = const ImmutablePoint(10, 20);
  var p2 = const ImmutablePoint(10, 20);
  identical(p1, p2); // true
  p1 === p2; // true
}

# 工厂构造函数

不会自动生成示例,而是通过代码来决定返回的实例

class Person{
  static Person instance;// 静态属性,它代表缓存的实例

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

    return Person.instance;
  }

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

// 看似有两次实例化,其实只有一次,节约内存,它实际上是单例模式
void main(){
  var p1 = new Person();
  var p2 = new Person();
  print(identical(p1, p2)); // true
}

# 赋予默认值

class Rect{
  num width;
  num height;

  Rect([num height = 2, num width = 2]){
    this.width = width;
    this.height = height;
  }// var r = new Rect()

  // 或
  Rect({num height = 2, num width = 2}){
    this.width = width;
    this.height = height;
  }// var r = new Rect()

  // 或
  Rect(): height = 2, width = 2{}// var r = new Rect()
}

# 重定向构造函数

class Point{
  double x,y,z;

  Point(this.x, this.y, this.z);// 三维空间点

  Point.towD(double x, double y): this(x, y, 0);// 二维空间点,this指向当前类
}

void main(){
  var p1 = new Point(10, 20, 30);
  p1.z; // 30

  var p2 = new Point.towD(10, 20);
  p2.z; // 0
}

# 访问修饰符

  • dart 中并没有像 typescript 那样的 private、protected、public 访问修饰符
  • 默认的访问修饰符是 public
  • 通过 _ 来实现 private,但只有把类单独抽离到 main.dart 文件中才能生效

./lib/Person.dart

class Person{
  String _name = '张三';
  String getName(){
    return this._name;
  }
}

./main.dart

import './lib/Person.dart';

void main(){
  var p = new Person();
  print(p._name); // 报错
  print(p.getName()); // 张三
}

# Getter 和 Setter

class Circle{
  num r;

  Circle(this.r);

  // Getter
  num get area{
    return 3.14 * this.r * this.r;
  }

  // Setter
  set setR(value){
    this.r = value;
  }
}

void main(){
  var c = new Circle(10);
  print(c.area); // 314.0
  c.setR = 20;
  print(c.area); // 1256.0
}

# Static 关键字

  • 指定静态属性和静态方法
  • 通过类名直接访问静态属性和静态方法
class Person{
  static String name = '张三';
  int age = 18;

  static void greet(){
    // print(age);// 静态方法中 不能访问 非静态属性和方法
    // print(this.name);// 不能通过 this 访问静态属性
    print(name);
  }

  void say(){
    print(this.age);
    print(this.name);// 非静态方法中 可以访问 静态属性和方法
  }
}

void main(){
  print(Person.name); // 张三
  Person.greet(); // 张三
}

# 元数据

  • 以 @ 开头,给代码标记一些额外的信息,其实并不影响程序执行
  • @override,表示重写父类方法
  • @required,表示参数是必填的
  • @deprecated,表示方法已过时

# 继承

  • 子类通过 extends 关键字继承父类
  • 通过 @override 标记覆写方法(子类与父类同名的方法)
  • 通过 super 访问父类的属性和方法
class Father {
  String name = '刘备';
  int age;
  num _money = 100000;

  Father([this.age = 20]);

  void printInfo() {
    print("${this.name}---${this.age}");
  }

  get getMoney {
    return this._money;
  }
}

class Son extends Father {
  String name = '刘禅';

  Son(String name, int age) {
    this.name = name;
    this.age = age;
  }

  @override
  void printInfo() {
    String fatherName = super.name;

    // super._money;// 报错,父类的私有属性不能被子类访问
    num fatherMoney = super.getMoney;// 可以访问公共的get方法

    print(
        "I am son of $fatherName,name is ${this.name},age is ${this.age},I have old money $fatherMoney");
  }
}

void main() {
  Son son = new Son("Tom", 18);
  son.printInfo();// I am son of 刘备,name is Tom,age is 18,I have old money 100000
}

# 抽象类

  • 用 abstract 关键字修饰的类
  • 充当普通类的模板,约定一些必要的属性和方法
  • 抽象方法是指没有方法体的方法
  • 抽象类不能被实例化
  • 抽象类可以被普通类继承(extends),此时,子类必须实现抽象类的所有抽象方法
  • 抽象类可以充当接口被实现(implements),此时普通类必须实现抽象类的所有抽象方法和属性
abstract class Phone{
  // 声明抽象方法
  void processor();
  void camera();

  // 抽象类中的普通方法
  void call(){
    print('打电话');
  }
}

class Xiaomi extends Phone{
  // 普通类继承了抽象类,必须实现所有抽象方法
  @override
  void processor(){
    print('骁龙888');
  }

  @override
  void camera(){
    print('1亿像素');
  }
}

void main(){
  var phone = new Phone();// 报错,抽象类不能被实例化

  var xiaomi = new Xiaomi();
  xiaomi.processor(); // 骁龙888
  xiaomi.camera(); // 1亿像素
  xiaomi.call(); // 打电话
}

# 接口

  • 接口可以看作一个个小零件,类实现接口,就相当于组装了这些小零件。
  • 接口在 Dart 中就是一个类,使用 implements 实现
  • 普通类实现接口,必须重写接口中所有的属性和方法
  • 一个类可以实现多个接口,如 class C implements A, B{...}
abstract class Processor{
  String cores;// 内核
  arch(String name);// 芯片制程
}

abstract class Camera{
  String pixels;// 像素
  brand(String name);// 品牌
  aperture(String name);// 光圈
}

class Phone implements Processor, Camera{
  @override
  String cores;

  @override
  arch(String name){
    print('芯片制程:$name');
  }

  @override
  String pixels;

  @override
  brand(String name){
    print('品牌:$name');
  }

  @override
  aperture(String name){
    print('光圈:$name');
  }
}

# 混入

  • 混入Mixin 是一段公共代码,旨在提高代码的复用性
    • class MixinA{...}
    • mixin MixinB{...}
  • 作为 Mixin 类只能继承自 Object,不能继承其它类,不能有构造函数
  • 使用 with 关键字来混入 Mixin 类,class MyClass with MixinA, MixinB{...}
  • 后混入覆盖前混入
mixin MixinA{
  String name = 'MixinA';

  void printA(){
    print('A');
  }
}

mixin MixinB{
  String name = 'MixinB';

  void printB(){
    print('B');
  }
}

class MyClass with MixinA, MixinB{
  void printName(){
    print(this.name);
  }
}

void main(){
  var myClass = new MyClass();
  myClass.printName(); // MixinB,后引入覆盖前引入
  myClass.printA(); // A
  myClass.printB(); // B
}

# 泛型

  • 在函数、类、接口中指定宽泛数据类型的语法
    • 泛型函数
    • 泛型类
    • 泛型接口
  • 使用泛型,可以减少重复的代码

# 泛型函数

通常,在<``>中,使用一个字母来代表类型,如 E、T、K、V 等,格式:

返回类型 函数名<输入类型>(参数类型 参数){
  函数体
}
// 不指定类型,但缺少了校验功能(例如校验输入和输出同个类型)
getData(value){
  return value
}

// 使用泛型函数,保留校验功能
T getData<T>(T value){
  return value
}

getData<int>(20);
getData<String>('hello');

// 只约定参数类型,不约定函数返回类型
getData<T>(T value){
  return value
}

# 泛型类

class MyClass<T>{
  Set s = new Set<T>();

  void add(T value){
    this.s.add(value);
  }

  void info(){
    print(this.s);
  }
}

void main(){
  var mc1 = new MyClass<int>();
  mc1.add(1);
  mc1.add(2);
  mc1.add(3);
  mc1.info(); // {1, 2, 3}

  var mc2 = new MyClass<String>();
  mc2.add('a');
  mc2.add('b');
  mc2.add('c');
  mc2.info(); // {a, b, c}
}

List、Set 都是泛型类

var l1 = new List<int>();
var l2 = new List<String>();

# 泛型接口

abstract class Cache<T>{
  Object getByKey(String key);
  void setByKey(String key, T value);
}

class FileCache<T> implements Cache<T>{
  @override
  Object getByKey(String key){
    return null;
  }

  @override
  void setByKey(String key, T value){
    print('文件缓存:$key $value');
  }
}

void main(){
  var fc = new FileCache<String>();
  fc.setByKey('name', '张三');

  var fc2 = new FileCache<Map>();
  fc2.setByKey('name', {'name': '张三'});
}

# 使用泛型限制参数类型

class A{}

class B{}

class C extends A{}

class Foo<T extends A>{
  String toString(){
    return 'Instance of Foo<$T>';
  }
}

void main(){
  var foo = new Foo<A>();
  print(foo); // Instance of Foo<A>

  var foo2 = new Foo<B>();// 报错,B 不是 A 的子类

  var foo3 = new Foo<C>();// 有效, C 是 A 的子类,输出 Instance of Foo<A>
}

# 枚举

枚举是数量固定的常量值

// 通过 enum 声明
enum Color {
  red,
  green,
  blue
}

// 获取所有枚举值列表
List<Color> colors = Color.values;

// 通过 index 获取值的索引
Color.red.index; // 0
Color.green.index; // 1

// 通过下标访问
Color.values[0]; // Color.red

// 通过 forEach 遍历
Color.values.forEach((item){
  print(item, item.index);// Color.red 0
});

# 库与生态

# 自定义库

# 通过 library 声明库

  • 通过 import '库的位置/库名称.dart' 引入库

  • 每个 Dart 文件默认都是一个库,只是没有使用 library 显示声明,使用小写字母+下划线命名

library my_main
void my_main(){

}
  • 使用 _ 表示库内私有,库内可以访问,库外不可访问

./lib/Person.dart

library my_person;

class Person{
  String _name = '张三';
  String getName(){
    return this._name;
  }
}

./main.dart

import './lib/Person.dart';

void main(){
  var p = new Person();
  print(p._name); // 报错
  print(p.getName()); // 张三
}

# 系统库

  • 通过 import 'dart:库名称' 引入库

  • 通过光标 + Ctrl + 左键 可以查看库源码(路径为dart-sdk/lib/....dart)

import 'dart:core';// 核心库默认引入
import 'dart:math';

# 第三方库

来源(三个镜像):

使用:

  • 在项目根目录下创建 pubspec.yaml 文件
  • 在 pubspec.yaml 文件中声明依赖
  • 执行 pub get 下载依赖
包最后下载到 user/user_name/AppData/Local/Pub/Cache/hosed/...
  • 使用 import 'package:xxx/xxx.dart' 引入库

pubspec.yaml

name: my_app

description: A new Flutter project.

environment:
  sdk: ">=2.7.0 <3.0.0"

dependencies:
  http: ^0.12.2

# 引入部分库

仅引入需要的内容,既是按需加载

  • 包含引入(show)
  • 排除引入(hide)

lib/common.dart

void f1(){}
void f2(){}
void f3(){}

show.dart

import './lib/common.dart' show f1, f2;

void main(){
  f1();
  f2();
  f3(); // 报错,未暴露
}

hide.dart

import './lib/common.dart' hide f1, f2;

void main(){
  f1(); // 报错,已排除
  f2(); // 报错,已排除
  f3();
}

# 解决库冲突

  • 库冲突,是不同库之间有同名属性或方法而产生的
  • typescript 中使用命名空间来处理,dart 中使用 as 关键字赋予库别名来处理
import 'dart:math' as math;
import 'lib/function.dart' as func;

void main(){
  print(math.max(1, 2)); // 2
  print(func.max(1, 2)); // 2
}

# 延迟加载

既是懒加载,通过 deferred as 标识需要延迟加载的库

import 'dart:math' deferred as math;

void main() async{
  await math.loadLibrary();
  print(math.max(1, 2)); // 2
}

# 组装库

  • 目的: 将多个库组装成一个库,方便引用
  • 使用 part 与 part of 建立库之间的关联