学Flutter之前你需要掌握的Dart基础知识(总结)

600 阅读10分钟

前言

本篇文章主要整理下在开始学习Flutter之前需要接触Dart的一些基础知识点,第一,给自己做一个总结,第二,防止新手重走弯路,相信你看完本篇文章,再去接触Flutter会变得容易一些,本篇文章所有的内容基于dart2.17.6版本。

在学习之前我们需要了解一个重要的概念,dart中的一切都是对象,包括null、函数等,对象对应的又都是一个具体的实体类,并且所有类的顶端都是Object,可以说dart是一个完全的面向对象的语言。

基本的数据类型

基本的数据类型常用的差不多就是这6个:数字字符串布尔ListSetMap

数字 num

dart中,数字类型统称为num类型,而num类型又分为double小数类型和int整数类型。
定义时,如果我们知道确切的数据类型,一般定义具体的int或者double类型。

例如:

num a = 1;
int b = 1;
double c = 0.5;
print(a);
/// 不报错
a = b+c;
print(a);
/// 编译期报错 num不可以与int 或double 计算 
// b= b+a;
print(b);
print(c);

运行结果:

image.png

关于num的一些常规用法,可以参考我之前这篇Dart中的常用运算符、算法汇总

字符串 String

dart中,字符串是UTF-16编码的的字符序列,可以用双引号或单引号赋值,单引号可以不使用转义符直接显示双引号,双引号同理,另外,还可以使用三个单引号或三个双引号赋值段落文章,无需换行转义符。

例如:

  String a = '"Dart基础知识"';
  String b = "'Dart基础知识'";
  String c = '''Dart
基础知识''';
// 支持显示emoji表情编码显示。
  String d = String.fromCharCodes(Runes("\u2665"));
 print(a);
print(b);
print(c);
print(d);

运行结果:

image.png

字符串还有一些特定的操作,比如运算符'a' * 2 = 'aa';Runes显示emoji的操作等,有兴趣的可以看String的源码。

布尔 bool

dart中,布尔类型使用bool关键字表示,为truefalse两个编译期常量,作为判断条件使用。

三元运算符判断:dart中支持三元运算符的判断,并可以嵌套使用。

例如我们需要判断一个字符串的内容:

num a = 1;
int b = 1;

String x = a == 0 ? "x" : "y";

String y = a == 0
    ? "x"
    : b == 0
        ? "y"
        : "z";
print(x);
print(y);
// 输出结果 为 y 、z。

相当于简化版 多重的 if else 判断。

List [ ]

dart中,List用来表示数组集合用中括号[]表示,支持泛型,内容有序可重复,

abstract class List<E> implements EfficientLengthIterable<E> {}

dart中默认可以存储不同数据类型,

// 定义a 数组
List a = ["1", 1, true, 0.5];
// 输出 a = [1, 1, true, 0.5];

定义了数组泛型,数组内数据类型也就固定了,在实际开发中,我们常用的还是固定的数据类型数组。

// 定义b 数组为int数组
List b = <int>[1,2,3];
// 输出 b = [1,2,3];

// 定义int类型空数组
List c = <int>[];

Set { }

dart中,Set用来表示无重复集合用大括号{}表示,支持泛型,内容无序不可重复,包含两个子类HashSetLinkedHashSet
HashSet是无序的,LinkedHashSet是有序的,维护了一个链表数据结构,我们创建的Set对象默认实现的就是LinkedHashSet,Set的重要的特点不保存重复数据。在某些数据去重的场景下我们可以使用Set。

abstract class Set<E> extends EfficientLengthIterable<E> {}

同List一样,Set默认也是可以存储不同类型的数据,并且自动去重。

Set s = {"a",1,1,true};
// 输出 s = {a, 1, true};

同样的,我们给他设置泛型,实际开发中,我们常用的还是固定泛型的数据。

Set s = <int>{1,2,3};
// 输出 s = {1, 2, 2};

Map {k : v}

dart中,Map为键值对存储数据结构用大括号加冒号表示{ k : v },包含两个子类HashMapLinkedHashMap
HashMap是无序的,key不可以重复,如果key重复,后者value将会覆盖前面的数据,LinkedHashMap是有序的,维护了一个链表数据结构,我们创建的Map对象默认实现的就是LinkedHashMap,和Set数据结构类似,一个是单数据结构,一个是键值对数据结构。这块数据结构知识点有兴趣的可以深入研究,这里就不展开讲了。

