An excerpt from Dart: Up and Running

Chapter 2. Dart 语言概览

本章介绍 Dart 语言的主要特性 -- 变量、操作符、类和库 等。 本章假设你具有其他编程语言的背景知识。

Note

你可以按照 the section called “开始使用 Dart” 中介绍的方法创建个命令行应用来 动手演示这些特性。

如果你想了解某个特性的详细信息,可以参考 Dart 语言规范

一个基本的 Dart 程序

下面的代码使用很多 Dart 语言的基本特性:

lang-dart
// 定义一个方法
printNumber(num aNumber) {
  print('The number is $aNumber.'); // 在控制台打印信息
}

// 应用开始执行的入口
main() {
  var number = 42;           // 定义并初始化一个变量
  printNumber(number);       // 调用一个方法
}

下面是上面的代码使用到的可以应用其他(几乎)所有 Dart 应用的 特性:

// 这是一个注释。

使用 // 表明后面的文字为注释。 另外还可以用 /* ... */。详细信息,参考 the section called “注释”

num

一种类型。其他内建的类型有 String、int、和 bool。

42

一个数字 literal(字面量)。 字面量是一种编译期常量。

print()

一种打印结果的助手方法。

'...' (或者 "...")

声明 String 的 字面量。

$variableName (或者 ${expression})

字符串插值(String interpolation):在一个字符串字面量中包含 一个变量或者表达式,最终该变量或者表达式的值将替换在字符串中。详细信息请参考: the section called “Strings”

main()

Dart 程序必需的顶级入口函数。 Dart 程序从该函数开始执行。 详细信息请参考: the section called “ main() 方法”

var

一种定义无类型变量的方法。

Note

我们的代码遵守 Dart 风格指南 中介绍的格式。例如,我们用了两个空格的缩进。

重要的概念

在学习 Dart 语言的时候,请记住如下概念:

  • 变量引用的任意内容都是一个 对象(object),任意对象都是一个 类(class) 的实例。甚至 数字( numbers)、方法( functions)、 和null 都是对象。所有的对象都从 Object 类继承下来。

  • 指定静态类型(例如前面示例的 num ) 明确你的意图,并且可以使用 工具的静态分析工具,但是类型是可选的。(在调试代码的时候,你可能已经注意到了,虽然你指定了静态类型,但是在调试的时候 该变量并没有指定具体的类型,而是使用了一个特殊的类型: dynamic)。

  • Dart 在运行代码之前先解析代码。 你可以给 Dart 提供 一些扑捉错误和帮助提高代码运行效率的建议,例如使用 类型、或者使用编译期常量。

  • Dart 支持顶级方法(例如 main()),也支持实例函数和类函数(静态函数)。 你还可以在一个方法内创建另外一个方法(nested(嵌入方法) 或者 local functions(本地方法))。

  • 同样,Dart 也支持顶级 变量,也支持实例变量和类变量(静态变量)。 实例变量有时候被称之为 值域或者属性(fields 或者 properties)。

  • 和 Java 不同, Dart 没有 publicprotected、 和 private 关键字。如果名字 以下划线(_)开头,则是库私有成员。详细信息请参考: the section called “库和可见性”

  • 标识符(名字) 可以以字母或者 下划线 开头,后面可以跟随 任意的字母和数字的组合。

  • 有时候需要区分一个东西是 expression(表达式) 还是一个 statement(语句), 所以我们将区分这两个词语。

  • Dart 工具可以报告两种错误信息:警告和错误。 警告只是提示你代码可能有潜在的问题,但是并不阻止代码执行。 错误可能是编译时错误也可能是运行时错误。一个编译时错误阻止代码运行,而运行时 错误在遇到的时候会抛出一个 exception

  • Dart 有两种 运行模式: 生产模式和检测模式( production and checked)。生产模式效率高,但是测试模式可以在开发的时候帮助调试代码。

关键字

Table 2.1, “Dart 关键字” 列出了 Dart 语言中的 关键字。

Table 2.1. Dart 关键字

abstract *continueextendsimplements *part *throw
as *defaultfactory *import *rethrowtrue
assertdofalseinreturntry
breakdynamic *finalisset *typedef *
caseelsefinallylibrary *static *var
catchenumfornewsupervoid
classexport *get *nullswitchwhile
constexternal *ifoperator *thiswith


在上表中,带有星号(*)的关键字为 内置的标识符(built-in identifiers)。 虽然你应该把内置的标识符按照关键字对待,其实真正的限制是无法用内置的标识符作为类或者类型的名字。 使用内置的标识符可以简化从 JavaScript 移植到 Dart 的过程。 例如,有些 JavaScript 代码具有一个名字为 factory 的变量; 当移植到 Dart 时无需重命名该变量。 13年十月更新:添加 enum、 rethrow。 Dart 保留关键字:语言规范的 Section 16.1.1 列举了保留的关键字; 12.31 介绍了内置标识符的参考索引。

运行时模式

我们建议在检测模式开发,在生产模式部署。

生产模式是 Dart 程序的默认运行模式,优化了运行速度。 生产模式忽略 assert statements(断言语句) 和静态类型。

检测模式 是开发友好模式, 可以在运行的时候帮助你捕获一些类型错误信息。例如, 如果你给一个类型为 num 的变量 赋值为一个非数字的值,在检测模式会抛出一个异常。

变量

下面是创建变量并赋值的示例:

lang-dart
var name = 'Bob';

变量是引用。名字为 name 的变量包含一个指向内容为 Bob 的 String 对象。

默认值

没有初始化的变量的值为 null。即使类型为数字的变量其值也是 null,不要忘了 数字在 Dart 中也是对象。

lang-dart
int lineCount;
assert(lineCount == null); 
// 变量的初始值为 null。

Note

在生产模式下 assert() 方法不会被调用。在检测 模式下,如果 判断条件 值为 false ,则 assert(判断条件) 抛出一个异常。详细信息请参考: the section called “Assert(断言)”

可选类型

在声明变量的时候可以设置一个静态类型:

lang-dart
String name = 'Bob';

添加类型可以明确表达你的意图,可以替代注释。有些工具(例如编译器和编辑器)可以 使用类型信息帮助你编写代码。

Note

本章内容按照 Dart 风格指南 推荐的方式用 var 来定义 局部变量。

Final 和 Const

如果你重来不需要修改变量的值,则可以用 final 或者 const 来修饰该变量,可以替代 var 关键字也可以用在类型声明之前。 一个 final 类型变量只能设置一次值,一个 const 变量是编译期常量。

定义为 final 的本地变量、顶级变量、或者类变量在第一次 使用的时候初始化:

lang-dart
final name = 'Bob';   // Or: final String name = 'Bob';
// name = 'Alice';    // 这行代码有错误

Note

懒加载 final 变量可以让应用启动速度加快。

const 来定义 编译期常量。 如果是类常量则用 static const 来定义。 实例变量无法用 const 修饰。 在定义变量的时候,可以设置其值为编译期常量,例如: 字符字面量、const 变量或者常量数字的数学运算结果:

lang-dart
const bar = 1000000;       // 压力单位 (in dynes/cm2)
const atm = 1.01325 * bar; // 标准大气压

内置类型

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

  • numbers

  • strings

  • booleans

  • lists (也被称之为 arrays)

  • maps

  • symbols

你可以用字符字面量来初始化上面这些类型。例如 'this is a string' 是一个定义字符串的 字符字面量,而 true 是一个布尔值字符字面量。

由于 Dart 总的变量都是引用一个对象,所以可以使用 构造函数 来初始化变量。 一些内置类型有自己的构造函数。 例如,可以用 Map() 构造函数来创建一个 map 对象: new Map()

Numbers

Dart 支持两种类型数字:

int

整数值

double

IEEE 754 标准定义的 64-bit (双精度) 浮点数。

