End

Dart 开发语言概览

本文地址


目录

Dart 开发语言概览

重要概念

以下均假设开启了 空安全

  • 所有变量引用的都是 对象,每个对象都是一个 类 的实例
    • 数字、函数以及 null 都是对象
    • 除去 null 以外, 所有的类都继承于 Object
  • Dart 是强类型语言,但是在声明变量时可不显示指定类型,因为 Dart 可以进行类型推断
  • 变量在未声明为可空类型时不能为 null
    • 可以通过在类型后加上问号 ? 将类型声明为可空
    • 可以在表达式后添加 ! 来断言表达式不为空(为空时将抛出异常)
  • Dart 中可使用 Object? 或者 dynamic 表示任意类型
    • 在编译时,任何成员对 dynamic 类型值访问都是允许的,但在运行时可能会引发异常
    • 避免使用 dynamic,除非你希望禁用静态检查 (Indicates that you want to disable static checking)
  • Dart 支持泛型,比如 List<int>
  • Dart 支持顶级函数,支持定义属于类或对象的函数(即 静态 和 实例方法),支持 函数嵌套 或 局部函数
  • Dart 支持顶级变量,支持定义属于类或对象的变量(即 静态 和 实例变量),实例变量有时称之为域或属性
  • Dart 没有类似于 Java 那样的 public、protected 和 private 成员访问限定符
    • 标识符可以以字母或者下划线 _ 开头
    • 以下划线 _ 开头时表示该标识符在内是私有的
  • Dart 中表达式有值而语句没有值
  • Dart 工具可以显示 警告 和 错误 两种类型的问题
    • 警告表明代码可能有问题但不会阻止其运行
    • 错误分为编译时错误和运行时错误;编译时错误代码无法运行;运行时错误会在代码运行时导致异常

变量

  • 变量仅存储对象的引用,而非对象的值
  • 在 Dart 中一切皆为对象,数字也不例外
  • 建议通过 var 声明局部变量,而非使用指定的类型
    • 因为函数往往很短,省略局部变量类型会将读者的注意力集中在变量的 名称 及初始化值上
  • 并不需要在声明变量时初始化,只需在第一次用到这个变量前初始化即可

late

  • 可以在声明变量时用 late 修饰,用来延迟初始化一个变量
  • late 修饰的变量在使用前没有初始化,会抛出运行时异常 -- 即使变量是可空类型
  • late 修饰的变量在声明时指定了初始化方法,那么初始化过程会且仅会发生在第一次被使用的时候
String? s1;
late String? s2;

void main() {
  assert(s1 == null);
  print(s1); // null
  print(s2); // LateInitializationError: Field 's2' has not been initialized.
}
  • 在生产环境,assert() 的调用将会被忽略掉
  • 在开发过程,assert(condition) 将会在 condition 为 false 时抛出一个异常

final 和 const

  • 关键字 finalconst 可以替代 var 关键字,也可以加在一个具体的类型前
  • final 变量只可以被赋值一次
  • const 变量是一个编译时常量, const 变量同时也是 final
  • 如果使用 const 修饰类中的变量,则必须加上 static 关键字,即 static const, 顺序不能颠倒
final s1 = null;
final String s2 = "s2";
late final s3;

const name = "bqt";
const double pi = 3.14;
  • const 也可以用来创建 常量值,该常量值可以赋予给任何变量
  • const 也可以用来修饰 构造函数,这种类型的构造函数创建的对象是不可改变的
var c1 = const [1, 2];   // 普通变量,可以被修改
final c2 = const [1, 2]; // final 变量,不可修改
const c3 = [1, 2];       // const 变量,不可修改
const c4 = const [1, 2]; // const 变量,不可修改

void main() {
  c1 = [666]; // [666]
  print(c1);
}

注释

  • 单行注释: //
  • 多行注释: 以 /* 开始,以 */ 结尾
  • 文档注释: 以 /// 或者 /** 开始
    • 文档注释可以是多行注释,也可以是单行注释
    • 可以使用中括号引用类、方法、字段、顶级变量、函数和参数
    • 在生成的文档中,[xxx] 会成为一个链接,指向 xxx 方法/类/字段的文档

