Dart

# Dart

[TOC]

# 一、基础知识

# 1.1 程序入口

每个app都必须有一个顶级的main()函数作为应用程序的入口点。

main() {}
1

# 1.2 控制台输出

print('Hello world!');
1

# 1.3 变量

参考教程:Flutter 知识梳理 (Dart) - Dart 中 static, final, const 区别 (opens new window)

  • String name = 'dart'; 只能是字符串类型。
  • var otherName = 'Dart';接收任何类型的变量,一旦赋值,类型就会确定,不再改变。
  • Object x;所有类型都是Object的子类,即Dart一切皆对象,可赋值任意类型,后期也可改变赋值类型。
  • dynamic t;同Object,不同在于,dynamic声明的对象编译器会提供所有可能的组合, 而Object声明的对象只能使用Object的属性与方法, 否则编译器会报错。故使用dynamics时要小心引入运行时错误。动态类型
  • final str='str'// final String str = 'str'const str='str'// const String str = 'str'final和const只能被设置一次,并且变量类型可以省略。两者区别在于,const 变量是一个编译时常量,final变量在第一次使用时被初始化。
  • 同JS一样,有词法作用域。

# 1.4 检查null或零

未初始化的变量的初始值为null。数字在Dart中也被当成对象,所以只要是带有数字类型的未初始化变量的值都是“null"。

只有布尔值”true"被视为true。

var name = null;
if(name == null){
    print('yes');
}else{
    print('no');
}
// no
1
2
3
4
5
6
7
  • null-aware运算符:null检查最佳实践

    • ?. 运算符在左边为null的情况下会阻断右边的调用。(与js的&&很像)
    • ?? 运算符在左边为null时为其设置默认值。(||)
    var name;
    if(name??true){
        print('yes');
    }
    // yes
    
    1
    2
    3
    4
    5

# 1.5 Functions

  • Dart函数声明如果没有显式声明返回值类型时会默认当做dynamic处理,注意,函数返回值没有类型推断:
// 声明
fn() {
    return true;
}

bool fn() {
    return true;
}
1
2
3
4
5
6
7
8
  • 可选的位置参数。包装一组函数参数,用[]标记为可选的位置参数,并放在参数列表的最后面
String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}
1
2
3
4
5
6
7
  • 可选的命名参数
//设置[bold]和[hidden]标志
void enableFlags({bool bold, bool hidden}) {
    // ... 
}
enableFlags(bold: true, hidden: false);
1
2
3
4
5

不能同时使用可选的位置参数和可选的命名参数。

  • 默认参数值
    • 默认值只能是编译时常量
void enableFlags ({bool bold = false, bool hidden = false}) {...}
1
  • 一个函数可以作为另一函数的参数。(同JS)
void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

// 将 printElement 函数作为参数传递。
list.forEach(printElement);

// 1 2 3
1
2
3
4
5
6
7
8
9
10
  • 果没有明确指定返回值, 函数体会被隐式的添加 return null; 语句。

# 1.6 异步编程

# 1.6.1 Future

类似于JS的Promise。Future 的所有API的返回值仍然是一个Future对象,所以可以很方便的进行链式调用。

  • 用于表示未来某个时间可能会出现的可用值或错误。
Future.delayed(new Duration(seconds: 2),(){
   //return "hi world!";
   throw AssertionError("Error");  
}).then((data){
   //执行成功会走到这里  
   print("success");
}).catchError((e){
   //执行失败会走到这里  
   print(e);
}).whenComplete((){
   //无论成功或失败都会走到这里
});
1
2
3
4
5
6
7
8
9
10
11
12
  • Future.wait
    • 等待多个异步任务都执行结束后才进行一些操作
Future.wait([
  // 2秒后返回结果  
  Future.delayed(new Duration(seconds: 2), () {	// 模拟数据获取的异步任务
    return "hello";
  }),
  // 4秒后返回结果  
  Future.delayed(new Duration(seconds: 4), () {
    return " world";
  })
]).then((results){
  print(results[0]+results[1]);
}).catchError((e){
  print(e);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 1.6.2 async 和 await

功能和用法:于JS一致。

# 1.6.3 Stream

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: (){

});

I/flutter (17666): hello 1
I/flutter (17666): Error
I/flutter (17666): hello 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 1.7 内建类型

Number String Boolean List Set Map Rune Symbol

  • 是 Set 还是 Map ? Map 字面量语法同 Set 字面量语法非常相似。 因为先有的 Map 字母量语法,所以 {} 默认是 Map 类型。 如果忘记在 {} 上注释类型或赋值到一个未声明类型的变量上, 那么 Dart 会创建一个类型为 Map 的对象。

# 1.8 运算符

  • /~/都是除,前者正常返回,后者返回整数部分。

  • 优先级自左向右 >= 、==、&&、|| 、? :

  • 算术运算符

assert(5 / 2 == 2.5); // 结果是双浮点型
assert(5 ~/ 2 == 2); // 结果是整型
assert(5 % 2 == 1); // 余数
1
2
3
  • 赋值运算符

    • 使用 ??= 运算符时,只有当被赋值的变量为 null 时才会赋值给它。
  • 条件表达式

    • expr1 ?? expr2 :如果 expr1 是 non-null, 返回 expr1 的值; 否则, 执行并返回 expr2 的值。
  • 级联运算符

querySelector('#confirm') // 获取对象。
  ..text = 'Confirm' // 调用成员变量。
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

// 等价于
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
1
2
3
4
5
6
7
8
9
10

# 1.9 控制流程语句

  • 和 JavaScript 不同, Dart 的判断条件必须是布尔值,不能是其他类型。

  • 闭包在 Dart 的 for 循环中会捕获循环的 index 索引值, 来避免 JavaScript 中常见的陷阱。

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());
// 0 1
1
2
3
4
5
6
  • 如果不需要使用当前计数值, 使用 forEach() 是非常棒的选择。

  • 现了 Iterable 的类(比如, List 和 Set)同样也支持使用 for-in 进行迭代操作。

  • 在非空 case 中实现 fall-through 形式, 可以使用 continue 语句结合 lable 的方式实现:

var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed;
  // Continues executing at the nowClosed label.

  nowClosed:
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
  • case 语句可以拥有局部变量, 这些局部变量只能在这个语句的作用域中可见。

  • 如果 assert 语句中的布尔条件为 false , 那么正常的程序执行流程会被中断,只在开发环境中有效。assert 的第二个参数可以为其添加一个字符串消息。

# 1.10 类

class class_name {  
   <fields>	// 声明的任何变量
   <getters/setters>	// 显示覆盖默认值
   <constructors>	// 负责为类的对象分配内存
   <functions>	// 方法
}
1
2
3
4
5
6

与 Java 不同,Dart 没有关键字 “public” , “protected” 和 “private” 。 如果标识符以下划线(_)开头,则它相对于库是私有的。

  • 使用 ?. 来代替 . , 可以避免因为左边对象可能为 null , 导致的异常。
// 如果 p 为 non-null,设置它变量 y 的值为 4。
p?.y = 4;
1
2
  • 通常模式下,会将构造函数传入.的参数的值赋值给对应的实例变量。
class Point {
  num x, y;

  Point(num x, num y) {
    // 还有更好的方式来实现下面代码,敬请关注。
    this.x = x;
    this.y = y;
  }
}

// 等价于
class Point {
  num x, y;

  // 在构造函数体执行前,
  // 语法糖已经设置了变量 x 和 y。
  Point(this.x, this.y);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 1.10.1 扩展类(继承)

  • 使用 extends 关键字来创建子类, 使用 super 关键字来引用父类。
  • 子类继承除父类的构造函数之外的所有属性和方法。(构造函数是类的特殊函数)
  • dart不支持多重继承,即一个类可以从多个类继承。
class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • 重写类成员
    • 使用 @override 注解指出想要重写的成员。
    • 重写方法时,函数参数的数量和类型必须和父级匹配。如果参数数量或其数据类型不匹配,Dart编译器会抛出错误。
class SmartTelevision extends Television {
  
  void turnOn() {...}
  // ···
}
1
2
3
4
5

# 1.10.2 枚举类型

  • 不需要示例化,可以直接拿来用。
enum Color {red, green, blue}
1
  • 枚举中的每个值都有一个 index getter 方法。
print(Color.blue.index);	// 2
1

# 1.10.3 getter和setter

  • getter没有参数并返回一个值。
  • setter有一个参数并不返回值。
class Student {
   String name;
   int age;

   String get stud_name {
      return name;
   }

   void set stud_name(String name) {
      this.name = name;
   }

   void set stud_age(int age) {
      if(age<= 0) {
        print("Age should be greater than 5");
      }  else {
         this.age = age;
      }
   }