intdouble 都是 num 的子类。 num 类定义了基础的操作符,例如 +、 -、 /、 和 *、,以及位操作符,例如 >>。同时还定义了一些常用函数: abs() ceil()、 和 floor() 等。 如果 nub 和子类没有你需要的功能,请查找 Math 库。 (在 Dart 代码生成的 JavaScript 中,大整数 和在 Dart VM 中执行的 Dart 代码有点区别。)

Integer 是没有小数点的整数。下面是定义 integer 的一些示例:

lang-dart
var x = 1;
var hex = 0xDEADBEEF;
var bigInt = 346534658346524376592384765923749587398457294759347029438709349347;

如果包含小数点,则为 double。 下面是定义 double 的示例:

lang-dart
var y = 1.1;
var exponents = 1.42e5;

下面是字符串和数字之间的转换示例:

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

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

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

// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');

int 定义了常用的位移操作符 (<<, >>), AND (&), 或者 OR (|) 。示例:

lang-dart
assert((3 << 1) == 6);  // 0011 << 1 == 0110
assert((3 >> 1) == 1);  // 0011 >> 1 == 0001
assert((3 | 4)  == 7);  // 0011 | 0100 == 0111

Strings

Dart 字符串是一个 UTF-16 编码单元序列。 可以用单引号或者双引号定义 一个 String:

lang-dart
var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to just use the other string delimiter.";

通过使用 ${表达式} 语法可以把表达式放到字符串中间。 如果表达式为一个标识符,则可以省略 {}。 Dart 调用 对象的 toString() 函数来获取该对象的字符串值。

lang-dart
var s = 'string interpolation';

assert('Dart has $s, which is very handy.' ==
       'Dart has string interpolation, which is very handy.');
assert('That deserves all caps. ${s.toUpperCase()} is very handy!' ==
       'That deserves all caps. STRING INTERPOLATION is very handy!');

Note

== 操作符测试两个对象是否相等。 具有同样字符的字符串是相等的。

可以通过相邻字符串字面量或者 + 操作符来连接字符串:

lang-dart
adjacent_string_literals.dart
var s1 = 'String ' 'concatenation'
         " works even over line breaks.";
assert(s1 == 'String concatenation works even over line breaks.');

var s2 = 'The addition operator '
         + 'works, as well.';
assert(s2 == 'The addition operator works, as well.');

使用三个双引号或者单引号可以创建多行字符串:

lang-dart
var s1 = '''
You can create
multi-line strings like this one.
''';

var s2 = """This is also a
multi-line string.""";

在前面添加一个 r 可以创建 raw(未转义) 字符串:

lang-dart
var s = r"In a raw string, even \n isn't special.";

在字符串中也可以使用 Unicode 转义:

lang-dart
ch02/quoting.dart
print('Unicode escapes work: \u2665'); // Unicode escapes work: [heart]

关于使用 String 的更多信息,请参考 the section called “字符串(String)和正则表达式”

Booleans

Dart 用 bool 类型代表布尔值。类型 bool 只有两个对象: 布尔字面量 truefalse

当 Dart 需要布尔值的时候, 只有 true 被认为是 true。 其他所有的值都认为是 false。和 JavaScript 不同, 像 1"aString"、和 someObject 在 Dart 中都是 false。

例如 下面的代码在 JavaScript 和 Dart 中都是合法的:

lang-dart
var name = 'Bob';
if (name) {
  print('You have a name!'); // 在 JavaScript 中会执行该语句 ,而 Dart 中不会执行。
}

由于 name 不是 一个 null 对象,所以在 JavaScript 运行上面代码则会打印出 You have a name!。而 在 Dart 的 生产模式 下,不会输出任何内容,原因在于, name 被转换成了 false (因为 name != true)。在 Dart 的 检测模式 下,由于 name 变量不是一个布尔值 会导致抛出异常。

下面是另外一个 JavaScript 和 Dart 代码行为不一致的示例:

lang-dart
if (1) {
  print('JavaScript prints this line because it thinks 1 is true.');
} else {
  print('Dart in production mode prints this line.');
  // 在 Dart 的检测模式下 if (1) 会抛出异常。
}

Note

上面的两个示例只有在生产模式下能够运行。在检测模式下,如果把非布尔值当做布尔值用 会抛出异常。

Dart 只把 true 当做 true,而不是其他对象,可以避免很多不可预料的问题。你不应该这样用:if (nonbooleanValue),而 应该用你期望的值。例如:

lang-dart
// 检测空字符串
var fullName = '';
assert(fullName.isEmpty);

// 检测是否为 0
var hitPoints = 0;
assert(hitPoints <= 0);

// 检测 null.
var unicorn;
assert(unicorn == null);

// 检测  NaN.
var iMeantToDoThis = 0/0;
assert(iMeantToDoThis.isNaN);

Lists

array(数组)也许是大多数编程语言中最常用的集合。 在 Dart 中, 数组为 List 对象, 所以一般我们都称之为 lists(列表)

Dart 列表字面量定义和 JavaScript 中的 数组定义类似。下面 是一个定义 Dart 列表的示例:

lang-dart
var list = [1,2,3];

列表的索引从 0 开始, 0 是第一个元素而 list.length - 1 是最后一个元素。 获取列表的长度和元素与 JavaScript 中的操作一样:

lang-dart
var list = [1,2,3];
assert(list.length == 3);
assert(list[1] == 2);

List 类定义了很多操作列表的函数。详细信息请参考 the section called “Generics(泛型)”the section called “集合”

Maps

一般来说,一个 map 是一个把键(key)和值(value)关联起来的对象。 键和值都可以为任意类型的对象。一个 只能出现一次,但是一个 可以设置到多个键上。 Dart 通过 map 字面量和 Map 类型支持 Map。

下面是用 map 字面量创建 Map 对象的一些示例:

lang-dart
var gifts = {
// Keys       Values
  'first'  : 'partridge',
  'second' : 'turtledoves',
  'fifth'  : 'golden rings'
};

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

也可以用 Map 构造函数创建这些对象:

lang-dart
ch02/map_constructor.dart
var gifts = new Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var nobleGases = new Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';

往 map 对象中添加 键-值 对和 JavaScript 中的操作一样:

lang-dart
var gifts = { 'first': 'partridge' };
gifts['fourth'] = 'calling birds';    // Add a key-value pair

从 map 中获取值也和 JavaScript 一样:

lang-dart
var gifts = { 'first': 'partridge' };
assert(gifts['first'] == 'partridge');

如果一个 键 不在 map 中,则返回的值为 null:

lang-dart
var gifts = { 'first': 'partridge' };
assert(gifts['fifth'] == null);

.length 来获取 map 中 键值对的个数:

lang-dart
var gifts = { 'first': 'partridge' };
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);

关于 map 的详细信息请参考:the section called “Generics(泛型)”the section called “Maps”

Symbols(符号)

一个 Symbol 对象代表 Dart 代码中的一个操作符或者一个标识符。你可能无需使用 Symbol,但是对于需要访问 标识符 名字的 API 来说, Symbol 是非常有用的,对于混淆过的代码, Symbol 也返回混淆之前的标识符名字。

要获取一个标识符的 Symbol,可以 用 symbol 字面量定义: 一个 #后面跟着标识符的名字

lang-dart
#radix  // The symbol literal for an identifier named 'radix'.
#bar    // The symbol literal for an identifier named 'bar'.

关于 symbol 的详细信息,请参考 the section called “dart:mirrors - Reflection(反射)”

Functions(方法)

下面是实现一个方法的示例:

lang-dart
void printNumber(num number) {
  print('The number is $number.');
}

虽然风格指南中建议设置参数和返回值的类型,但是你也可以不这么做:

lang-dart
printNumber(number) {          // 不设置类型也可以
  print('The number is $number.');
}

对于只有一个表达式的方法,你可以用缩写形式:

lang-dart
printNumber(number) => print('The number is $number.');

=> expr; 语法是 { return expr;} 的缩写形式。 在上面的 printNumber() 方法中,表达式为 顶级方法 print()

Note

arrow (=>) 和 分号 (;) 之间只能是 expression(表达式),不能用 statement(语句) 。 例如,不能用 if statement,但是可以用 条件 (?:) 表达式