内置类型

内置类型

数字 int double

  • 整数 int 是不带小数点的数字
  • 如果包含小数点,那么它就是浮点型 double
  • 整型字面量可自动转换成浮点数字面量
  • 数字字面量为编译时常量
  • 很多算术表达式,只要其操作数是常量,则表达式结果也是编译时常量

var x = 1;
var hex = 0xDEADBEEF; // 16^8 = 2^(4*8) 即 32 位

var y = 1.1;
var exponents = 1.42e5;
double z = 1; // Equivalent to double z = 1.0.

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

字符串 String

  • 可以使用单引号或者双引号来创建字符串
  • 使用单引号创建字符串时,可以使用斜杠来转义那些与单引号冲突的字符串
  • 如果 ${表达式} 的结果为一个对象,则 Dart 会调用该对象的 toString 方法来获取一个字符串
  • 可以使用 + 运算符或 并列放置 多个字符串(即便它们不在同一行)来连接字符串
  • 使用三个单引号或者三个双引号也能创建多行字符串
  • 在字符串前加上 r 作为前缀创建 raw 字符串, 即不会被做任何处理(比如转义)的字符串
var s1 = '可以拼接'
    '字符串'
    "即便它们不在同一行。";
assert(s1 == '可以拼接字符串即便它们不在同一行。');

var s2 = '''
You can create
multi-line strings like this one.
''';
print(s2);

var s3 = r'在 raw 字符串中,转义字符串 \n 会直接输出 "\n" 而不是转义为换行。';
print(s3);

const cList = [1, 2];
const cString = '$cList'; // 仅 null、num、String、bool 可以作为字符串字面量的插值表达式

布尔 bool

  • Dart 使用 bool 关键字表示布尔类型
  • 布尔类型只有两个对象 truefalse,两者都是编译时常量

数组 List

  • 可以在 Dart 的集合类型的最后一个项目后添加逗号
  • 在 List 字面量前添加 const 关键字会创建一个编译时常量
  • 扩展操作符 ...: 将一个 List 中的所有元素插入到另一个 List 中
  • 如果扩展操作符右边可能为 null ,可以使用 ...? 来避免产生异常
var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);

var constantList = const [1, 2, 3];

var list2 = [0, ...list]; // 扩展操作符
assert(list2.length == 4);
var list3 = [0, ...?list]; // 空感知扩展操作符

在构建集合时,可以使用条件判断 if 和循环 for

var nav = ['Home', 'Furniture', 'Plants', if (promoActive) 'Outlet'];

var listOfInts = [1, 2, 3];
var listOfStrings = ['#0', for (var i in listOfInts) '#$i'];
assert(listOfStrings[1] == '#1');

无序集合 Set

  • Set 是一组特定元素的无序集合
  • 可以使用在 {} 前加上类型参数的方式创建一个空的 Set,或者将 {} 赋值给一个 Set 类型的变量
  • 可以在 Set 变量前添加 const 关键字创建一个 Set 编译时常量
  • 可以像 List 一样支持使用扩展操作符以及 Collection if 和 for 操作
var set = const {'a', 'c', '1', 'b'};

var names1 = <String>{}; // 创建一个空的 Set
Set<String> names2 = {}; // 创建一个空的 Set
var names3 = {};         // 类型为 Map<dynamic, dynamic>

names1.add("bqt");          // 添加项目
names1.addAll(names2);      // 添加所有项目
print(names1.elementAt(0)); // 获取项目
print(names1.length);       // 获取元素数量

键值对 Map

  • Map 是用来关联 keys 和 values 的对象,其中键和值都可以是任何类型的对象
  • 每个 键 只能出现一次,但是 值 可以重复出现多次
  • 在一个 Map 字面量前添加 const 关键字可以创建一个 Map 编译时常量
  • 可以像 List 一样支持使用扩展操作符以及 Collection if 和 for 操作
var map1 = Map<String, String>();
map1['first'] = 'bqt';

var map2 = { 2: 'bqt'}; // 类型推断为 Map<int, String>

函数

