【自种树自乘凉】Dart 入门

421 阅读8分钟

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

目录:

  1. 基础
  2. 变量
  3. 运算符
  4. 函数
  5. 对象
  6. 异步

一. 基础

1. 概念

  • 在变量中可以放置的所有东西都是对象,而每个对象都是类的实例。无论数字、函数和 null 都是对象。所有对象都继承自 Object 类
  • 尽管 Dart 是强类型的,但类型声明是可选的,因为 Dart 可以推断类型。如果要明确说明不需要任何类型,请使用特殊类型 dynamic
  • Dart 支持通用类型,如 List<int>(整数列表)或 List<dynamic>(任何类型的对象列表)。
  • Dart 支持顶级函数(如 main()),以及绑定到类或对象(分别是静态方法(static)和实例(instance)方法)的函数。您还可以在函数(嵌套或局部函数)中创建函数。
  • 类似地,Dart 支持顶级变量,以及绑定到类或对象(静态和实例变量)的变量。实例变量有时被称为字段或属性。
  • 与 Java 不同,Dart 没有公开、保护和私有的关键字。如果标识符以下划线(_)开头,则该标识符对其库是私有的
  • 标识符可以以字母或下划线(_)开头,然后是这些字符加上数字的任何组合。
  • 有时候,某事物是一个表达(expression)还是一个语句(statement)是很重要的,所以这两个词要准确。
  • Dart 工具可以报告两种问题:警告和错误。警告只是表明您的代码可能不工作,但它们不会阻止您的程序执行。错误可以是编译时错误,也可以是运行时错误。编译时错误阻止了代码的执行;运行时错误导致代码执行时引发异常。

2. 内置类型

  • numbers(int 和 double)
  • strings
  • booleans
  • lists
  • maps
  • runes(用于在字符串中表示 Unicode 字符)
  • symbols

3. 枚举

声明一个枚举类型:

enum Color { red, green, blue }

获取每个值的索引:

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

获取枚举中的所有值:

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

二. 变量

1. var

类似于 JavaScript 中的 var,它可以接收任何类型的变量,但最大的不同是 Dart 中 var 变量一旦赋值,类型便会确定,则不能再改变其类型。(有差异是因为 Dart 本身是一个强类型语言,任何变量都是有确定类型的)

var t;
t = "pany";
// 下面代码在 Dart 中会报错,因为变量 t 的类型已经确定为 String
t = 1000;

2. dynamic 和 Object

dynamic 与 var 一样都是关键词,声明的变量可以赋值任意对象。

Object 是 Dart 所有对象的根基类,也就是说所有类型都是 Object 的子类(包括 Function 和 Null),所以任何类型的数据都可以赋值给 Object 声明的对象。

dynamic 与 Object 相同 之处在于,他们声明的变量可以在后期改变赋值类型。

dynamic a; 
Object b;
a = "a";
b = 'b';
a = 1000; // 没有问题
b = 1000; // 没有问题

dynamic 与 Object 不同 的是,dynamic 声明的对象编译器会提供所有可能的组合,而 Object 声明的对象只能使用 Object 的属性与方法,否则编译器会报错。

dynamic a;
Object b;
main() {
    a = "";
    b = "";
    printLengths();
}   
printLengths() {
    print(a.length); // no warning
    print(b.length); // warning:The getter 'length' is not defined for the class 'Object'
}

dynamic 的这个特点使得我们在使用它时需要格外注意,这很容易引入一个运行时错误。

3. final 和 const

如果您从未打算更改一个变量,那么使用 final 或 const。两者区别在于:const 变量是一个编译时常量(引用与值都不能改变),final 变量在第一次使用时被初始化(值能改变)。

注意:实例变量可以是 final,但不能是 const

// 可以省略 String 这个类型声明
final a = "a"; // final String str = "a"; 
const b = "b"; // const String str1 = "b";

const 关键字不只是声明常量变量,你还可以使用它来创建常量值,以及声明创建常量值的构造函数,任何变量都可以赋一个常量值:

var a = const [];
final a = const [];
const a = []; // 等同于:const a = const [],因为后面的 const 是可选的

4. 显式声明要推断的类型

String name = 'pany';
String name = true; // 这句会报错

5. 默认值

未初始化的变量的初始值为 null,甚至具有数字类型的变量最初也是 null,因为数字就像 Dart 中的其他东西一样是对象。

生产环境中 assert() 调用被忽略。开发环境中 assert(condition) 的 condition 条件不为真时抛出异常

int i;
assert(i == null);

三. 运算符

JavaScript 里面没有的三个常用运算符:

1. ?. (根据条件访问成员)

foo?.bar = 4;

foo?.bar 获取 bar 属性,如果 foo 为空则返回 null

2. .. (联级)

注意:严格地说,级联 “..” 表示法不是运算符,这只是 Dart 语法的一部分。

  List<int> listInt = List()
    ..add(0)
    ..add(1)
    ..add(2)
    ..removeAt(1);
  print(listInt); // [0, 2]