=> 也可以使用参数类型(代码风格指南不建议使用类型):

lang-dart
printNumber(num number) => print('The number is $number.'); // Types are OK.

调用方法的示例:

lang-dart
printNumber(123);

方法参数有两种类型:必需的和可选的。 必需的参数出现在可选参数前面。

可选参数

可选参数可以是命名参数或者位置参数。但是不能同时使用这两种类型。

可选参数可以有默认值。默认值必需为编译期常量,例如 字面量定义。 如果没有提供默认值,则默认值为 null

可选命名参数

使用 paramName: value 可以设置方法的命名参数。例如:

lang-dart
enableFlags(bold: true, hidden: false);

{param1, param2, …} 来定义方法的 命名参数:

lang-dart
/// Sets the [bold] and [hidden] flags to the values you specify.
enableFlags({bool bold, bool hidden}) {
  // ...
}

使用冒号 (:) 来设置默认值:

lang-dart
/**
 * Sets the [bold] and [hidden] flags to the values you specify,
 * defaulting to false.
 */
enableFlags({bool bold: false, bool hidden: false}) {
  // ...
}

enableFlags(bold: true); // bold 的值为 true; hidden 没指定则为默认值 false.

可选位置参数

位于 [] 中间的参数为可选位置 参数

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

下面是没有设置可选位置参数的调用示例:

lang-dart
assert(say('Bob', 'Howdy') == 'Bob says Howdy');

下面是设置了可选位置参数的示例:

lang-dart
assert(say('Bob', 'Howdy', 'smoke signal') ==
  'Bob says Howdy with a smoke signal');

= 设置默认值:

lang-dart
String say(String from, String msg,
  [String device='carrier pigeon', String mood]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  if (mood != null) {
    result = '$result (in a $mood mood)';
  }
  return result;
}

assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');

main() 方法

每个应用都有一个顶级的 main() 方法, 该方法为程序的执行入口。 main() 方法的返回值为 void 并有个可选 List<String> 参数来接收程序参数。

下面是一个 web 应用中的 main() 方法示例:

lang-dart
main() {
  querySelector("#sample_text_id")
    ..text = "Click me!"
    ..onClick.listen(reverseText);
}

下面是带有两个参数的命令行程序 main() 方法示例:

lang-dart
ch02/args.dart
// 通过命令运行该程序: dart args.dart 1 test
void main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2); 
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test'); 
}

可以用 args 库 来定义和解析命令行参数。

(方法作为一等对象)Functions as First-Class Objects

可以把方法作为方法的参数使用:

lang-dart
printElement(element) {
  print(element);
}
  
var list = [1,2,3];
list.forEach(printElement); // 把 printElement 方法当做参数来用

还可以用一个变量来保存方法引用:

lang-dart
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

(语法作用域)Lexical Scope

Dart 是一种语法作用域语言,也就是说 变量的作用域 是根据代码布局静态判断的。 可以根据“变量位于花括号内外”来判断一个变量是否在作用域中。

下面是一个嵌套函数变量作用域示例:

lang-dart
var topLevel = true;
main() {
    var insideMain = true;
    
    myFunction() {
      var insideFunction = true;
      
      nestedFunction() {
        var insideNestedFunction = true;
        assert(topLevel);
        assert(insideMain);
        assert(insideFunction);
        assert(insideNestedFunction);
      }
    }
}

注意 nestedFunction() 可以 使用外面所有花括号内的变量。

(语法闭包)Lexical Closures

一个 closure(闭包) 是一个可以 访问位于其语法作用域内变量的方法对象,即时该方法用在其定义的作用域之外,也可以访问这些变量。

方法可以覆盖定义在附近作用域的变量 (Functions can close over variables defined in surrounding scopes)。 在下面的示例中, adder() 捕获变量 addBy。 无论在哪里使用返回的 adder 函数,该函数都记得 addBy 参数的值。

lang-dart
ch02/function_closure.dart
/// Returns a function that adds [addBy] to a number.
Function makeAdder(num addBy) {
  adder(num i) {
    return addBy + i;
  }
  return adder;
}

main() {
  var add2 = makeAdder(2); // Create a function that adds 2.
  var add4 = makeAdder(4); // Create a function that adds 4.

  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

判断函数相等

下面是测试顶级方法、静态函数、和实例函数相等的示例:

lang-dart
ch02/function_equality_2.dart
foo() {}               // A top-level function

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

main() {
  var x;
  
  // Comparing top-level functions.
  x = foo;
  assert(x == foo);
  
  // Comparing static methods.
  x = SomeClass.bar;
  assert(x == SomeClass.bar);
  
  // Comparing instance methods.
  var v = new SomeClass();
  var w = new SomeClass();
  var y = v;
  x = v.baz;
  
  assert(x == y.baz);
  assert(v.baz != w.baz);
}

返回值

每个方法都有一个返回值。如果没有指定返回值,则默认会在函数最后附加一个 return null; 的语句。

操作符

Dart 定义了 Table 2.2, “操作符和优先级” 表中的操作符。 很多操作符都可以重写,在 the section called “重写操作符” 介绍了如何重写操作符。

Table 2.2. 操作符和优先级

描述操作符
一元后缀操作符expr++    expr--    ()    []    .
一元前缀操作符-expr    !expr    ~expr    ++expr    --expr   
乘除*    /    %    ~/
加减+    -
位移<<    >>
位与 AND&
位异或 XOR^
位或 OR|
关系和类型测试>=    >    <=    <    as    is    is!
相等==    !=   
逻辑与 AND&&
逻辑或 OR||
条件表达式expr1 ? expr2 : expr3
级联操作符..
赋值=    *=    /=    ~/=    %=    +=    -=    <<=    >>=    &=    ^=    |=   

当使用操作符的时候,就会创建一个 表达式。 下面是一些操作符表达式的示例:

lang-dart
a++
a + b
a = b
a == b
a? b: c
a is T

Table 2.2, “操作符和优先级” 中,每一行操作符都比其下面的操作符优先级要高。 例如,除法操作符 %== 操作符优先级高,而 == 又比 && 优先级高。 所以下面的两行代码是一样的:

lang-dart
if ((n % i == 0) && (d % i == 0)) // 使用括号提高代码可读性
if (n % i == 0 && d % i == 0)     // 虽然难读,但是效果一样

Warning

对于连接两个操作数的操作符来说,左边的操作数决定使用那个版本的操作符。例如 ,如果你有一个 Vector 对象和一个 Point 对象, aVector + aPoint 则用 Vector 版本的 + 操作符。

算术操作符

Dart 支持常规的算术操作符,如 Table 2.3, “算术操作符” 表格所示:

Table 2.3. 算术操作符

操作符描述
+加号
减号
-expr负号
*乘号
/除号
~/整除号
%取余

示例:

lang-dart
ch02/arithmetic_operators.dart
assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5);   // Result is a double
assert(5 ~/ 2 == 2);    // Result is an integer
assert(5 % 2 == 1);     // Remainder

print('5/2 = ${5~/2} remainder ${5%2}'); // 5/2 = 2 remainder 1

Dart 也支持前缀和后缀 加和减 操作符。

Table 2.4. 加减操作符

操作符描述
++varvar = var + 1 (expression value is var + 1)
var++var = var + 1 (expression value is var)
--varvar = var – 1 (expression value is var – 1)
var--var = var – 1 (expression value is var)

示例:

lang-dart
var a, b;

a = 0;  
b = ++a;         // Increment a before b gets its value.
assert(a == b);  // 1 == 1  

a = 0;
b = a++;         // Increment a AFTER b gets its value.
assert(a != b);  // 1 != 0

a = 0;
b = --a;         // Decrement a before b gets its value.
assert(a == b);  // -1 == -1

a = 0;
b = a--;         // Decrement a AFTER b gets its value.
assert(a != b) ; // -1 != 0

关系和相等操作符

Table 2.5. 关系和相等操作符

