开发FlutterApp之前我们肯定要先了解Dart这门语言及语言的特性、语法等。最近看了大量的
Dart语言相关内容,本章会来简述。
目录
- 概念及优点
- 变量
- 函数
- 闭包
- 异步支持
概念及优点:
- Dart:
Google及全球的其他开发者,使用 Dart 开发了一系列高质量、 关键的 iOS、Android 和 web 应用。 Dart 非常适合移动和 web 应用的开发。
1.高效
Dart 语法清晰简洁,工具简单而强大。 输入检测可帮助您尽早识别细微错误。 Dart 拥有久经考验的 核心库(core libraries) 和一个已经拥有数以千计的 packages 生态系统
2.快速
Dart 提供提前编译优化,以在移动设备和 web 上实现可预测的高性能和快速启动。
3.可移植
Dart 可以编译成 ARM 和 x86 代码,因此 Dart 移动应用程序可以在 iOS,Android 及 更高版本上实现本地运行。 对于 web 应用程序,Dart 可以转换为 JavaScript。
4.易学
Dart 是面向对象的编程语言,语法风格对于许多现有的开发人员来说都很熟悉。了解Java、JS语言 ,使用 Dart 也就很简单,也有Swift的一些特性。
5.响应式
Dart 可以便捷的进行响应式编程。由于快速对象分配和垃圾收集器的实现, 对于管理短期对象(比如 UI 小部件), Dart 更加高效。 Dart 可以通过 Future 和 Stream 的特性和API实现异步编程。
变量
- var
自动推断类型(这点与OC、Java不同),接收任何类型的的变量,但是一旦赋值,类型就不能改变,即本来是字符串,之后就只能是字符串(这点与JS不同)。
var a = "字符串";
//主意:如果这样就会报错,类型在第一次指定后就不能改变
a = 1;
原因:Dart是强类型语言,任何变量都有各自的类型,编译时会根据首次赋值数据的类型来推断其类型,编译结束后其类型不能更改。JS是纯粹的弱类型脚本语言,var只是变量的声明。
- dynamic
dynamic与var一样都是关键词,声明的变量可以赋值任意类型对象。声明的变量可以在后期改变赋值类型。即本来是字符串,之后可以赋值为number等其他类型。
dynamic a = "字符串";
//不会报错
a = 1;
- Object
Object与dynamic一样也是声明的变量可以赋值任意类型对象,声明的变量可以在后期改变赋值类型。
Object b = "hello world";
//不会报错
b = 10;
不同之处,dynamic声明的对象编译器会提供所有可能的组合,至少不会报错(但有可能运行时会因为找不到之前预制的组合,造成崩溃), 而Object声明的对象只能使用Object的属性与方法, 否则编译器会报错。
dynamic a = "";
Object b = "";
//编译器不报错,不警告。
print(a.length);
//编译器会警告报错(Object没有length的getter方法):The getter 'length' is not defined for the class 'Object'
print(b.length);
注意:dynamic可以理解为id类型,任何类型都可以转换成id(dynamic)类型,可以用id(dynamic)去接,编译器不会报错,但是在运行时可能会产生错误出现崩溃现象。
- final
final 为运行时常量。
final修饰的常量必须在声明的时候就进行初始化,而且在初始化之后值不可变
final a = "名字";
//会报错
a = "性别";
- const
const 为编译时常量。
const不仅仅可以声明常数变量,也可以声明常量值以及声明创建常量值的构造函数,任何变量都可以有一个常量值;
final aList = const[];
const bList = const[];
var cList = const[];
这里的aList和bList就是两个空的、不可变的列表集合,而cList则是空的、可变的列表集合;
需要注意的是:cList可以重新赋值,可以改变,而aList和bList不可以重新赋值;
- 函数
Dart是面向对象的语言,所以即使是函数也是对象,并且有一个类型Function。这意味着函数可以赋值给变量或作为参数传递给其他函数,这是函数式编程的典型特征。
1.函数声明
返回类型 方法体 (参数1, 参数2, ...){
方法体...
return 返回值
}
String getPerson(String name, int age){
return name + '${age}';
}
//如果返回类型不指定时,此时默认为dynamic。
2.箭头函数
对于只包含一个表达式的函数,可以使用简写语法。
getPerson(name, age) => name+ ', $age' ;
bool isNoble (int atomicNumber)=> _nobleGases [ atomicNumber ] != null ;
3.函数作为变量(方法对象)、入参
//函数作为变量
var method1 = (str){
print(str)
};
method1("kakalala");
//函数作为参数
void execute(var callbackMethod){
callbackMethod();
}
//两种
execute(() => print("xxx"));
execute(method1("kakalala"));
4.可选参数(可选位置参数、可选命名参数)
- 可选位置参数:[param1, param2, …],可以设置默认参数
包装一组函数参数,用[]标记为可选的位置参数,并放在参数列表的最后面:
getPerson(String name, [int age = 99, String gender = "御姐"]){
print ("name = $name, age = $age, gender = $gender");
}
//getPerson() ;这种不传参是会报错的。
getPerson(null) ;
getPerson('不知火') ;
getPerson('不知火', 100);
getPerson('不知火', null, "萝莉");
控制台输出
flutter: name = null, age = 99, gender = 御姐
flutter: name = 不知火, age = 99, gender = 御姐
flutter: name = 不知火, age = 100, gender = 御姐
flutter: name = 不知火, age = null, gender = 萝莉
注意:name参数是必须传入的,否则会报错。后边的可选位置参数如果不传会是null,传null还是会返回null。可选位置参数可以设置默认参数。
- 可选命名参数:{param1, param2, …}
在传入的时候,需要指定下对应的参数名,放在参数列表的最后面,用于指定命名参数。可以设置默认参数。
getPerson(String name, {int age = 100, String gender = "狼狗"}){
print("name = $name, age = $age, gender = $gender");
}
//getPerson() ;这种不传参是会报错的。
getPerson(null) ;
getPerson('烬天玉藻前') ;
getPerson('烬天玉藻前', age: 99 );
getPerson('烬天玉藻前', gender: "御姐" );
getPerson('烬天玉藻前', age: 99, gender: "奶狗");
控制台输出:
flutter: name = null, age = 100, gender = 狼狗
flutter: name = 烬天玉藻前, age = 100, gender = 狼狗
flutter: name = 烬天玉藻前, age = 99, gender = 狼狗
flutter: name = 烬天玉藻前, age = 100, gender = 御姐
flutter: name = 烬天玉藻前, age = 99, gender = 奶狗
注意:固定参数必须传入(那怕传个null),可选命名参数可以设置默认参数。
- 默认参数值
默认参数值即我们在方法的参数列表上面使用 “=” 号给入一个常量值,如果没有传入该值的时候,就使用我们给入的常量值。
注意,不能同时使用可选的位置参数和可选的命名参数
//这种是不可以的,错误事例。
getPerson(String name, {int age = 100, String gender = "狼狗"}, [int age2 = 1002, String gender2 = "狼狗2"]){
}
闭包
闭包是一个方法(对象),闭包定义在其它方法内部,能够访问外部方法的局部变量,并持有其状态。
void main() {
// 创建一个函数add1,返回加2
Function add1 = addNum(2);
// 创建一个函数add2,返回加4
Function add2 = addNum(4);
// 2 + 3 = 5
print(add1(3));
// 4 + 3 = 7
print(add2(3));
}
// 返回一个函数对象,功能是返回累加的数字
Function addNum(int addBy){
return (int i) => addBy + I;
}
控制台输出:
flutter: 5
flutter: 7
异步支持
Dart代码运行在一个单线程,如果Dart代码阻塞了—例如,程序计算很长时间,或者等待I/O,整个程序就会冻结。
Dart异步函数:Future、Stream,设置好耗时操作后返回,不会阻塞线程。
async和await关键词支持了异步编程,允许写出和同步代码很像的异步代码。
Future
Future与JS中的Promise和Swift的RXSwift非常相似,其语法也是链式函数调用,该函数异步操作执行后,最终返回成功(执行成功的操作)、失败(捕获错误或者停止后续操作),失败和成功是对立的只会出现一种。
注意:Future 的所有API的返回值都是一个Future对象,所以可以进行链式调用。
- Future构造函数
Future(FutureOr computation())
computation 的返回值可以是普通值或者是Future对象,但是都是Future对象接收
Future<num> future1 = Future(() {
print('async call1');
return 123;
});
//直接调用
future1.then((data) {
//执行成功会走到这里
print(data);
}, onError: (e) {
print("onError: \$e");
}).catchError((e) {
//执行失败会走到这里
print(e);
}).whenComplete(() {
//无论成功或失败都会走到这里
});
Future<Future> future2 = Future((){
print('async call2');
return future1;
});
//嵌套调用
future2.then((value) => value).then((value) => {
print('222---'+value.toString())
});
控制台打印
Reloaded 1 of 499 libraries in 154ms.
flutter: async call1
flutter: 123
flutter: async call2
flutter: 222---123
注意:computation函数体中的代码是被异步执行的,与JS中Promise构造函数的回调执行时机不一样,如需要被同步执行,则使用如下这个命名构造函数:
Future.sync(FutureOr computation())
//该段代码放到上边代码之后执行
Future<num> future3 = Future.sync((){
print('sync call');
return 333;
});
future3.then((value) => {
print('sync'+'$value')
});
控制台输出
flutter: sync call
flutter: sync333
flutter: async call1
flutter: 123
flutter: async call2
flutter: 222---123
由此可见,future3(sync)方法会先执行,之后在执行之前的future1、future2.可见正常的future中的computation函数体中的代码是被异步执行的。
- Future.then
then中接收异步结果
Future.delayed(new Duration(seconds: 2),(){
return "延迟2s执行";
}).then((data){
print(data);
});
- Future.catchError
捕获错误
Future.delayed(new Duration(seconds: 2),(){
//return "延迟2s执行";
throw AssertionError("Error");
}).then((data){
//执行成功会走到这里
print("success");
}).catchError((e){
//执行失败会走到这里
print(e);
});
在异步任务中抛出了一个异常,then的回调函数将不会被执行, catchError回调函数将被调用;并不是只有 catchError回调才能捕获错误,then方法还有一个可选参数onError(之前介绍结构体时已经提到),我们也可以它来捕获异常:
Future.delayed(new Duration(seconds: 2), () {
//return "延迟2s执行";
throw AssertionError("Error");
}).then((data) {
print("success");
}, onError: (e) {
print(e);
});
- Future.whenComplete
不管成功失败都要处理事件的场景,会调用此方法,比如在网络请求前弹出加载对话框,在请求结束后关闭对话框。这种场景,有两种方法,第一种是分别在then或catch中关闭一下对话框,第二种就是使用Future的whenComplete回调:
Future.delayed(new Duration(seconds: 2),(){
//return "延迟2s执行";
throw AssertionError("Error");
}).then((data){
//执行成功会走到这里
print(data);
}).catchError((e){
//执行失败会走到这里
print(e);
}).whenComplete((){
//无论成功或失败都会走到这里
});
- Future.wait
需要等待多个异步任务都执行结束后再统一进行一些操作(比如我们有一个界面,需要先分别从两个网络接口获取数据,获取成功后,我们需要将两个接口数据进行特定的处理后再显示到UI界面上)
Future.wait就是做这件事的(类似RXswift的zip函数),它接受一个Future数组参数,只有数组中所有Future都执行成功后,才会触发then的成功回调,只要有一个Future执行失败,就会触发错误回调。
Future<List<Future>> future4 = Future.wait([
// 2秒后返回结果
Future.delayed(new Duration(seconds: 2), () {
return "延迟2s执行";
}),
// 4秒后返回结果
Future.delayed(new Duration(seconds: 4), () {
return " 延迟4s执行";
})
])
future4.then((value) => {
print(value[0]+ value[1]);
}).catchError((e){
print(e);
});
控制台输出
//等待4s输出
flutter: 延迟2s执行 延迟4s执行
- 回调地狱(Callback Hell)
代码中有大量异步逻辑,并且出现大量异步任务依赖其它异步任务的结果时,必然会出现回调中套回调情况。我们需要使用async/await和Future.then来解决这种问题。
使用场景:大量依赖的业务逻辑,登录流程逻辑等
先来看下回调地狱例子:
//先分别定义各个异步任务
Future<String> future1(String str1){
...
//第一个任务
};
Future<String> future2(String str2){
...
//第二个任务
};
Future future3(String info){
...
// 第三个任务
};
future1("str1").then((str2){
//1返回数据,作为2的参数
future2(str2).then((info){
//2的返回数据,作为3的入参
future3(info).then((){
//获取3的返回数据
...
});
});
})
Future消除Callback Hell
使用Future的链式机制,依次向下就避免了嵌套。跟JS的Promise完全一样。不足之处是还是有一层回调。
future1("str1").then((str2){
return future2(str2);
}).then((info){
return future3(info);
}).then((e){
//执行3接下来的操作
}).catchError((e){
//错误处理
print(e);
});
async/await消除callback hell
上边的方式虽然避免了嵌套,但是在每个方法还是有一层回调。我们可以使用async/await来实现像同步代码那样来执行异步任务而不使用回调的方式。
task() async {
try{
String str2 = await future1("str1");
String info = await future2(str2);
await future3(info);
//执行接下来的操作
} catch(e){
//错误处理
print(e);
}
}
- async/await
async用来表示函数是异步的,定义的函数会返回一个Future对象,可以使用then方法添加回调函数
await 后面是一个Future,表示等待该异步任务完成,异步完成后才会往下走;await必须出现在 async 函数内部。
async/await将一个异步流用同步的代码表现出来。
async/await只是一个语法糖,JS编译器或Dart解释器最终都会将其转化为一个JS的Promise和Dart的Future的调用链。
Stream
如果说Future是可以接收单个异步事件返回单个事件的成功失败,那么Stream就可以接收多个异步事件,并返回多个事件的成功失败,供使用者使用。
使用场景:多次读取数据的异步任务场景,如网络内容下载、文件读写等
该例子借助了其他地方的例子。
Stream.fromFutures([
// 1秒后返回结果
Future.delayed(new Duration(seconds: 1), () {
return "hello 1";
}),
// 抛出一个异常
Future.delayed(new Duration(seconds: 2),(){
throw AssertionError("Error");
}),
// 3秒后返回结果
Future.delayed(new Duration(seconds: 3), () {
return "hello 3";
})
]).listen((data){
print(data);
}, onError: (e){
print(e.message);
},onDone: (){
});
控制台输出
flutter: hello 1
flutter: Error
flutter: hello 3
Future.wait是函数中存在多个延时操作,则以延时最长操作完成后统一返回,其他的延时操作等待最长的延时操作完成。
async/await:处理多个异步操作,前后有依赖逻辑的,使用异步实现同步。
Stream:统一监听该Stream中的多个异步延时操作返回,相当于之前的多个Future异步处理统一监听。
- 其中Future和Stream只做了常用的方法和函数的介绍,更详细的会在之后依次给大家做下总结。
到这里大概把Dart中经常使用的语法和属性方法介绍了一遍,有错误或者理解不到位的地方,可以提出,共同进步。
Dart相比Java和JavaScript还是有许多优点有优势的,Dart既能进行服务端脚本、APP开发、web开发,但是生态目前不足,不过Flutter目前火热,相信生态之后会越来越好。