Dart 是一种真正面向对象的语言,所以函数也是对象并且类型为 Function,这意味着函数可以被赋值给变量或者作为其它函数的参数。

  • 建议在公开的 API 上定义返回类型,不过也可以不定义
  • 如果函数体内只包含一个表达式,可以使用 箭头 表达式 => _表达式_ 代替 { return _表达式_; }
    • 只能是 表达式 而不能是 语句

参数

函数可以有两种形式的参数:必要参数可选参数。必要参数定义在参数列表前面,可选参数则定义在必要参数后面。

其中,可选参数可以是 命名参数位置参数

  • 向函数传入参数或者定义函数参数时,可以使用 尾逗号
  • 可以将 命名参数 放在参数列表的任意位置,但通常将 位置参数 放在最前面比较合理
  • 定义函数时,使用 {_参数1_, _参数2_, …} 来指定命名参数,使用 [] 来指定位置参数

命名参数

  • 命名参数默认为可选参数,除非他们被特别标记为 required
  • 如果没有为命名参数提供默认值,也没有使用 required 标记,那么它就要定义为可空的类型,默认值是 null
  • 当调用函数时,可以使用 _参数名_: _参数值_ 指定一个命名参数的值
  • 标记为 required 的命名参数仍然可以是可空的类型

注意: 某些 API(特别是 Flutter 控件的构造器)只使用命名参数

main 函数

每个 Dart 程序都必须有一个 main() 顶级函数作为程序的入口, main() 函数返回值为 void 并且有一个 List<String> 类型的可选参数。

使用 命令行 访问带参数的 main() 函数示例:

// dart TestDart.dart 20094 bqt
void main(List<String> arguments) {
  print(arguments); // [20094, bqt]
}

可以通过 参数库 定义和解析命令行参数。

匿名函数

没有名字的函数称之为 匿名函数Lambda 表达式Closure 闭包

如果函数体内只有一行返回语句,你可以使用胖箭头缩写法。粘贴下面代码到 DartPad 中并点击运行按钮,验证两个函数是否一致。

list
    .map((item) => item.toUpperCase())
    .forEach((item) => print('$item: ${item.length}'));

词法闭包

闭包 即一个函数对象,即使函数对象的调用在它原始作用域之外,依然能够访问在它词法作用域内的变量。

Function makeAdder(int addBy) => (int i, int j) => addBy + i + j;

void main() {
  var add2 = makeAdder(2);
  var add4 = makeAdder(4);

  assert(add2(1, 0) == 3);
  assert(add4(1, 1) == 6);
}

函数相等

void foo() {}          // A top-level function

class A {
  static void bar() {} // A static method
  void baz() {}        // An instance method
}

void main() {
  Function x;

  x = foo;
  assert(foo == x);     // top-level functions

  x = A.bar;
  assert(A.bar == x);   // static methods

  x = A().baz;
  assert(A().baz != x); // refer to different instances, so they're unequal
}

函数返回值

所有的函数都有返回值。没有显式返回语句的函数, 最后一行默认为执行 return null;

foo() {}

assert(foo() == null);

运算符

  • 一旦使用了运算符,就创建了表达式
  • 对于有两个操作数的运算符,左边的操作数决定了运算符的功能
    • 比如表达式 aVector + aPoint 中所使用的是 Vector 对象中定义的相加运算符 (+)
    • 表达式 x == y 返回对 x 调用 == 方法的结果,参数为 y
  • ?[]: 左侧调用者不为空时,访问 List 中特定位置的元素
assert(5 / 2 == 2.5); // 除,Result is a double
assert(5 ~/ 2 == 2);  // 除并取整,Result is an int
assert(5 % 2 == 1);   // 取模,Remainder