操作符描述
==相等
!=不等
>大于
<小于
>=大于等于
<=小于等于

== 来测试两个对象 xy 是否代表同一个东西。 (在极少数情况下,你需要知道两个对象是否是同一个对象,则使用 identical() 函数。) 下面是 == 操作符工作原理:

  1. 如果 x 或者 y 是 null,只有当另外一个也是 null 的时候才返回 true, 否则只有一个为 null 则返回 false。

  2. 然后执行 x.==(y) 函数。 (没错, == 操作符就是第一个对象的函数。 你也可以重写大部分操作符,包括 ==,在 the section called “重写操作符” 介绍如何重写操作符。)

下面是使用关系和等于操作符的示例:

lang-dart
ch02/op_equality.dart
assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(2 <= 3);

类型测试操作符

asis、和 is! 操作符是运行时判断类型的操作符。

Table 2.6. 类型测试操作符

操作符描述
asTypecast(类型转换)
is如果 对象是该类型 则返回 true
is!如果 对象是该类型 则返回 false

只有当 obj 实现了 T 接口(类) 的时候, obj is T 的结果才是 true 。 例如 obj is Object 总是返回 true。

as 操作符把一个对象转换为 另外一个类型。 通常 用来替换 is 判断后跟一个使用该 对象的表达式。 例如,下面的示例:

lang-dart
if (person is Person) {               // Type check
  person.firstName = 'Bob';
}

使用 as 操作符可以简化代码:

lang-dart
(person as Person).firstName = 'Bob';

Note

注意:上面的代码并不等同。如果 person 是 null 或者 不是一个 Person,第一个示例 (使用 is 的) 不执行后面的代码; 而第二个示例 (使用 as 的) 则会抛出一个 异常

赋值操作符

前面已经见到过了,用 = 来赋值。也可以用复合赋值操作符 来赋值,例如 +=

Table 2.7. 赋值操作符

=–=/=%=>>=^=
+=*=~/=<<=&=|=

下面是复合赋值操作符的工作原理

 复合赋值操作符等同的表达式
对于一个操作符 op:a op= ba = a op b
示例 a += ba = a + b

下面示例使用了 赋值和复合赋值操作符:

lang-dart
var a = 2;           // Assign using =
a *= 3;              // Assign and multiply: a = a * 3
assert(a == 6);

逻辑操作符(逻辑运算符)

逻辑操作符可以组合和取反义操作。

Table 2.8. 逻辑操作符

操作符描述
!expr取表达式的反义 (如果表达式为 true,则结果为 false,反之亦然。)
||逻辑或 OR
&&逻辑与 AND

下面是使用逻辑操作符的示例:

lang-dart
if (!done && (col == 0 || col == 3)) {
  // ...Do something...
}

位和移位操作符

在 Dart 中可以操作每个数字的字节。 通常在整数上使用 位和移位操作符。如下表所示 Table 2.9, “位和移位操作符” 的操作符:

Table 2.9. 位和移位操作符

操作符描述
&AND
|OR
^XOR
~expr一元位补码操作符 (0s 变为 1s; 1s 变为 0s)
<<左移
>>右移

下面是使用位和移位操作符的示例:

lang-dart
final value = 0x22;
final bitmask = 0x0f;

assert((value & bitmask)  == 0x02);  // AND
assert((value & ~bitmask) == 0x20);  // AND NOT
assert((value | bitmask)  == 0x2f);  // OR
assert((value ^ bitmask)  == 0x2d);  // XOR
assert((value << 4)       == 0x220); // Shift left
assert((value >> 4)       == 0x02);  // Shift right

其他操作符

其他一些没介绍的操作符,但是大部分你应该在其他地方都看到过了。

Table 2.10. 其他操作符

操作符名字描述
()方法调用代表调用一个方法
[]List 访问获取 list 某个位置的内容
expr1 ? expr2 : expr3条件表达式如果 expr1 是 true,执行 expr2;否则,执行 expr3
.成员访问访问一个成员,例如 foo.bar 选择属性 ,访问 foo 对象的 bar 属性
..级联操作符可以在同一个对象上执行多个炒作 ,参考 the section called “Classes”

控制流程语句

使用如下控制语句可以控制 Dart 代码的执行流程:

  • ifelse

  • for 循环

  • whiledo-while 循环

  • breakcontinue

  • switchcase

  • assert

使用 try-catchthrow 也可以影响执行流程,在 the section called “Exceptions(异常)” 中有详细介绍。

If 和 Else

Dart 支持 if 语句和可选的 else 语句。 在 the section called “其他操作符” 还介绍了条件表达式 (?:)。

lang-dart
if (isRaining()) {
  you.bringRainCoat();
} else if (isSnowing()) {
  you.wearJacket();
} else {
  car.putTopDown();
}

注意: Dart 和 JavaScript 不同, 在 Dart 中只有 true 才被当做 true,所有其他对象都是 false。详细信息参考 the section called “Booleans”

For 循环

如下是标准的 for 循环示例:

lang-dart
ch02/flow_for_loops.dart
var message = new StringBuffer("Dart is fun");
for (var i = 0; i < 5; i++) {
  message.write('!');
}

Dart for循环中的闭包捕获 循环的索引,避免 JavaScript 中出现的弊端。例如:

lang-dart
ch02/flow_for_loops.dart
var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());

打印的结果为 0 然后是 1,和预期的结果一样。 而在 JavaScript 中,结果会是 22

如果循环的对象是一个 Iterable ,则可以用 forEach() 函数。如果无需关心当前的循环索引,则使用 forEach() 是更好的选择:

lang-dart
ch02/flow_for_loops.dart
candidates.forEach((candidate) => candidate.interview());

像 List 和 Set 这种 Iterable 对象还支持 for-in 循环,在 in the section called “Iteration(遍历)” 有介绍:

lang-dart
ch02/flow_for_loops.dart
var collection = [0, 1, 2];
for (var x in collection) {
  print(x);
}

While 和 Do-While

一个 while 循环会在循环开始之前先 计算条件的值:

lang-dart
while(!isDone()) {
  doSomething();
}

而 一个 do-while 循环会先循环后计算 条件的值:

lang-dart
do {
  printLine();
} while (!atEndOfPage());

Break 和 Continue

使用 break 来终止 循环:

lang-dart
while (true) {
  if (shutDownRequested()) break;
  processIncomingRequests();
}

使用 continue 来跳到下一个循环:

lang-dart
for (int i = 0; i < candidates.length; i++) {
  var candidate = candidates[i];
  if (candidate.yearsExperience < 5) {
    continue;
  }
  candidate.interview();
}

如果使用 list 或者 set 则实现方式可以有所不同:

lang-dart
candidates.where((c) => c.yearsExperience >= 5)
          .forEach((c) => c.interview());

Switch 和 Case

Dart 中的 Switch 语句用 == 来比较 integer、 string、或者编译期常量 。被比较的对象必须为同一个类的实例(子类型不可以),并且类必须没有重写 == 操作符。

规则需要每个非空 case 块以 break 语句结尾。 其他非空 case 块可以是一个 continuethrow、或者 return 语句。

使用一个 default 语句来执行 不匹配任何 case 语句的情况:

lang-dart
var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClosed();
    break;
  case 'PENDING':
    executePending();
    break;
  case 'APPROVED':
    executeApproved();
    break;
  case 'DENIED':
    executeDenied();
    break;
  case 'OPEN':
    executeOpen();
    break;
  default:
    executeUnknown();
}

下面的示例代码在 case 块中忘记添加 break 语句了, 这样会产生一个错误:

lang-dart
var command = 'OPEN';
switch (command) {
  case 'OPEN':
    executeOpen();
    // ERROR: 丢掉 break 语句会抛出一个异常!!

  case 'CLOSED':
    executeClosed();
    break;
}

然而, Dart 支持空 case 块,空快可以允许执行后面一个 case 块的功能:

