三十一. Dart

164 阅读12分钟

1 基础

1.1 认识Dart

  • flutter SDk中已经内置了Dart, 但是如果想单独学习Dart,并且运行自己的Dart,最好安装一个DartSDk.

  • Google为Flutter选择了Dart就已经是既定的事实,无论你多么想用熟悉的语言,比如JavaScript,Java,Swift,C++等来开发flutter,至少目前都是不可以的.

  • 终端 Dart test.dart aaa bbb 来运行dart文件

  • main函数是入口函数, Dart也支持模板字符串.

1.2 helloworld

main(List<String> args) {
    print("hello world");
}

// 单行注释
/* */ 多行注释
/// 文档注释, 以后可以通过dartdoc将注释转成文档(文档注释支持markdown语法)

1.3 声明变量

明确的声明: String name ="tom"
类型推导:
    var age = 9;   已经推导出你的类型是int, age = "aaa" 会报错
    final height = 1.88  只能被赋值一次,  再height = 2 会报错
    const job = 'it' 只能被赋值一次,  再 job = "dd" 会报错
    
    final 可以开始不赋值,只能赋值一次, 可以是静态的值也可以是运行时返回来的一个值.
    const 定义时必须赋值, 并且只能赋值一次, 只能是静态的值.
    ---------------------------------------------------
    
    class Person{
        String name = "";
        Person(String name) {
            this.name = name 
        }
    }
    final p1 = Person("tom")
    final p2 = Person("tom")
    p1和p2不是同一个对象, identical(p1, p2)这个函数可以看两个对象是否相等.
    const p3 = Person("会报错");
    
    ===================================================
    class Person4{
        final String name; // 所有的成员变量都是final修饰的, 构造器才可以用const修饰
        const Person4(this.name);
    }
    
    const p4 = Person4("不会报错");   // 常量构造函数
    const p5 = Person("不会报错");   // p5和p4是同一个对象

1.4 Number类型

  • int, double
(1) 字符串转数字:
var a = int.parse("111");
var b = double.parse("234.06");

(2) 数字转字符串:
var a1 = 333;
var a2 = 22.456
var t = a1.toString();
var m = a2.toString();
3.1415.toStringAsFixed(2);   // 四舍五入,保留2位有效数字

(3) int常用方法
    num.isEven;   // 是否奇数
    num.isOdd;   // 是否偶数
    num.isEmpty;   // 是否为空
    num.abs();   // 决定值
    num.toDouble();   // 转换为浮点型
(4) double常用属性
    num.toInt();   // 向下取整
    num.floor();   // 向下取整
    num.truncate();   // 丢弃数值的小数部分 向下取整
    num.floorToDouble();   // 向下取整再转为浮点型
    num.truncateToDouble();   // 丢弃小数部分 并转为浮点型
    num.round();   // 四舍五入取整
    num.ceil();   // 向上取整
    num.ceilToDouble();   // 向上取整再转为浮点型
    10.remainder(4);   // 10对4取余为2
(5) 12.gcd(18);   // 最大公约数, 6
(6) 155000.toStringAsExponential(3);   // 科学计数法
(7) 10.compareTo(12);   // 比较, 相同返回0 前边大返回1 后大返-1
(9) num.runtimeType;   // 运行时的类型
(10) num.isFinite;   // 是否为有限值
(11) num.isInfinite;   // 是否为无限值
(12) num.isNaN;   // 是否为非数值
(13) num.isNegative;   // 是否为负数
(14) num.sign;   // 获取值的符号
(15) num.bitLength;   // 获取当前int类型值需要的最小数 int独有属性


1.5 布尔类型

  • true false
  • flutter 没有非空即真,没有非0即真
  • if(flag) 这个flag必须是一个静态的布尔类型的值
  • isBool.toString(); 转字符串

1.6 String类型

Dart的字符串是不可变类型,  所有的方法都不会改变原来字符串,  而是返回一个新的字符串.
定义: 
    var s1 = "ddd"
    var s2 = 'aaa'
    var s1 = """ddd"""
    var s1 = ''' ttt
            www ''';   // 可以换行
    
