Dart 语法游历记【上】「译」

说明

从今天起我计划看一下 Flutter ,既然要学习 Flutter 势必要先看看 Dart 的语法了,于是就找到了官网的一篇文章 《A tour of the Dart language》,接下来几篇也算是对这篇文章的翻译吧,希望我能从中学到东西,也希望对大家有所帮助。

接下来会向大家展示 Dart 语言的语法特征,从变量和操作符到类和库,这里已经假设你掌握了一门计算机语言,熟悉计算机编程。关于更多 Dart 知识建议你参考语法示例 《Language samples》

启动 DartPad

DartPad 会在顶栏显示当前实例的状态,方便进行查看。打开 DartPad

Dart 中的默认入口函数是 main 函数,可以省略 void 返回声明和 String[] args 参数,例如可以这样写:

定义变量和 js 很像,使用关键字 var, 定义方法和 java 很像,可以省略 void 描述。

1
2
3
4
5
6
7
8
9
printHello(String language){
  print("我要学习 $language 语言");
}

main(){
    var hello = "helloword";
    print(hello);
    printHello('Dart');
}

基础语法

上面已经通过一个变量,一个方法的定义基本了解了 Dart 的语法,事实上很多地方和 Java 语言很类似,这个语言对于 Java 开发者很容易掌握的。

我们先来罗列几个大家熟知的东西:

  1. 单行注释
1
//这是单行注释
  1. 数据类型,例如:int、String、List、和 bool.

Dart 语言对以下类型有特殊的支持:

  • numbers(包括 int 和 double 这一点和 C 语言有点类似,int 的字节数取决于平台,最长不超过 64 位)
  • strings
  • booleans
  • lists
  • sets
  • maps
  • runes (这个大家不熟悉吧,后面我们再看)
  • symbols(这个想必了解 JavaScript 的比较熟悉吧)
  1. 单引号 '' 和双引号 "" 都是字符串。

  2. 同样使用 class 关键字定义一个类:

1
2
3
4
5
class Bicycle {
  int cadence;
  int speed;
  int gear;
}
  1. 同样使用 print() 打印控制台。

  2. 操作符大同小异,基本一样,例如 >, <, && 等。

  3. 控制流程也基本相同,没啥区别,例如: if, for, while, switch 等。

基于上面的一些熟悉知识,接下来我们要走进一些 Dart 所特有的世界,所以必须熟知以下概念:

  1. 变量中的所有东西都是一个对象,而每个对象都是一个类的实例,所有对象都继承自 Object,例如我们上面提到的 numbers 类型、null 类型也都是对象.

  2. Dart 是一个强类型语言,但是类型声明是可选的,因为可以自动推断出来对应类型,例如上面的 var hello = "HelloWorld" 其实可以不用声明为 String hello.

  3. Dart 支持全局函数,入口函数 main() ,类的静态函数,和对象的方法,甚至我们可以在函数中创建函数,这一点和 js 很像。

  4. Dart 支持全局变量,静态变量和成员变量。

  5. 和 Java 不同的是 Dart 中没有 public, private 等标识符,默认都是公有的,使用下划线 _ 开头作为私有化标识。

  6. Dart 既支持语句又支持表达式,例如条件表达式 condition ? expr1 : expr2.

  7. Dart 工具可以报告两种类型的问题: 警告 warning 和 错误 errors. 警告只是表明您的代码可能无法工作,但它们不会阻止程序执行。错误可以是编译时的,也可以是运行时的。编译时错误完全阻止了代码的执行;运行时错误会导致在执行代码时引发异常。

变量

未初始化的变量的初始值为 null。即使是带有 numbers 类型的变量最初也是 null,因为 numbersDart 中的其他所有东西一样都是对象。

1
2
int lineCount;
assert(lineCount == null);

另外,在 Dart 中我们还可以指定变量为动态类型 dynamic.

1
dynamic name = 'Bob';

如果我们要声明一个常量可以使用 const 修饰,如果我们不想改变一个变量的值则可以用 final 修饰,使用 const 修饰的变隐式也是 final 的。一个类的实例变量不能是 const 的。

1
2
3
4
final name = 'Bob'; //没有指明类型
final String nickname = 'Bobby';

name = "Alice";  //继续给 final 变量赋值,这样会 Error

数据类型

int, double, String, Booleans 我们已经很熟悉了,这里就不看了,接下来看几个 Dart 的特殊数据类型。

数组列表

Dart 中数组是 List 对象,所以我们称为 lists. 这个和 JavaScript 中的数组很相似。

1
var list = [1, 2, 3];

上面的 list 编译器会自动推断列表类型是 List<int>. 如果你此时尝试将一个 String 类型放入此数组就会报错。

1
list[0] = "4";

