Dart & Flutter系列教程【2】——Dart语言

622 阅读8分钟

参考文档: 学习Dart语言,看这一篇文章就够了! 参考视频:B站大地老师的Dart&Flutter系列教程。

概述

Dart是脚本语言,与Flutter配合使用效果更佳!

Dart由谷歌开发的计算机编程语言,可以被用于web、服务器、移动应用和物联网等领域的开发。

Dart官网:Dart programming language | Dart

入口方法

main(){
    print('hello dart');
}
//若没有返回值,则void
void main(){
    print('hello dart');
}

Dart使用时不需要预先定义变量类型,dart会自动推导。该特性与笔者学习过的Kotlin特性一样。

但注意当显式声明时不一样!

//Dart
var str = 'i am dart';
string str = 'i am dart';
var str =123;
int str =123;

Dart的命名规则

  • 变量名称必须有数字,字母,下划线,和美元符号($)组成
  • 标识符的开头不能是数字开头
  • 标识符不能使用关键字和保留字
  • 变量的名称区分大小写的
  • 见名知意

Dart 常量

  • Dart常量使用final和const修饰

  • const修饰的常量在一开始需要赋值(编译时已经赋好值了,不能调用其他函数进行赋值)

  • final修饰的常量一开始可以的时候不赋值,但赋值后不可修改

const a = 0;
a = 1; //错误,常量值不能再修改了

final a = new DateTime.now(); //给a运行时赋值

数据类型:

常用数据类型:

  1. Numbers数值: int,double
  2. String字符串:String
  3. Booleans布尔:bool
  4. List数组:在Dart中数组是列表对象
  5. Maps字典:Map为键值对相关对象

var:可变 val:不变 任何值在定义时都存在类型推导!

数值类型

int 整型

int a = 1;

double 浮点型

double b = 2.5;
double b = 2;//不报错,自动补齐小数点后0

布尔类型

bool b1 = true;
bool b2 = false;
var b1 = true;

List集合类型

第一种定义List的方式

var l1 = ['a','b','c'];

第二种定义List的方式

新版本的dart里面不能使用该方法

var l2 = new List();
l2.add('a');
l2.add('b');
l2.add('c');

可以用的:通过[]创建的集合(不管最初创建是有初始还是空的)他的容量是可以变化的,也不限制类型

var l2 = [];
l2.add("张三");
l2.add(2);

第三种定义List方式:指定list的类型

var l1 = <String>["张三","李四"];

第四种定义List的方式

//var l4 = List.filled(length,fill);//传入集合长度和初始内容,之后不能超过也不能修改长度
var l4 = List.filled(2,"");//传入数据通过下标或者add函数

var l5 = List<String>.filled(2,"");//合法

list的相关方法

  1. toList() 转换为List类型
  2. reversed() 对列表倒序排列,输出结果为被()包裹的输出
  3. addAll() 传入的是数组,可以同时多个增加,例如addAll(['桃子','香蕉'])
  4. join() 转换为字符串,并以传入字符分割
  5. split() 以传入字符分割,并转换为集合
  6. indexOf('苹果') 查找到返回索引值,没有查到返回-1
  7. remove 删除对应内容
  8. removeAt 删除传入索引对应值

如果是普通的list,修改长度后再输出值则会输出空!

maps类型

第一种定义方式

var persion={
    "name":"Dart",
    "age":"8"
};
print(persion); //输出 {name: Dart, age: 8}
print(persion['name']); //输出 Dart
print(persion['age']); //输出 8

第二种定义方式

var persion1=new Map();
persion1["name"]="张三";
persion1["age"]="9";
print(persion1);                //输出 {name: 张三, age: 9}
print(persion1['name']);        //输出 张三
print(persion1['age']);         //输出 9

常用方法

key:values

  1. containValue() value是否包含传入值

  2. keys() 所有的keys

  3. toList() 以列表输出

  4. 对象名.forEach((key, value){ print("keykey---value"); })

    简写为