lang-dart
var command = 'CLOSED';
switch (command) {
  case 'CLOSED':     // 空块会漏到下个块执行
  case 'NOW_CLOSED':
    //  CLOSED 和 NOW_CLOSED 都会执行该代码
    executeNowClosed();
    break;
}

如果你真的需要执行下一个 case 块中的代码,则可以用一个 continue 语句和一个 label 组合:

lang-dart
var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed; // 继续执行 nowClosed label

nowClosed:
  case 'NOW_CLOSED':
    // CLOSED 和 NOW_CLOSED 都会执行该代码
    executeNowClosed();
    break;
}

case 块可以有本地变量, 该变量只在该 case 块中可见。

Assert(断言)

使用 assert 语句来检测执行条件,如果提供的条件是 false 则中断执行并抛出异常。下面是一个 assert 示例:

lang-dart
assert(text != null);  // 确保 text 不为 null
assert(number < 100);  // 确保该值小于 100.
assert(urlString.startsWith('https')); // 确保字符串以 https 开头

Note

Assert 语句只在检测模式下有效。 在生产模式下无任何效果。

assert 的参数可以是任何结果为 布尔 值的表达式或者方法。 如果表达式或者方法的返回值为 true, 则通过断言检测代码继续执行。否则,断言将抛出一个异常 (是一个 AssertionError)。

Exceptions(异常)

Dart 代码可以抛出和捕获异常。异常代表不可预期的错误情况。如果没有捕获异常, 则抛出该异常的 isolate 将被挂起,通常该 isolate 就退出执行了。

和 Java 语言不同, Dart 的所有异常都是 非检查异常(unchecked exceptions)。 函数不用声明其可能抛出的异常,并且你也无捕获任何 exceptions(异常)

Dart 提供了 ExceptionError 类型,和很多预定义的子类。你也可以定义自己的异常类型。 然而,Dart 程序可以把任意非 null 对象当做异常抛出,而不仅仅是 Exception 和 Error 对象。

Throw

下面是 抛出 异常的示例:

lang-dart
throw new ExpectException('Value must be greater than zero');

还可以抛出其他任意对象:

lang-dart
throw 'Out of llamas!';

由于抛出异常是一个表达式,所有在任意可以用表达式的地方都可以抛出异常, 例如 => 语句。

lang-dart
distanceTo(Point other) => throw new UnimplementedError();

Catch

捕获异常可以阻止异常继续传递下去。捕获异常提供了一个处理该异常的机会:

lang-dart
try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}

要处理可能抛出多个类型异常的情况,你可以用多个 catch 语句。 如果第一个 catch 语句不匹配该异常,则执行下一个。 如果一个 catch 语句没有设置特殊的异常类型,则该 catch 语句可以处理 所有 类型的异常:

lang-dart
try {
  breedMoreLlamas();
} on OutOfLlamasException {           // 一个具体的异常
  buyMoreLlamas();
} on Exception catch(e) {             // 任意 Exception 异常
  print('Unknown exception: $e');
} catch(e) {                          // 处理所有异常
  print('Something really unknown: $e');
}

如上代码所示, 你可以用 on 或者 catch ,也可以同时使用。当需要指定异常类型的时候,用 on。 当需要使用 异常 对象的时候,用 catch

Finally

finally 块中的代码不管是否出现异常 都会执行。如果没有 catch 捕获异常,则异常会在执行 完 finally 块之后继续传递下去:

lang-dart
try {
  breedMoreLlamas();
} finally {
  cleanLlamaStalls();  // 总是清理现场,不管是否抛出异常
}

finally 块在 catch 块之后执行:

lang-dart
try {
  breedMoreLlamas();
} catch(e) {
  print('Error: $e');  // 先处理异常
} finally {
  cleanLlamaStalls();  // 再执行清理
}

更多信息请参考 the section called “异常(Exceptions)”

Classes

Dart 是面向对象的语言,支持 类和单继承。 每个对象都是一个类的实例,所有的类都是 Object 的子类。

可以用 new 关键字和 类的 构造函数 创建对象。 构造函数的名字可以为 ClassName 或者 ClassName.identifier。例如:

lang-dart
var jsonData = JSON.decode('{"x":1, "y":2}');

var p1 = new Point(2,2);               // 用 Point() 创建一个 Point 对象。
var p2 = new Point.fromJson(jsonData); // 用 Point.fromJson() 构造函数创建 Point 对象

对象有成员变量 构成的数据和方法 (函数 实例变量 )。 当调用一个函数的时候, 该函数可以访问对象的数据:

使用一个圆点 (.) 来访问对象的变量或者函数:

lang-dart
var p = new Point(2,2);

p.y = 3;             // 设置实例变量 y 的值
assert(p.y == 3);    // 访问实例变量 y 的值

num distance = p.distanceTo(new Point(4,4)); // 调用 p 的 distanceTo() 函数

当想在同一个对象上执行多个操作的时候,可以用级联操作符 (..) :

lang-dart
querySelector('#button')
    ..text = 'Click to Confirm'                        // Get an object. Use its
    ..classes.add('important')                         // instance variables
    ..onClick.listen((e) => window.alert('Confirmed!')); // and methods.

有些类提供了常量构造函数。可以用来创建编译期常量对象,用 const 关键字而不是 new 来调用这种构造函数:

lang-dart
var p = const ImmutablePoint(2,2);

用常量构造函数创建两个一样的常量,这两个 常量是 同一个 实例:

lang-dart
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a,b)); // 同一个实例!

下面的小节介绍如何定义类

实例变量

下面是定义实例变量的示例:

lang-dart
ch02/instance_variables.dart
class Point {
  num x;      // 定义一个实例变量 (x),初始值为 null。
  num y;      // 定义 y, 初始值为  null。
  num z = 0;  // 定义 z, 初始值为 0。
}

所有没有初始化的实例变量的值都是 null

所有实例变量都自动生成一个隐含的 getter 函数。非 final 实例变量也生成了一个隐含的 setter 函数。详情请参考 the section called “Getters 和 setters”

lang-dart
ch02/instance_variables.dart
class Point {
  num x;
  num y;
}

main() {
  var point = new Point();
  point.x = 4;             // 调用 x 的 setter 函数
  assert(point.x == 4);    // 调用 x 的 getter 函数
  assert(point.y == null); // 默认值为 null。
}

如果你在定义实例变量的时候就设置她的值,而不是在构造函数或者其他函数中设置,则该实例创建的时候赋值变量的值。 赋值操作发生在构造函数和初始化列表执行之前。

构造函数

构造函数的名字和类的名字一样(还可以附加其他关键字,the section called “命名构造函数”)。 常用的构造函数就是用来创建一个类的实例的:

lang-dart
ch02/constructor_long_way.dart
class Point {
  num x;
  num y;

  Point(num x, num y) {
    // 这种初始化赋值操作还有更好的实现方式,请往下看!
    this.x = x;
    this.y = y;
  }
}

this 关键字引用当前实例。

Note

只有在有名字冲突的时候才使用 this 。 其他情况, Dart 风格指南建议忽略 this

把构造函数参数赋值给实例变量是如此的常见,所以 Dart 提供了一个语法糖让该操作更加便捷:

lang-dart
class Point {
  num x;
  num y;

  // 在构造函数体执行之前设置实例变量的语法糖
  Point(this.x, this.y);
}

默认构造函数

如果没有定义构造函数,则会生成一个默认构造函数。 默认构造函数没有参数,并调用没有参数的 superclass(父类) 构造函数。

构造函数不支持继承

子类并没有继承父类的构造函数。 一个没有定义构造函数的子类只有无参数无名子的默认构造函数。

命名构造函数

用命名构造函数来提供多个构造函数或者提供更加清晰的信息:

lang-dart
ch02/named_constructor.dart
class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // 命名构造函数
  Point.fromJson(Map json) {
    x = json['x'];
    y = json['y'];
  }
}

注意:构造函数不支持继承,所以子类也没有继承父类的命名构造函数。 如果你想让子类也能用父类的命名构造函数,则你必需在子类中定义并实现该构造函数。