结果会报异常:

1
2
3
4
5
6
Error compiling to JavaScript:
main.dart:7:13:
Error: A value of type 'String' can't be assigned to a variable of type 'int'.
  list[1] = "4";
            ^
Error: Compilation failed.

我们也可以使用 const 关键字创建一个常量列表,这个东西就有点像 Python 中的 Tuple 元祖了。

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

列表操作符

Dart 中提供了两个操作符来方便我们把列表插入到另一个列表中,... 三个点,还有一个是支持空值的 ...? 操作符。

1
2
3
var listA = [1, 2, 3];
var listB = [...listA, 4];
print(listB);  //[1, 2, 3, 4]

假设我们将上面的 4 换成字符串 "4" 呢?最终 listB 到底是 int 类型的列表还是 String 类型的列表?

1
2
3
4
var listA = [1, 2, 3];
var listB = [...listA, "4"];
print(listB[0] is int);  //true
print(listB[0] is String);  //false

你就会发现 listB 是一个 int 类型的列表,如果我们把创建 listB 的顺序掉换,则会产生不同的结果。

1
2
3
4
var listA = [1, 2, 3];
var listB = ["4", ...listA];
print(listB[0] is int);   //false
print(listB[0] is String);  //true

假设我们将 ... 的操作符去掉会是什么结果呢?

1
2
3
var listA = [1, 2, 3];
var listB = ["4", listA];
print(listB); //[4, [1, 2, 3]]

如果我们的 listA 列表是一个 null,此时使用 ... 操作符会报错:

1
2
var listA;
var listB = ["4", ...listA];

这个时候就可以使用 ...? 操作符来避免此异常。

1
2
var listA;
var listB = ["4", ...?listA];

if 和 for 列表

在 Dart 中有一个令你吃惊的语法,就是我们可以在创建列表的时候使用 iffor.

1
2
3
var isopen = false;
var listA = [1, 2, 3, if(isopen) 4 else 5];
print(listA); //[1, 2, 3, 5]

再来看看 for 的使用:

1
2
3
4
var isopen = false;
var listA = [1, 2, 3, if(isopen) 4 else 5];
var listB = [1, 2, 3, for(var i in listA) "listA-item$i"];
print(listB);  //[1, 2, 3, listA-item1, listA-item2, listA-item3, listA-item5]

集合列表

我们可以使用 {} 创建一个空的 Set 集合,也可以直接赋值,使用方法和上面的 list 一样。

1
2
3
4
var isopen = false;
var listA = {1, 2, 3, if(isopen) 4 else 5};
var listB = {1, 2, 3, for(var i in listA) "listA-item$i"};
print(listB);  //{1, 2, 3, listA-item1, listA-item2, listA-item3, listA-item5}

此时我们可以使用集合的特有方法 addaddAll 进行添加元素:

1
2
3
4
5
6
var isopen = false;
var listA = {1, 2, 3, if(isopen) 4 else 5};
var listB = {1, 2, 3, for(var i in listA) "listA-item$i"};
listB.add(4);
listB.addAll({5, 6, 7});
print(listB);  //{1, 2, 3, listA-item1, listA-item2, listA-item3, listA-item5, 4, 5, 6, 7}

我们会发现这个集合也是一个有序的集合,不能等价于 Java 中的 Set.

数据字典

如果我们直接使用 {} 来创建一个空的 Set 集合,事实上是一个 map 字典。

1
2
3
4
5
//var names = <String>{};
//Set<String> names = {}; // This works, too.
var names = {}; // Creates a map, not a set.
names.add("1");
print(names);

上面的 names 默认类型是 Map<dynamic, dynamic> 所以在使用 add() 方法的时候会报错。

在 Dart 中创建一个数据字典的方法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
};