(1)字符串和表达式拼接
    var name = "tom"
    var message = " my name is ${name} , 类型是 ${name.runtimeType}"
    也可以用 s1 + s2 进行拼接

(2) 使用r不会被转义
    r'hello  \n  Tom';
(3) 字符串空格拼接
    String str = '单引号空格字符串' '拼接' '1234';
(4) 字符串换行拼接
    String str = '单引号'
        '换行' '空格'
        '拼接';
(5) str.isEmpty 是否为空    str.isNotEmpty是否不为空

(6) str.startsWith('http');   // 判断是否以http开始
(7) str.endWith('http');   // 判断是否以http结束
(8) str.contains('http');   // 判断是否包含

(9) 字符串截取
    str.substring(0,3);
(10) 打印某个字符 首次出现的下标
    str.indexOf('t');
(11) 打印某个字符 末次出现的下标
    str.lastIndexOf('t');
    
(12) str.toLowerCase();   // 转小写
(13) str.toUpperCase();   // 转小写
(14) str.trim();   // 清空格
(15) str.trimLeft();   // 清除左边空格
(16) str.trimRight();   // 清除右边空格
(17) str.split("-");   // 分隔字符串
(18) str.replaceAll('old', 'new');   // 替换

(19) 正则表达式 
    RegExp(r'正则表达式')

1.7 List列表 数组

(1)
var list1 = ["abc", "cba", "nba"];   // 使用var声明会自动推断类型
var list1 = <String>['1234','456'];
List<String> list1= ['qwer'];
var list1 = List.filled(5, ' ');   // 定义固定长度的数组
(2)
list1.add("mba");   // 数组增加元素
list1.addAll([4,5,6]);   // 数组拼接
list1.insert(3,4);   // 数组插入元素
list1.insertAll(9,[4,5]);   // 数组整体插入9为下标
(3)
list1.remove(value);   // 删除元素
list1.removeAt(index);   // 通过下标删除元素
list1.removeLast();   // 删除最后一个元素
list1.removeRange(2,4);   // 范围删除,传入区间下标
list1.removeWhere((item) => item>4);
list1.clear();   // 删除所有元素
(4)
list1.setRange(2,5,[1,1,1,1]);   // 修改区间为指定的数值 2,5是下标 包左不包右,  注意第三个参数元素不够用的问题
list1.setAll(2,[1,1,1,1]);   // 修改某索引后的值,  第二个参数数组内有几个值就改变几个,  注意越界第二个参数元素多的问题
(5)
list1.fillRange(2,5,8);   // 将指定区间的每个元素都 替换成某个值
list1.replaceRange(2,5,[7,7,7,7,7,7]);   // 将指定区间的每个元素都 替换成参数数组内的值
(6)
其他类型转List  
    final temp = <int,String> {1:'aaa', 2:'ccc',3:'ddd'};
    final klist = temp.keys.toList(growable: false);
    final klist = temp.values.toList(growable: false);
数组转换为字符串
    list1.join('=');
(7) 
list1.indexOf(5);   // 查找元素是5的下标值 (首次)
list1.lastIndexOf(5);   // 查找元素是5的下标值 (末次)
list1.sublist(3,7);   // 截取区间值,返回数组,  不改变原来的数组list1
list1.getRange(3,5);   // 截取区间值返回迭代器
list1.any((value) => value > 5);   // 判断数组内是否有满足条件的值, 返回布尔
list1.contains(6);   // 判断是否包含, 返回布尔
list1.firstWhere((value) => value > 3);   // 获取满足条件的第一个元素
list1.lastWhere((value) => value > 3);   // 获取满足条件的最后一个元素
list1.indexWhere((value) => value>3);   // 获取满足条件的第一个元素的索引
list1.lastIndexWhere((value) => value>3);   // 获取满足条件的最后一个元素的索引

// 查找是否存在满足条件的唯一值,存在返回这个元素,不存在执行第二个函数, 存在多个会报错
list1.singleWhere((item)=>item>7 , orElse:() {print("不存在"); return -1;});  
list1.singleWhere( (item) => item>50 , orElse:() => '不存在' ) );

(8)
list1.length;   // 数据长度
list1.reversed;   // 数组逆序
list1.isEmpty;   // 数组是否为空   isNotEmpty
list1.first;   // 获取第一个元素
list1.toSet();   // 数组改变类型 会去重

