淘先锋技术网

首页 1 2 3 4 5 6 7

Dart

开发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目前火热,相信生态之后会越来越好。