变量使用
dart 所有类型皆为object类型
Object 可以变换类型,Object关键字只是做了声明而已。
dynamic 可以变换类型,且能使用相应类型的内置函数(Object的话,只能使用Object类的内置方法)
var 声明的变量赋值之后,就会确定变量的类型(强类型)
final 常量关键字,运行时 才会确定修饰常量 的值
const 常量关键字,编译时就确定常量 的值
dart 单引号和双引号声明字符串都是可以的
r前缀 raw:原始,表示字符串里的特殊字符不进行转译
void main(){
Object obj = 0; // dart 所有类型皆为object类型
obj = "asdf"; // 可以变换类型,Object关键字只是做了声明而已。
// print(obj.length());//报错
dynamic a = "asdf"; // 可以变换类型,且能使用相应类型的内置函数(Object的话,只能使用Object类的内置方法)
print("字符串长度:${a.length}");
a = -123;
print("$a, ${a.abs()}");
var i = 0; // 声明的变量赋值之后,就会确定变量的类型(强类型)
// i = "asdfdf"; //报错,因为已经是确定的类型(int)
// 运行时 才会确定b 的值
final b = 1;
// 编译时就确定c 的值
const c = 2;
// const d = b; // 报错,因为const关键字的时间节点比 final的要提前点。
const d = c;
/// TODO 2 dart 单引号和双引号声明字符串都是可以的
String abc = "asdfdf";
abc = 'aaaaqwereee';
// r前缀 raw:原始,表示字符串里的特殊字符不进行转译
abc = r"\n1\t2";
print("$abc 的长度:${abc.length}");
}
函数使用
函数声明
如果不指定返回类型,此时默认为dynamic,不是bool
var _nobleGases = [1,2,3,null]; // type: List<int?>
// TODO 1.函数声明
bool isNoble(int atomicNumber){
return _nobleGases[atomicNumber] != null;
}
/// 不指定返回类型,此时默认为dynamic,不是bool
isNoble1(int atomicNumber){
return _nobleGases[atomicNumber];
}
void main(){
int num = 2;
print("isNoble($num) = ${isNoble(num)}");
print(isNoble1(num));
}
定义一个类型
如果指定的类型 不指定返回类型,则默认 dynamic
// 定义一个类型:这里表示的是
typedef callback(); //不指定返回类型,此时默认为dynamic
void testCb(callback cb){
print(cb());
}
void main(){
testCb(() => 111);
testCb(() => "string");
}
函数作为变量
// TODO 2.函数作为变量
var sayMet = (str){
print("method as variable, parameter=$str");
};
void main(){
sayMet("hi word");
}
函数作为参数传递
// TODO 3. 函数作为参数传递
// 定义函数 execute,它的参数类型为函数
void execute(var met){ // dynamic 类型
if(met is Function){
print("parameter is Function, and then execute it below");
met(); // 执行传入的函数
}else{
print("parameter isn't Function");
}
}
void main(){
execute(()=>print("aaaaaaaaa"));
}
可选的位置参数与命名参数
可选的位置参数:包装一组函数参数,用 [] 标记为可选的位置参数,并放在参数列表的 【最后面】
可选的命名参数:定义函数时,使用{param1, param2, …},放在参数列表的【最后面】 ,用于指定命名参数。
注意,不能同时使用可选的位置参数和可选的命名参数。
// TODO 3.1 可选的位置参数,用[]标记
// 包装一组函数参数,用[]标记为可选的位置参数,并放在参数列表的【最后面】
String say(String from, String msg, [String? device]){
var result = "$from says $msg";
if(device != null){
result = "$result with a $device";
}
return result;
}
// TODO 3.2 可选的命名参数,用{}标记
// 定义函数时,使用{param1, param2, …},放在参数列表的【最后面】,用于指定命名参数。
// 注意,不能同时使用可选的位置参数和可选的命名参数。
void enableFlags({required bool bold, bool? hidden}){
// ......
}
void main(){
var word = say("you", "i am hungry"); // 不带可选参数调用
print(word);
word = say("you", "msg", "computer");
print(word);
enableFlags(hidden: true, bold: false);
}
操作符使用
强制转型 as
//TODO 1 强制转型 as
Object a = 12;
var a1 = a as int;
print(a1);
判断类型 is
//TODO 2 判断类型 is
Object b = 10;
print(b is bool);
print(b is! num);
赋值操作符 ??=
//TODO 3 赋值操作符 ??=
// var k;
var k = "123";
k ??= "aaa"; // 如果k 没有值,才会赋值
print(k); // log: 123
条件操作符
//TODO 4 条件操作符
var c = null;
// 4.1
var d = c == 10 ? 11 : 12; // c == 10 成立 11,否则12
print(d); // log: 12
// 4.2
var d1 = c ?? 13; // c 不为 null 就返回 c, 否则13
print(d1); // log: 13
级联操作符 ..
void main(){
//TODO 5 级联操作符 ..
// 类似于java的构建者模式的链式调用,
// 区别在于使用级联操作符的话 类的方法即使不返回当前对象也能链式调用
Builder()..a()..b();
}
class Builder{
void a(){ // 使用级联操作符即使不返回当前对象也能链式调用
print("Builder a()");
}
void b(){
print("Builder b()");
}
}
安全操作符 ?
//TODO 6 安全操作符 ?
var str = null;
print(str?.length);
类的继承与组合
Dart 是不支持多继承的,但是它支持 mixin,mixin 可以 “组合” 多个类。mixin不允许有构造方法
/// Dart 是不支持多继承的,但是它支持 mixin
/// mixin 可以 “组合” 多个类。
class Person{
say(){
print("say");
}
}
mixin Eat{
// mixin不允许有构造方法
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{}
void main(){
var man = Man();
man.code();
}
私有属性
属性名前面带"_" 的表示私有属性,否则都是公有
class Point{
// 属性名前面带"_" 的表示私有属性,否则都是公有
var _x = 8;
var y = 10;
Point(this._x, this.y);
// 命名构造方法
Point.A(this._x);
Point.fromMap(var map):_x=map["x"], y=map["y"]{
print("");
}
set x(int x){
_x = x;
}
int get x => _x;
}
class Person{
final String name;
final int age;
const Person(this.name, this.age); // 常量构造方法
}
class Manager{
static var _manager;
//工厂构造方法 必须返回一个 实例对象
factory Manager.getInstance(){
_manager ??= Manager._newInstance();
return _manager;
}
Manager._newInstance();//私有构造函数
void msg1(){
print('Manager.msg1');
}
}
void main(){
var p = Point(1, 2);
// p._x; 无法访问其私有属性
p.x = 30; // 调用了 set x(int x)
print(p.y);
print(p.x);
// 能够节省内存
var per1 = const Person("张三", 18);
var per2 = const Person("张三", 18);
print("per1 和per2是否为同一个对象:${per1.hashCode == per2.hashCode} ${per1 == per2}");
// 工厂构造方法
var m = Manager.getInstance();
m.msg1();
}
异步函数
Dart类库有非常多的返回Future或者Stream对象的函数。async 和 await 关键词支持了异步编程
Future.then
// TODO 1. Future.then
void use_then(){
print("start");
Future.delayed(Duration(seconds: 2), (){
print("2 second has passed");
return "hi world";
}).then((value) {
print("then the value is $value");
return "then";
}).then((value) {
print("then2");
});
}
Future.catchError 捕获异常/错误
// TODO 2. Future.catchError 捕获异常/错误
void use_catch_err(){
Future.delayed(Duration(seconds: 1), (){
// throw AssertionError("error");
throw Exception("exception");
}).then((value){
print("success"); // 因为抛出了异常,所以then并不会执行
}).catchError((e){
print(e);
});
}
void use_catch_err1(){
Future.delayed(Duration(seconds: 1), (){
throw AssertionError("Error");
}).then((value) => print("success"), onError: (e){
print("then 函数的可选参数 onError: $e");
});
}
Future.whenComplete 无论成功或失败都会执行
// TODO 3. Future.whenComplete 无论成功或失败都会执行
void use_when_complete(){
Future.delayed(Duration(seconds: 2), (){
// throw AssertionError("error when_complete");
}).then((value){
print("success");
}, onError: (e){
print("error : $e");
}).whenComplete((){
print("whenComplete");
});
}
Future.wait 能够等待多个异步任务结果
// TODO 4.1 Future.wait 能够等待多个异步任务结果
void use_wait(){
Future.wait([ // future数值
Future.delayed(Duration(seconds: 1), (){
// throw AssertionError("ssssss");
return "hello";
}),
Future.delayed(Duration(seconds: 1), (){
return "word";
}),
]).then((values) =>
print("${values[0]} ${values[1]}")
).catchError((e){
print(e);
});
}
回调地狱问题
现在有个需求场景是用户先登录,登录成功后会获得用户ID,然后通过用户ID,再去请求用户个人信息,获取到用户个人信息后,为了使用方便,我们需要将其缓存在本地文件系统
/// 现在有个需求场景是用户先登录,登录成功后会获得用户ID,
/// 然后通过用户ID,再去请求用户个人信息,获取到用户个人信息后,
/// 为了使用方便,我们需要将其缓存在本地文件系统
Future<String> login(String usrName, String pwd){
return Future.delayed(Duration(seconds: 2));
}
Future<String> getUsrInfo(String id){
return Future.delayed(Duration(seconds: 3));
}
Future saveUsrInfo(String usrInfo){
return Future.delayed(Duration(seconds: 1));
}
// TODO 4.2.1 回调地狱问题
void cb_hell(){
// 过多的嵌套会导致的代码可读性下降以及出错率提高,并且非常难维护,
// 这个问题被形象的称为回调地狱(Callback Hell)
login("usrName", "pwd").then((id){
//登录成功后通过,id获取用户信息
getUsrInfo(id).then((usrInfo){
//获取用户信息后保存
saveUsrInfo(usrInfo).then((val){
//保存用户信息,接下来执行其他操作
//...
});
});
});
}
// TODO 4.2.2 消除回调地狱
// 方式一
void solve_cb_hell1(){
login("usrName", "pwd").then((id){
return getUsrInfo(id);
}).then((usrInfo){
return saveUsrInfo(usrInfo);
}).then((e){
//执行接下来的操作
}).catchError((e){
//错误处理
print(e);
});
}
// 方式二
Future solve_cb_hell2() async { //async用来表示函数是异步的
///其实,无论是在 JavaScript 还是 Dart 中,async/await 都只是一个【语法糖】,
///编译器或解释器最终都会将其转化为一个 Promise(Future)的调用链。
try {
String id = await login("usrName", "pwd");
String usrInfo = await getUsrInfo(id);
await saveUsrInfo(usrInfo);
} catch(e) {
print(e);
}
}
Stream,常用于会多次读取数据的异步任务场景
如网络内容下载、文件读写(每次读取一部分,目的为了减少内存占用。而不需要一次性把文件内容全部读取进内存)等。
// TODO 5. Stream,常用于会多次读取数据的异步任务场景,
// 如网络内容下载、文件读写(每次读取一部分,目的为了减少内存占用。
// 而不需要一次性把文件内容全部读取进内存)等。
void use_stream(){
Stream.fromFutures([
Future.delayed(Duration(seconds: 2), (){
return "hello 1";
}),
Future.delayed(Duration(seconds: 3), (){
throw AssertionError("Error");
}),
Future.delayed(Duration(seconds: 1), (){
return "hello 3";
}),
]).listen((data) {
print("listen $data");
}, onError: (e){
print("onError $e");
}, onDone: (){
print("onDone");
});
}
异步通信
事件机制
微任务的优先级大于普通任务的优先级
Future 和 Stream都属于 Event-Loop
send并不会马上执行,而是保存到了消息队列,当main()执行完 再由event-loop去查询执行,如上图
void test_task(){
var recPort = ReceivePort();
recPort.listen((message) {
print(message);
if(message == "close"){
recPort.close();
}
});
/// send并不会马上执行,而是保存到了消息队列,当main()执行忘再由event-loop去查询执行
recPort.sendPort.send("发送消息给消息接收器1!");
Future.microtask(() {
print("微任务执行1");
});
recPort.sendPort.send("发送消息给消息接收器2!");
Future.microtask(() => print("微任务执行2"));
recPort.sendPort.send("发送消息给消息接收器3!");
Future.microtask(() => print("微任务执行3"));
recPort.sendPort.send("close");
print("main print");
/// 执行顺序 main > microtask > event
}
log:
isolate使用
Isolate.spawn(methodName, sandPort) 产生内存隔离块 方法,并传入消息接口用于通信
isolate 会形成独立的内存块以隔绝外部,以此达到异步的目的
//TODO 6 isolate机制 进行异步通信
void entryPoint(SendPort sendPort){ // 这形成独立内存块
print("entryPoint 休眠两秒...");
sleep(Duration(seconds: 2));
print("entryPoint 休眠结束");
sendPort.send("close");
}
void entryPoint1(SendPort sendPort){
sendPort.send("独立内存已就位,主内存收到请回答");
var recPort = ReceivePort(); // 也注册一个接收器
recPort.listen((message) {
print(message);
});
sendPort.send(recPort.sendPort);
// sendPort.send("close");
}
void use_isolate(){
/// isolate 会形成独立的内存块以隔绝外部,以此达到异步的目的
// 内存块 1
var memory1Name = "memory1";
var recPort = ReceivePort("memory1Name");
recPort.listen((message) {
print("$memory1Name 接收到信息:$message");
if(message == "close"){
recPort.close(); // close后才会触发 onDone()方法
}
}, onDone: (){
print("$memory1Name 结束!");
});
/// 产生内存隔离块,并传入消息接口用于通信
Isolate.spawn(entryPoint, recPort.sendPort);
print("---isolate是异步的,并不会阻碍线程的执行---");
// 内存块 2
var recPort1 = ReceivePort(); // 通过注册接收口来和内存块通信
recPort1.listen((message) {
print(message);
if(message == "close"){
recPort1.close();
}
if(message is SendPort){
(message as SendPort).send("主内存收到!");
}
});
/// 产生内存隔离块,并传入消息接口用于通信
Isolate.spawn(entryPoint1, recPort1.sendPort);
}
广播
stream 默认为单体订阅,想要多个订阅的就需要将其转变为广播( stream.asBroadcastStream() )
//TODO 7 广播
void subscribe1_default(){
//TODO type 7.1 单体订阅
// stream 默认为单体订阅,想要多个订阅的就需要将其转变为广播( stream.asBroadcastStream() )
var stream = Stream.value(1);
stream.listen((event) {
print("stream1 $event");
});
// stream.listen((event) { // 报错:Bad state: Stream has already been listened to.
// print("stream2 $event");
// });
}
void substribe2_brocast(){
//TODO type 7.2 广播
/// 作为广播后就没有订阅量的限制
var stream = Stream.fromIterable([1,2,3,4,5,6]);
stream.listen((event) {
print("转变为广播后订阅1 $event");
});
stream.listen((event) {
print("转变为广播后订阅2 $event");
});
}
void substribe3_ctrl(){
//TODO type 7.3 广播控制器
var stream = Stream.fromIterable([1,2,3,4,5,6]);
var streamCtl = StreamController.broadcast();
streamCtl.stream.listen((event) {
print("广播1:$event");
});
streamCtl.stream.listen((event) {
print("广播2:$event");
});
streamCtl.addStream(stream);// 不能同时使用 addStream() 和 add(), 否则报错
// streamCtl.add(1); Bad state: Cannot add new events while doing an addStream
// streamCtl.add(2);
}