(9) 数组遍历
    list1.forEach( (item){print(item);});
    list1.map( (item) => item>3 );
    for( var i = 0; i< list1.length; i++) { }
    for (var value in list) {}
    
    list.reduce( (value, item) ) =>  value + 1;
    list.sort();   // 数组排序
    
    f11.forEach((item) => { print(item) });   在dart的箭头函数中 函数体只能有一行 并且不能写结束的分号; 大括号可以省略.

1.8 集合Set ???

元素不重复, 无法通过下标取值

var set = {"abc", "cba", "nba"};
var set = <int> {2,3,4,5};
var set1 = new Set();
    set1.add(33);
    set1.addAll([77,65,53,99]);    // 添加多个


// 以下两步实现数组去重
var temp = Set<String>.from(list1);   // 列表转集合
var temp = list1.toSet();   // 列表转集合
temp.toList();   // 集合转列表

// 特殊操作
var set1 = {1,2,3,4,5};
var set2 = {4,5,6,7,8,9};
    set1.intersection(set2);   // 求交集
    set1.union(set2);   // 求并集
    set1.difference(set2);   // 求差集,set1有set2没有


set1.first;   // 返回第一个元素
set1.last;   // 返回最后一个元素


1.9 映射Map

(1) 
// 创建
    var map = {'tt': '字典', 'll': 数组};   // 使用var关键字声明参数,会自动推断类型.
    Map<String, int> map = {'key1': 111, 'key2': 123};

// 先声明后添加
    Map map = {};
    map['key1'] = 66;

// of创建方法
    map1 = Map.of(map);

// fromEntries 创建方法
    var map1 = Map.fromEntries(map.entries);

// identity创建方法, 创建一个严格的map
    var map1 = Map.identity();
    map1['ziDian'] = '字典';
    print('打印==$map1'); //打印=={ziDian: 字典}

// unmodifiable创建方法   创建一个不可修改、基于哈希值的Map,包含other所有的项
    var map = {'ziDian': '字典', 'shuZhu': '数组'};
    var map1 = Map.unmodifiable(map);
    print("打印==$map1"); //打印=={ziDian: 字典, shuZhu: 数组}
// fromIterables创建方法
    List<String> keys = ['ziDian', 'shuZhu'];
    List<String> values = ['字典', '数组'];
    var map = Map.fromIterables(keys, values);
    print("打印==$map");    //打印=={ziDian: 字典, shuZhu: 数组}

(2)
map.length;   // 字典长度
map.isEmpty;   // 是否为空
map.runtimeType;   // 运行时类型
map.hashCode;   // 打印哈希值
    var map = {'ziDian': '字典', 'shuZhu': '数组'};
    print("打印==${map.hashCode}"); //打印==986719720
map.keys.toList();   // 转字符串
map.values.toList();   // 转字符串
map['tt'];   // 根据key取值和赋值

(3)
// 添加元素  添加元素(如果key已存在,则是更新value)
    var map = {'ziDian': '字典', 'shuZhu': '数组'};
    map['ziFu'] = '字符';
    map['ziDian'] = '其他';
    print('打印==$map'); //打印=={ziDian: 其他, shuZhu: 数组, ziFu: 字符}

    var map = {'ziDian': '字典', 'shuZhu': '数组'};
    map['string'] = '字符串';
    map.addAll({'int': '整型'});
    print("打印==$map"); //打印=={ziDian: 字典, shuZhu: 数组, string: 字符串, int: 整型}

// putIfAbsent方法 添加 (如果key不存在,则添加,否则,不添加)
    var map = {'ziDian': '字典', 'shuZhu': '数组'};
    map.putIfAbsent('ziDian', () => '其他');
    print('打印==$map'); //打印=={ziDian: 字典, shuZhu: 数组}

// 是否包含指定的key
    var map = {'ziDian': '字典', 'shuZhu': '数组'};
    print("打印==${map.containsKey('ziDian')}"); //打印==true

// 是否包含指定的value
    var map = {'ziDian': '字典', 'shuZhu': '数组'};
    print("打印==${map.containsValue('字典')}"); //打印==true

