Dart基础语法 | 青训营笔记

363 阅读11分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 12 天

Dart作为Flutter框架使用的语言,虽然近几年不温不火,但随着Flutter框架日益成熟,以及其性能稳定性的不断优化,已经有很多人更倾向于使用Flutter构建跨端应用程序。

Dart 变量

Dart 是一个强大的脚本类语言,可以不预先定义变量类型,自动会类型推断。

Dart 中定义变量可以通过 var 关键字、类型关键字来声明变量。

var str1 = 'this is var';
String str2 = 'this is String';
int num1 = 123;
print('$str1 - $str2'); // 相当于 js 中的 ${str1} - ${str2}

注意: var 后就不要写类型,写了类型就不要 var。

字符串类型

String str = 'hello dart';
print(str);

数字类型

int myNum = 1234;
print(myNum);

常用数据类型

Numbers(数值):

  1. int
  2. double

Strings(字符串

  • String

Booleans(布尔)

  • bool

List(数组)

  • 在Dart中,数组是列表对象,所以大多数人只是称它们为列表

Maps(字典)

通常来说,Map 是一个键值对相关的对象。 键和值可以是任何类型的对象。每个 键 只出现一次, 而一个值则可以出现多次

类型校验

var str = '';
// str = 1234; // 报错
print(str);

Dart 常量

永远不改变的量,使用 finalconst 来修饰。

final 和 const 的区别

  1. const 值不变,一开始就得赋值;
  2. final 可以开始不赋值,只能赋一次;
  3. final 不仅有 const 的编译时敞亮的特性,最重要的是它是运行时常量,并且 final 是惰性初始化,即在运行时第一次使用前才初始化。

Dart 的命名规则

1、变量名称必须由数字、字母、下划线和美元符($)组成;

  1. 注意:标识符开头不能是数字;
  2. 标识符不能是保留字和关键字;
  3. 变量的名字是区分大小写的如: age和Age是不同的变量。在实际的运用中,也建议,不要用一个单词大小写区分两个变量;
  4. 标识符(变量名称)一定要见名思意 :变量名称建议用名词,方法名称建议用动词*

入口方法

入口方法的两种定义方式

main() {
    print('你好 dart');
}
// void 表示 main 方法没有返回值
void main() {
    print('你好 dart')
}

类型详解

字符串类型

定义方式

基本:

var str1 = 'this is str1';
var str2 = 'this is str2';
print(str1);
print(str2);
String str1 = 'this is str1';
String str2 = 'this is str2';
print(str1);
print(str2);

进阶:

String str = '''this is str1
this is str2
this is str3
''';
printf(str); // 自带换行和空格

字符串拼接

String str1 = '你好';
String str2 = 'dart';
print("$str1 - $str2");
print(str1 + ' - ' + str2);

数值类型

int

必须是整型

int a = 123;
a = 45;
print(a);

double

既可以是整形,也可以是浮点型

double b = 23.5;
print(b); // 23.5
b = 24;
print(b); // 24.0

运算符

与 JavaScript、c 等语言一致。

布尔类型

bool

bool flag1 = true;
bool flag2 = false;
print(flag1);
print(flat2);

注意bool flag = 123 等赋值行为是错误的

if 判断语句

与 JavaScript、c 等语言一致。

List 集合(数组)类型

定义方式

1:普通方式

var list1 = ['张三', '李四', 20, true];
print(list1); // [张三, 李四, 20, true]
print(list1[0]); // 张三

2:指定类型

var list2 = <String>['张三''李四'];
print(list2);
var list3 = <int>[1, 2, 3];
print(list3);

3:追加数据

var list4 = [];
list4.add('张三');
list4.add('李四')

4:指定长度

var list5 = List.filled(2, '');
print(list5); // [,]
list5[0] = '三三';
list5[1] = '思思';
print(list6);
// list5.add('呜呜'); // 报错

5:前缀

List list6 = ['香蕉', '苹果', '西瓜'];
print(list6);
list6.add('草莓');

注意: 通过 List.filled 创建的集合长度是固定的:

var list6 = List.filled(2, '');
// list6.length = 0; // 报错
var list7 = <String>['张三', '李四'];
print(list7);
list7.length = 0; // 可以改变
print(list7); // []

List 常用的属性和方法

常用属性:

属性名作用
length长度
reversed反转
isEmpty是否为空
isNotEmpty是否不为空
List myList = ['香蕉', '苹果', '西瓜'];
print(myList.length);
print(myList.isEmpty);
print(myList.isNotEmpty);
print(myList.reversed); // 对列表倒序排序 (西瓜, 苹果, 香蕉)
var newMyList = myList.reversed.toList();
print(newMyList);

常见方法:

方法名作用注意
add增加
addAll拼接数组
indexOf查找传入具体值
remove删除传入具体值
removeAt删除传入索引值
fillRange修改
insert(index, value)插入指定位置插入数据
insertAll(index, list)插入指定位置插入list数据
tolist()转换其他类型转换成 List
join()转换List 转换成字符串
split()转换字符串转换为List
forEach/where/any/every遍历
List myList = ['香蕉', '苹果', '西瓜'];
// 1. add
myList.add('桃子'); // 增加一个数据
print(myList);
// 2. addAll
myList.addAll(['草莓', '桃子', '菠萝']);
print(myList);
// 3. indexOf
print(myList.indexOf('苹果')); // indexOf 查找数据,查找不到返回 -1,查找到返回索引值
// 4. remove
myList.remove('西瓜');
print(myList);
// 5. removeAt
myList.removeAt(1);
print(myList);
// 6. fiilRange -> 修改
List myList = ['香蕉', '苹果', '西瓜'];
// myList.fillRange(1, 2, 'aaa'); // [1, 2)
myList.fillRange(1, 3, 'aaa'); // 
print(myList);
// 7. insert -> 插入一个
myList.insert(1, 'aaa');
print(myList);
// 8. insertAll -> 插入多个
myList.insertAll(1, ['aaa', 'bbb']);
print(myList);
// 7. join -> List 转换为 String
List myList = ['香蕉', '苹果', '西瓜'];
var str = myList.join('-');
print(str); // 小脚-苹果-西瓜
// 8. split -> String 装换为 List
var str = '香蕉-苹果-西瓜';
var list = str.split('-');
print(list); // ['香蕉', '苹果', '西瓜']

Map(字典)

定义方式

1:推荐

var person = {
    "name": '张三',
    "age": 20,
    "work": ['程序员', '送外卖']
};
print(person);
print(person['name']); // 只能通过 [index] 索引

2:new Map()

var p = new Map();
p['name'] = '张三';
p['age'] = 20;
p['work'] = ['程序员', '送外卖'];

3:前缀

Map p = {
    "name": "张三""age": 20
};
print(p);

5.5.2 Map 常见属性和方法

常用属性:

属性名作用
keys获取所有的 key值
values获取所有的 values值
isEmpty是否为空
isNotEmpty是否不为空
// 1. kyes
Map person = {
    "name": '张三',
    "age": 30,
    "sex": '男'
};
print(person.keys); // (name, age)
print(person.keys.toList()); // [name, age]
print(person.values); // (张三,30)
print(person.values.toList()); // [张三,30]
// 2. isEmpty、isNotEmpty
print(person.isEmpty);
print(person.isNotEmpty);

常用方法:

方法名作用
remove(key)删除指定 key的数据
addAll({...})合并映射,给映射内添加属性
containsValue查看映射内的值,返回 true/false
forEach/where/any/every遍历
Map person = {
    "name": '张三',
    "age": 30,
    "sex": '男'
};
// 3. addAll
person.addAll({
    "work": ['敲代码', '送外卖'],
    "height": 182
});
print(person);
// 4. remove
person.remove('sex');
print(person);
// 5. containsValue
print(person.containsValue('张三'));

Set

Set 是没有顺序且不能重复的集合,所以不能通过索引去获取值。用它最主要的的功能就是去除数组重复内容。

var s = new Set();
s.add('香蕉');
s.add('苹果');
s.add('苹果');
print(s); // {香蕉, 苹果}
print(s.toList()); // [香蕉, 苹果]
List myList = ['香蕉', '苹果', '西瓜''香蕉', '苹果', '西瓜''香蕉', '苹果', '西瓜'];
var s = new Set();
s.adAll(myList);
print(s); // {'香蕉', '苹果', '西瓜'}
print(s.toList()); // ['香蕉','苹果','西瓜']

List、Map、Set 公有方法

forEach、map、where、any、every

List

List myList = ['香蕉', '苹果''西瓜']// for
// forEach
for(var i = 0; i < myList.length; i++) {
    print(myList[i]);
};
for(var item in myList) {
    print(item)
};
myList.forEach((value) {
    print(value); // 香蕉\n苹果\n西瓜
})
// map
List newList = [1, 3, 4];
var newList = myList.map((value) {
    return value * 2
});
print(newList); // (2, 6, 8)
print(newList.toList()); // [2, 6, 8]
// where
List myList = [1, 3, 4, 5, 6, 7, 8, 9];
var newList = myList.where((value) {
    return value > 5;
});
print(myList.toList()); // [6, 7, 8, 9] 
// any
List myList = [1, 3, 4, 5, 6, 7, 8, 9];
var f = myList.any((value) {
    return value > 5; // 只要集合里面有满足条件的就返回 true
});
print(myList); // true
// every
var f2 = myList.every((value) {
    return value > 5; // 集合里面的每一个元素都要满足条件才会返回 true
});
print(f2); // false

Map、Set

var s = new Set();
a.addAll([1, 22, 33]);
s.forEach((value) => print(value)); // 1\n22\n33
Map person = {
    "name": '张三',
    "age": 20
};
person.forEach((key, value) {
    print('$key -- $value'); // name -- 张三
})

类型判断

is 关键字判断类型

var str = '1234';
if(str is String) {
    print('String');
} else if(str is int) {
    printf('int');
} else {
    print('others');
}

Dart 函数

函数定义、传参

// main 函数外部调用 - 全局函数
void printInfo() {
    print('自定义方法')
}
​
String printUserInfo() {
    return 'this is str';
}
List getList() {
    return ['这是', 'List', '类型']
}
// void 表示没有返回值
void main() {
    print('调用系统内置的方法');
    printInfo();
    // main 函数内部定义
    int getNum() {
        var myNum = 123;
        return myNum;
    }
    var n = getNum();
    print(n);
    
    var str = printUserInfo();
    print(str);
    
    print(getList());
    
    void xxx() {
        aaa() { // 外部无法访问,局部作用域
            print('aaa');
        }
        aaa();
    }
}

实例

1-n 之和

int sum(int n) {
    var sum = 0;
    for(var i = 1; i <= n; i++) {
        sum += i;
    }
    return sum;
}
void main() {
    print(sum(15));
}

可选参数

String printUserInfo(String username, [int? age, String? sex]) {
  if (age != null) {
    return '姓名:$username --- 年龄:$age';
  }
  return '姓名:$username --- 年龄:保密';
}
void main() {
  print(printUserInfo('郭兴俊'));
}

实参带默认参数

String printUserInfo(String username, [int? age, String? sex = '男']) {
  if (age != null) {
    return '姓名:$username --- 年龄:$age --- $sex';
  }
  return '姓名:$username --- 年龄:保密 --- $sex';
}
​
void main() {
  print(printUserInfo('郭兴俊'));
  print(printUserInfo('cyq', 18));
  print(printUserInfo('wz', 18, '女'));
}

定义一个命名参数的方法

String printUserInfo(String username, { int? age, String? sex = '男' }) {
  if (age != null) {
    return '姓名:$username --- 年龄:$age --- $sex';
  }
  return '姓名:$username --- 年龄:保密 --- $sex';
}
​
void main() {
  print(printUserInfo('张三', age: 20)); // 不能直接写20,要写 age: 20
}

把方法当做参数

void main() {
  fn1() {
    print('fn1');
  }
  fn2(Function fn) {
    fn();
  }
  fn2(fn1);
}

匿名函数

void main() {
  var fn = () {
    print('我是一个匿名方法');
  }
  fn();
}

6.2 箭头函数

List list = ['苹果', '香蕉''西瓜'];
list.forEach((value) {
    print(value);
});
​
list.forEach((value) => print(value)}) // 没有分号,只能写一句
​
list.forEach((value) => {
    print(value); // 只能写一行
});