3. ??

var a = b ?? 'pany';

如果 b 为 null,则返回 'pany',否则返回 b 的值

四. 函数

1. 函数声明

bool isNoble(int a) {
  return _nobleGases[a] != null;
}

2. 函数作为参数

typedef bool CALLBACK();

// 不指定返回类型,此时默认为 dynamic,不是 bool
isNoble(int a) {
  return _nobleGases[a] != null;
}

void test(CALLBACK cb){
   print(cb()); 
}
// 报错,isNoble 不是 bool 类型
test(isNoble);

3. 函数声明的简写

bool isNoble(int a) => _nobleGases[a] != null ;

4. 函数表达式

var say = (str) {
  print(str);
};
say("hi world");

5. 可选的位置参数

String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

say('Bob', 'Howdy');
say('Bob', 'Howdy', 'smoke signal');
  • 第一句结果是:Bob says Howdy
  • 第二句结果是:Bob says Howdy with a smoke signal

6. 可选的命名参数

// 设置 bold 和 hidden 标志
void enableFlags({bool bold, bool hidden}) {
    // ... 
}

enableFlags(bold: true, hidden: false);

注意:不能同时使用位置参数和命名参数。

7. 词法闭包

Function makeAdder(num addBy) {
  return (num i) => addBy + i;
}

void main() {
  var add2 = makeAdder(2);
  var add4 = makeAdder(4);
  print(add2(3) == 5); // true
  print(add4(3) == 7); // true
}

makeAdder() 捕获变量 addBy。无论返回的函数到哪里,它都会记住 addBy。

8. 判断函数相等

void foo() {} // 顶级函数

class A {
  static void bar() {} // 静态方法
  void baz() {} // 实例方法
}

void main() {
  var x;
  // 比较顶级函数
  x = foo;
  print(foo == x); // true
  // 比较静态方法
  x = A.bar;
  print(A.bar == x); // true
  // 比较实例方法
  var v = A();
  var w = A();
  var y = w;
  x = w.baz;
  print(y.baz == x); // true
  print(v.baz != w.baz); // true
}

9. 如果没有指定返回值,那么函数返回 null

foo() {}
print(foo() == null); // true

五. 对象

1. 判断两个对象是否相等:identical

  • 这里使用常量构造函数举例(使用常量构造函数创建编译时常量,请将 const 关键字放在构造函数名之前)
  • 构造两个相同的编译时常量会生成一个单一的、规范的实例(如下例)
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // 它们是同一个实例

2. 获取对象的类型:runtimeType

print('对象a的类型是:${a.runtimeType}');

3. 构造函数

注意:1. 构造函数不继承 2. 没有构造函数的类会有默认构造函数(没有参数,没有名称)

class Point {
  num x, y;
  Point(this.x, this.y); // 构造函数
}

命名构造函数:

class Point {
  num x, y;
  Point(this.x, this.y);
  Point.origin() { // 命名构造函数
    x = 0;
    y = 0;
  }
}

当然还有其他的构造函数,例如“工厂构造函数”、前文提到的“常量构造函数”等等...

六. 异步

Dart 类库有非常多的返回 Future 或者 Stream 对象的函数。 这些函数被称为 异步函数

1. Future

Future 与 JavaScript 中的 Promise 基本一致。

1.1. Future.then

// 用 Future.delayed 模拟一下耗时任务
Future.delayed(new Duration(seconds: 2),(){
   return "hi world!";
}).then((data){
   print(data); // 两秒后打印 "hi world!"
});

1.2. 捕获异常:

1.2.1 Future.catchError:
Future.delayed(new Duration(seconds: 2),(){
   // return "hi world!";
   throw AssertionError("Error");  
}).then((data){
   // 执行成功会走到这里  
   print("success");
}).catchError((e){
   // 执行失败会走到这里  
   print(e);
});
1.2.2 onError:
Future.delayed(new Duration(seconds: 2), () {
    // return "hi world!";
    throw AssertionError("Error");
}).then((data) {
    print("success");
}, onError: (e) {
    print(e);
});

1.3. Future.whenComplete

使用场景:在网络请求前弹出加载对话框,在请求结束后关闭对话框。

Future.delayed(new Duration(seconds: 2),(){
   // return "hi world!";
   throw AssertionError("Error");
}).then((data){
   // 执行成功会走到这里 
   print(data);
}).catchError((e){
   // 执行失败会走到这里   
   print(e);
}).whenComplete((){
   // 无论成功或失败都会走到这里
});

1.4. 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]); // 4秒后打印 “hello world”
}).catchError((e){
  print(e);
});

2. async/await

async/await 与 JavaScript 中的 async/await 用法一模一样,笔记到这里就跳过了。

无论是在 JavaScript 还是 Dart 中,async/await 都只是一个语法糖,编译器或解释器最终都会将其转化为一个 Promise(Future)的调用链。

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

参考:

Flutter 实战 和 Dart2 中文文档