调用不是默认的父类构造函数

默认情况下,子类的构造函数会调用父类的默认构造函数。 如果父类没有无名无参数的默认构造函数,则子类必需 手工的调用一个父类构造函数。 在冒号 (:) 后面和构造函数体之前指定要调用的父类构造函数。

lang-dart
ch02/op_as.dart
class Person {
  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person 没有默认构造函数
  // 所以必需指定 super.fromJson(data) 构造函数
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // 打印结果:
  // in Person
  // in Employee
}

由于调用父类构造函数的参数在执行构造函数之前执行, 所有可以用表达式作为参数,例如 用一个方法:

lang-dart
ch02/method_then_constructor.dart
class Employee extends Person {
  ...
  Employee() : super.fromJson(findDefaultData());
}

Warning

调用父类构造函数的参数无法访问 this 对象。 例如,参数可以访问静态函数当时无法访问实例函数。

初始化列表

除了调用父类构造函数外,也可以在执行构造函数体之前来初始化实例变量。 用逗号分割多少初始化变量。

lang-dart
ch02/initializer_list.dart
class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // 初始化列表在构造函数体执行之前设置实例变量
  Point.fromJson(Map json) : x = json['x'], y = json['y'] {
    print('In Point.fromJson(): ($x, $y)');
  }
}

Warning

右边的初始化列表无法访问 this.

重定向构造函数

有些构造函数只是调用 同一个类 的另外一个构造函数。 重定向构造函数体是空的,被调用的构造函数放到冒号后面。

lang-dart
ch02/along_x_axis.dart
class Point {
  num x;
  num y;

  Point(this.x, this.y);                // 该类的主要构造函数
  Point.alongXAxis(num x) : this(x, 0); // 调用主要构造函数
}

常量构造函数

如果你的类生成从来不改变的对象,则可以把这些对象定义为编译期常量。 用一个 const 构造函数并把实例变量设置为 final 来实现该功能。

lang-dart
ch02/immutable_point.dart
class ImmutablePoint {
  final num x;
  final num y;
  const ImmutablePoint(this.x, this.y);
  static final ImmutablePoint origin = const ImmutablePoint(0, 0);
}

工厂构造函数

如果一个构造函数并不总是创建一个新的对象,则可以用 factory 关键字来实现构造函数。 例如,一个工厂构造函数可以从缓存中返回一个实例,也可以返回一个子类型的实例。

下面演示了从缓存返回实例的工厂构造函数:

lang-dart
ch02/factory_constructor.dart
class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to the _ in front of its name.
  static final Map<String, Logger> _cache = <String, Logger>{};
  
  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = new Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }
  
  Logger._internal(this.name);
  
  void log(String msg) {
    if (!mute) {
      print(msg);
    }
  }
}

Note

工厂构造函数无法访问 this

new 关键字调用工厂构造函数:

lang-dart
ch02/factory_constructor.dart
var logger = new Logger('UI');
logger.log('Button clicked');

函数

函数定义了一个对象的行为。

实例函数

对象的实例函数可以访问实例变量和 this。 下面示例的 distanceTo() 函数就是一个 实例函数:

lang-dart
ch02/distance_to.dart
import 'dart:math';

class Point {
  num x;
  num y;
  Point(this.x, this.y);

  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}

Getters 和 setters

Getters 和 setters 是特殊的用来设置和访问实例变量的函数。 每个实例变量都有一个隐含的 getter,非 final 变量还有一个 setter 函数。 通过显式的实现 getter 和 setter 可以附件额外的功能。 用 getset 关键字实现 getter 和 setter:

lang-dart
ch02/rectangle.dart
class Rectangle {
  num left;
  num top;
  num width;
  num height;

  Rectangle(this.left, this.top, this.width, this.height);

  // 定义两个计算出来的属性:right 和 bottom.
  num get right             => left + width;
      set right(num value)  => left = value - width;
  num get bottom            => top + height;
      set bottom(num value) => top = value - height;
}

main() {
  var rect = new Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

有了 getters 和 setters,你可以先直接用实例变量, 当发现需要添加额外操作了再用 getter 和 setter 包起来, 这样调用代码无需做任何修改。

Note

对于 ++ 这种操作符而言,不管是否显式定义 getter 情况都有点特殊。 为了避免其他副作用,操作符只调用一次 getter 并缓存该返回值。

抽象函数

实例函数、 getter、 和 setter 都可以为抽象函数, 只定义接口,由子类来实现该函数。 用分号 (;) 结尾 没有函数体的函数就是抽象函数:

lang-dart
ch02/doer.dart
abstract class Doer {
  // ...Define instance variables and methods...
 
  void doSomething(); // 定义一个抽象函数
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // ...Provide an implementation, so the method is not abstract here...
  }
}

调用一个抽象函数在运行的时候会出现异常。

参考 the section called “抽象类”

重写操作符

可以重新 Table 2.11, “可被重写的操作符” 表中的操作符。例如, 如果你定义一个 Vector 类,则可以重写 + 函数来实现 vector 相加操作。

Table 2.11. 可被重写的操作符

<+|[]
>/^[]=
<=~/&~
>=*<<==
%>> 

下面是一个重写 +- 操作符的示例:

lang-dart
ch02/vector.dart
class Vector {
  final int x;
  final int y;
  const Vector(this.x, this.y);

  Vector operator +(Vector v) { // Overrides + (a + b).
    return new Vector(x + v.x, y + v.y);
  }

  Vector operator -(Vector v) { // Overrides - (a - b).
    return new Vector(x - v.x, y - v.y);
  }
}

main() {
  final v = new Vector(2,3);
  final w = new Vector(2,2);

  assert(v.x == 2 && v.y == 3);         // v   == (2,3)
  assert((v+w).x == 4 && (v+w).y == 5); // v+w == (4,5)
  assert((v-w).x == 0 && (v-w).y == 1); // v-w == (0,1)
}

如果重写 == ,则也应该重写 Object 的 hashCode getter。 重写 == 的示例在 the section called “实现 map 的 key” 有介绍。 关于重写的更多信息,参考 the section called “继承一个类”

抽象类

使用 abstract 修饰符来定义一个 抽象类,抽象类无法被实例化。 抽象类通常用于定义接口并具有一些基本行为实现。如果你想让你的抽象类看起来是可被实例化的,则可以提供一个 工厂构造函数

抽象类一般都有 抽象函数。 下面是一个定义抽象类和抽象方法的示例:

lang-dart
ch02/abstract.dart
// 该类为 abstract 的,所以无法实例化
abstract class AbstractContainer {
  // ...Define constructors, fields, methods...

  void updateChildren(); // 抽象函数
}

下面的类虽然有抽象函数,但是并不是抽象类,所以可以实例化:

lang-dart
ch02/abstract.dart
class SpecializedContainer extends AbstractContainer {
  // ...Define more constructors, fields, methods...

  void updateChildren() {
    // ...Implement updateChildren()...
  }
// Abstract method causes a warning but doesn't prevent instantiatation.
  void doSomething(); 
}

隐式接口(Implicit Interfaces)

每个类都隐式的定义了一个包含所有实例变量和所实现所有接口的接口。 如果你想创建一个类 A 支持 类 B 的 API,但是又不想继承类 B 的实现,则类 A 可以实现 类 B 的隐式接口。

类通过 implements 语句来定义其实现的其他类的接口, 并实现需要的 API。 例如:

lang-dart
ch02/imposter.dart
// 一个  person 类, 隐式接口包含  greet().
class Person {
  final _name;          // 该变量在隐式接口中,但是是库返回可见的
  Person(this._name);   // 这是个构造函数,不在隐式接口中
  String greet(who) => 'Hello, $who. I am $_name.'; // 在隐式接口中
}

// 实现 Person 的隐式接口。
class Imposter implements Person {
  final _name = "";      // We have to define this, but we don't use it.
  String greet(who) => 'Hi $who. Do you know who I am?';
}

greetBob(Person person) => person.greet('bob');