   int get stud_age {
      return age;     
   }
}  
void main() {
   Student s1 = new Student();
   s1.stud_name = 'MARK';
   s1.stud_age = 0;
   print(s1.stud_name);
   print(s1.stud_age);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 1.10.4 关键字

# 1.10.4.1 this

this关键字指向类的当前实例。这里,参数名称和类字段的名称是相同的。

# 1.10.4.2 static

static关键字可以应用于一类,即,数据成员字段和方法。静态变量保留其值,直到程序完成执行。静态成员由类名引用。

class StaticMem {
   static int num;  
   static disp() {
      print("The value of num is ${StaticMem.num}")  ;
   }
}  
void main() {
   StaticMem.num = 12;  
   // initialize the static variable }
   StaticMem.disp();   
   // invoke the static method
}
1
2
3
4
5
6
7
8
9
10
11
12
# 1.10.4.3 super

super关键字用来指一类的直接父类。关键字可用于引用变量,属性或方法的超类。

void main() {
   Child c = new Child();
   c.m1(12);
}
class Parent {
   String msg = "message variable from the parent class";
   void m1(int a){ print("value of a ${a}");}
}
class Child extends Parent {
   
   void m1(int b) {
      print("value of b ${b}");// value of b 12
      super.m1(13);// value of a 13
      print("${super.msg}");// message variable from the parent class
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 1.11 异常处理

参考教程:https://www.jianshu.com/p/f331898f3183

# 1.11.1 抛出异常

  • 可以抛出任意对象。
throw FormatException('Expected at least 1 section');
throw 'Out of llamas!';
1
2

# 1.11.2 捕捉异常

  • 按类型捕捉异常。
try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // 一个指定的异常
  buyMoreLlamas();
} on Exception catch (e) {
  // 捕获任意一个异常
  print('Unknown exception: $e');
} catch (e,s) {	// 第一个是抛出的异常,第二个是堆栈跟踪( StackTrace对象)。
  // 没有指定类型,处理所有
  print('Something really unknown: $e');
} finally {
    // 最后执行清理
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 部分处理异常,允许传播。
try {
    dynamic foo = true;
    print(foo++); // 运行时错误
} catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // 允许调用者看到异常
}
1
2
3
4
5
6
7

# 二、常用API

# 2.1字符串与数字互转

// String -> int
var one = int.parse('1');

// String -> double
var onePointOne = double.parse('1.1');

// int -> String
String oneAsString = 1.toString();

// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
1
2
3
4
5
6
7
8
9
10
11

# 2.2 List

# 2.2.1 初始化

List list = List();	// list.length == 0
List list = List(2);	// list.length == 2
List list = List<String>();	// list.add(String)
List list = [1,2];	// list.add('1') -> error
List list = ['1',2,true];	// list.add(1.1)
1
2
3
4
5

# 2.2.2 常用属性

list.length;
list.isEmpty;
list.isNotEmpty;
list.first;
1
2
3
4

# 2.2.3 添加数据

List list = [1,2,3];

// 末尾添加
list.add(4); // [1,2,3,4]
list.add([5,6]);	// [1,2,3,4,5,6]

// 向指定位置添加
list.insert(0, 66);	// [66,1,2,3,4,5,6]
list.insertAll(1,[22,22]);	// [66,22,22,1,2,3,4,5,6]
1
2
3
4
5
6
7
8
9

# 2.2.4 删除数据

List list = [1,2,3]// 删除指定元素
list.remove(1);	// [2,3]

// 删除末尾元素
list.removeLast();	// [2]

// 删除指定位置的元素
list.removeAt(0);	// []

list = [1,2,3];

// 删除指定区域的元素
list.removeRange(0,1);	// [2,3]

// 删除指定要求的元素,返回void
list.removeWhere((item) => item == 2);	// [3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 2.2.5 遍历

List list = [1,2,3];

list.forEach((e) => print(e)); // 1 2 3

// 返回一个新集合
list.map((e) => e+1);	// (2,3,4)

// 是否包含某个元素
list.contains(1);	// true

list.sort((n1,n2) => n2 - n1);	// [3,2,1]

// reduce() 将数组中的每一个值与前面返回的值相加,最后返回相加的总和
list.reduce((cur, next) => cur+next);	// 6

// fold() 用法跟 reduce() 基本一样,只不过是可以提供一个初始值
list.fold(4, (cur, next) => cur+next);	// 10

// 判断数组中的每一项是否均达到了某个条件
list.every((e) => e < 4));	// true

// where() 返回数组中满足给定条件的元素集合
list.where((e) => e < 3);	// (1,2)

// firstWhere() 返回数组中满足给定条件的第一个元素
list.firstWhere((e) => e == 3, orElse: ()=> null);	// 3

// singleWhere() 返回数组中满足给定条件的唯一一个元素,若有多个元素满足条件会抛出异常
list.singleWhere((e) => e !=3, orElse: ()=> null);	// Uncaught Error: Bad state: Too many elements

// 扁平化遍历
list = [[1,2],[3]];
list.expand((e) => e);	// (1,2,3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 2.2.6 其他

List list = [1,2,3];

// 克隆(深拷贝)
List list1 = List.from(list);

// 取n个元素
list.take(2);	// (1,2)

// 跳过n个元素
list.skip(2);	// (3)

// 转map
list.asMap();	// {0:1, 1:2, 2:3}
    
// 获取下标集合
list.asMap().keys;	// (0,1,2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 2.3 DateTime

参考教程:https://www.cnblogs.com/lxlx1798/p/11267411.html

# 三、小case

# 3.1 Timer创建循环执行与取消

  _startTimer() {
    /*创建循环*/
    _timer = new Timer.periodic(new Duration(seconds: 2), (timer) {
      setState(() {
        /* do something */
      });
    });
  }

  _cancelTimer() {
    _timer?.cancel();
  }
1
2
3
4
5
6
7
8
9
10
11
12

# 3.2 处理异步操作

Future openImagePicker () {
    // dart:async提供了Completer类,通过实例这个类生成Future
    Complete completer = new Completer();
   
    // ImagePicker 是一个图片选择插件
    ImagePicker.singlePicker(
       context, 
       singleCallback: (data) {
         // 控制completer.future的成功状态
         completer.complete(data);
       },
       failCallback:(err) {
         // 控制completer.future的失败状态
         completer.catchError(err); 
       }
    );
     
    return completer.future;
}

// 使用
openImagePicker().then((data) {}).catchError((err){});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22