概述
- 强类型语言,静态类型。语法类似于 Java
- 支持两种编译模式:
- JIT: 即时编译,开发期间做到更快的编译和更快的代码重载。但也有劣势就是在运行时需要将代码编译为机械码,那么直观感受就是慢,所以我们在开发期间有时候会发现卡顿,但是打 release 包之后效果会变好。
- AOT: 事前编译,release 期间已经编译为二进制代码,所以加载会更快更流畅。
常用数据类型
任何语言的基础都是数据类型,dart 也一样。dart 中主要有数字、字符串、布尔、集合和 var 类型。
num 类型
num 是 dart 中的数字类型的父类,它有两个子类: int 和 double。当类型指定为 num 的时候可以赋值为小数也可以赋值为整数,但是一旦指定了某一个具体的类型,就只能赋值这一类型的值。
1 | void _numType() { |
其中这些类型之间是可以相互转换的,如:
1 | print(num1.abs()); // 取绝对值 输出 2.0 |
String 类型
String 的定义也比较简单,它可以使用单引号也可以使用双引号定义,定义一个 String 类型后面可以通过逗号隔开定义多个变量。在 dart 中拼接字符串可以是 + 号链接,还可以使用 $ 符号进行拼接,如果是变量则直接使用 $xx,如果是表达式则使用 ${xxx} 将其包裹起来。
1 | void _stringType() { |
字符串还有一些常用的 API,比如字符串截取,获取指定字符串位置,匹配字符串开头等等。
1 | print(str3.substring(1, 5)); //符串数据 输出 符串数据 |
布尔类型
Dart 中布尔类型是强 bool 类型,只有 bool 类型的值为 true,才被认为是 true。bool 还有一些常用的运算符比如 || (或) 或者 && (且),在运算的时候,教大家一个口诀就是,且同真为真,或同假为假。
1 | void _boolType() { |
集合类型
dart 中定义集合直接使用 List list=[],如果没有指定泛型,默认类型为 dynamic 类型,集合中可以添加任何数据类型的数据,一旦指定了泛型,那么就只能添加约束的类型数据。除了初始化的时候添加元素另外还可以通过 API 的方式像集合中添加数据,add 添加单一数据,addAll 添加集合。还有另外一种方式是通过 List.generate 来生成集合,它的第一个参数表示集合的大小,第二个参数表示集合的元素。
1 | void _listType() { |
有了集合那么我们就需要遍历它,dart 中常用的遍历集合方式有:
1 | for (int i = 0; i < list.length; i++) { |
上面三种遍历方式输出结果都为:
1 | 1, 2, 3, "list","hello",1, 2, 3, 4, 5 |
Map 类型
Map 类型是将 key 和 value 相关联的对象,key 和 value 可以是任意类型的数据,并且 key 是唯一的,如果 key 重复那么后添加的会覆盖之前添加的数据。定义 map 类型直接看代码:
1 | void _mapList() { |
下面我们看下 Map 的遍历:
1 | map.forEach((key, value) { |
dynamic、var、Object 的区别
dynamic: 动态数据类型,是所有 dart 对象的基础类型, 在大多数情况下,通常不直接使用它,通过它定义的变量会关闭类型检查,这意味着 dynamic x = ‘hello world’; 调用 x.foo() 方法时静态类型检查不会报错,但是运行时会 crash,因为 x 并没有 foo() 方法,所以建议大家在编程时不要直接使用 dynamic。
var: 是一个关键字,意思是 “我不关心这里的类型是什么。”,系统会自动推断类型 runtimeType;而且一旦指定了类型,就不可以修改。
Object: 是 dart 对象的基类,当你定义: Object obj=xxx 时这时候系统会认为 obj 是个对象,你可以调用 obj 的 toString() 和 hashCode() 方法,因为 Object 提供了这些方法,但是如果你尝试调用 obj.foo() 时,静态类型检查会进行报错。
综上不难看出 dynamic 与 Object 的最大的区别是在静态类型检查上。
面向对象
类的定义和构造
定义一个 dart 类,使用 class 关键字加上类名,构造方法和类名相同,默认所有的类都是继承 Object 的。其中可以定义一些方法和变量,如:
1 | class Person { |
一个类继承另一个类使用关键字 extends,如果父类没有默认无参构造函数,那么子类需要使用 super 对父类进行初始化,子类的变量使用 this.xxx 来指定初始化,如:
1 | class Worker extends Person { |
上面代码中的: super(name,age) 被称作为初始化列表,除了调用父类构造器,还可以初始化实例变量,不同的初始化变量之间用逗号隔开。
1 | class Worker extends Person { |
我们还可以通过命名构造函数的方式初始化实例类。使用就是类名.xx() 即可,主要注意的是当有变量的类型是 final 的时候,命名构造方法就要求其在构造方法中指定进行初始化。
1 | class Worker extends Person { |
工厂构造函数就是都只返回同一个实例类,可以理解为 Java 中的单例模式。
1 | class Logger { |
还有一种构造方法是命名构造和工厂构造的结合体,通常在网络请求的时候将 json 映射为 object 的时候使用,它有一个好处就是不需要将类的 final 变量作为参数传递给构造方法。提供了一种灵活获取类对象的方式。定义格式: factory 类名.方法名。
1 | class Worker extends Person { |
dart 默认都会隐式的定义 setter 方法,对非空的变量还会增加 getter 方法。但是加了私有后,外界就无法访问变量了,需要我们手动的添加 set 和 get 方法。
1 | class Worker extends Person { |
抽象类和方法
使用 abstract 修饰符来定义一个抽象类,该类不能被实例化。抽象类在定义接口的时候非常有用,实际上抽象类中也包含一些实现。如果一个类继承了抽象类要么实现它的抽象方法,要么也将自己定义成抽象类。一个抽象类中不一定要有抽象方法,但是有抽象方法的类一定是抽象类。
1 | //抽象类 |
mixins
mixins 是在多个类层次结构中重用代码的一种方式。要使用 mixins,在 with 关键字后面跟一个或多个 mixin 的名字 (用逗号分开),并且 with 要用在 extends 关键字之后。
mixins 的特征: 实现 mixin ,就创建一个继承 Object 类的子类 (不能继承其他类),不声明任何构造方法,不调用 super。
1 | //声明mixins类 |
方法定义
方法是由返回值类型+方法名+参数构成。其中返回值类型可缺省,也可为 void 或者具体的类型。正常情况下都是有方法名的,但有一种特殊就是匿名方法。参数分为参数类型和参数名,其中参数类型可缺省。参数又分为可选参数和默认参数,他们都使用 {} 来定义。
1 | class FunClass { |
我们来调用打印下看看:
1 | FunClass funClass = FunClass(); |
匿名方法有时候也被称为 lambda 或者 closure 闭包,可以把匿名方法赋值给一个变量,直接调用变量名即可。
1 | var printI = (i) => print(i); |
使用:
1 | FunClass funClass = FunClass(); |
泛型
泛型主要是解决类、接口、方法的复用性、以及对不特定数据类型的支持。
1 | class Cache<T> { |
这里我们定义了一个缓存类,存储的类型被定义为泛型,提高代码的复用度。
有时候我们在实现类似通用接口的泛型中,期望的类型是某些特定类型时,这时可以使用类型约束。
1 | class Member<T extends Person> { |