main() {
  print(greetBob(new Person('kathy')));
  print(greetBob(new Imposter()));
}

下面是实现多个接口的示例:

lang-dart
ch02/point_interfaces.dart
class Point implements Comparable, Location {
  // ...
}

继承一个类

extends 来继承类,在子类中用 super 访问父类:

lang-dart
smart_tv.dart
class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  ...
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  ...
}

子类可以重写实例函数、getter 、和 setter。 下面是一个重写 Object 类 noSuchMethod() 函数的示例,该函数在 调用一个不存在的函数或者实例变量时触发:

lang-dart
ch02/no_such_method.dart
class A {
  // Unless you override noSuchMethod, using a non-existent member
  // results in a NoSuchMethodError.
  void noSuchMethod(Invocation mirror) {
    print('You tried to use a non-existent member: ${mirror.memberName}');
  }
}

可以用 @override 注解来表明你需要 重写该函数或者变量:

lang-dart
ch02/ch02_meta/bin/ch02_override.dart
class A {
  @override
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}

如果用 noSuchMethod() 实现了所有可能的 getter、 setter、 和一个类的函数,则可以用 @proxy 注解来避免警告信息:

lang-dart
ch02/ch02_meta/bin/ch02_proxy.dart
@proxy
class A {
  void noSuchMethod(Invocation mirror) {
    // ...
  }
}

关于注解的详细信息,参考 the section called “Metadata(元数据)”

在类中添加功能: Mixins(混入)

Mixins 是一种不用继承就可以在一个类中 添加功能的方法。

with 关键字后跟 一个或者多个 mixin 名字来实现 mixin。 下面的代码演示了 mixin 的两个示例:

lang-dart
ch02/mixins.dart
class Musician extends Performer with Musical {
  ...
}

class Maestro extends Person with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

如何创建 mixin:定义一个继承 Object 的类,不定义构造函数也不调用 super 即可。例如:

lang-dart
ch02/mixins.dart
abstract class Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;
  
  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

详细信息请参考 Mixins in Dart 文章。

类(静态)变量和函数

static 关键字实现类 变量和函数。

静态变量

静态变量 (类变量) 用于类范围的状态和常量:

lang-dart
ch02/color.dart
class Color {
  static const RED = const Color('red'); // 一个静态常量
  final String name;                     // 一个实例变量
  const Color(this.name);                // 一个常量构造函数
}

main() {
  assert(Color.RED.name == 'red');
}

静态变量在使用的时候才初始化。

静态函数

静态函数 (类函数) 没有在实例上操作,也无法访问 this。例如:

lang-dart
ch02/point.dart
import 'dart:math';

class Point {
  num x;
  num y;
  Point(this.x, this.y);

  static num distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

main() {
  var a = new Point(2, 2);
  var b = new Point(4, 4);
  var distance = Point.distanceBetween(a,b);
  assert(distance < 2.9 && distance > 2.8);
}

Note

对于常用的工具类函数,可以用顶级函数来替代静态函数。

可以把静态函数当做编译期常量。 例如,你可以把静态函数当做参数传递给常量构造函数。

Generics(泛型)

如果你查看 List 的 API 文档,你会发现其类型定义为 List<E>。 <...> 说明 List 是一个 generic(泛型) (或者 parameterized(可参数化的)) 类型 。 习惯上用一个字母来表达类型定义,例如 E、 T、 S、 K、和 V。

为何使用泛型?

由于 Dart 的类型是可选的,所以你可以从来不用泛型。 泛型可以帮助你注释你的代码,让你的意图表达的更清晰。

例如,如果你想让一个 list 里面只包含 string, 你可以通过 List<String> (读作 string 类型的列表 ) 来表达你的意图。 这样,你的同事或者 IDE 以及运行在检测模式下的 Dart VM 可以检测是否保存该 list 中的对象为 string。如果不是,则会提醒你可能出错了。 下面是一个示例:

lang-dart
ch02/generics.dart
var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
// ...
names.add(42); // 在检测模式会出错 (生产模式可以运行).

另外用泛型也可以减少重复代码。 泛型可以让你实现在多个类型之间公用的操作, 同时还没丧失代码静态分析和检测模式提醒的优势。 例如,你想创建一个缓存对象的接口:

lang-dart
abstract class ObjectCache {
  Object getByKey(String key);
  setByKey(String key, Object value);
}

后来你又需要一个 String 类型的缓存,所以你又创建一个:

lang-dart
abstract class StringCache {
  String getByKey(String key);
  setByKey(String key, String value);
}

后来,你又需要一个其他类型的缓存,所以你想到一个办法。

泛型可以完美的解决该问题。

lang-dart
abstract class Cache<T> {
  T getByKey(String key);
  setByKey(String key, T value);
}

在上面的代码中,T 是一个占位符类型。 你可以认为这是一个调用者在使用的时候会设置的类型。

使用集合字面量

List 和 map 的字面量定义也可以泛型化。 只需要在字面量前面添加一个 <泛型类型> ( 用于 lists ) 或者 <keyType, valueType> (用于 maps) 即可。 如果你想在检测模式使用类型警告提示,则可以使用泛型字面量来定义集合。 下面是一个示例:

lang-dart
var names = <String>['Seth', 'Kathy', 'Lars'];
var pages = <String, String>{
    'index.html':'Homepage',
    'robots.txt':'Hints for web robots',
    'humans.txt':'We are people, not machines' };

使用泛型构造函数

在构造函数类名后面放一个尖括号和类型(<...>) 可以使用泛型构造函数。例如:

lang-dart
var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = new Set<String>.from(names);

下面的代码创建了一个 key 为 int,value 为 View 对象的 map:

lang-dart
var views = new Map<int, View>();

泛型集合和类型

Dart 的泛型类型被 固化了, 这表明在运行时也带有类型信息。 例如,你可以在生产模式下测试集合类型:

lang-dart
var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

is 表达式只检查 集合 的类型而不检查里面的对象类型。 在生产模式下,一个 List<String> 集合可能包含非 stirng 对象。 可以通过检查每个集合里面的对象类型或者在一个异常处理器中处理每个集合元素 (参考 the section called “Exceptions(异常)”)。

Note

和 Java 不同, Java 中的泛型信息被 清除了, 所以在运行时是没有类型参数的。 在 Java 中 你可以测试一个 对象是否为 List,但是没法测试一个对象是否为 List<String>

关于泛型的更多信息,请参考 Optional Types in Dart 文章。

库和可见性

使用 importpart、 和 library 指令可以帮助创建模块化和可共享的代码库。 库不仅仅只包含 API,还包含可见性:以下划线(_)开头的标识符只能在库内访问。 每一个 Dart 应用都是一个库

库可以用 package(包) 来发布。参考 the section called “pub: Dart 包管理器” 了解 pub 的信息。

使用库

import 来定义一个库的命名空间并使用一个库。

例如, Dart web 应用通常都使用 dart:html 库,可以这样导入该库:

lang-dart
ch02/libraries/using_libraries.dart
import 'dart:html';

import 必需的一个参数为库的 URI[1]。对于 内置的库, URI 用 特殊的 dart: 协议。 其他库,可以用文件路径或者 package: 协议。 package: 协议的库由包管理器管理,例如 pub 工具。示例:

lang-dart
ch02/libraries/using_schemes.dart, mylib, utils
import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';

设置库的前缀

如果两个库具有同样的标识符,则会出现命名冲突,可以用库前缀解决命名冲突。

lang-dart
ch02/libraries/library_prefix.dart, lib1, lib2
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// ...
var element1 = new Element();      // 用 lib1 的 Element
var element2 = new lib2.Element(); // 用 lib2 的 Element

只导入库的一部分

如果你只使用库的一部分,可以导入只使用的功能。

lang-dart
ch02/libraries/library_partil.dart, lib1, lib2
import 'package:lib1/lib1.dart' show foo; // Import only foo.
import 'package:lib2/lib2.dart' hide foo; // Import all names EXCEPT foo.

实现一个库

library 指令来命名一个库, 用 part 来设置库中的其他文件。

Note

在一个应用(包含 顶级 main() 方法的文件 )中无需用 library ,但是使用 library 可以在多个文件中实现你的应用。

定义一个库

library 标识符 指定当前库的名字:

lang-dart
ch02/ballgame.dart
library ballgame;   // 定义这是一个名字为 ballgame 的库

import 'dart:html'; // 该应用使用 html 库
// ...Code goes here...

关联库中的文件

在往库中添加文件,在带有 library 指令的文件中用 part fileUrifileUri 是要包含的文件路径。 然后在该文件的实现中用 part of identifier 语句定义( identifier 是 库的名字)。 下面的示例中,用 partpart of 实现一个包含三个文件的库。

第一个文件 ballgame.dart 定义了 ballgame 库,导入其他需要的库并指定 ball.dartutil.dart 文件也属于该库:

lang-dart
ch02/ballgame.dart
library ballgame;

import 'dart:html';
// ...Other imports go here...

part 'ball.dart';
part 'util.dart';

// ...Code might go here...

第二个文件 ball.dart 定义了 part of ballgame 语句, 并实现了 ballgame 的一部分代码:

lang-dart
ch02/ball.dart
part of ballgame;

// ...Code goes here...

第三个文件 util.dart 同样 实现了 ballgame 的其他代码:

lang-dart
ch02/util.dart
part of ballgame;

// ...Code goes here...

重新定义库(export)

使用从重新定义库功能(export 指令) 可以组合和重新打包库(可以包含所有代码也可以只包含部分代码)。 例如,你有一个非常大的库,里面包含了一堆小库。或者你想创建一个只包含其他库中一部分功能的库。

lang-dart
ch02/french.dart, togo.dart, french_togo.dart
// In french.dart:
library french;
hello() => print('Bonjour!');
goodbye() => print('Au Revoir!');

// In togo.dart:
library togo;
import 'french.dart';
export 'french.dart' show hello;

// In another .dart file:
import 'togo.dart';

void main() {
  hello();   //print bonjour
  goodbye(); //FAIL
}

Isolates

很多流行的浏览器,甚至是移动平台的浏览器,都运行在多核 CPU 上。 为了使用多核优势,开发者通常都使用共享内存的方式来运行多个线程。 但是,共享状态并发编程是非常容易出错的,并且导致代码更加复杂。

而 Dart 的代码都是运行在 isolates 中而不是线程中。 每个 isolate 具有自己的内存堆,确保每个 isolate 堆之 无法共享状态。

Typedefs

在 Dart 中, 方法也是对象。 用 typedef 或者 function-type alias 可以给一个方法类型设置一个名字,并定义参数和返回值。 当方法类型赋值到一个变量时, typedef 保留类型信息。

如下是一个没有用 typedef 的示例:

lang-dart
ch02/sorted_collection.dart
class SortedCollection {
  Function compare;

  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}

int sort(Object a, Object b) => ... ; // Initial, broken implementation.

main() {
  SortedCollection collection = new SortedCollection(sort);

  // 我们只知道 compare 是一个方法,但是不知道是什么类型的方法
  assert(collection.compare is Function);
}

当把 f 赋值给 compare 时类型信息丢失了。 f 的类型是 (Object, Object) int (这里的 代表返回值类型),当然 compare 也是 Function 类型。 如果用显式的名字和返回值来定义该方法,则开发者和工具都可以使用该信息。

lang-dart
typedef int Compare(Object a, Object b);

class SortedCollection {
  Compare compare;