abstract class Map<K, V> {

map默认也是支持不同数据结构的,

// 默认支持不同类型的数据
Map map = {"a": "0",1:1};

但是实际开发中,我们一般将key的数据类型固定,value可以使用不同的数据类型,一般是这样定义数据结构。

// key为String,value 可以任意值
Map<String,Object> map = {"a": "0", "b": 1};

// 定义空map直接用大括号表示
Map<String,Object> map2 = {};

以上就是我们实际开发中常用的基本的数据类型。

变量声明

首先需要确定Dart是强类型语言,变量一旦赋值类型不可改变,这一点是跟Java一样的。

常量的声明

dart中,编译期的常量我们使用const关键字声明,运行时常量使用final关键字声明。

例如:

final int? a;// 待首次初始化完毕不可再次更改
/// 编译期常量 int 可省略
const int a = 1; // 或 const a = 1;

变量

声明变量前我们先了解下dart的空安全

空安全理解起来也非常的简单,就是我们在声明变量之前默认此变量为空,用?表示,比如以下代码:

// 表示a变量有可能为空
int? a;
// 表示 a 不可能为空 默认值= 0 
int a = 0;

dart的空安全解决了空指针异常的问题,尤其在方法入参时数据为空导致的空指针异常,也间接提高了代码的健壮性。

声明:
变量的声明一般我们使用具体的类型指定,同时还可以使用var关键字延迟指定,虽然dart支持延迟推断指定类型,但由于dart是强类型语言,所以在变量类型固定的时候声明变量不建议使用var关键字,缺点是影响代码的可读性。

late关键字,当我们可以百分百保证使用此变量的时候不为空,但又不想使用?这么麻烦, 这时候我们可以使用late关键字赋值,表示当前变量延迟初始化,并保证使用时不为空。
例如:

int? a;
int b = 0;
// 不需要?表示
late int a;

dynamic关键字,当我们无法确定当前数据类型时,可以使用dynamic来表示一个动态的变量,与var不同的是,他是真正的动态变量,可以在确定类型之后再次赋值其他类型。

dynamic z;
z = "dart";
print(z);
z = 123;
print(z);
z = true;
print(z);

运行结果:

image.png

dynamic不同于Objectdynamic是完全开放的,编译期的静态检查是完全通过的,他可以使用任何方法而编译期不报错,Object则不行。所以,使用dynamic关键字一定要慎之又慎,很有可能今天运行的好好的,明天就崩了。

私有变量: 使用开头为_的变量名表示。等同于java中的private

class A {
// 外部无法直接访问 _a变量
int? _a;
}

继承、实现、混入

dart中,支持单继承,多实现,多混入。

理解起来就是:dart中只能继承一个类,可以实现、混入多个接口。

但是在dart中是没有接口这个概念的,有的只是类、抽象类的概念。

继承extends
dart中父类的构造方法是无法继承的,可以继承父类的其他变量以及方法。

实现implements
实现的也是class,必须实现父类的所有方法,不管是不是抽象的。这个在我实际开发中一般少用。

混入with: dart独有的特性,with的并不是class关键字的类,而是mixin关键字,例如下方代码:我们用mixin创建了生命和动物,那么男人类就可以混入生命直接使用性别男的特点,猫属于生命和动物,他就可以同时混入生命和动物,实现吃和跑的快的特点,然而并不会成为生命和动物的子类,因为动物的很多特点猫也许没有,所以,dart中混入的概念极大的将类与类之间的关系解耦,降低了类与类之间的复杂程度,我们只需关心mixin当前包含的功能,而不会对混入他的类造成任何影响。混入在实际开发中比较常用,开发中常用混入可以降低代码的复杂度。

/// 生命
mixin Life {
  eat() {
    /// 
  }
  /// 性别男特点
  man(){}
  /// 性别女特点
  woman(){}
}

/// 动物
mixin Zoo {

  /// 速度快特点
  quickly() {}
  ///...
}


class Man with Life {

  @override
  man() {
    return super.man();
  }
}

class Cat with Life, Zoo {

  @override
  eat() {
   return super.eat();
  }

  @override
  quickly() {
    return super.quickly();
  }

}

以上就是dart中类之间的关系简单介绍。比较重要的就是特有的混入的概念,这个要好好掌握。

函数

函数也成为了方法,dart中,函数也是一个对象,用Function()表示,例如创建一个返回函数对象:

Function()? back;

调用这个函数使用back?.call();方法,上方函数是无参的,dart中函数的传参方式有必传参、可传可不传参两种方式。使用起来也很简单。

必传: 直接在小括号内即可,如果不使用?标记,dart默认为此参数不会空,你要传带?的参数编译期就会报错警告。

Function(int page)? loadData;

可传可不传: 选择传递参数也有两种方式,一种以以[]中括号的顺序传参,一种是以{}大括号键值对命名式传参,

顺序传参:

Function(int page, [String id])? loadData;
test() {
  loadData?.call(1, "");
}

命名传参:

Function(int page, {String id})? loadData;

test() {
  loadData?.call(1, id: "");
}

既然函数都是对象了,那函数当然也可以作为参数传递给另一个函数,比如将back函数作为参数传递给loadData函数:

Function()? back;
Function(int page, Function()? back)? loadData;
test() {
  loadData?.call(1,(){
    
  });
}

以上就是函数的基本使用,如何创建回调函数,了解更多可以看看这篇Dart中的函数Function解析

异步编程

dart中,异步编程使用 Futureasync-await一对关键字来实现,上方我们创建函数我们是没有设置返回值的,默认是void,接着以loadData为例,从网络获取数据可以说必须是异步操作,否则UI线程直接卡死,其他耗时操作一般我们也都要异步进行操作,那么我们修改上方我们创建的函数,

/// 创建异步函数
Future<String?> Function(int page, Function()? back)? loadData;
/// 测试调用异步函数 必须使用async、await关键字
Future<String?> test() async {
  return await loadData?.call(1, () {});
}

上方代码表示我们创建了一个异步函数,并且调用了他返回了异步Future对象,这就是dart中简单的异步编程,除了Future以外,dart还支持Stream异步,是以订阅监听方式来实现异步通知,这个类内容比较多,这里就不展开讲了,但是用的比较多的场景还是Fluture

其他

以上就是我认为的学Flutter之前dart入门知识,希望对你有所帮助 , 当然在实际开发中,我们还需要掌握具体类的具体用法等等,当然好的基础还有加上规范的代码,dart的编写规范可以看看Flutter中如何规范你的代码这篇。有漏掉的基础欢迎指正~