// 遍历
    var map = {'ziDian': '字典', 'shuZhu': '数组'};
    map.forEach((key, value) {
       print('key = $key, value = $value');
    });
    
// 转成字符串
    var map = {'ziDian': '字典', 'shuZhu': '数组'};
    print("打印==${map.toString()}");  //打印=={ziDian: 字典, shuZhu: 数组}

// 删除
    var map = {'ziDian': '字典', 'shuZhu': '数组'};
    print("打印==${map.remove('ziDian')}"); //打印==字典
    print("打印==${map.remove('code')}"); //打印==null

// 清空
    var map = {'ziDian': '字典', 'shuZhu': '数组'};
    print("打印==${map.clear()}"); //打印=={}

// 
// 
// 
// 


1.10 Runes符文 (数据类型) ???

  • Runes对象是一个32位字符对象.它可以把文字转成符号表情或特定的文字.
  • 例如 '\u{1f44d}' 转换成

1.11 Symbol 反射 ???

  • 是使用#号开头的标识符

1.12 dynamic

  • 动态数据类型

1.13 运算符

= 赋值
/  除
~/ 除取整
%  除取模
类型判断运算符 is is!
条件访问属性?.  如果有才去访问,    var obj; print(obj?.length);

??= 判空赋值, 是判断name自己是否有值,如果没值 则给name自己赋个值
    var name = "tom";
    name ??= "jack";   // 如果name为null的时候就 让name的值为jack

?? 判空赋值
    var name = null;
    var temp = name ?? "jack"   // 如果name为null 则把jack赋值给temp

级联运算符:
main(List<String> args) {
    var p = Person();
    p.name = "tom";
    p.run();
    p.eat();
    // 级联运算符
    var p = Person()
            ..name = "tom"
            ..eat()
            ..run();
    
}

class Person {
    String name;
    void run() {
        print("running");
    }
    void eat() {
        print("running");
    }
}

2. 条件语句和函数

2.1 if和else

2.2 for循环

for(var i = 0; i<5;i++) {
    print(i);
}

for(var name in arr) {
    print(name);
}

2.3 函数

2.3.1 函数基础

dart中没有函数的重载

(1)声明:
    // 直接声明
    int sum(int n1, int n2) {
        return n1 + n2
    }
    // 返回值类型可以省略, 开发中不推荐
    sum(int n1, int n2) {
        return n1 + n2
    }
    
    // 匿名函数
    
    // 箭头函数
    
    // 立即执行函数
    ((int age) {print(age);})(17);
    
 
(2)参数:
    void say1(Strign name) {   // 必选参数
        print("")
    }
    
    void say2(Strign name, [int age=12, double height = 1.9]) {   // 位置可选参数
        print("")
    }
    
    void say3(Strign name, {int age=12, double height=1.9}) {   // 命名参数
        print("")
    }
    
    say3("tom", age: 5, height: 1);   // 调用的时候必须给相应的实参命名
    --------------------
    只有可选参数才可以有默认值
    
(3) 作用域和闭包

(4) 异步函数
    JavaScript中,异步调用通过promise来实现,    async函数返回一个promise, await用于等待Promise
    然而在Dart中, 异步调用通过Future来实现,     async函数返回一个Future,  await用于等待Future  
    

2.3.2 函数是一等公民

  • 在很多语言中,函数并不能作为一等公民来使用, 比如Java/OC, 这种限制让编程不够灵活,所以现在的编程语言基本都支持函数作为一等公民来使用,Dart也支持.
  • 这个意味着你可以将函数赋值给一个变量,也可以将函数作为另外一个函数的参数或者返回这来使用.
(1) 函数做为另外一个函数的参数
void test(Function foo) {
    foo(23);
}

int bar(a) {
    print("函数被调用")
    return 66;
}
------------------------
test(bar);

test((a) { 
    print("匿名函数被调用");
    return 10
});