  SortedCollection(this.compare);
}

int sort(Object a, Object b) => ... ; // Initial, broken implementation.

main() {
  SortedCollection collection = new SortedCollection(sort);
  assert(collection.compare is Function);
  assert(collection.compare is Compare);
}

Note

当前,typedef 只能定义方法类型。 我们希望能够增强该功能。

由于 typedef 只是简单的别名,并提供了一种检测方法类型的方式。 例如:

lang-dart
typedef int Compare(int a, int b);

int sort(int a, int b) => a - b;

main() {
  assert(sort is Compare);  // True!
}

Metadata(元数据)

用元数据给你的代码提供额外的信息。元数据注解 使用 @ 字符开头,后面跟着一个引用合作 编译期常量(例如 deprecated)或者调用一个 常量构造函数。

下面三个注解,所有的 Dart 代码都可以使用: @deprecated@override、 和 @proxy。参考 the section called “继承一个类” 查看使用 @override@proxy 的示例。下面是一个使用 @deprecated 注解的示例:

lang-dart
ch02/ch02_meta/bin/ch02_meta.dart
class Television {
  /// _Deprecated: Use [turnOn] instead._
  @deprecated      // Metadata; makes Dart Editor warn about using activate().
  void activate() {
    turnOn();
  }

  /// Turns the TV's power on.
  void turnOn() {
    print('on!');
  }
}

可以定义自己的注解,下面是定义一个带有两个参数的 @todo 注解:

lang-dart
ch02/ch02_meta_create/todo.dart
library todo;

class todo {
  final String who;
  final String what;
  
  const todo(this.who, this.what);
}

下面是使用 @todo 的示例:

lang-dart
ch02/ch02_meta_create/metadata_user.dart
import 'todo.dart';

@todo('seth', 'make this do something')
void doSomething() {
  print('do something');
}

Metadata 可以出现在 library、 class、 typedef、 type parameter、 constructor、factory、 function、 field、 parameter、或者 variable declaration 、import 或者 export 之前。 以后,可以通过反射来获取元数据信息。 状态跟踪: issue #6614

注释

Dart 支持单行注释、多行注释和文档注释。

单行注释

单行注释以 // 开头。位于 // 之后的字符都会被 Dart 编译器 忽略掉。

lang-dart
main() {
  // TODO: refactor into an AbstractLlamaGreetingFactory?
  print('Welcome to my Llama farm!');
}

多行注释

多行注释以 /* 开头,以 */结尾。位于 /**/ 之间的 内容被编译器忽略(文档注释除外)。

lang-dart
ch02/multi_line_comments.dart
main() {
  /*
   * This is a lot of work. Consider raising chickens.

  Llama larry = new Llama();
  larry.feed();
  larry.exercise();
  larry.clean();
   */
}

文档注释

文档注释是以 /** 或者 /// 开头的多行或者单行注释。 连续多行使用 /// 和多行文档注释效果一样。

在文档注释中,Dart 编译器只处理位于方括号之间的内容。 可以用方括号引用 类、函数、变量、顶级变量、方法、和参数。 方括号内的参数在词法范围内查找代码中对于的标示符。

下面是一个引用其他类的文档注释。

lang-dart
ch02/doc_comments.dart
/**
 * A domesticated South American camelid (Lama glama).
 * 
 * Andean cultures have used llamas as meat and pack animals
 * since pre-Hispanic times.
 */
class Llama {
  String name;

  /**
   * Feeds your llama [Food].
   * 
   * The typical llama eats one bale of hay per week.
   */
  void feed(Food food) {
    // ...
  }

  /// Exercises your llama with an [activity] for
  /// [timeLimit] minutes.
  void exercise(Activity activity, int timeLimit) {
    // ...
  }
}

在生成的文档中, [Food] 变成链接到 Food 类 API 文档的超级链接。

可以用 Dart 编辑器来解析 Dart 代码并生成 HTML 文档,编辑器使用的是 SDK 中的 dartdoc 工具。 生成的文档请参考 Dart API 文档。 关于如何组织注释结构的建议,请参考 Dart 文档注释指南。

总结

本章总结了 Dart 语言常用的特性。 为了避免打破现有代码,更多的特性作为 minxin 实现 http://www.dartlang.cc/articles/mixins/。详细信息 参考 Dart 语言 规范相关文章 例如 Dart 习惯用法。



[1] URI 代表 uniform resource identifier(统一资源标识符)。 URLs (uniform resource locators(统一资源定位符)) 是一种常见的 URI。