6.3 自执行方法

((int n) {
    print('我是自执行方法$n');
})(12);

6.4 闭包

闭包:函数嵌套函数, 内部函数会调用外部函数的变量或参数, 变量或参数不会被系统回收(不会释放内存)

闭包写法:函数嵌套函数,并 return 里面的函数,这样就形成了闭包。

为什么使用闭包:

  1. 全局变量特点:全局变量常驻内存、污染全局
  2. 局部变量特点:不常驻内存,会被垃圾回收机制回收、不会污染全局

使用闭包可以常驻内存而又不污染全局:

fn() {
    var a = 123; /* 不会污染全局、常驻内存 */
    return () {
      a++;
      print(a);
    };
}

Dart 类、对象

面向对象编程(OOP)的三个基本特征:封装、继承、多态

  • 封装:封装是对象和类概念的主要特性。封装,把客观事物封装成抽象的类,并且把自己的部分属性和方法提供给其他对象调用,而一部分属性和方法则隐藏。
  • 继承:面向对象编程(OOP)语言的一个主要功能就是"继承"。继承是指一种能力:它可以使用现有类的功能,并在无须重新编写原来的类的情况下对这些功能进行扩展。
  • 多态:允许将子类型的指针赋值给父类类型的指针,同一个函数调用会有不同的执行效果。