类型判断

  • as 类型转换(也用作指定 库前缀
  • is 如果对象是指定类型则返回 true
  • is! 如果对象是指定类型则返回 false

二进制位运算

二进制位运算符仅适用于整数

  • & 按位与
  • | 按位或
  • ^ 按位异或
  • ~表达式 按位取反
  • << 位左移
  • >> 位右移
  • >>> 无符号右移

条件表达式

  • ??=: 用来为值为 null 的变量赋值
  • 表达式 1 ?? 表达式 2: 如果表达式 1 为非 null 则返回其值,否则执行表达式 2 并返回其值
  • foo?.bar: 条件访问成员,如果 foo 为 null 则返回 null ,否则返回 bar
var a = null;
a ??= "bqt";
print(a); // bqt

var b = null;
var name1 = b ?? "bqt1";    // 条件表达式
print("b=$b name1=$name1"); // b=null name1=bqt1

var name2 = b ??= "bqt2";   // 赋值语句
print("b=$b name2=$name2"); // b=bqt2 name2=bqt2

级联运算符

  • 可以在同一个对象上连续调用多个变量或方法
  • 级联运算符可以嵌套
  • 忽略任何可能返回的值
  • 如果级联操作的对象可能为 null,则对第一个操作使用空短路级联 ?..。从 ?.. 开始保证不会对空对象尝试任何级联操作
var paint = getPaint()
  ?..color = Colors.black
  ..strokeCap = StrokeCap.round
  ..strokeWidth = 5.0;

流程控制语句

For 循环

可迭代对象可以使用 forEach() 方法

var list = [1, 2, 3];
var addInt = (e) => print(e + 10);
list.forEach(addInt); // 11 12 13

for 循环中的闭包会自动捕获循环的 索引值 以避免 JavaScript 中一些常见的陷阱

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}

for (final c in callbacks) {
  c(); // 代表调用一个方法
}

上述代码执行后会输出 0 和 1,但是如果在 JavaScript 中执行同样的代码则会输出两个 2

Switch 和 Case

  • Switch 语句中使用 == 来比较两个对象
  • 比较的两个对象必须是同一类型不能是子类并且没有重写 == 操作符
  • 支持空的 case 语句,允许以 fall-through 的形式执行
  • 每一个非空的 case 子句都必须有一个 break 语句
    • 也可以通过 throw 或者 return 来结束非空 case 语句
    • 也支持使用 continue label 来结束
  • 每个 case 子句都可以有局部变量, 且仅在该 case 语句内可见
var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    var a = 1;
    break;
  case 'NOW_CLOSED':
    var a = 2;
    break;
  default:
    var a = 3;
}

assert

  • assert 的第一个参数可以是值为布尔值的任何表达式
    • 如果表达式的值为 true,则断言成功,继续执行
    • 如果表达式的值为 false,则断言失败,抛出一个 AssertionError 异常
  • assert 的第二个参数可以为其添加一个字符串消息

断言是否生效依赖开发工具和使用的框架:

  • Flutter 在 调试模式 时生效
  • 其他一些工具,比如 dart run 以及 dart compile js, 通过在运行时添加命令行参数 --enable-asserts 时生效
  • 在生产环境代码中,断言会被忽略,此时传入 assert 的参数不被判断。

异常

与 Java 不同的是,Dart 的所有异常都是非必检异常,方法不必声明会抛出哪些异常,并且也不必捕获任何异常。

抛出异常

  • 可以将任何非 null 对象作为异常抛出
  • 但优秀的代码通常会抛出 Error 或 Exception 类型的异常
  • 抛出异常是一个表达式,可以在 => 语句中使用
void main() {
  try {
    getName();
  } catch (e) {
    print(e);
  }
}

void getName() => throw Exception("抛出异常");

捕获异常

  • 可以使用 oncatch 来捕获异常,使用 on 来指定异常类型,使用 catch 来捕获异常对象,两者可同时使用
  • 可以为 catch 方法指定两个参数,第一个参数为抛出的异常对象,第二个参数为栈信息 StackTrace 对象
  • 可以使用 rethrow 将捕获的异常再次抛出
try {
  // ···
} on NoSuchMethodError {
  print('NoSuchMethodError');
} on Exception catch (e) {
  print('Exception details:\n $e');
} catch (e, s) {
  print('Stack trace:\n $s');
  rethrow;
}

Finally

  • 无论是否抛出异常,finally 语句始终执行
  • 如果没有指定 catch 语句来捕获异常,则异常会在执行完 finally 语句后抛出

2023-06-24

posted @ 2023-06-24 23:30  白乾涛  阅读(215)  评论(0编辑  收藏  举报