s.fotEach((value)=>print(value));
  1. map 返回value*2等
  2. where 返回符合条件的value
  3. any 返回是否满足条件(仅一个满足条件就行
  4. every 返回是否满足条件(所有都满足条件

类型判断(is 关键字)

var str = '111';
if (str is String) {//✔
    print("str is String type");
} else if (str is int) {
    print("str is int type");
} else {
    print("str is other type");
}

运算符

  1. 加+
  2. 减-
  3. 乘*
  4. 除/
  5. 取整~/
  6. 取余%

字符串类型

  1. 单引号与双引号
var str1 = 'I am str1';
var str2 = "I am str2";

String str3 = 'I am str1';
String str4 = "I am str2";
  1. 三引号
String str5 = """
i am str1
i am str2
i am str3
"""
  1. 字符串拼接
print("$str1 $str2");
print(str1 + str2);

运算符

算数运算符

5/4=1.25, 有小数

取整

int a=5;
int b=4;
 
print(a+b);  //加
print(a-b);  //减
print(a*b);  //乘
print(a/b);  //除
print(a%b);  //取余
print(a~/b); //取整(重点)

关系运算符

int a=5;
int b=4;
 
print(a==b);  //是否相等
print(a!=b);  //是否不相等
print(a>b);  //是否大于
print(a<b);  //是否小于
print(a>=b);  //是否大于或者等于
print(a<=b); //是否小于或者等于

逻辑运算符

取反(!)

var b =false;
print(!b);       //输出为true

且运算(&&)

var a = true;
var b = false;
print(b && a);    // 输出false

或运算( | | )

var a = true;
var b = false;
print(b || a);    // 输出true

赋值运算

直接赋值(=)

int b = 6;

判断为空之后赋值(??=)

//当b在之前没有被赋值,则在这行代码中将被赋值
int b;
b??=6;

复合赋值运算符(+=,-=,*=,/=,%=,~/=)

int b = 6;
b += 10;
print(b);   //输出 16

条件表达式

if else

switch case

三目运算( ? :)

??运算符

解释:当a为null时,赋值??符号后的10给a,然后将a赋值给b

当a不为null时,直接将a赋值给b

var a; 
var b = a ?? 10;
print(b);    // 输出10

类型转换

//将字符串转为整型
String str ='111';
var num = int.parse(str);

//将整型转为字符串
var num = 18;
var str = num.toString();
print(str is String);//true
String str = '';   //字符串为空,转换为整型会报错
try {
  var myNum = int.parse(str);
  print("myNum");
} catch (err){
  print("转换错误");
}

循环语句

for循环

while和do while

break和continue

函数

void main(List<String> args) {
  print(printInfo());
}

int printInfo(){
  print("我是一个自定义方法");
  app(){
    print("内置方法");
  }
  app();
  return 123;
}

方法的定义

方法传参

位置可选参数

  • []内为可选的传参。
String getPersionInfo(String name, [int age, String sex]) {
  return "name : $name   ;    age : $age    ;    sex : $sex";
}
print(getPersionInfo("name"));                // 输出name : name   ;    age : null    ;    sex : null
print(getPersionInfo("name", 8));             //输出name : name   ;    age : 8    ;    sex : null
  • 可选参数可以通过"="赋一个默认值。当调用方法时没有传入参数sex,则sex默认被赋值为"man"。赋值
String getPersionInfo(String name, [int age, String sex = "man"]) {
  return "name : $name   ;    age : $age    ;    sex : $sex";
}

名字可选参数

String getPersionInfo(String name, {int age, String sex = "man"}) {
  return "name : $name  -----  age : $age  -----  sex : $sex";
}
 
print(getPersionInfo("Dart", age: 8));                //输出  name : Dart  -----  age : 8  -----  sex : man
print(getPersionInfo("Dart", sex: "girl"));           //输出  name : Dart  -----  age : null  -----  sex :girl
print(getPersionInfo("Dart", sex: "girl", age: 8));   //输出  name : Dart  -----  age : 8  -----  sex : girl

在{}外的参数为必传参数,而{}里的参数为可选参数,即可传可不传,可以不按照{}里的参数顺序传参,而当传{}中参数时,用参数名:参数值的方式。

方法可以作为参数传递

method1(){
    print("I am method one");
}

method2(f()){
    f();
}

//调用method2
method2(method1);

与匿名方法一样

var fn=(){
    print("一个匿名方法");
};

fn是一个变量,代表着这个(){ print("一个匿名方法"); };方法,可以直接当作参数传递。

箭头函数

箭头函数的指向后面只能有一句话

// 当数组中的元素大于5,则返回5
List list = [2, 4, 6, 5, 8];
var newList = list.map((e) {
  if (e > 5) {
    return 5;
  }
  return e;
});
print(newList.toList());

//结果:[2, 4, 5, 5, 5]


//用箭头后
//使用三目运算,将函数体简化成一句话,输出的结果和上面的例子是一样的
List list = [2, 4, 6, 5, 8];
var newList1 = list.map((e) => e > 5 ? 5 : e);
print(newList1.toList());

需求:定义一个方法isEvenNumber来判断一个数是否是偶数,将10传入,判断1到n=10之间的偶数并输出

void main(List<String> args) {
  bool isEvenNumber(int n){
    if(n%2==0){
        return true;
    }
    return false;
  }

  printNum(int n){
    for(var i=1; i<n; i++){
      if(isEvenNumber(i)){
        print(i);
      }
    }
  }
  printNum(10);
}

匿名方法

没有名字的方法,匿名方法可以赋值给一个变量。

常见使用场景:

  1. 将一个匿名函数赋值给一个变量;
  2. 在传参的时候,把匿名函数作为参数传递。
var fun = (){//定义一个变量fun(),这个变量的值是后面的方法体(匿名方法)
    print("我是匿名方法");
};

fun();//fun()是一个变量,在这里使用了这个变量

当可以带参数时

var fun = (int i) {               //定义了一个变量fun,使用这个变量的时候需要带上一个参数
  print("我是匿名方法 :$i");
};
 
fun(2);                           //这里使用fun(2)这一个变量   

自执行方法

不需要自动调用该方法,当程序启动时候就自动那个执行该段代码

((){

    //这里写语句

})();

若需要传参

//在括号里定义传入参数,在前面括号定义形参,最后的括号传入实参
((int i){
    print(i);
    print("这是一段自执行代码!");
})(50);    
//50
//这是一段自执行代码!

闭包

局部变量定期被回收;全局变量能在任何地方被调用,但容易污染全局。

常驻内存,但又不污染全局。

void main(List<String> args) {
  
  fn(){
    var a = 123;//不污染全局,同时常驻内存
    return(){
      a++;
      print(a);
    };
  }

  var b = fn();
  b();
  b();
  b();
}

输出:

124
125
126

类,对象

Dart基础定义

  • 面向对象
  • 三个基本特征:封装、继承、多态
  • Dart所有东西都是对象!这与Kotlin高度相似,所有对象都是继承自Object类。
  • Dart是单继承的面向对象语言:
    • 单继承指一个子类最多只能有一个父类。
    • 多继承会带来二义性
  • 一个类通常由属性和方法组成

类的构造函数

  1. 默认构造函数 一个类里面仅有一个,默认调用。

  2. 命名构造函数

    当通过指定的命名构造函数实例化对象时,会调用命名构造函数,命名构造函数可以有多个。

class Persion {     //persion为类名
      String name;//属性
      int age;
 
 //默认构造函数,当实例化一个对象时,会自动调用到该函数
  Persion(this.name, this.age); 
 
  Persion.now() {
      print("这是一个命名构造函数");
  }
 
  getInfo() {   //方法
        print("name : $name  age : $age");
  }
}
 
void main() {
    //实例化对象(调用了默认构造函数)
      Persion man = new Persion("ShenZhen", 40);    
      man.getInfo();
      
     //实例化对象的时候调用了命名构造函数
      Persion man2 = new Persion.now();             
}

类的私有方法和属性

Dart语言中使用下划线_表示该方法或属性是私有的

int _a;

class Persion {
  String _name;                            //私有属性
 
  Persion(this._name);                     //公有方法,返回私有属性
 
  getName() {
    return _name;
  }
}
 
void main() {
  Persion man = new Persion("Dart");
  String myName = man.getName();           //Persion类的实例对象通过Persion类的公有方法getName()获取类中的私有属性
  print(myName);
}

ps:只有当类定义在其他独立的文件上时下划线表示私有性才是有效的,若和主入口函数main()在同一个文件下,私有性不会生效。

类中的getter和setter修饰

用方法前面的get、set去限制是get set方法

class Persion {
  String _name;
 
  Persion(this._name);
 
  get getName {
    return _name;
  }
 
  set setName(value) {
    _name = value;
  }
}
 
void main() {
  Persion man = new Persion("深圳");           //实例化一个Persion对象
  print(man.getName);                          //和调用类的属性的方式一样。通过“对象.属性”的方式调用get修饰的方法体
 
  man.setName = "惠州";                        //通过“对象.属性 = 值”的方式调用set修饰的方法体
  print(man.getName);
}

初始化列表

class Rect{
  int height;
  int width;
  Rect(): height = 2, width = 10{
  }

  getArea(){
    return this.height*this.width;
  }
}

void main(List<String> args) {
  Rect r = new Rect();
  print(r.getArea());
}

输出${}的作用

class Rect{
  int height;
  int width;
  Rect(): height = 2, width = 10{
    //下面两种方法输出值相同
    print("${this.height}");
    print(this.height);

    //区别
    print("${this.height}----${this.width}");
    print(this.height+this.width);
    // print(this.height+"----"+this.width);//出错,不合法
  }

  getArea(){
    return this.height*this.width;
  }
}

void main(List<String> args) {
  Rect r = new Rect();
  print(r.getArea());
}

类中的静态方法和静态成员

static 关键词实现类级别的变量和函数

class Persion {
  static String name = "深圳";//name为static修饰的静态变量
 
  static void show() {
    print("name : $name");
  }
}
 
void main() {
  print(Persion.name);                        //使用name这个属性时直接通过“类名.属性”的方式       
}

ps:静态方法不能访问非静态成员,非静态方法可以访问静态成员、静态方法、非静态成员。

对象操作符

  1. ? 条件运算符
  2. as 类型转换
  3. is 类型判断
  4. .. 级联操作

? 条件运算符

在对象后面使用,判断该对象是否为null

class Persion {
  String name = "深圳";
  Persion(this.name);
  void show() {
    print("name : $name");
  }
}
 
void main() {
  Persion man;//这里只是定义了一个Persion的对象man,但是没有给man赋值
  print(man?.name);//这里会报错,使用了条件运算符?判断man是一个空值,故不会打印也不会报错
}

as 类型转换

使用as进行类型的转换

man as Persion   //将对象man转换为Persion对象

is 类型判断

用is判断该变量是什么数据类型

Persion man=new Persion("name");
if(man is Persion){                          //判断man是否是Persion类型
  print("true");//✔
}

.. 级联操作

在对象后面使用级联连符号,赋予属性和调用方法。(类似Java中的Builde建造者模式)

class Persion {
  String name ;
  int age ;
 
  Persion(this.name,this.age);
 
  void show() {
    print("name : $name  and  age : $age");
  }
}
 
void main() {
  Persion man = new Persion("深圳",40);
  man..name = "惠州"//使用..name后返回的还是man对象,可以进行接下来..age的操作
     ..age=50
     ..show();
}

继承

  1. 一个子类继承自一个父类,那么这个子类的实例化对象直接使用这个父类的属性或方法。继承相关关键字:extent、super。
  2. 注释1:重写父类的方法
  3. 注释2:通过super关键字调用父类的方法
class Person {
  String name ;
  int age;
  Person(this.name,this.age);
  void show() {
    print("name : $name  and  age : $age");
  }
}
 
class Superman extends Persion{//Superman继承Persion
  
  Superman(String name, int age) : super(name, age);//super()里的参数是要传递给父类的参数
  
  //注释1:在子类中重写父类的方法
  /*void show(){
      print("姓名:$name---年龄:$age");
  */
  
  //注释2:用super调用父类方法
  /*
  void show(){
      super.show();
  }
  */
}
 
void main() {
  Superman man = new Superman("深圳",40);//Superman实例化对象
  man.show();//Superman实例化的对象可以直接使用父类Persion的方法show();
}

结果: name:深圳 and age:40

注释1去掉注释后: 姓名:深圳---年龄:40

注释2去掉注释后: name:深圳 and age:40

抽象类(与Java类似)

抽象类在笔者看来,最大的作用就是规范,在多人协作时能限制传入该方法的信息。

  1. abstract关键字代表抽象
  2. Dart中没有方法体的方法为 抽象方法;Dart中抽象类不能通过abstract声明
  3. 子类继承抽象类必须实现里面的抽象方法;
  4. 将抽象类当做接口实现时必须实现抽象类里面定义的所有属性和方法;
  5. 抽象类不能实例化,只有继承他的子类可以。

extends抽象类 和 implement 的区别:

  1. 如果要复用抽象类里面的方法,并且要用抽象方法约束自类的话我们就用extend继承抽象类
  2. 如果只是把抽象类当作标准的话我们就用implement实现抽象类
abstract class Animal {                                //Animal 为抽象类
  eat();                                               //没有实现方法体,默认是一个抽象方法
}
class Dog extends Animal{
  @override
  eat() {                                             //如果在Dog类中没有定义eat()方法,将会报错
    // do something
  }
}

多态

一句话理解就是,在动物这个分类中有狗和猫,但两种不同的动物,狗是旺旺叫,猫是喵喵叫。

Animal d = new Dog();                   //使用d.eat()的时候会调用Dog类中复写的eat()方法
Animal c = new Cat();                   //使用c.eat()的时候会调用Cat类中复写的eat()方法

接口(一个类可以实现多接口)

Dart语言中普通的类或者抽象类都可以作为接口被实现。implement关键字

ps:属性和方法均需要重写

abstract class Animal {                           //抽象类,用作接口
  String size;
  eat() {
    //do something
  }
}
 
class Dog implements Animal {                      //implements 用于实现接口
  @override
  String size;                                     //需要重新定义属性size
  
  @override
  eat() {                                         //需要重新定义方法eat()
    // do something
  }
}

mixins(为了实现类似多继承)

mixins:混入,在类中混入其他功能。一种全新的特性

关键字:with

Dart2.x中使用minins的条件:

  1. 作为minxins的类只能继承自Object,不能继承其他类
  2. 作为minxins的类不能有构造函数
  3. 一个类可以minxins多个minxins类
  4. minxins绝不是继承,也不是接口,而是一种全新的特性
class A {                    //A作为minxins类,只能继承自Object
  doA() {
    print("I am A");
  }
  
  run(){
      print("run A");
  }
}
 
class B {                   //B作为minxins类,只能继承自Object
  doB() { 
    print("I am B");
  }
  
  run(){
      print("run B");
  }
}
 
class C with A, B {}//C混合了A类和B类,类似继承,C的实例化类可以使用A类以及B类中的方法               
 
main() {
  C c = new C();
  c.doA();//I am A
  c.doB();//I am B
  c.run();//run B
}

当A与B中有同名方法,C混合A、B后将会调用with关键字上靠后的混合类的方法。

泛型

T代替所有传入的可能类型,并且该方法可选择是否支持类型校验。

泛型方法

T getData<T>(T value) {                  //传入的实参是什么类型,则“T”就代表该类型
  return value;
}
 
print(getData<String>("深圳"));          //<String>中的String为检验传的参数是否是String类型
//输出结果:深圳

泛型类

实例化类的时候没有指定类型

class ListTest<T>{
    List list = new List<T>();
    
    void printList(){
        for(var i = 0; i<this.list.length; i++){
            print(this.list[i];
        }
    }
    
    void add(T t){
        this.list.add(t);
    }
}
    
main(){
        ListTest list = new ListTest;//实例化ListTest
        list.add(1);
        list.add(2);
        list.add(3);
        list.add("深圳");
        list.printList();
}

结果: 1 2 3 深圳

实例化类的同时指定了类型

当实例化泛类型的时候传入了指定类型,那么在调用其中该泛型类中的方法时会进行类型校验,只能使用指定的类型。否则将会报错。

main() {
  ListClass list = new ListClass<int>();//指定了实例化ListClass 类时传入的类型为int类型
  list.add(1);
  list.add(2);
  list.add(3);
  list.add("深圳");//报错
  list.printInfo();
}

泛型接口

在具体的类实现了泛型接口后,实例化该类需要指定传入的类型

abstract class Cache<T> {//抽象类Cache,此处做接口使用
  getByKey(String key);
  setByKey(String key, T value);
}
 
class FileCache<T> implements Cache<T> {//Cache类的实现类
  @override
  getByKey(String key) {
  }
 
  @override
  setByKey(String key, T value) {}
}
 
main() {
  FileCache fileCache = new FileCache<String>();//实例化FileCache对象的时候,指定"T"的类型
  fileCache.setByKey("name", "深圳");
  fileCache.setByKey("name", 123);//报错,指定setByKey()的第二个参数为String类型,但是这里传入了int类型
}

Dart库

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

library指令可以创建一个库,每个Dart文件都是一个库(即使不用library指令来指定)

Dart中的库有三种:

  • 自定义的库
  • 系统内置库
  • Pub包管理系统中的库(第三方库)

自定义库

格式:

import 'lib/xxx.dart';
当一个类的内容过多时,若把这个类与main()主方法或与其他类写在同一个文件中,将会导致这个文件过大而不便于管理,此时我们可以把这个类独立成一个文件。当另一个类需要使用这个独立成文件的类时,通过以上的引用格式,就可以使用这个独立类里的方法了。

系统内置库

dart:math';

格式

import 'dart:math';

里面有math计算相关的函数

min(20,10);
max(20,10);

dart:io';

格式

import 'dart:io';
  1. async 让方法变成异步方法。
  2. await 等待异步方法执行完毕。

////

  1. 只有asyn方法才能使用await关键字去调用方法
  2. 如果调用别的async方法必须使用await关键字
import 'dart:io';

main(){
    test1();//调用test1()方法,要使用await关键字,否则若有返回值会报错
    test2();//调用test2()方法
    print("----over----");
}

test1() async{//以为该方法调用了异步方法test3,所以此方法为async异步方法
    print("test---1");
    await test3();//调用了异步方法test3(),所以这里必须用await关键字调用
    print("test---1.1");
}

test2(){
    print("test---2");
}

test3() async{//声明test3()方法为异步方法
    print("test---3");
}    

结果:

test---1
test---3
test---2
----over----
test---1.1

解释:在main()方法中依次调用了test1()方法和test2()方法,test1()方法为异步方法,故不需要test1()方法执行完才开始执行test2()方法,在test1()方法中调用了异步方法test3(),这里使用了await关键字来调用test3()方法,此时test2()方法同时在执行。

Pub包管理系统中的库(第三方库)

  1. 从下面网址中找到使用的库:

pub.dev/packages

pub.flutter-io.cn/packages

pub.dartlang.org/flutter/

  1. 找到你想导入的包

image.png

  1. 点击Installing

image.png

从而了解到

dependencies:
  http:^0.13.5
  1. 在项目中创建pubspec.yaml文件,写入以下数据
name: xxx
description: A new flutter module project.
dependencies:
  http: ^0.13.5
  1. 运行put get,获取远程库/包下载到本地

  2. 点击Example,了解如何使用库(看文档)

    • import对应库 eg.import 'package:http/http.dart' as http;
    • copy使用的语句

image.png

注意点:Flutter、sdk的版本适配问题;format指语言格式,使用时在http后添加即可。

Dart库冲突的解决办法

当两个库中有相同名称的标识符时,我们不能辨别我们要使用到的标识符是属于哪一个库里面:(对比

在Java中通常是通过导入完整的包名路径来指定。

在Dart语言中要使用到库的重命名的方法。

例:

在当前类中引用了两个库文件,Persion1.dart和Persion2.dart,着两个库文件中都有对Persion类的定义,当前类中有一个main()方法,在这个方法实例化了一个Persion类,IDE不清楚调用的为哪一个库文件里定义的Persion类。

import 'package:flutter_app_demo14/Persion1.dart';
import 'package:flutter_app_demo14/Persion2.daert';

main(){
    Persion p = new Persion();//报错,IDE不清楚调用的是哪一个库文件里定义的Persion类

解决办法: 使用as关键字给引用到的库重命名

格式

//库名 as XXX
import 'package:Dart/Persion1.dart';
import 'package:Dart/Persion2.dart' as lib;//as关键字给库重命名为lib

main(){
    Persion p = new Persion();//这里Persion使用的时Persion1.dart里的
    lib.Persion p1 = new lib.Persion();//这里Persion使用的是Persion2.dart里的
}

库的部分导入

部分导入的两种模式:

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

import 'Person1.dart' show getName;//此时可以使用Persion1.dart库文件中的getName()方法

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

import 'Persion1.dart' hide getName;//此时不可以使用Persion1.dart库文件中的getName()方法

库的延迟导入

另外说明

  1. part 'comman.dart' 整一个文件太大,将其分段
  2. export 'dart:convert' 导出这个库