箭头函数,函数体只有一行代码
test((a) => {print("箭头函数被调用")})
----------------------------
void test(Function foo) {
    foo(23);
}
// 真实开发一般很少 直接用Function, 而是指名具体类型(函数签名)
void test2(int foo(int num1, num2)) {
    foo(20, 30);
}
test2((num1, num2) {
    return num1+num2
})
--------------------------------------
// 函数签名 int foo(int num1, num2) 这种方式不好阅读, 用下边这种方式
typedef Calculate = int Function(int num1, num2);   // 就相当于让Calculate等于我们这个函数签名
void test3(Calculate foo) {
    foo();
}


(2) 函数做返回值
Calculate demo() {
    return (n,m) {
        return n+m;
    }
}

3. 类和对象

  • Dart是一个面向对象的语言,面向对象中非常重要的概念就是类,类产生了对象.
  • dart中可以实现一个类,implement
  • dart没有关键字来定义接口的,默认情况下所有的class都是一个隐式的接口
  • 抽象类做继承比较多一点
  • 接口是用来做实现的

3.1 类的定义

在Dart中,定义类用class关键字
类通常有两部分组成: 成员(member) 和 方法(method)
var p1 = Person();   // 我们现在调用的Person类调用的是一个默认的构造函数
class Person {
    String name;
    int age;
}
--------------------
(2)
一旦自己写了构造函数默认的构造函数就会被覆盖掉

// var p2 = Person2(); // 此行会报错
var p2 = Person2('tom',2); 
var p3 = Person2.abc('jack', 19, 1.88);

class Person2 {
    String name;
    int age;
    double height;
    
    Person2(String name, int age, {double height}) {
        this.name = name;
        this.age = age;
        this.height = height;
    }
    Person2(this.name, this.age, {this.height});   // 这是上边这种的简写
    
    // 命名构造函数( 相当于函数的重载 )
    Person2.abc(this.name, this.age, this.height);
    
    Person2.abc2(Map<String, dynamic> m){
        this.name = m["name"];
        this.age = m["age"];
        this.height = m["height"];
    };
    
    @override
    String toString() {    // 重写toString方法
        return "名字${this.name}";
    }
}


默认情况下所有的类都继承自Object
Object obj = "tom";   // 父类的引用指向子类的对象
obj.substring(1);   // 会报错

dynamic obj2 = "tom";
obj2.substring(1);   // 不会报错, dynamic 会根据赋的值类型 来动态的确定类型.
-----------------------------------------------------------------------
(3)初始化列表 (调用时机是在构造函数体执行之前 去执行)
class Person3 {
    final String name;
    final int age = 19;  // fainal定义的必须要赋初始值
    
    Person3(this.name) {
    
    }
}


class Person4 {
    final String name;
    final int age;  // fainal定义的必须要赋初始值
    
    Person4(this.name)
    {   // 在执行这个大括号的时候他表示的是 ,初始化列表已经执行完毕了,
        this.age = 10;   // 会报错
    }
}

class Person5 {
    final String name;
    final int age;  // fainal定义的必须要赋初始值
    
    Person5(this.name,{int age}):this.age = age ?? 10
    {   
    }
    
    // Person(this.name, {this.age = 10});   // 这种写法也是可以的, 这个只能是赋值操作
}
--------------------------------------------
(4)重定向构造函数

class Person6 {
    String name;
    int age;
    
    // Person(this.name): age=0;
    Person6(String name): this._internal(name, 0);   // 构造函数的重定向
    Person6._internal(this.name, this.age);
}

---------------------------------
(5) 常量构造函数
const p1 = const Person("tom");
const p2 = const Person("tom");   // 使用常量构造函数声明对象,要用const关键字 否则无效, 还是不同的对象
print(identical(p1,p2));   // 指向同一块内存空间

class Person7 {
    final String name;
    final int age = 12;
    const Person7(this.name);
}

(6) 工厂构造函数  ???
Dart提供了factory关键字,默认不需写return this, 工厂构造行数需要自己手动返回一个对象,最大的特点就是可以手动的返回一个对象.

需求: 传入的color是相同的,返回同一个对象. 传入的name是相同的,返回同一个对象

class Person8 {
    String name;
    String color;
    static final Map<String, Person> _nameCache = {};
    static final Map<String, Person> _colorCache = {};
    
    
    factory Person8.mmm(String name) {
        if(_nameCache.containsKey(name)) {
            return _nameCache[name];
        }
        else {
            final p = Person(name, "default");
            _nameCache[name] = p;
            return p;
        }
    }
    
