Dart 诞生于 2011 年,但是在 2017 之前并不是很受欢迎。 在 2017 年 Google 宣布了跨平台移动应用开发的 Flutter beta 版之后,Dart 的受欢迎程度一直在上升。
它被用于web、服务器、移动应用和物联网等领域的开发。
Dart 本身是一个强类型语言,任何变量都是有确定类型的,在 Dart 中,当用var声明一个变量后,Dart 在编译时会根据第一次赋值数据的类型来推断其类型,编译结束后其类型就已经被确定。
var t = "flutter";
t=10;/// 报错
Object 是 Dart 所有对象的根基类,dynamic与Object声明的变量都可以赋值任意对象,且后期可以改变赋值的类型,这和 var 是不同的。
具体不同,后面会介绍。
dynamic a = "123";
Object b = "123";
dart2.12加入空安全。
在dart中的一切皆是对象,包括数字、布尔值、函数等,它们和Java一样都继承于Object,所以它们的默认值也就是null(dart2.12之前默认值是null,2.12之后必须赋初值)。在dart主要有: 布尔类型bool、数字类型num(数字类型又分为int,double,并且两者父类都是num)、字符串类型String、集合类型(List, Set, Map)。
结构:【类型】【变量名】= 【赋值】
/// 变量-数据类型
void variable() {
var t = "hi world";
bool isBool = false;
String name = "flutter";
int age = 1;
double height = -12.1;
/// 扩展,直接使用数学函数
// print("height ${height.abs()}");
//打印类型
print(t.runtimeType);
print(isBool.runtimeType);
print(name.runtimeType);
print(age.runtimeType);
print(height.runtimeType);
}
运行结果:
String
bool
String
int
double
细节一:num类型的变量,自带了数学函数Math功能,不像其他语言
double height = -12.1;
print("height ${height.abs()}");
/// 输出12.1
new 关键词是可选的,创建对象,不需要显示声明关键字new。
/// 集合操作
void collection() {
List<String> colorList = ['red', 'yellow', 'blue', 'green'];
/// 语法提示,不需要使用new关键字
// List colorList2 = new List.empty();
/// forEach箭头函数遍历
colorList.forEach((String color) => print(color));
/// map函数的使用
print(colorList.map((color) => '1+$color').join(","));
/// 直接使用{}形式初始化
Set<String> colorSet = {'red', 'yellow', 'blue', 'green'};
/// for-in遍历
for (var color in colorSet) {
print(color);
}
/// map初始化
Map<String, int> colorMap = {'white': 0xffffffff, 'black': 0xff000000};
print(colorMap.containsKey('green')); //false
print(colorMap.containsValue(0xff000000)); //true
}
从dart 2.12开始,支持空安全。就是在类型后面加个?
/// 空安全
void nullSafety(){
/// 默认是null
String? nullStr;
int? nullInt;
bool? nullBool;
Map<String, int>? nullMap;
print("init nullStr = $nullStr");
/// 报错,Error: Non-nullable variable 'str' must be assigned before it can be used.
String str;
// str = "init";
print("is $str");
}
如果使用String str 申明变量,但又不初始化,在使用的时候就会报错误。
正式使用它之前必须得保证初始化过了,否则会报错
void main() {
var awesome = Awesome();
print('awesome: ${awesome.isAwesome}');
lateVoid();
}
/// 可以先不初始化
late String description;
void lateVoid() {
description = 'Flutter!';
print(description);
}
前面介绍了dart中常用的变量,还可以使用var、dynamic、object用于定义变量,通过这种方式定义变量不需要指定变量类型。
使用var、dynamic、object,可以不用指明变量类型。他们区别是什么:
var,编译时会检测类型,如果类型推断出是string类型,再赋值int,会报错。
dynamic,类型是可变的,先复制string再复制int是可以的,编译时不会揣测数据类型,但是运行时会推断。
Object,类型是可变的,先复制string再复制int是可以的,编译时会检测类型。
void varAndDynamic(){
/// var定义的类型是不可变的,类型推断出是string类型,再赋值int,会报错
//var
var str = "flutter";
print(str.runtimeType); //String
print(str); //hello world
// str = 1;
//dynamic
/// 编译时不会揣测数据类型,但是运行时会推断
dynamic mic = "flutter";//编译时不会揣测数据类型,但是运行时会推断
print(mic.runtimeType);//String
print(mic);//hello world
/// 但是这样的坏处就是会让dart的语法检查失效,所以有可能会造成混乱而不报错,所以不要直接使用dynamic
mic.foo();
/// 通过它定义的变量会关闭类型检查,这段代码静态类型检查不会报错,但是运行时会crash,因为mic并没有foo()方法,
/// 所以建议大家在编程时不要直接使用dynamic
mic=1;
print(mic.runtimeType);//int 说明类型是可变的
print(mic);//1
//Object
Object object = "flutter";
print(object.runtimeType);//String
print(object);//hello world
object=1;
print(object.runtimeType);//int 说明类型是可变的
print(object);//1
//object.foo();静态类型检查会运行报错会报错
object.foo();
}
如果你不想更改一个变量,可以使用关键字 final 或者 const 修饰变量。一个 final 变量只可以被赋值一次;一个 const 变量是一个编译时常量(const 变量同时也是 final 的)。
final:其值在初始化后不可改变;
const:只能被设一次值,在声明处赋值,且值必须为编译时常量;用于修饰常量。
/// final和const
void finalAndConst(){
/// 可以用做静态常量
const int age = 20;
/// final不能改变
final String name = 'Flutter';
/// 以下不能改变
// name = "zzb";
print("name $name");
final int count = 2 * 2;
/// final只用来修饰变量,但无法限制变量的实例内部发生改变
final baz = [1];
// baz=[1,2,3,4]; //出错 此调用修改了变量的实例 即:[1] 和[1,2,3,4]是不同的对象
baz[0] = 2; //正常执行,只修改了变量引用对象的成员变量的值
print(baz);
/// 对比不同点,打印时间
final String finalTimeStamp = DateTime.now().toString();
const String constTimeStamp = DateTime.now().toString();//出错,编译时就要确认内容
print("finalTimeStamp $finalTimeStamp");
}
在dart中如果条件表达式结果不满足条件(表达式为false),则可以使用 assert 语句中断代码的执行。特别是在Flutter源码中随处可见都是assert断言的使用。注意: 断言只在检查模式(debug调试)下运行有效,如果在生产模式运行,则断言不会执行。
assert(text != null);//text为null,就会中断后续代码执行
assert(urlString.startsWith('https'));
官方不推荐使用forEach方法进行,建议使用for…in替换。
void collection() {
List<String> colorList = ['red', 'yellow', 'blue', 'green'];
/// forEach箭头函数遍历
colorList.forEach((String color) => print(color));
}
lint语法检测:给出的提示
foreach与fon-in方法pk forEach方法在回调方法出现的弊端。
Dart是一种真正的面向对象的语言,所以即使是函数也是对象,并且有一个类型Function。
返回类型,函数名,入参
bool isNull(int? num) {
return num == null;
}
如果函数体内只包含一个表达式,你可以使用简写语法:
bool isNull2 (int? num) => num == null;
Dart函数声明如果没有显式声明返回值类型时会默认当做dynamic处理,注意,函数返回值没有类型推断,不建议使用。
isNull3(int? num) {
return num == null;
}
有两种形式的参数:必要参数 和 可选参数。必要参数定义在参数列表前面,可选参数则定义在必要参数后面。可选参数可以是 命名的 或 位置的。
/// 必选参数
void upload(String filePath, String fileName, int? fileSize) {
}
/// 命名位置参数
void upload2(String filePath, String fileName, {int? fileSize}) {
}
/// 可选位置参数
void upload3(String filePath, String fileName, [int? fileSize]) {
}
/// 测试以上方法
/// 测试以上方法
void testParam(){
/// 必选参数
upload("/user", "name", 0);
/// 命名位置参数,两种用法
upload2("/user", "name");
upload2("/user", "name", fileSize: 10);
/// 可选位置参数
upload3("/user", "name");
upload3("/user", "name", 10);
}
可选命名参数在Flutter中使用非常多。注意,不能同时使用可选的位置参数和可选的命名参数。
命名位置参数,最大的好处是能知道,传递参数的含义,再配上关键词required,required表示这个参数是必须的。
/// required使用
void upload4({required String filePath, required String fileName, int? fileSize}) {
}
/// required使用
upload4(filePath: "/user", fileName: "name");
upload4(filePath: "/user", fileName: "name", fileSize: 10);
对象的 成员 由函数和数据(即 方法 和 实例变量)组成。
如果你没有声明构造函数,那么 Dart 会自动生成一个无参数的构造函数并且该构造函数会调用其父类的无参数构造方法。
/// 默认构造函数
class Point{
int x = 0;
int y = 0;
}
Point point = Point();
需要注意的是:使用了有参构造函数和命名式构造函数,那默认的构造函数就无效了。无法直接使用
Point point = Point();
构造函数:声明一个与类名一样的函数即可声明一个构造函数。
命名式构造函数:可以为一个类声明多个命名式构造函数来表达更明确的意图:
class Point {
int x = 0;
int y = 0;
int? z = 0;
/// constructor,构造函数,有参数构造函数
Point(int x, int y) {
this.x = x;
this.y = y;
}
/// 简化,构造函数
// Point(this.x, this.y);
/// 命名式构造函数,明确知道意图
Point.origin() {
x = 5;
y = 5;
}
/// 常用命名式构造函数,把json的数据转成对象
Point.fromJson(Map<String, dynamic> json) {
x = json['x'];
y = json['y'];
}
void printPoint() {
print("x = $x y = $y");
}
/// 距离
int distanceTo() {
return 0;
}
}
/// 测试构造函数
void testPoint() {
/// 无法直接使用
// Point point = Point();
/// 构造函数
Point point = Point(2, 2);
point.printPoint();
/// 命名式构造函数
Point point2 = Point.origin();
point2.printPoint();
/// 命名式构造函数
Point point3 = Point.fromJson({'x': 1, 'y': 2});
point3.printPoint();
/// 可以为空的对象,调用方法前可以使用运算符?. 来判断是否有该方法
/// 可以避免因为左边表达式为 null 而导致的问题
Point? point4;
point4?.printPoint();
}
Dart和其他面向对象语言不一样,Data中没有 public private protected这些访问修饰符合,但是我们可以使用_把一个属性或者方法定义成私有。
私有方法必须要抽离在单独文件中,否则不生效。
class PrivateClass{
/// 私有变量
int _i = 1;
int j = 2;
PrivateClass();
void printContent() {
print("打印变量_i = $_i j = $j");
}
/// 私有方法,
void _printContent2(){
print("打印变量_i = $_i j = $j");
}
}
PrivateClass privateClass = PrivateClass();
privateClass.printContent();
/// 修改公共属性
privateClass.j = 3;
privateClass.printContent();
/// 无法修改私有属性,报错The setter '_i' isn't defined for the type 'PrivateClass'.
privateClass._i = 3;
/// 无法使用私有方法,The method '_printContent2' isn't defined for the type 'PrivateClass'.
privateClass._printContent2();
如果父类没有匿名无参数构造函数,那么子类必须调用父类的其中一个构造函数。
1.子类使用extends关键词来继承父类
2.子类会继承父类里面可见的属性和方法,但是不会继承构造函数
3.子类能复写父类的方法 getter和setter
class Person {
String? firstName;
Person.fromJson(Map data) {
print('in Person');
}
void eat() {
print("eat");
}
void play(){
print("play");
}
}
class Teacher extends Person {
Teacher.fromJson(Map data) : super.fromJson(data) {
print('in Teacher');
}
/// 重写类成员
@override
void eat() {
/// 调用父类方法
super.play();
print("eat fish");
}
}
/// 测试继承
void testExtends() {
Teacher teacher = Teacher.fromJson({});
teacher.eat();
}
输出
in Person
in Teacher
play
eat fish
Dart 是不支持多继承的,Mixin 是一种在多重继承中复用某个类中代码的方法模式,“组合”类可以访问mixin类的方法、变量而不必成为其子类。使用 with 关键字来使用 Mixin 模式。
定义一个 Person 类,实现吃饭、说话、走路和写代码功能,同时定义一个 Dog 类,实现吃饭、和走路功能:
/// 定义一个 Person 类,实现吃饭、说话、走路和写代码功能,同时定义一个 Dog 类,实现吃饭、和走路功
class Person {
say() {
print('say');
}
}
mixin Eat {
eat() {
print('eat');
}
}
mixin Walk {
walk() {
print('walk');
}
}
mixin Code {
code() {
print('code');
}
}
class Dog with Eat, Walk{}
class Man extends Person with Eat, Walk, Code{}
/// 测试mixin
void testMixin(){
Man man = Man();
man.say();//say
man.code(); //code
man.eat();//eat
Dog dog = Dog();
dog.eat(); //eat
}
打印结果
say
code
eat
eat
mixins是普通的类,我们可以从中扩展方法(或变量)而不扩展类。
Dart之Mixin详解,Flutter中大量使用了这种方式。
Dart类库有非常多的返回Future或者Stream对象的函数。 这些函数被称为异步函数:它们只会在设置好一些耗时操作之后返回,比如像 IO操作。而不是等到这个操作完成。
async和await关键词支持了异步编程,允许您写出和同步代码很像的异步代码。
Future与JavaScript中的Promise非常相似,表示一个异步操作的最终完成(或失败)及其结果值的表示。Future 的所有API的返回值仍然是一个Future对象,所以可以很方便的进行链式调用。
/// 测试future
void testFuture() {
print("start future");
Future.delayed(Duration(seconds: 2), () {
throw ArgumentError("Error");
// return "hi world!";
}).then((data) {
print(data);
}).catchError((e){
//执行失败会走到这里
print("失败了 $e");
});
print("end future");
}
执行结果:
start future
end future
失败了 Invalid argument(s): Error
就可以说明Future是一个异步操作。
比如我们有一个界面,需要先分别从两个网络接口获取数据,获取成功后,我们需要将两个接口数据进行特定的处理后再显示到UI界面上。它接受一个Future数组参数,只有数组中所有Future都执行成功后,才会触发then的成功回调,只要有一个Future执行失败,就会触发错误回调。
void testFutureWait(){
Future<String> demo1() {
return Future.value("1");
}
Future<String> demo2(){
return Future.value("2");
}
Future<dynamic> demo3(){
return Future.delayed(Duration(seconds: 4), () {
// throw ArgumentError("Error");
return "hi world!";
});
}
/// 返回错误
Future<String> demo4(){
return Future.error("error");
}
/// 内部执行错误,被捕获了,wait是无法检测到
Future<void> demo5(){
return Future.delayed(Duration(seconds: 4), () {
throw ArgumentError("Error");
// return "hi world!";
}).catchError((e){
//执行失败会走到这里
print("内部捕获 $e");
});
// return Future.error("error");
}
/// 正常流程
List<dynamic> list1 = [demo1(), demo2(), demo3()];
/// 包含一个错误
List<dynamic> list2 = [demo1(), demo2(), demo3(), demo4()];
Future.wait<dynamic>([...list2]).then((value) => print(value)).catchError((e){
//执行失败会走到这里
print("失败了 $e");
});
}
使用 async 和 await 的代码是异步的,但是看起来有点像同步代码。async能把普通函数变成异步函数。
Dart中的async/await 和JavaScript中的async/await功能和用法是几乎一样。
/// 定义Future函数
Future<String> checkVersion() async{
return "1.2";
}
//先分别定义各个异步任务
Future<String> login(String userName, String pwd){
...
//用户登录
};
Future<String> getUserInfo(String id){
...
//获取用户信息
};
Future saveUserInfo(String userInfo){
...
// 保存用户信息
};
接下来,执行整个任务流:
login("alice","******").then((id){
//登录成功后通过,id获取用户信息
getUserInfo(id).then((userInfo){
//获取用户信息后保存
saveUserInfo(userInfo).then((){
//保存用户信息,接下来执行其它操作
...
});
});
})
如果业务逻辑中有大量异步依赖的情况,过多的嵌套会导致的代码可读性下降以及出错率提高,并且非常难维护,也被叫做“回调地狱”。
使用async/await可以解决以上问题。
task() async {
try{
String id = await login("alice","******");
String userInfo = await getUserInfo(id);
await saveUserInfo(userInfo);
//执行接下来的操作
} catch(e){
//错误处理
print(e);
}
}
官方文档-streams
Stream 是一系列异步事件的序列。上面的Future,是返回一个异步任务,那Stream就是可以串行返回多个异步任务。
Stream有两种类型,一种是点对点的单订阅流(Single-subscription),另一种则是广播流(Broadcast)。
主要作用是监听事件流,常作为事件总线使用。
单订阅流的特点是只允许存在一个监听器,即使该监听器被取消后,也不允许再次注册监听器。
这种流可以在同一时间设置多个不同的监听器同时监听,同时你也可以在取消上一个订阅后再次对其发起监听。
创建Stream的方式有很多,跟Future类似
这里介绍StreamController来创建Stream,这里就以广播流的形式来介绍一下。
void testStreamController(){
/// 创建广播流
/// onListen,有监听事件就回调
/// onCancel,关闭stream事件流就回调
StreamController sc = StreamController.broadcast(
onListen: ()=>print("onListen"),
onCancel: ()=>print("onCancel"),
);
/// 监听事件
sc.stream.listen((String event){
print("第一个监听 $event");
});
sc.stream.listen((String event){
print("第二个监听 $event");
});
/// 放入事件
sc.add('event1');
// sc.add("event2");
/// 关闭
sc.close();
}
运行结果:
onListen
第一个监听 event1
第二个监听 event1
onCancel
Flutter中的EventBus的原理就是根据Stream实现。
主要介绍了dart的变量、函数、类、异步编程。今天课程主要是为打下个基础,学会这些内容基本就能满足基本的Flutter开发,学习以上内容后,推荐再去阅读官方的Dart语言规范和用法示例,能更好的配合团队使用Dart来开发项目。如下