Dart 所有的东西都是对象,所有的对象都继承自 Object类。

Dart 是一门使用类和单继承的面向对象语言,所有的对象都是类的实例,并且所有的类都是 Object 的子类。

一个类通常由 属性方法 组成。

class 声明

class Person {
    Stream name = '张三';
    int age = 23;
    void getInfo() {
        // print('类中的方法');
        // print('$name -- $age');
        print('${this.name} -- ${this.age}');
    }
    void setInfo(age) {
        this.age = age;
    }
}
​
void main() {
    // 实例化
    // var p1 = new Person();
    Person p1 = new Person();
    print(p1.name);
    print(p1.getInfo());
    
    p1.setInfo(25); // 传入年龄
}

构造函数

class Person {
    String name;
    int age;
    /*
    Person(String name, int age) {
        print('这个方法在Person对象实例化后触发');
        this.name = name;
        this.age = age;
    }
    */
    // => 构造函数简写
    Person(this.name, this.age);
    void getInfo() {
        // print('类中的方法');
        // print('$name -- $age');
        print('${this.name} -- ${this.age}');
    }
}
​
void main() {
    Person p1 = new Person('王五', 25);
    p1.getInfo();
}

命名构造函数

构造函数可以写多个

class Person {
    Stream name = '张三';
    int age = 23;
    Person(this.name, this.age);
    Person.now() {
        print('我是命名构造函数');
    }
    Person.setInfo(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
​
void main() {
    Person p1 = new Person(); // 默认实例化类的时候调用的是,默认构造函数
    Person p2 = new Person.now('王五', 55); // 命名构造函数,
}

将类单独抽离成一个模块

Dart 中的私有方法和私有属性

Dart 和其他面向对象语言不一样,Data中没有 public、private、protected 这些访问修饰符,但是我们可以使用 _ 把一个属性或方法定义成私有。

但是必须抽离为 单个文件 才可以生效。

class Animal {
    String _name;
    int age;
    Animal(this._name, this.age);
    void printInfo() {
        print('${this._name} -- ${this.age}');
    }
    String getName() {
        return this._name;
    }
    void _run() {
        print('这是一个私有方法');
    }
    execRun() {
        this._run(); // 类里面方法的互相调用
    }
}
​
void main() {
    Animal a = new Animal('小狗', 3);
    // print(a._name); // 错误
    print(a.getName());
    a._run(); // 错误
    a.execRun(); // 间接地调用私有方法
}

get 和 set 修饰符

需求:获取正方形面积

普通实现

class Rect {
    num height;
    num width;
    area() {
        return this.height * this.width;
    }
    Rect(this.height, this.width);
}
void main() {
    Rect r = new Rect(10, 4);
    print('面积:${r.area()}');
}

get 和 set 实现

class Rect {
    num height;
    num width;
    get area {
        return this.height * this.width;
    }
    set areaHeight(value) {
        this.height = value;
    }
    Rect(this.height, this.width);
}
void main() {
    Rect r = new Rect(10, 4);
    // print('面积:${r.area}'); // 注意调用调用直接通过访问属性的方式访问 area
    r.areaHeight = 2;
}

Dart 类中的初始化列表

class Rect {
    num height;
    num width;
    Rect():height=2,width=10 {
        print('${this.height} -- ${this.width}');
    }
    getArea() {
        return this.height * this.width;
    }
}
void main() {
    Rect r = new Rect();
    print('面积:${r.getArea()}');
}

静态成员

Dart 中的静态成员:

  1. 使用 static 关键字来实现类级别的变量和函数;
  2. 静态方法不能访问非静态成员,非静态方法可以访问静态成员。
class Person {
    static String name = '张三';
    static void show() {
        print(name);
    }
}
main() {
    var p = new Person();
    // p.show(); // 报错
    print(Person.name); // 访问静态成员
    Person.show();
}
class Person {
    static String name = '张三';
    int age = 20;
    static void show() {
        print(name);
    }
    void printInfo() { // 既可以访问静态成员,也可以访问非静态成员
        print(name); // 访问静态成员
        print(this.age); // 访问非静态成员
        show(); // 调用静态方法
    }
    static void printUserInfo() {
        print(name); // 静态属性
        show(); // 静态方法
        // print(this.age); // 静态方法没法访问非静态的属性
        // print(age); // 静态方法没法访问非静态的属性
    }
}
main() {
    Person p = new Person();
    P.printInfo();
    
}

Dart 对象操作符

Dart 中的对象操作符:

操作符作用备注
?条件运算符了解
as类型转换
is类型判断
..级联操作连缀(重要)
class Person {
    String name;
    num age;
    Person(this.name, this.age);
    void printInfo() {
        print('${this.name} -- ${this.age}');
    }
}
main() {
    // 1. ?
    Person? p;
    p?.printInfo();
    // 2. is
    if(p is Person) {
        p.name = '李四';
    }
    if(p is Object) {
        p.name = '李四';
    }
    p.printInfo();
    // 3.as
    var p1;
    p1 = '';
    p1 = new Person('张三'20);
    (p1 as Person).printInfo();
    
    // 4. ..
    /*
    Person p1 = new Person('张三'20);
    p1.printInfo();
    p1.name = '李四';
    p1.age = 50;
    p1.printInfo();
    */
    p1..name = '李四'
      ..age = 50
      ..printInfo();
}

继承

面向对象的三大特征: 封装继承多态

Dart 种类的继承:

  1. 子类使用 extends 关键词来继承父类;
  2. 子类会继承父类里面可见的属性和方法,但是不会继承构造函数;
  3. 子类能复写父类的方法 getter 和 setter。
class Person {
    String name = '张三';
    num age = 20;
    void printInfo() {
        print('${this.name} --- ${this.age}');
    }
}
class Web extends Person {
    
}
main() {
    Web w = new Web();
    print(w.name);
}

super 关键词

class Person {
    String name;
    num age;
    Person(this.name, this.age);
    Person.xxx(this.name, this.age);
    void printInfo() {
        print('${this.name} -- ${this.age}');
    }
}
​
class Web extends Person {
    // Web(super.name, super.age);
    String sex = '';
    Web(String name, num age, String sex): super(name, age) {
        this.sex = sex;
    }
    // 如果想为Person某个静态初始化成员赋值
    /*
        Web(String name, num age, String sex): super.xxx(name, age) {
            this.sex = sex;
        }
    */
    run() {
        print('${this.name} -- ${this.age} -- ${this.sex}');
    }
}
main() {
    Person p = new Person('李四', 20);
    p.printInfo();
    Person p1 = new Person('李四', 20);
    p1.printInfo();
    
    Web w = new Web('张三', 12);
    w.printInfo();
    w.run(); // 自己方法有 run,则运行子类的 run方法;如果子类没有 run方法,则从父类中找 run方法。
}

子类复写父类

class Person {
    String name;
    num age;
    Person(this.name, this.age);
    void printInfo() {
        print('${this.name} -- ${this.age}');
    }
    work() {
        print('${this.name}在工作......');
    }
}
class Web extends Person {
    Web(String name, num age): super(name, age);
    run() {
        print('run');
    }
    // 复写父类的方法
    @override // 可以写也可以不谢
    void printInfo() {
        print('姓名:${this.name} -- 年龄:${this.age}');
    }
    run() {
        print('run');
        super.work(); // 子类调用父类的方法
    }
}
main() {
    Web w = new Web('李四', 20);
    w.printInfo();
}

Dart 中的抽象类、多态和接口

抽象类

Dart 中抽象类:Dart 抽象类主要用于定义标准,子类可以继承抽象类,也可以实现抽象类接口。

  1. 抽象类通过 abstract 关键字来定义;
  2. Dart 中的抽象方法不能用 abstract 声明,Dart 中没有方法体的方法我们称为抽象方法;
  3. 如果子类继承抽象类必须得实现里面的抽象方法;
  4. 如果把抽象类当做接口实现的话,必须得实现抽象类里面定义的所有属性和方法;
  5. 抽象类不能被实例化,只有继承它的子类可以。

extends 抽象类 和 implements 的区别:

  1. 如果要复用抽象类里面的方法,并且要用抽象方法约束子类的话,我们就用 extends 继承抽象类。
  2. 如果只是把抽象类当做标准的话,我们就用 implements 实现抽象类。

案例: 定义一个 Animal 类,要求它的子类必须包含 eat 方法。

abstract class Animal {
    eat(); // 抽象方法
    run(); // 抽象方法
    // 抽象类无法被实例化,只有继承它的子类才能实例化
    /*
        printInfo() {
            print('我是一个抽象类的方法')
        }
    */
}
class Dog extends Animal {
    @override
    eat() {
        print('小狗在啃骨头');
    }
    @override
    run() {
        print('小狗在跑');
    }
}
class Cat extends Animal {
    @override
    eat() {
        print('小猫在吃鱼');
    }
    @override
    run() {
        print('小猫在跑');
    }
}
main() {
    Dog d = new Dog();
    d.eat();
    Cat c = new Cat();
    c.eat();
}

多态

允许将子类类型的指针赋值给父类类型的指针,同一个函数调用会有不同的执行效果。

子类的实例赋值给父类的引用。

多态就是父类定义一个方法不去实现,让继承他的子类去实现,每个子类有不同的表现。

abstract class Animal {
    eat();
}
class Dog extends Animal {
    @override
    eat() {
        print('小狗在啃骨头');
    }
}
class Cat extends Animal {
    @override
    eat() {
        print('小猫在吃鱼');
    }
    run() {
        print('小猫在跑');
    }
}
main() {
    Animal d = new Dog();
    d.eat();
    Animal c = new Cat();
    // c.run(); // 虽然Cat 有run 方法,但是 Animal 没有,所以不能调用
}

接口

接口:就是约定、规范

和 Java 一样,dart 也有接口,但是和 Java 还是有区别的。

  1. dart 的接口没有 Interface 关键字定义接口,而是普通类或抽象类都可以作为接口被实现。。 同样适用 implements 关键字进行实现。
  2. 但是 dart 的接口有点奇怪,如果实现的类是普通类,会将普通类和抽象中的属性的方法全部需要覆写一遍。
  3. 因为抽象类可以定义抽象方法,普通类不可以,所以如果要实现像 Java 接口那样的方式,一般会使用抽象类。
  4. 建议使用抽象类定义接口。
abstract class Db {
  String uri = ''; // 数据库的链接地址
  add(String data);
  save();
  delete();
}
​
class Mysql implements Db {
  Mysql(this.uri);
  @override
  add(String data) { 
      print('这是mysql的data方法' + data);
  }
  save() { return null; }
  delete() { return null; }
  @override
  late String uri;
}
​
class Mssql implements Db {
  Mssql(this.uri);
  @override
  add(String data) { return null; }
  save() { return null; }
  delete() { return null; }
  @override
  late String uri;
}
​
main() {
    Mysql mysql = new Mysql('xxxx');
    mysql.add('1234');
}

文件抽离

Dart 中 implements 实现多个接口

abstract classA {
    String name;
    printA();
}
abstract classB {
    printB();
}
class C implements A, B {
    @override
    String name;
    @override
    printA() {
        print('printA');
    }
    @override
    printB() {
        print('printB');
    }
}
​
void main() {
    C c = new C();
    C.printA();
}

Dart 中的 mixins

mixins 的中文意思是混入,就是在类中混入其他功能。

在 Dart 中可以使用 mixins 实现类似多继承的功能。

因为 mixins 适用的条件,以下是 Dart2.x 中使用 mixins 的条件:

  1. 作为 mixins 的类只能继承自 Object,不能继承其他类;
  2. 作为 mixins 的类不能有构造函数
  3. 一个类可以 mixins 多个 mixins 类;
  4. mixins 绝不是继承,也不是接口,而是一种全新的特性。
class A {
    String info = 'this is A';
    void printA() {
        print('A');
    }
    void run() {
        print('A Run');
    }
}
class B {
    void printB() {
        print('B');
        print('B Run');
    }
}
class Person {
    Object name;
    num age;
    Person(this.name, this.age);
    void printPerson() {
        print('这是 Person 类\n ${this.name} --- ${this.age}');
    }
    void run() {
        print('Person run');
    }
}
class C extends Person with A, B { // A 先,所以下面 c.run() 先执行 A中的 run(),但是 Person 中的 run不会执行
    C(String name, num age): super(name, age);
}
void main() {
    var c = new C('张三', 20);
    c.printA();
    c.printB();
    print(c.info);
    c.printPerson();
    c.run();
}
class A {
    String info = 'this is A';
    void printA() {
        print('A');
    }
}
class B {
    void printB() {
        print('B');
    }
}
class C with A, B {}
void main() {
    var c = new C();
    print(c is C); // true
    print(c is B); // true
    print(c is A); // true
    var a = new A();
    print(a is Object); // true => 所有的类都继承于 Objet 类
}

Dart 泛型、泛型方法、泛型类、泛型接口

通俗理解:泛型就是解决 类、接口、方法 的复用性、以及对不特定数据类型的支持(类型校验)

// 代码冗余
String getData1(String value) {
    return value;
}
int getData2(int value) {
    retun value;
}
// 不指定类型放弃了类型检查。我们现在想实现的是传入什么就返回什么。比如:传入 number 类型必须返回 number
getData(value) {
    return value;
}
// 传入和返回类型都会校验
T getData<T>(T value) {
  return value;
}
// 传入校验,返回不校验
getData<T>(T value) {
    return value;
}
void main() {
  print(getData(21));
  print(getData<String>('12345'));
  print(getData<int>(12));
}

泛型类

Mylist 中的 list 不仅可以增加 int类型,也可以增加 String类型

class MyList<T> {
  List list = <T>[];
  void add(T value) {
    this.list.add(value);
  }
​
  List getList() {
    return list;
  }
}
​
main() {
  MyList l1 = new MyList<String>();
  l1.add('张三');
  // l1.add(12); 
  print(l1.getList());
}

泛型接口

Dart中的泛型接口:

实现数据缓存的功能:有文件缓存、和内存缓存。内存缓存和文件缓存按照接口约束实现。

  1. 定义一个泛型接口 约束实现它的子类必须有getByKey(key) 和 setByKey(key,value)
  2. 要求setByKey的时候的value的类型和实例化子类的时候指定的类型一致
// 代码冗余
abstract class ObjectCache {
  getByKey(String key);
  void setByKey(String key, Object value);
}
​
abstract class StringCache {
  getByKey(String key);
  void setByKey(String key, String value);
}
abstract class Cache<T> {
    getByKey(String key);
    void setByKey(String key, T value);
}
class FileCache<T> implements Cache<T> {
    @override
    getByKey(String key) {
        return null;
    }
    @override
    void setByKey(String key, T value) {
        print('我是文件缓存, 把 key=${key} vlue=${value} 写入到了内存中');
    }
}
class MemoryCache<T> implements Cache<T> {
    @override
    getByKey(String key) {
        return null;
    }
    @override
    void setByKey(String key, T value) {
        print('我是内存缓存, 把 key=${key} vlue=${value} 写入到了内存中');
    }
}
​
void main() {
    // MemoryCache m = new MemoryCache<String>();
    MemoryCache m = new MemoryCache<Map>();
    m.setByKey('index', { "name": '张三', "age": 20 });
}

Dart 中的库、自定义库、系统库、第三方库

在 Dart 中,库的使用是通过 import 关键字引入的。

library 指令可以创建一个库,每个 Dart 文件就是一个库,即使没有使用 library 指令来指定。

Dart 中的库主要有三种:

库名说明
自定义库import 'lib/xxx.dart'
系统内置库import 'dart:math'; import 'dart:io'; import 'dart:convert'
Pub 包管理系统中的库pub.dev/packages / pub.flutter-io.cn/packages / pub.dartlang.org/flutter/
  1. 需要在自己的项目创建一个 pubspec.yaml,内容如下;

    name: 'xxx'

    description: A new flutter module project.

    dependencies:

    http: ^0.12.0+2

    data_format: ^1.0.6

  2. 在 pubspec.yaml 文件配置名称。描述、依赖等信息;

  3. 然后运行 pub get 获取包下载到本地;

  4. 项目中引入库 import 'package:http/http.dart' as http; 看文档使用。

引入库的方法

导入自己的库

import 'lib/Animal.dart';

导入系统内置库

import 'dart:math';

导入系统内置库实现请求数据

async 和 await

这两个关键字的使用只需要记住两点:

只有async方法才能使用await关键字调用方法

如果调用别的async方法必须使用await关键字

async是让方法变成异步。

await是等待异步方法执行完成。

import 'dart:io';
import 'dart:convert';
​
void main() async{
  var result = await getDataFromZhihuAPI();
  print(result);
}
​
​
//api接口: http://news-at.zhihu.com/api/3/stories/latest
getDataFromZhihuAPI() async{
  //1、创建HttpClient对象
  var httpClient = new HttpClient();  
  //2、创建Uri对象
  var uri = new Uri.http('news-at.zhihu.com','/api/3/stories/latest');
  //3、发起请求,等待请求
  var request = await httpClient.getUrl(uri);
  //4、关闭请求,等待响应
  var response = await request.close();
  //5、解码响应的内容
  return await response.transform(utf8.decoder).join();
}

Dart 导入 Pub 包管理系统中的库

pub包管理系统:

  1. 从下面网址找到要用的库

pub.dev/packages

pub.flutter-io.cn/packages

pub.dartlang.org/flutter/

  1. 创建一个pubspec.yaml文件,内容如下

name: xxx

description: A new flutter module project.

environment:

sdk: ">=2.7.0 <3.0.0"

dependencies:

    `http: ^0.13.5`
  1. 配置dependencies
  2. 运行pub get 获取远程库
  3. 看文档引入库使用
import 'dart:convert' as convert;
import 'package:http/http.dart' as http;
import 'package:date_format/date_format.dart';

Dart 库的重命名(解决命名冲突)

冲突解决:

当引入两个库中有相同名称标识符的时候,如果是java通常我们通过写上完整的包名路径来指定使用的具体标识符,甚至不用import都可以,但是Dart里面是必须import的。当冲突的时候,可以使用as关键字来指定库的前缀。如下例子所示:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

部分导入

如果只需要导入库的一部分,有两种模式:

模式一:只导入需要的部分,使用show关键字,如下例子所示:

import 'package:lib1/lib1.dart' show foo;

模式二:隐藏不需要的部分,使用hide关键字,如下例子所示:

import 'package:lib2/lib2.dart' hide foo;

延迟加载

也称为懒加载,可以在需要的时候再进行加载。

懒加载的最大好处是可以减少APP的启动时间。

懒加载使用deferred as关键字来指定,如下例子所示:

import 'package:deferred/hello.dart' deferred as hello;

当需要使用的时候,需要使用loadLibrary()方法来加载:

greet() async {
   await hello.loadLibrary();
   hello.printGreeting();
}

Null safety 以及可控类型、非空断言

Null safety 翻译成中文的意思是空安全。

Null safety 可以帮助开发者避免一些日常开发中很难被发现的错误,并且额外的好处是可以改善性能。

Flutter2.2.0 (2021年5月19日发布),之后的版本都要求使用 null safety

类型作用
?可空类型
!类型断言

可空类型

void main() {
    String? username = '张三'; // String? 表示 username 是一个可空类型
    username = null;
    print(username);
}
List<String>? list = ['1', '2'];
list = null;
print(list);
String? getData(apiUrl) {
    if(apiUrl != null) {
        return 'this is server data';
    }
    return null
}
void main() {
    print(getData('xxx'));
    print(getData(null));
}

类型断言

String? str = 'this is str';
str = null;
print(str!.length); // 类型断言:如果str不等于null,会答应str的长度,如果str等于null,抛出异常

late 关键字

late 关键词主要用于延迟初始化。

class Person {
    late String name; // 不加 late,namename必须有初始值
    late int age;
    void setName(String name, int age) {
        this.name = name;
        this.age = age;
    }
    String getName() {
        return '${this.name} -- ${this.age}';
    }
}
void main() {
    Person p = new Person();
    p.setName('张三', 20);
    print(p.getName());
}

required 关键词

最开始 @required 是注解

现在它已经作为内置修饰符。

主要用于允许根据需要标记任何命名参数(函数或类),使得它们不为空。因为可选参数中必须有个 required

String printUserInfo(String username, { required int age, required String sex }) { // 形参,也可以不加required,写成 int age = 10 指定默认值。
    if(age != 0) {
        return '姓名:$username -- 性别:$sex -- 年龄:$age';
    }
    return '姓名:$username -- 性别:$sex -- 年龄:保密';
}
void main() {
    print(printUserInfo('张三', age: 20, sex: '未知' ));
}
// name可以传入可以不传入 age必须传入
class Person {
    String? name; // 表示可空属性
    late int age;
    Person({ this.name, required this.age });
    String getName() {
        return '${this.name} -- ${this.age}';
    }
}
void main() {
    Person p = new Person(
        name: '张三',
        age: 20
    );
    print(p.getName());
}