    factory Person8.ccc(String color) {
        if(_colorCache.containsKey(color)) {
            return _colorCache[color];
        }
        else {
            final p = Person("default", color);
            _colorCache[color] = p;
            return p;
        }
    }
    
    Person8(this.name, this.color);
}

3.2 访问修饰 和 setter,getter 和 static

(1)
Dart与TypeScript不同,没有访问修饰符(public, protected, private)
Dart的类中,默认的访问修饰符是public, 如果以_开头则表示私有

(2)
Dart没有public provide...等关键字, 就是在变量之前价格下划线, 表示在当前包里边可以访问
<p align=left></p>
class Person9 {
    final String name;
    set setName(String name) {   // 方法名后需要小括号,  使用的时候不需要小括号
        this.name = name;
    }
    String get getName {   // 方法名后不需要小括号了,  使用的时候不需要小括号
        return name;
    }
}

(3) static关键字用来指定静态成员
    通过static修饰的属性是静态属性
    通过static修饰的方法是静态方法
    静态成员可以通过类名直接访问(不需要实例化), 因静态方法可以不实例化就可以使用 所以静态方法中不能使用this,   静态方法中只能访问静态属性.
    非静态的方法可以访问静态属性
    在外部非静态方法不用能类名点调用
    在外部不能通过实例对象访问静态属性

3.3 元数据

元数据以@开头, 可以给代码标记一些额外的信息
元数据可以用在 库,类,构造器,函数,字段,参数或变量声明的前边
(1)@override (重写)
    某个方法前边添加该注解后, 表示重写了父类中的同名方法
(2)@required (必填)
    可以通过@required来注解Dart中的命名参数,用来指示它是必填参数
(3)@deprecated (弃用)
    若某个类 某个方法加上该注解之后, 表示此方法此类不再建议使用
    

3.4 继承 和 抽象类 和接口

  • 类的继承只有单继承, 继承的时候必须实现抽象方法
  • Dart 接口可以多实现 implements, 实现的时候要实现所有方法
  • 如果原来的类实现已经有实现了,可以用混入with
(1)
子类通过extends关键字继承父类, 继承后,子类可以使用父类中可见的内容

子类可以通过super关键字来引用父类中的可见内容

子类对象调用say() 如果类中已经有了say()方法,就会调用自己的say(), 如果没有再去调用父类的say()

如果子类定义的say()方法,父类中已经有了, 那么建议前边加上@override, 不加也行.

子类构造函数 必须给父类构造函数传值 Son(String job) : super(job);

(2) abstract
抽象类可以没有方法的实现, 也可以有方法的实现.
抽象类不能实例化 ,继承抽象类必须实现抽象方法.
external 可以将方法的声明和实现分开写, 方法的实现可以用patch关键字.
抽象方法必须定义在抽象类中, 抽象类中也可以定义普通方法.

(3) implements接口  

(4) Mixin 混入是一段公共代码.混入有两种声明方式:   ???
    将类当作混入  class Abc{ }
        被Mixin的类只能继承自Object, 不能继承其他类. 
        被Mixin的类不能有构造函数
    使用mixin关键字直接声明 mixin Cdd{ } 
    
    混入可以提高代码复用的效率,普通类可以通过with来使用混入, 可以使用多个混入
    

3.5 泛型

  • 泛型是在函数,类,接口中 指定宽泛数据类型的语法
  • 泛型可以作用在不同的语法上, 作用在函数上叫泛型函数.
  • 通常写在尖括号中
(1) 泛型函数
返回类型  函数名 <泛型>  (参数类型 参数) {  }
String getStr<T> (T num) { return num.toString; }

getData<int>(12);

(2) 泛型类
(3) 泛型接口

3.8 库的定义 ???

  • library关键子, 通常在定义库时,我们可以使用library关键字给库起一个名字.
  • 但目前我们发现,库的名字并不影响导入,因为import语句用的是字符串URL
library math;
  • part 关键字,在之前我们使用student.dart作为演练的时候,只是将该文件作为一个库. 在开发中,如果一个库文件太大,用part拆分, 现在官网已经不建议用这个part关键字了. 用export代替