var nobleGases = {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

符文字符串

这里的符文可不是游戏里面的符文,也是将 Runes 直译过来的。符文是字符串的 UTF-32 代码点。在字符串中表示 32 位 Unicode 值需要特殊语法,常用方法是 \uXXXX,其中 XXXX 是 4 位十六进制值,比如小心心(♥)是 \u2665。要指定多于或少于 4 个十六进制数字,请将值放在大括号中。 比如,微笑(😆)是 \u{1f600}

如果你需要读写单个 Unicode 字符,可以使用 characters 包中定义的字符串操作方法。

1
2
3
4
5
6
import 'package:characters/characters.dart';
...
var hi = 'Hi 🇩🇰';
print(hi);   //Hi 🇩🇰
print('The end of the string: ${hi.substring(hi.length - 1)}');  //The end of the string: ???
print('The last character: ${hi.characters.last}\n');  //The last character: 🇩🇰

函数

Dart 是一种真正的面向对象语言,因此即使函数也是对象,并且具有类型 Function。这意味着函数可以赋值给变量,也可以作为参数传递给其他函数。还可以像调用函数一样调用 Dart 类的实例。

1
2
3
String getName() {
  return "Bruce";
}

和大多数语言一样,Dart 的函数定义很简单,而且可以缺省返回类型,因为编译器可以帮助我们自动判断类型。

1
2
3
getName() {
  return "Bruce";
}

默认参数

而且在 Dart 中函数可以通过 {} 指定函数参数的名称和设置默认值,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
isEqual(int a, int b){
  return a == b;
}

//使用 {} 改写

isEqual({int a = 1, int b}){
  return a == b;
}

main(){
  print(isEqual()); //false
}

这个时候我们可以指定部分参数的值,也可以完全不指定,使用默认值,例如:

1
print(isEqual(b:1)); //true

也可以使用 @required 将参数指定为必要参数。

可选参数

通过 [] 符号,可以指定该位置的参数是可选的,例如:

1
2
3
4
5
6
7
8
isEqual(int a, [int b]){
  if(b == null) b = 1;
  return a == b;
}

main(){
  print(isEqual(1));
}

上面的 int b 就是一个可选参数,我们可以使用 isEqual(1) 调用,也可以使用 isEqual(2, 2) 调用,b 参数可省略。

函数变量

正如前面所说,Dart 中的函数是一个 Function 类型的对象,因此它可以被赋值给一个变量。

1
2
3
4
5
6
7
8
var eq = (int a, [int b]){
  if(b == null) b = 1;
  return a == b;
};

main(){
  print(eq(1));
}

函数作为参数

函数作为一个对象,它当然也可以被作为参数被传递:

1
2
3
4
5
6
7
execute(var callback){
    print(callback(1, 1));
}

main(){
  execute((a, b) => a == b);
}

函数闭包

关于闭包的概念可以参考我的另一篇关于 JavaScript 中的闭包的文章 传送门

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Function makeAdder(int addBy) {
  return (int i) => addBy + i;  //返回的是一个内部函数
}

void main() {
  // 创建一个函数 add2
  var add2 = makeAdder(2);

  // 创建一个函数 add4
  var add4 = makeAdder(4);

  assert(add2(3) == 5);  //相当于 (3) => 2 + 3
  assert(add4(3) == 7);
}

这个闭包中变量 addBy 随着函数一直存在于内存中,我们相当于定义了一个可以配置的动态函数对象,相当牛逼。

异常

你的Dart代码可以抛出异常和捕获异常。异常就是出现预期之外的结果的错误。如果没有捕获异常, isolate 将会使异常挂起,往往会导致 isolate 和程序终止。

与 java 相反,Dart 中所有异常都是不需检测的异常。方法并不会声明它将抛出哪些异常,而且你不需要去捕捉任何异常。

Dart 除了提供 Exception、Error 类型以外还提供了众多预定义的子类型。当然,你可以定义你自己的异常类型。毕竟,Dart程序可以将任何非空对象作为异常抛出,不只局限与异常和错误对象。

throw 抛出异常

1
throw new FormatException('Expected at least 1 section');

你也可以将任意对象作为异常抛出:

1
throw 'Out of llamas!';

catch 捕获异常

捕获了一个异常后,就停止了捕获异常过程。捕获一个异常,你就有机会去处理它:

1
2
3
4
5
try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}

为了处理含有多种类型异常的代码,你可以选择多个 catch 子句。第一个匹配抛出对象类型的 catch 子句将会处理这个异常。如果 catch 子句未说明所捕获的异常类型,这个子句就可处理任何被抛出的对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // 一个具体异常
  buyMoreLlamas();
} on Exception catch (e) {
  // 任意一个异常
  print('Unknown exception: $e');
} catch (e) {
  // 非具体类型
  print('Something really unknown: $e');
}

像上面展示的代码一样,你可以用 on 或者 catch ,或者两者都用。当你需要指定异常类型的时候用 on,当你的异常处理者需要异常对象时用 catch。

finally

为了确保不论是否抛出异常,代码都正常运行,请使用 finally 子句。如果没有 catch 匹配子句的异常, finally子句运行以后异常将被传播:

1
2
3
4
5
6
try {
  breedMoreLlamas();
} finally {
  // 即使抛出一个异常时也会进行清理
  cleanLlamaStalls();
}

在匹配了所有 catch 之后,子句 finally 运行了。

1
2
3
4
5
6
7
try {
  breedMoreLlamas();
} catch(e) {
  print('Error: $e');  // 先处理异常
} finally {
  cleanLlamaStalls();  // 然后清理
}

关于 Dart 最基础的语法已经学习完毕,后面计划再续一篇关于类和泛型的翻译文章。