Dart语言之旅翻译完整版

Posted 绎创+

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dart语言之旅翻译完整版相关的知识,希望对你有一定的参考价值。

Flutter踩坑之旅之Dart学习笔记


写在前面

本文是参考了Dart官网https://www.dartlang.org 的教程、参考的机翻以及对于其他语言学习的经验来翻译的本入门教程,译者精力和学历有限,如有不妥之处欢迎指正。

本文出于知识共享的目的,转载请注明出处,如果用于商用请联系 admin@jieec.cn 本文也发布在了杰易创绎Jiee Create http://www.jieec.cn/?p=228 本文PDF版部分文档内容可以在Jieec Server中获取 https://server.jieec.cn

在线编译

DartPad https://dartpad.dartlang.org/

概念

  • Dart变量中的内容都是对象,对象都是类的实例,所有对象都是从Object类继承的

  • Dart要求规定类型,但是可以自行推断类型。var num = 10; 就可以推断其类型为int(不需要类型之dynamic)

  • Dart之泛型类型List  ( 整数列表)or List  任何类型的对象列表

  • 主函数main(),支持自定义函数和函数嵌套

  • 支持全局变量,私有变量。实例的变量可以称为字段或者属性

  • 不具备数据保护相关的关键字,用(_)开头确定私有

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

关键字

| 关键字列表 | | --- | :---: | ---: | | abstract 2 | dynamic 2 | implements 2 | show 1 | | as 2 | else | import 2 | static 2 | | assert | enum | in | super | | async 1 | export 2 | interface 2 | switch | | await 3 | external 2 | is | sync 1 | | break | extends | library 2 | this | | case | factory 2 | mixin 2 | throw | | catch | false | new | true | | class | final | null | try | | const | finally | on 1 | typedef 2 | | continue | for | operator 2 | var | | covariant 2 | Function 2 | part 2 | void | | default | get 2 | rethrow | while | | deferred 2 | hide 1 | return | with | | do | if | set 2 | yield 3 |

避免使用关键字作为标识符,如果有必要标数字的关键字可以作为标识符:

  • 1:上下文关键字标识符,仅在特定位置有含义

  • 2:内置标识符,简化js转移dart的工作,不能作为类或者类型的标识符,不能用作导入前缀

  • 3:dart 1.0发布后添加的异步支持相关的更新,有限的保留字。不能使用await or yield作为任何函数体中的标识符标记async,async* or sync*

变量

* var(属于Object)& dynamic的辨析

  • var可以被解析为任意类型,但是一旦确定就不可更改!

  • dynamic的值不可以被编译器解析或者类型检查

  • dynamic编译到Object中,dynamic编译时存在,运行时不存在

默认值

未初始化的默认值都为null,断言assert(condition);

final & const

不打算更改变量的值用final或者const定义变量。final变量只能被设置一次;const变量是编译时常量(const变量是隐式final的)。final的全局变量和类变量第一次使用的时候要被初始化。

注意:在实例变量中只可以用final而非const。必须在构造函数体之前初始化final变量(在变量声明,构造函数参数或者构造函数的初始化列表中。)

示例:

 
   
   
 
  1.  final name = 'Bob';//无法再更改值

如果const变量在类内,请标记为static const,const属于Object类

 
   
   
 
  1.  const bar = 1000000;//压力单位(dynes/cm2)

  2.  const double atm = 1.01325*bar;//标准大气压

const不仅仅可以声明常变量,也可以用来创建常量值constant values,以及声明常量值的构造函数,任何变量都可以具有常量值。

 
   
   
 
  1.  var foo = const [];

  2.  final bar = const [];

  3.  const baz = [];//与"const []"等价

  • 可以省略声明const的初始化表达式,如上所示。不要冗余使用const!

  • 你可以修改非final,非const变量的值,即使变量曾用过const的值

 
   
   
 
  1.  foo = [1,2,3];√

  2.  baz = [1,2,3];×

内置类型

  • 数字

  • 字符串

  • 布尔型

  • 列表(数组)

  • 地图map

  • 符文runes(用于在字符串中表示Unicode字符)

  • 符号symbols

因为Dart中的每个变量都引用一个对象 - 一个类的实例 - 您通常可以使用构造函数来初始化变量。一些内置类型有自己的构造函数。例如,您可以使用Map()构造函数来创建地图。 ——来源于官网引用

数字

  1. int

整数值不大于64位,具体视平台而定

  1. double

64位双精度浮点数,IEEE 754标准

int & double都属于数字这一类型,数字基本运算符有+、-、*、/之类的,也包括abs()、ceil()、floor()和其他方法。(类似于">>"的位运算符,在int类中被定义),如果在num及其子类中没有要找的,去dart:math库中看看!

int示例

 
   
   
 
  1.  int x = 1;

  2.  int hex = 0xDEADBEEF;

double示例

 
   
   
 
  1.  double y = 1.1;

  2.  double exponents = 1.42e5;

以下是字符串如何转化为数字,反之亦然

 
   
   
 
  1.  // String -> int

  2.  var one = int.parse('1');

  3.  assert(one == 1);


  4.  // String -> double

  5.  var onePointOne = double.parse('1.1');

  6.  assert(onePointOne == 1.1);


  7.  // int -> String

  8.  String oneAsString = 1.toString();

  9.  assert(oneAsString == '1');


  10.  // double -> String

  11.  String piAsString = 3.14159.toStringAsFixed(2);

  12.  assert(piAsString == '3.14');

int类型指定移位操作(<<、>>),AND(&)和OR(|)运算符。

 
   
   
 
  1.  assert((3<<1)==6);// 0011 << 1 == 0110

  2.  assert((3>>1)==1);// 0011 >> 1 == 0001

  3.  assert((3|4)==7);// 0011 | 0100 == 0111

Literal numbers(能力有限,不知道如何翻译)是编译时常量,很多的算术表达式也是编译时的常量,只要它们的操作数是编译为数字的编译时常量——引用自dart官网

 
   
   
 
  1.  const msPerSecond = 1000;

  2.  const secondsUntilRetry = 5;

  3.  const msUntilRetry = secondsUntilRetry*msPerSecond;

字符串

Dart字符串是一系列的UTF-16代码单元,可以使用 '' or "" 创建字符串。

示例

 
   
   
 
  1.  var s1 = 'Single quotes work well for string literals.';

  2.  var s2 = "Double quotes work just as well.";

  3.  var s3 = 'It\'s easy to escape the string delimiter.'; // 译者注\'是转译输出'

  4.  var s4 = "It's even easier to use the other delimiter.";

你可以将表达式的值置入字符串中通过使用${expression}。如果表达式是标识符,你可以跳过{}(就是省略{})。要获取会对象对应的字符串,Dart会调用对象的toString()方法。

 
   
   
 
  1.  var s = 'string interpolation';


  2.  assert('Dart has $s, which is very handy.' == 'Dart has string interpolation, ' + 'which is very handy.');

  3.  assert('That deserves all caps. ' + '${s.toUpperCase()} is very handy!' == 'That deserves all caps. ' + 'STRING INTERPOLATION is very handy!');

"=="运算符是测试两个对象是否是等价的,如果它们包含相同的代码单元序列,那么就是等价的。

你可以使用运算符'+'连接字符串(译者省略了演示代码)

另一种创建多行字符串的方法是:使用三个单引号或者双引号

 
   
   
 
  1.  var s1 = '''

  2.  多行字符串1

  3.  ''';


  4.  var s2 = """

  5.  多行字符串2

  6.  """;

你可以通过添加前缀r来创建原始字符串(在原始字符串中,字符串是不会被特殊处理的)

 
   
   
 
  1.  var s = r'这是一个原始字符串';

有关如何在字符串中表示Unicode字符的详细信息,请参阅Runes

只要任何插值表达式是一个编译时常量,其值为null或数值,字符串或布尔值;文字字符串即为编译时常量。

演示代码

 
   
   
 
  1.  // 常字符串

  2.  const aConstNum = 0;

  3.  const aConstBool = true;

  4.  const aConstString = 'a constant string';


  5.  // 并非常字符串

  6.  var aNum = 0;

  7.  var aBool = true;

  8.  var aString = 'a string';


  9.  const aConstList = [1, 2, 3];

  10.  const validConstString = '$aConstNum $aConstBool $aConstString';

  11.  // const invalidConstString = '$aNum $aBool $aString $aConstList';

获取更多的字符串的使用信息,请参考字符串与正则表达式

布尔型

Dart用bool类表示布尔值,只有两个对象true、false,它们都是编译时常量。

Dart的类型安全意味着你不能使用类似于if(非布尔值)或者assert(非布尔值)的语句,不过可以明确地检查值,如示例:

 
   
   
 
  1.  // 检查空字符串

  2.  var fullName = '';

  3.  assert(fullName.isEmpty);


  4.  // 检查零

  5.  var hitPoints = 0;

  6.  assert(hitPoints <= 0);


  7.  // 检查null

  8.  var unicorn;

  9.  assert(unicorn == null);


  10.  // 检查NaN

  11.  var iMeantToDoThis = 0 / 0;

  12.  assert(iMeantToDoThis.isNaN);

列表

几乎每种语言中最常见的是数组或者有序对象组。在Dart中数组是List对象,因此大多人也称为列表。

Dart列表看起来想javascript数组。示例:

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

分析器推断list类型为list 。如果将非int类型对象加入此列表,会引发错误。获取更多的信息,阅读有关 类型推断

列表从零开始索引,0是列表的第一个元素索引,list.length - 1是列表的最后一个元素索引。可以像JavaScript那样获取一个列表的长度并引用元素。

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

  2.  assert(list.length == 3);

  3.  assert(list[1] == 2);


  4.  list[1] == 1;

  5.  assert(list[1] == 1);

创建一个编译时常量列表,在列表文字前添加const:

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

  2.  // 同样,值不可以更改

List类型有很多方便的方法来操作列表。有关更多信息,请参阅 泛型Generics集合Collections

地图型Maps

译者注:根据译者对于其他语言的学习和开发经验,Dart语言的Maps型和Python中的字典是一样的,其中键就是key,值就是value。

通常,map型是一个有键值对构成的对象。键和值可以是任意类型的对象。键不能重复,而同样的值是可以的。Dart对于map的支持是通过地图文字和Map类型。

以下是一些简单使用map文字创建的Dart maps:

 
   
   
 
  1.  var gifts = {

  2.    // key:value

  3.    'first': 'partridge',

  4.    'second': 'turtledoves',

  5.    'fifth': 'golden rings',

  6.  };


  7.  var nobleGases = {

  8.    2: 'helium',

  9.    10: 'neon',

  10.    18: 'argon',

  11.  };

解析推断gifts类型为Map ,nobleGases的类型为Map 。如果尝试去添加非对应类型的值,解析器或者运行的时候会报错。获取更多的信息,请参阅 类型推断

你也可以使用构造函数Map()创建相同的对象:

 
   
   
 
  1.  var gifts = Map();

  2.  gifts['first'] = 'partridge';

  3.  gifts['second'] = 'turtledoves';


  4.  // 不想写了,省略···

你可能想看到的是new Map(),而非Map()。从Dart 2开始,new关键字是可选的。有关详细信息,请参阅 使用构造函数

跟在JavaScript中一样,将新的键值对加到现有的地图中(译者注:在python的字典中其实也是这样的···)

 
   
   
 
  1.  var gifts = {'first': 'partridge'};

  2.  gifts['fourth'] = 'calling birds';

跟在JavaScript中一样的方式在地图中检索值,不在就返回null:(译者注:译者懒得注了)

 
   
   
 
  1.  var gifts = {'first': 'partridge'};

  2.  assert(gifts['first'] == 'partridge');

  3.  assert(gifts['fifth'] == null);

使用.length获取map中键值对的数目

 
   
   
 
  1.  var gifts = {'first': 'partridge'};

  2.  gifts['fourth'] = 'calling birds';

  3.  assert(gifts.length == 2);

常地图,定义与上面相似,不写了···

 
   
   
 
  1.  final constantMap = const {

  2.    2: 'helium',

  3.    10: 'neon',

  4.    18: 'argon',

  5.  };

  6.  // const改都报错,后面就不写了

获取更多的信息,请参阅 泛型Generics地图型Maps

符文

在Dart中,符文是字符串中UTF-32代码点

Unicode为世界上所有书写系统中的每一个字母、数字、符号都定义了唯一的数值。由于Dart字符串是一系列的UTF-16代码单元,因此在字符串中表示32位要用特殊的语法。

表达Unicode代码点的常用的方法是\uXXXX,其中XXXX是4位十六进制值。例如心(:heartpulse:)是\u2665。要指定多于或者少于4个十六进制值,请将值放在大括号里。例如,笑脸(:laughing:)是\u{1f600}。

该字符串类有几个属性,你可以用它来提取符文信息。codeUnitAt和codeUnit属性返回16位编码单元。使用该runes属性获取字符串的符文。

以下示例说明了符文,16位代码单元和32位代码点之间的关系。

 
   
   
 
  1.  main()

  2.  {

  3.    var clapping = '\u{1f44f}';

  4.    print(clapping);

  5.    print(clapping.codeUnits);

  6.    print(clapping.runes.toList());


  7.    Runes input = new Runes(

  8.      '\u2665  \u{1f605}  \u{1f60e}  \u{1f47b}  \u{1f596}  \u{1f44d}');

  9.    print(new String.fromCharCodes(input));

  10.  }

注意:使用列表操作符文要小心,这种方法容易分解,具体取决于特定的语言,字符集和操作。更多信息,请在Stack Overflow上参阅 如何在Dart中反转字符串? ——引用自dart官网

符号Symbols

符号对象表示Dart程序的操作或者声明。你可能不会去使用符号对象,但是它们对于API通过名称引用是非常有用的,因为缩小会更改标识符名称而不会更改标识符符号。(译者并不清楚官网这句话的具体含义,引用自Dart官网)

请使用符号文字获取标识符的符号(#后面跟着标识符):

 
   
   
 
  1.  #radix

  2.  #bar

符号文字是编译时常量!

函数Function

Dart是一门面向对象的语言,即便是函数也是对象,并且具有类型 Function 。这意味着函数可以被赋值给变量,或者作为参数传递给其他函数。你也可以跟调用函数一样,调用Dart类的实例。有关信息请参阅 可调用类Callable classes

以下是一个实现函数的示例:

 
   
   
 
  1.  bool isNoble(int atomicNumber){

  2.    return _nobleGases[atomicNumber] != null;

  3.  }

虽然Effective Dart建议 为公共API键入注释 ,但是省略类型,函数还是有效的

 
   
   
 
  1.  isNoble(atomicNumber){

  2.    return _nobleGases[atomicNumber] != null;

  3.  }

对于只含有一个表达式的函数,可以使用简写的语法:

 
   
   
 
  1.  bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

该语法是速记,有时候称为 箭头语法 。=> expr {return expr;}

只有 表达式 而非 语句 可以出现在 "=>" 和 ";" 之间。例如,不能在其中放入if语句,但可以使用条件表达式。 (译者注:也就是类似于其他语言也有的条件判断表达式比如 ? : 和 if语句的区别)

函数可以有两种类型的参数:必需和可选。首先列出所需的参数,然后列出任何可选参数。命名的可选参数也可以标记为 @required 。有关详细信息,参阅下一节。

可选参数

可选参数可以是位置参数,也可以是命名参数,不局限于这些。

可选命名参数

调用函数的时候,可以使用 paramName: value 指定的命名参数。例如:

 
   
   
 
  1.  enableFlags(bold: true, hidden: false);

定义函数时,使用 {param1, param2, ...} 指定命名参数:

 
   
   
 
  1.  // 设置[bold]和[hidden]标志flags...

  2.  void enableFlags({bool bold, bool hidden}){...}

Flutter 实例创建表达式变得会更复杂,因此窗口小部件构造函数仅使用命名参数,使实例创建表达式更易于阅读。

可以使用 @required 在任何Dart代码(不仅仅是Flutter)中注释命名参数,以指示它是 必需参数 。例如:

 
   
   
 
  1.  const Scrollbar({Key key, @required Widget child})

当一个构造Scrollbar,分析器会报错child不存在。

必需Required元meta 包中被定义。可以直接导入 package:meta/meta.dart ,也可以导入另一个导出的包 meta,例如Flutter的包 package:flutter/material.dart 。

可选位置参数

包装一组函数参数在[]内标记它们作为可选位置参数:

 
   
   
 
  1.  String say(String from, String msg, [String device]){

  2.    var result = '$from says $msg';

  3.    if (device != null){

  4.      result = '$result with a $device';

  5.    }

  6.    return result;

  7.  }

下面是一个没有可选参数情况下调用此函数的示例:

 
   
   
 
  1.  assert(say('Bob', 'Howdy') == 'Bob says Howdy');

下面是一个带有第三个参数情况下调用此函数的示例:

 
   
   
 
  1.  assert(say('Bob', 'Howdy', 'smoke signal') == 'Bob says Howdy with a smoke signal');

默认参数值

你的函数可以使用 "=" 给命名或者位置参数定义默认值。默认值必须是编译时常量。如果未设默认值,则默认值为null。

这是一个为命名参数设置默认值的示例:

 
   
   
 
  1.  // 设置[bold]和[hidden]flag

  2.  void enableFlags({bool bold = false,bool hidden = false}) {...}


  3.  // bold值为true hidden为false

  4.  enableFlags(bold: true);

在老代码中使用冒号取代等号设置命名参数,原因是原生的问题。只有冒号支持命名参数。这种支持遭到了反对,建议使用等号设置默认值。

这个示例演示了如何去给位置参数设置默认值

 
   
   
 
  1.  String say(String from, String msg, [String device = 'carrier pigeon', String mood]){

  2.    var result = '$from says $msg';

  3.    if (device != null){

  4.      result = '$result with a $device';

  5.    }

  6.    if (mood != null){

  7.      result = '$result (in a $mood mood)';

  8.    }

  9.    return result;

  10.  }


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

你也可以将列表或者地图作为默认值传递。下面的示例定义了一个doStuff()的函数,为list参数指定了一个默认的列表,为gifts参数指定了一个默认的地图。

 
   
   
 
  1.  void doStuff(

  2.  {List<int> list = const [1, 2, 3],

  3.    Map<String, String> gifts = const {

  4.      'first': 'paper',

  5.      'second': 'cotton',

  6.      'third': 'leather'

  7.    }

  8.  }){

  9.    print('list: $list');

  10.    print('gifts: $gifts');

  11.  }

main()函数

每一个app都必须有个顶级函数main(),相当于是app的入口。main()函数返回void并且有一个可选的List 参数。

这是一个web app的main()函数示例:

 
   
   
 
  1.  void main(){

  2.    querySeletor('#sample_text_id')

  3.      ..text = 'Click me!'

  4.      ..onClick.listen(reverseText);

  5.  }

..这个先于代码的语法称为 级联 。使用级联,你可以对单个对象的成员执行多个操作。

下面是一个带参数的main函数()的命令行app示例:

 
   
   
 
  1.  // 像这样运行app:dart args.dart 1 test

  2.  void main(List<String> arguments){

  3.    print(arguments);


  4.    assert(arguments.length == 2);

  5.    assert(int.parse(arguments[0]) == 1);

  6.    assert(arguments[1] == 'test');

  7.  }

你可以使用 args库 去定义和分析命令行参数。

函数作为第一类对象

你可以将一个函数作为参数传递给其他函数。比如:

 
   
   
 
  1.  void printElement(int element){

  2.    print(element);

  3.  }


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


  5.  // printElement作为参数传递

  6.  list.forEach(printElement);

你也可以将函数分配给一个变量。比如:

 
   
   
 
  1.  var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';

  2.  assert(loudify('hello') == '!!! HELLO !!!');

这个例子使用了一个匿名函数。想了解更多,请看下节教程。

匿名函数

大多的函数都是被命名过的,比如main()或者printElement()。你也可以创建一个无名函数叫做 匿名函数 ,有时候也叫做 lambda 或者 closure 。比如你可以给一个变量指定一个匿名函数,以便你可以从一个集合中添加或者删除。

匿名函数和一个命名函数看起来很相似——没有或者数个参数,在逗号和括号之间用逗号和可选类型注释分隔。

下面的代码块包含了函数的主体

 
   
   
 
  1.  ([Type] param1[, ...]){

  2.    codeBlock;

  3.  };

下面的例子中定义了一个带有未指定类型参数 item 的匿名函数。为列表中的每个项调用的函数将打印一个字符串,该字符串包含指定索引处的值。

 
   
   
 
  1.  var list = ['apples', 'bananas', 'oranges'];

  2.  list.forEach((item){

  3.    print('${list.indexOf(item)}: $item');

  4.    });

写入程序的输出结果为:

  1. 0: apples

  2. 1: bananas

  3. 2: oranges

如果函数只包含了一个语句,你可以使用箭头表示法让代码更短。复制下面的代码到DartPad,然后点击run证明它在功能上是等价的。

 
   
   
 
  1.  list.forEach(

  2.    (item) => print('${list.indexOf(item): $item}')

  3.    );

词汇作用域Lexical scope

Dart是一个有词汇作用域的语言,就意味着变量的作用域是静态定义的,只需通过代码的布局。你可以 “顺着花冠向外”译者觉得这可能是一个比喻)去看变量是否在作用域范围内。

以下是在每个范围级别具有变量的嵌套函数的示例:

 
   
   
 
  1.  bool topLevel = true;


  2.  void main() {

  3.    var insideMain = true;


  4.    void myFunction() {

  5.      var insideFunction = true;


  6.      void nestedFunction() {

  7.        var insideNestedFunction = true;


  8.        assert(topLevel);

  9.        assert(insideMain);

  10.        assert(insideFunction);

  11.        assert(insideNestedFunction);

  12.      }

  13.    }

  14.  }

注意 nestedFunction()是如何使用每一级的变量,一直到顶级。

词汇闭包Lexical closures

闭包closure 是一个在它的词汇范围有权访问变量的函数对象,即使函数原来的范围之外。

函数关闭在周围定义的变量。在下面的示例中,makeAdder()捕获变量addBy。无论返回的函数在哪里,它都会记住addBy。

 
   
   
 
  1.  // 返回一个函数,它为函数的参数添加[addBy]

  2.  Function makeAdder(num addBy){

  3.    return (num i) => addBy + i;

  4.  }


  5.  void main(){

  6.    // 创造一个函数添加2

  7.    var add2 = makeAdder(2);


  8.    // 创造一个函数添加4

  9.    var add4 = makeAdder(4);


  10.    assert(add2(3) == 5);

  11.    assert(add4(3) == 7);

  12.  }

测试函数是否等价

下面是一个测试顶级函数,静态方法,实例方法等价性的例子:

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

  2.  class A {

  3.    static void bar() {} // 静态方法

  4.    void baz() {} // 实例方法

  5.  }


  6.  void main() {

  7.    var x;


  8.    // 比较顶级函数

  9.    x = foo;

  10.    assert(foo == x);


  11.    // 比较静态方法

  12.    x = A.bar;

  13.    assert(A.bar == x);


  14.    // 比较实例方法

  15.    var v = A(); // A的实例#1

  16.    var w = A(); // A的实例#2

  17.    var y = w;

  18.    x = w.baz;


  19.    // 这些闭合参照的是一个实例(#2),所以它们是等价的

  20.    assert(y.baz == x);


  21.    // 这些闭合参照的是不同的实例,所以它们不等价

  22.    assert(v.baz != w.baz);

  23.  }

返回值

所有的函数都会返回值。如果没有返回值被指定,表达式返回null;隐式添加到函数体。

 
   
   
 
  1.  foo() {}

  2.  assert(foo() == null);

运算符

Dart定义了下述表格中的运算符。你可重载部分的运算符,在 重载运算符 中具体介绍。

| 类型 | 运算符 | | --- | :---: | ---: | | 一元后缀 | expr++ expr-- () [] . ?. | | 一元前缀 | -expr !expr ~expr ++expr --expr | | 乘式 | * / % ~/ | | 和式 | + - | | 移位 | << >> | | 位与 | & | | 位异或 | ^ | | 位或 | 丨 | | | 译者注:由于markdown的制表和位或运算的符号发生冲突,故使用的是丨(gun)代替 | | 关系与类型测试 | >= > <= as is is! | | 等价 | == != | | 逻辑与 | && | | 逻辑或 | 丨丨 | | | 译者注:同上 | | 如果null | ?? | | 控制表达式 | expr1 ? expr2 : expr3 | | 级联 | .. | | 赋值 | = *= /= ~/= %= += -= <<= >>= &= ^= 丨= ??= |

使用运算符可以创建表达式,下面是几个例子:

 
   
   
 
  1.  a++

  2.  a + b

  3.  a = b

  4.  a == b

  5.  c ? a : b

  6.  a is T

运算符表 中,每个运算符的优先级都高于后一行的优先级。例如 % 高于 == ,而 == 高于 && 。就意味着下面两行的代码执行方式是相同的。

 
   
   
 
  1.  // 圆括号提高了可读性

  2.  if ((n % i == 0) && (d % i == 0)) ...


  3.  // 难读,但是是等价的

  4.  if (n % i == 0 && d % i == 0)

注意:对于处理两个操作数的运算符,最左边的操作数确定运算符。——引用自官网

算术运算符

Dart支持通用的算术运算符,具体如下表:

| 运算符 | 意义 | | --- | :---: | ---: | | + | 加 | | - | 减 | | -expr | 一元减号,也表示否定(反转表达式符号)| | * | 乘 | | / | 除 | | ~/ | 除,返回整数结果 | | % | 取余 |

 
   
   
 
  1.  // ** 译者在此省略了部分演示代码**

  2.  assert(5 / 2 == 2.5);

  3.  assert(5 ~/ 2 == 2);

  4.  assert(5 % 2 == 1);


  5.  assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');

Dart也支持带前缀和后缀的自加或自减运算 (具体的优先级及取值参照其他语言的基础)

等价与关系运算符

| 运算符 | 意义 | | --- | :---: | ---: | | == | 等于;看下面讨论 | | != | 不等于 | | > | 大于 | | < | 小于 | | >= | 不小于 | | <= | 不大于 |

去测试两个对象 x、y 代表相同的东西,使用 == 运算符。(在极少数的情况下,你需要知道两个对象是不是同一个对象,使用identical()函数代替。)下面展示了 == 是如何工作的。

  1. 如果 x 或者 y 为null,如果他俩都是null返回true,不然返回false

  2. 返回方法调用的结果 x.==(y)。(这样是正确的,如同 == 的运算符是在第一个操作数上被调用的方法。你可以重载一些运算符,包括 == ,具体参考 重载运算符 )

举例略)

键入测试运算符

在程序运行的时候,as、is、is!运算符用于检查类型。

| 运算符 | 意义 | | --- | :---: | ---: | | as | 类型转换 | | is | 如果是指定类型返回true | | is! | 如果不是指定类型返回true |

使用as运算符将对象强制转换为特定的类型。通常,应该使用它作为is对使用该对象的表达式后跟对象的测试的简写。

 
   
   
 
  1.  if(emp is Person){

  2.    // 检查类型

  3.    emp.firstName = 'Bob';

  4.  }


  5.  // 你可以使用as让代码短一点

  6.  (emp as Person).firstName = 'Bob';

这两个写法是不等价的,如果emp为null或者不是Person,第一个例子啥都不干,第二个例子抛出异常。

赋值运算符

译者在这一部分省略了大量与其他语言重复的部分,只针对个别特殊的符号加以解析)

 
   
   
 
  1.  b ??= value; // 如果b为null,值将会赋给b;如果不为空,b还是原来的值

组合赋值运算符,略)

逻辑运算符

| 运算符 | 意义 | | --- | :---: | ---: | | !expr | 非运算 | | 丨丨 | 或运算 | | && | 与运算 |

按位和移位运算符

运算之前,将值转化为二进制)

译者按照 1为真,0为假编写解释)

| 运算符 | 意义 | | --- | :---: | ---: | | & | 全真则真,一假则假 | | 丨 | 一真则真,全假为假 | | ^ | 位异或运算,同为假,异为真 | | ~expr | 假为真,真为假 | | << | 左移位 | | >> | 右移位 |

条件表达式

Dart有两种运算符,可以简明使用if-else表达式

condition ? expr1 : expr2

真返回1结果,假返回2结果

expr1 ?? expr2

如果expr1非空,返回它的值;否则,返回2的结果

通过布尔表达式控制赋值,考虑?:

如果布尔表达式是用来测试是否为空的,考虑??

 
   
   
 
  1.  String playerName(String name) => name ?? 'Guest';

前面的例子至少可以使用两种方式来编写,但是不简洁:

 
   
   
 
  1.  // 精简使用?:

  2.  String playerName(String name) => name != null ? name : 'Guest';


  3.  // 使用if-else表达式写

  4.  String playerName(String name){

  5.    if (name != null){

  6.      return name;

  7.    }else{

  8.      return 'Guest';

  9.    }

  10.  }

级联符号(..)

级联(..)允许你对同一个对象进行一系列的操作。除了函数的调用之外,你还可以访问同一对象上的字段。这通常可以节省创建临时变量的步骤,让代码书写更流畅。

 
   
   
 
  1.  querySeletor('#confirm') // 获取一个对象

  2.    ..text = 'Confirm' // 使用它的一个成员

  3.    ..classes.add('important')

  4.    ..onClick.listen((e) => window.alert('Confirmed!'));

第一个方法调用,querySeletor(),返回一个选择器对象。级联表示法后面的代码对此对象进行操作,忽略可能返回的后续值。

前面的示例相当于:

 
   
   
 
  1.  var button = querySeletor('#confirm');

  2.  button.text = 'Confirm';

  3.  button.classes.add('import');

  4.  button.onClick.listen((e) => window.alert('Confirmed!'));

你也可以嵌套你的级联:

 
   
   
 
  1.  final addressBook = (AddressBookBuilder()

  2.        ..name = 'jenny'

  3.        ..email = 'jenny@example.com'

  4.        ..phone = (PhoneNumberBuilder()

  5.              ..number = '415-555-0100'

  6.              ..label = 'home')

  7.            .build())

  8.      .build();

注意在返回实际函数的对象上构造级联。比如,以下代码失败:

 
   
   
 
  1.  var sb = StringBuffer();

  2.  sb.write('foo')

  3.    ..write('bar'); // error:write方法没有定义为void

sb.write()调用返回void,你不能在void这一类函数上构造级联。

注:严格意义上来说,对于级联的两点符号并不是运算符,只是Dart语法的一部分。

其他类型运算符

在其他的示例中,你已经见过大多的剩余的运算符了:

运算符 名称 意义
() 函数应用 表示函数的调用
[] 列表访问 引用列表中指定索引处的值
. 成员访问权限 指表达式的属性;示例:fool.bar从表达式foo中选择了属性bar
?. 条件成员访问 跟 '.' 类似,但是运算符的左边可以为null,比如:foo?.bar 从foo中选择了bar属性即使foo为空(在这种情况下,foo?.bar的值为null)

了解更多的信息关于 . ,?. ,和 . 运算符,看 类Classes

流程控制语句

你可以用下述的Dart代码控制流程:

  • if和else

  • for循环

  • while和do-while循环

  • break和continue

  • switch和case

  • assert

你也可以使用try-catch和throw影响控制流程,在 异常Exceptions 中介绍。

if-else语句

Dart支持带有可选else的if语句,在下个例子中有演示。另见 条件表达式

 
   
   
 
  1.  if (isRaining()){

  2.    you.bringRainCoat();

  3.  } else if (isSnowing()){

  4.    you.wearJacket();

  5.  } else {

  6.    car.putTopDown();

  7.  }

不像JavaScript,条件必须是布尔值,不能为其他。具体信息看 布尔

for循环

你可以使用标准的for循环迭代。

 
   
   
 
  1.  var message = StringBuffer('Dart is fun');

  2.  for (var i = 0; i < 5; i++){

  3.    message.write('!');

  4.  }

在Dart中for循环内部的闭包捕获了索引的值,避免了JavaScript中常见的陷阱。

 
   
   
 
  1.  var callbacks = [];

  2.  for (var i = 0; i < 2; i++){

  3.    callbacks.add(() => print(i));

  4.  }

  5.  callbacks.forEach((c) => c());

如预期那样,先输出0后1。相反在JavaScript中这个例子先2后2。

如果迭代的对象是可迭代的,你可以使用forEach()方法。如果你不需要当前的迭代计数器,使用forEach()是个不错的选择。

 
   
   
 
  1.  candidates.forEach((candidate) => candidate.interview());

类似于列表和集合这样的可迭代类也支持迭代的for-in结构:

 
   
   
 
  1.  var collection = [0, 1, 2];

  2.  for (var x in collection){

  3.    print(x); // 0 1 2

  4.  }

while和do-while

while控制条件在循环之前

 
   
   
 
  1.  while (!isDone()) {

  2.    doSomething();

  3.  }

do-while控制条件在循环之后

 
   
   
 
  1.  do{

  2.    printLine();

  3.  } while(!atEndOfPage());

break和continue

使用break停止循环

 
   
   
 
  1.  while(true){

  2.    if (shutDownRequested()) break;

  3.    processIncomingRequests();

  4.  }

使用continue跳到下一个循环迭代

 
   
   
 
  1.  for (int i = 0; i < candidates.length; i++){

  2.    var candidate = candidates[i];

  3.    if (candidate.yearsExperience < 5){

  4.      continue;

  5.    }

  6.    candidate.interview();

  7.  }

如果你使用类似于列表、集合的 迭代 ,你可以以不同的方式写示例:

 
   
   
 
  1.  candidates

  2.      .where((c) => c.yearsExperience >= 5)

  3.      .forEach((c) => c.interview());

switch和case

跟C语言的结构并没有什么本质的区别,略,case内局部变量只在内部有效)

断言assert

如果布尔值为false,使用assert语句将中断程序的正常执行。你可以在这愉快之旅中的例子中感受assert语句的用法。

 
   
   
 
  1.  // 确认变量有个非空的值

  2.  assert(text != null);


  3.  // 确认值比100小

  4.  assert(number < 100);


  5.  // 确认是个https链接

  6.  assert(urlString.startsWith('https'));

注:断言语句对生产代码并没有什么影响;它们只是用于开发。Flutter允许断言在 debug模式 。仅限开发的工具(比如dartdevc)通常默认支持断言。一些工具如dart,dart2js通过命令行标志支持断言:--enable-asserts

将消息加到断言,添加字符串作为第二参数。

 
   
   
 
  1.  assert(urlString.startsWith('https'),'URL ($urlString) should start with "https".');

第一个参数assert可以是任何解析为布尔值的表达式。如果表达式的值为true,则断言成功并继续执行。如果为false,则断言失败并抛出异常(一个 断言错误

异常Exceptions

Dart代码可以抛出和捕获异常,异常是指意外事件发生的错误。如果未能捕获异常,则会暂停引发异常的隔离,并且通常会终止隔离和程序。

与Java相比,所有的Dart异常都是未检查的异常。方法不会声明它们会抛出的异常,你也没必要捕获异常。

Dart提供了 异常错误 两种类型,以及许多预定义的子类型,当然也可以自己定义的意外情况。Dart程序可以抛出任何非空对象-不仅仅是异常和错误对象-作为例外。

抛出throw

一个抛出或者引发异常的示例:

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

你也可以任意对象:

 
   
   
 
  1.  throw 'Out of llamas!';

注:生成有质量的代码通常会抛出 异常错误

因为抛出异常是一种表达,你可以使用 => 语句抛出异常,在任何可以表达的地方:

 
   
   
 
  1.  void distanceTo(Point other) => throw UnimplementedError();

捕获Catch

捕获,会阻止异常的传播(除非重新抛出异常)。捕获异常使有机会处理它:

 
   
   
 
  1.  try{

  2.    breedMoreLlamas();

  3.  } on OutOfLlamasException {

  4.    buyMoreLlamas();

  5.  }

要处理可能抛出多种异常的代码,可以指定多个捕获子句。与抛出对象类型匹配的第一个子句处理异常。如果捕获子句未指定类型,则该子句可以处理任何异常:

 
   
   
 
  1.  try{

  2.    breedMoreLlamas();

  3.  } on OutOfLlamasException {

  4.    // 一个指定异常

  5.    buyMoreLlamas();

  6.  } on Exception catch (e) {

  7.    // 其他任何异常

  8.    print('Unkown exception: $e');

  9.  } catch (e) {

  10.    // 没有指定类型,可以处理所有

  11.    print('Someting really unkown: $e');

  12.  }

如上面的代码所示,你可使用on或catch,或者一起用。使用on时需要指定异常的类型。使用catch时,你的异常处理程序需要异常对象。

你可以指定一个或者两个catch()参数。第一个抛出异常,第二个是堆栈跟踪(StackTrace 对象)

 
   
   
 
  1.  try{

  2.    // ...

  3.  } on Exception catch (e) {

  4.    print('Exception details:\n $e');

  5.  } catch (e, s) {

  6.    print('Exception details:\n $e');

  7.    print('Stack trace:\n $s');

  8.  }

要处理部分异常,并允许它传播,请使用关键字rethrow。

 
   
   
 
  1.  void misbehave() {

  2.    try {

  3.      dynamic foo = true;

  4.      print(foo++); // runtime错误

  5.    } catch (e) {

  6.      print('misbehave() partially handled ${e.runtimeType}.');

  7.      rethrow; // 允许调用者查看异常

  8.    }

  9.  }


  10.  void main(){

  11.    try{

  12.      misbehave();

  13.    } catch (e) {

  14.      print('main() finished handling ${e.runtimeType}.');

  15.    }

  16.  }

最后Finally

无论是否抛出异常,要确保某些代码的运行,请使用finally子句。如果没有catch子句匹配该异常,则在finally子句运行后传播异常。

 
   
   
 
  1.  try{

  2.    breedMoreLlamas();

  3.  } finally {

  4.    // 总是被清理,即使有其他异常

  5.    cleanLlamaStalls();

  6.  }

finally子句在任何匹配的catch子句之后运行:

 
   
   
 
  1.  try{

  2.    breedMoreLlamas();

  3.  } catch (e) {

  4.    print('Error: $e'); // 首先处理异常

  5.  } finally {

  6.    cleanLlamaStalls(); // 然后清理

  7.  }

了解更多,请阅读库之旅的 异常 部分。

Dart是一门面向对象的语言,具有类以及基于mixin的继承。每个对象都是一个类的实例,所有的类都来自于Object。基于Mixin的继承意味着即使每个类(除了Object)只有一个超类(父类),但是类体可以在多个类层次结构中重用。

使用类成员

对象有由函数和数据(分别为方法和实例变量)组成的成员。调用方法时,可以在对象上调用它:该方法可以访问该对象的函数和数据。

使用点(.)来引用实例变量或者方法:

 
   
   
 
  1.  var p = Point(2, 2);


  2.  // 设置实例变量y的值

  3.  p.y = 3;


  4.  // 获取y的值

  5.  assert(p.y == 3);


  6.  // 在p上调用distanceTo()

  7.  num distance = p.distanceTo(Point(4, 4));

使用 ?. 代替 . 避免最左边的操作数为空的情况

 
   
   
 
  1.  // 如果p不为空,把它的y的值设为4

  2.  p?.y = 4;

使用构造函数

你可以使用构造函数来创造对象。构造函数的名称可以是 ClassName 或 ClassName.identifier。举例,下面的代码创建了一个Point对象使用了构造函数 Point() 和 Point.fromJson():

 
   
   
 
  1.  var p1 = Point(2, 2);

  2.  var p2 = Point.fromJson({'x': 1, 'y': 2});

下面的代码的效果是一样的,但是在构造函数之前使用了可选的关键字 new

 
   
   
 
  1.  var p1 = new Point(2, 2);

  2.  var p2 = new Point.fromJson({'x': 1, 'y': 2});

版本提示:在Dart2中new才是可选的

一些类提供了常构造函数。使用常构造函数去创建一个编译时的常量,在构造函数的名称前添加关键词 const

 
   
   
 
  1.  var p = const ImmutablePoint(2, 2);

构造两个相同的编译时常量会产生一个规范的实例:

 
   
   
 
  1.  var a = const ImmutablePoint(1, 1);

  2.  var b = const ImmutablePoint(1, 1);


  3.  assert(identical(a, b));// 它们是相同的实例

在常量的上下文中,你可以省略构造函数或者文字前的 const 关键字,比如,看这段创造一个常地图代码。

 
   
   
 
  1.  // 这有太多的const关键字


  2.  const pointAndLine = const {

  3.    'point': const [const ImmutablePoint(0, 0)],

  4.    'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],

  5.  };

你可以省略除了第一个const关键字

 
   
   
 
  1.  // 只用一个const

  2.  const pointAndLine = {

  3.    'point': [ImmutablePoint(0, 0)],

  4.    'line': [ImmutablePoint(1,10), ImmutablePoint(-2, 11)],

  5.  };

如果一个常构造函数在常上下文外被调用没有const,它创建了一个非 常对象

 
   
   
 
  1.  var a = const ImmutablePoint(1, 1);// 创造了一个常对象

  2.  var b = ImmutablePoint(1, 1);// 未创造一个常对象


  3.  assert(!identical(a, b));// 它们不是相同的实例

版本提示:在Dart2中在常上下文中const才是可选的。

获取一个对象的类型

在程序执行的过程中获取对象的类型,你可以使用Object的runtimeType属性,返回 类型Type 对象。

 
   
   
 
  1.  print('The type of a is ${a.runtimeType}');

到目前为止,你已经了解如何使用类了。本节的其余部分将介绍如何实现类。

实例变量

这里展示了如何去声明一个实例变量:

 
   
   
 
  1.  class Point{

  2.    num x; // 声明实例变量x,初始值为空

  3.    num y; // 声明变量y,初始值为空

  4.    num z=0; // 声明变量,初始值为0

  5.  }

没有初始化的变量的值为null

所有的实例变量都生成一个隐式的getter方法。非最终实例变量(Non-final)也生成一个setter方法。获取更多的细节,参照 Getters和Setters

 
   
   
 
  1.  class Point {

  2.    num x;

  3.    num y;

  4.  }


  5.  void main(){

  6.    var point = Point();

  7.    point.x = 4; // 对x使用setter方法

  8.    assert(point.x == 4); // 对x使用getter方法

  9.    assert(point.y == null); // 默认值为null

  10.  }

如果你在实例对象声明的地方初始化对象(而不是在构造函数或者方法中),当实例被创建的时候值就已经被设置了,在构造函数和初始化列表执行之前。

构造函数

通过创建一个跟它的类同名的构造函数来实现构造函数的声明。(plus,可选的,还有一个额外的标识符,如同 命名构造函数 所述)

最常见的构造函数形式,即生成构造函数,创造类的一个新实例:

 
   
   
 
  1.  class Point {

  2.    num x, y;

  3.    Point (num x, num y) {

  4.      // 有更好的方法去做这个


  5.      this.x = x;

  6.      this.y = y;

  7.    }

  8.  }

this关键字指向现况的实例

使用this时这会有一个命名冲突。否则,Dart代码将会省略this。

将构造函数的值赋给实例变量的模式是很常见的。Dart有语法sugar使实现更简单:

 
   
   
 
  1.  class Point {

  2.    num x, y;


  3.    // 使用语法sugar设置x,y的值

  4.    // 在构造函数体运行之前

  5.    Point(this.x, this.y);

  6.  }

默认构造函数

如果自己没有声明构造函数,它会自己生成一个。默认生成的构造函数没有参数,并在超类(父类)中调用无参构造函数。

构造函数不是被继承的

子类不从超类(父类)中继承构造函数。声明没有构造函数的子类只有默认构造函数(无参数,无名称)。

命名构造函数

使用命名构造函数为类实现多个构造函数去实现更加的清晰。

 
   
   
 
  1.  class Point {

  2.    num x, y;


  3.    Point(this.x, this.y);


  4.    // 命名构造函数


  5.    Point.origin() {

  6.      x=0;

  7.      y=0;

  8.    }

  9.  }

记住构造函数是不能被继承的,就意味着超类(父类的)命名构造函数也不能被子类继承。如果希望使用超类中定义的命名构造函数创建子类,则必须在子类中实现该构造函数。

调用非默认的超类构造函数

默认的,在子类调用的超类构造函数是没有命名、没有参数的。超类构造函数在构造函数的开头被调用;如果一个初始化列表也被调用,它将在超类调用之前执行。总之,执行顺序如下:

  1. 初始化列表

  2. 超类无参构造函数

  3. 主类无参构造函数

如果超类没有未命名的无参构造函数,你必须手动在超类中调用一个构造函数。在冒号后面指定超类的构造函数,在构造函数体的前部。

在下面的示例中,Employee类的构造函数调用它的超类Person的命名构造函数

 
   
   
 
  1.  class Person {

  2.    String firstName;


  3.    Person.fromJson(Map data) {

  4.      print('in Person');

  5.    }

  6.  }


  7.  class Employee extends Person {

  8.    // Person没有默认的构造函数

  9.    // 你必须调用super.fromJson(data)

  10.    Employee.fromJson(Map data) : super.fromJson(data) {

  11.      print('in Employee');

  12.    }

  13.  }


  14.  main() {

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

  16.  }


  17.  // Prints:

  18.  // in Person

  19.  // in Employee


  20.  if (emp is Person){

  21.    // 类型检查

  22.    emp.firstName = 'Bob';

  23.  }

  24.  (emp as Person).firstName = 'Bob';

因为在调用构造函数之前会处理超类构造函数的参数,所以参数可以是一个表达式,比如函数调用:

 
   
   
 
  1.  class Employee extends Person {

  2.    Employee() : super.fromJson(getDefaultData());

  3.    // ...

  4.  }

注意:超类构造函数的参数没有this的权限,例如,参数可以调用静态方法但是不能调用实例方法。

初始化列表

除了调用超类的构造函数外,你也可以在构造函数体运行之前,初始化实例变量。用逗号分隔初始化:

 
   
   
 
  1.  // 初始化列表在构造函数体运行之前设置实例的变量


  2.  Point.fromJson(Map<String, num> json)

  3.    : x = json['x'],

  4.      y = json['y'] {

  5.        print('In Point.fromJson() : ($x, $y)');

  6.      }

注意:初始化程序的右侧无权访问this。

在开发期间,你可以在初始化列表验证输入的值

 
   
   
 
  1.  Point.withAssert(this.x, this.y) : assert(x >= 0) {

  2.    print('In Point.withAssert() : ($x, $y)');

  3.  }

设置final字段时,设置初始化列表很方便。在下面的示例中在初始化列表中初始化了三个final字段。

 
   
   
 
  1.  import 'dart:math';


  2.  class Point {

  3.    final num x;

  4.    final num y;

  5.    final num distanceFromOrigin;


  6.    Point(x, y)

  7.      : x = x,

  8.        y = y,

  9.        distanceFromOrigin = sqrt(x * x + y * y);

  10.  }


  11.  main() {

  12.    var p = new Point(2, 3);

  13.    print(p.distanceFromOrigin);

  14.  }

重定向构造函数

有时候构造函数只用于在同一个类中重定向到其他的构造函数。重定向构造函数的函数体是空的,构造函数的调用出现在冒号之后。

 
   
   
 
  1.  class Point {

  2.    num x, y;


  3.    // 这个类的主构造函数

  4.    Point(this.x, this.y);


  5.    // 代表主构造函数

  6.    Point.alongXAxis(num x) : this(x, 0);

  7.  }

常构造函数

如果你的类生成不打算更改的对象,你可以使这些对象为编译时常量。为了去实现,定义一个const构造函数,并保证所有的实例变量都是final型。

 
   
   
 
  1.  class ImmutablePoint {

  2.    static final ImmutablePoint origin  = const ImmutablePoint(0, 0);


  3.    final num x, y;

  4.    const ImmutablePoint(this.x, this.y);

  5.  }

常构造函数并不总是创造常量。更多细节,去看 使用构造函数 那一节。

工厂构造函数

使用关键字 factory 当实现构造函数不总在它的类中创建新的实例。比如,工厂构造函数可能从缓存(cache)中返回实例,或者返回子类型实例。

下面的示例演示了一个构造函数从缓存中返回了一个对象:

 
   
   
 
  1.  class Logger {

  2.    final String name;

  3.    bool mute = false;


  4.    // _cache是库私有,因为标识符前面的_

  5.    static final Map<String, Logger> _cache = <String, Logger>{};


  6.    factory Logger(String name) {

  7.      if (_cache.containsKey(name)) {

  8.        return _cache[name];

  9.      } else {

  10.        final logger = Logger._internal(name);

  11.        _cache[name] = logger;

  12.        return logger;

  13.      }

  14.    }

  15.  }


  16.  Logger._internal(this.name);


  17.  void log(String msg) {

  18.    if(!mute) print(msg);

  19.  }

工厂构造函数没有this的权限。

调用工厂构造函数跟调用其他的构造函数一样:

 
   
   
 
  1.  var logger = Logger('UI');

  2.  logger.log('Button clicked');

方法

方法是赋予对象动作的函数。

实例方法

在对象中的实例方法有使用实例对象和this的权限。下面例子中的distanceTo()方法就是一个实例方法的示例:

 
   
   
 
  1.  import 'dart:math';


  2.  class Point{

  3.    num x, y;


  4.    Point(this.x, this.y);


  5.    num distanceTo(Point other) {

  6.      var dx = x - other.x;

  7.      var dy = y - other.y;

  8.      return sqrt(dx * dx + dy * dy);

  9.    }

  10.  }

Getters和Setters

Getters和Setters是提供了读写对象属性的特殊方法。回想一下,每一个实例变量都有一个隐性的getter,如果合适的话还有一个setter。你可以通过get和set关键字实现创建额外的属性。

 
   
   
 
  1.  class Rectangle {

  2.    num left, top, width, height;


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


  4.    // 定义两个计算的属性:right和bottom

  5.    num get right => left + width;

  6.    set right(num value) => left = value - width;

  7.    num get bottom => top + height;

  8.    set bottom(num value) => top = value - height;

  9.  }


  10.  void main() {

  11.    var rect = Rectangle(3, 4, 20, 15);

  12.    assert(rect.left == 3);

  13.    rect.right = 12;

  14.    assert(rect.left == -8);

  15.  }

通过getters和setters,你从实例变量开始,稍后使用方法包装它们,无需更改客户端的代码。

注意:无论是否明确定义了getter,如同++的运算符都会如于其那样工作。去避免任何不可预测的影响,运算发只需要调用一次getter,并将其值保存在临时变量中。

抽象类

使用abstract修饰符定义抽象类——不能被实例化的类,抽象类对于定义接口是非常有用的,通常还有一些实现。如果你想使抽象类初始化,那就定义一个 factory(工厂)构造函数

抽象类一般有抽象方法,下面是一个带有抽象方法的抽象类的示例:

 
   
   
 
  1.  // 这个类被定义为抽象类,不能被实例化

  2.  abstract class AbstractContainer {

  3.    // 定义构造函数,域,方法。。。


  4.    void updateChildren(); // 抽象方法

  5.  }

隐式接口

每个类都隐式定义了一个接口,接口包含所有该类的实例成员及其实现的接口。如果你想在不继承B类的情况下创建支持B类API的A类,A类应该实现B接口。

类通过在implements子句中声明它们,然后提供接口所需的API来实现一个或者多个接口:

 
   
   
 
  1.  // 一个Person,隐式接口包含greet()

  2.  class Person {

  3.    // 在界面中,仅能在这个库中可见

  4.    final _name;


  5.    // 因为是个构造函数,所以不在界面中

  6.    Person(this._name);


  7.    // 在界面中

  8.    String greet(String who) => 'Hello, $who.I am &_name.'

  9.  }


  10.  // Person接口的实现

  11.  class Impostor implements Person {

  12.    get _name => '';


  13.    String greet(String who) => 'Hi $who . Do you know who I am?'

  14.  }


  15.  String greetBob(Person person) => person.greet('Bob');


  16.  void main(){

  17.    print(greetBob(Person('Kathy')));

  18.    print(greetBob(Impostor()));

  19.  }

这是一个指定类实现多个接口的示例:

 
   
   
 
  1.  class Point implements Comparable, Location {...}

扩展类(继承与派生)

使用extends创造子类,并且使用super指向超类:

 
   
   
 
  1.  class Television {

  2.    void turnOn() {

  3.      _illuminateDisplay();

  4.      _activateIrSensor();

  5.    }

  6.    // ...

  7.  }


  8.  class SmartTelevision extends Television {

  9.    void turnOn() {

  10.      super.turnOn();

  11.      _bootNetworkInterface();

  12.      _initializeMemory();

  13.      _upgradeApps();

  14.    }

  15.    // ...

  16.  }

重载成员

子类可以重写实例方法,getters,setters。你可以使用 @override 注释来指示你有意重载的成员:

 
   
   
 
  1.  class SmartTelevision extends Television {

  2.    @override

  3.    void turnOn() {...}

  4.    // ...

  5.  }

在代码中,去限制方法参数或者实例变量的类型是 类型安全 的,你可以使用 协变关键字covariant keyword

重载运算符

你可以重载下表中的运算符。举个例子,如果你定义一个Vector类,你可以定义一个 + 方法相加两个vector(译者注:可以参考C++在这部分的内容)。

| 可重载运算符表 | | :---: | :---: | :---: | | < | + | 丨 | [] | | > | / | ^ | []= | | <= | ~/ | & | ~ | | >= | * | << | == | | - | % | >> |

你可能注意到了!=是不可以重载的运算符,表达式e1 != e2只是!(e1 == e2)的句法糖(译者注:看到这里,译者觉得所谓句法糖的意思,貌似是等价写法的意思,嘤嘤嘤)

下面是类重载运算符的一个例子:

 
   
   
 
  1.  class Vector {

  2.    final int x, y;


  3.    Vector(this.x, this.y);


  4.    Vector operator + (Vector v) => Vector(x + v.x, y + v.y);

  5.    Vector operator - (Vector v) => Vector(x - v.x, y - v.y);


  6.    // 运算符 == 和 哈希码并没有显示。更多的细节在后面的note中。

  7.  }


  8.  void main() {

  9.    final v = Vector(2, 3);

  10.    final w = Vector(2, 2);


  11.    assert(v + w == Vector(4, 5));

  12.    assert(v - w == Vector(0, 1));

  13.  }

如果你重载了 == ,你也应该重载对象的哈希码getter。举个例子重载 == 和哈希码,参见 实现地图键(在库之旅中)

更多关于重载的信息,通常参考 扩展类(继承)

没有这个方法noSuchMethod()

要在代码中尝试使用不存在的方法或实例变量时,检测或做出反应,你可以重载noSuchMethod():

 
   
   
 
  1.  class A {

  2.    // 除非你重载noSuchMethod,否则使用不存在的成员会导致NOSuchMethodError。


  3.    @override

  4.    void noSuchMethod(Invocation invocation) {

  5.      print('You tried to use a non-existent member: ' + '${invocation.memberName}');

  6.    }

  7.  }

不可以 调用一个没有实现的方法,除非满足下面的任意一点:


  • 接收器具有静态的类型dynamic



  • 接收器具有静态的类型定义了没有实现的方法(抽象是可以的),接收器的dynamic类型有noSuchMethod()的实现,跟类对象是不一样的


更多请参照 noSuchMethod转发规范noSuchMethod forwarding specification

枚举类型Enumerated types

枚举类型,通常被称为 enumerations 或者 enums ,是被用来代表常量的固定数字的特殊一类。

使用枚举

使用关键字enum声明枚举类型:

 
   
   
 
  1.  enum Color { red, green, blue }

每个枚举的值都有一个 索引index getter,返回从零开始的索引。

 
   
   
 
  1.  assert(Color.red.index == 0);

  2.  assert(Color.green.index == 1);

  3.  assert(Color.blue.index == 2);

去获取枚举中所有值的列表,你可以使用枚举的values常方法

 
   
   
 
  1.  List<Color> colors = Color.values;

  2.  assert(Color[2] == Color.blue);

你可以在switch语句使用枚举,如果你不处理枚举的所有的值你将会收到警告。

 
   
   
 
  1.  var aColor = Color.blue;


  2.  switch (aColor) {

  3.    case Color.red:

  4.      print('Red as roses!');

  5.      break;

  6.    case Color.green:

  7.      print('Green as grass!');

  8.      break;

  9.    default: // 避免警告

  10.      print(aColor);

  11.  }

枚举类型有下面的限制:

  • 你不可以创建子类,混入,或者实现枚举

  • 你不可以明确地实例化枚举

了解更多,参见 Dart语言规范

向类添加功能:混入mixins

Mixins是一种在多个类层次结构中重用类代码的方法。

使用 mixin,请使用with关键字后跟一个或多个mixin名称。以下示例显示了两个使用mixins的类:

 
   
   
 
  1.  class Musician extends Performer with Musical {

  2.    // ...

  3.  }


  4.  class Maestro extends Person with Musical, Aggressive, Demented {

  5.    Maestro(String maestroName) {

  6.      name = maestroName;

  7.      canConduct = true;

  8.    }

  9.  }

去实现mixin,创建一个类并扩展对象、不要声明构造函数。除非您希望mixin可用作常规类,否则请使用mixin关键字而不是class。

 
   
   
 
  1.  mixin Musical {

  2.    bool canPlayPiano = false;

  3.    bool canCompose = false;

  4.    bool canConduct = false;


  5.    void enterainMe() {

  6.      if (canPlayPiano) {

  7.        print('Playing Piano');

  8.      } else if(canConduct) {

  9.        print('Waving hands');

  10.      } else {

  11.        print('Humming to self');

  12.      }

  13.    }

  14.  }

可以使用mixin指定一个明确的类型,举个例子,你的mixin可以调用一个没有定义的方法——使用on指向调用的超类。

 
   
   
 
  1.  mixin MusicalPerformer on Musician {

  2.    // ...

  3.  }

版本提醒:在Dart2.1中介绍了对于mixin的支持。早期版本的代码通常使用抽象类代替。更多关于2.1中mixin变化的信息,参见 Dart SDK改变日志 Dart SDK changelog 和 2.1 mixin 规范 2.1 mixin specification。

类变量和方法

使用static关键字去实现类范围的变量和方法:

静态变量

静态变量(类变量)对类范围的状态和常量是非常实用的:

 
   
   
 
  1.  class Queue {

  2.    static const initialCapacity = 16;

  3.    //...

  4.  }


  5.  void main(){

  6.    assert(Queue.initialCapacity == 16);

  7.  }

静态变量直到使用的时候才能被初始化。

此页遵循样式指南建议更偏向为常名称使用lowerCamelCase。

静态方法

静态方法(类方法)不在实例中作用,因此无this权限。

 
   
   
 
  1.  import 'dart:math';


  2.  class Point {

  3.    num x, y;

  4.    Point(this.x, this.y);


  5.    static num distanceBetween(Point s, Point b) {

  6.      var dx = a.x - b.x;

  7.      var dy = a.y - a.y;

  8.      return sqrt(dx * dx + dy * dy);

  9.    }

  10.  }


  11.  void main() {

  12.    var a = Point(2, 2);

  13.    var b = Point(4, 4);

  14.    var distance = Point.distanceBetween(a, b);

  15.    assert(2.8 < distance && distance < 2.9);

  16.    print(distance);

  17.  }

对于常用或广泛使用的实用程序和功能,请考虑使用顶级函数而不是静态方法。

你可以使用静态方法作为编译时常量。举个例子,可以将静态方法作为参数传递给常量构造函数。

泛型Generics

如果你查看基础API文档数组,列表,你将会看到类型其实是List 。<...>将列表标记为泛型(或者参数化)类型——具有正式参数类型的参数。按照惯例,类型变量有单个字母名称,比如E,T,S,K和V。

为什么要使用泛型?

译者注:泛型即为模板

泛型常被用于类型安全,然而它们有比仅仅让你的代码运行的更多好处。

  • 正确指定泛型类型可以生成更好的代码。

  • 您可以使用泛型来减少代码重复。

如果你希望列表只包含字符串,你可以声明它为List (读作“字符串列表”)。这样,你,你的程序和你的工具检测列表中的非字符串参数是个错误。

 
   
   
 
  1.  var names = List<String>();

  2.  names.addAll(['Seth', 'Kathy', 'Lars']);

  3.  names.add(42); // 错误

使用泛型的另一个原因是减少代码重复。泛型允许您在多种类型之间共享单个接口和实现,同时仍然利用静态分析。比如创建一个缓存对象的接口

 
   
   
 
  1.  abstract class ObjectCache {

  2.    Object getByKey(String key);

  3.    void setByKey(String Key, Object value);

  4.  }

你发现你想创建一个指定字符串版本的接口,你创建另一个接口:

 
   
   
 
  1.  abstract class StringCache {

  2.    String getByKey(String key);

  3.    void setByKey(String Key, String value);

  4.  }

然后你又想创建一个指定数字版本的接口。。。

泛型类型可以解决你创建所有的这些接口的问题。取而代之,你可以带有类型参数创建单个接口:

 
   
   
 
  1.  abstract class Cache<T> {

  2.    T getByKey(String key);

  3.    void setByKey(String key, T value);

  4.  }

在这段代码中,T是替身类型。它是一个占位符,您可以将其视为开发人员稍后定义的类型。

使用集合文字

列表和集合文字可以被参数化。参数化文字除了在括号之前添加类型,其他就像你之前看到的那样。

 
   
   
 
  1.  var names = <String>['Seth', 'Kathy', 'Lars'];

  2.  var pages = <String, String>{

  3.    'index.html': 'Homepage',

  4.    'robot.txt': 'Hints for web robots',

  5.    'humans.txt': 'We are people, not machines'

  6.  };

使用带有构造函数的参数化类型

在使用构造函数的时候,指定一个或者多个类型,将类型放在类名后面的尖角括号之中

 
   
   
 
  1.  var names = List<String>();

  2.  names.addAll(['Seth', 'Kathy', 'Lars']);

  3.  var nameSet = Set<String>.from(names);

下面的代码创建了一个整型键、View类型的地图

 
   
   
 
  1.  var views = Map<int, View>();

泛型集合和它们包含的类型

Dart泛型的类型具体化,意味着它们在运行时携带类型信息。

 
   
   
 
  1.  var names = List<String>();

  2.  names.addAll(['Seth', 'Kathy', 'Lars']);

  3.  print(names is List<String>); // 输出true

相反,在Java中泛型擦除,就意味着在运行中删除泛型类型参数。在Java之中,你可以测试对象是否List,但是不能测试它是否是List

限制参数化类型

当你实现泛型类型之时,你需要去限制参数的类型。你可以使用extends。

 
   
   
 
  1.  class Foo<T extends SomeBaseClass> {

  2.    String toString() => "Instance of 'Foo<$T>'";

  3.  }


  4.  class Extender extends SomeBaseClass {...}

可以使用SomeBaseClass或其任何子类作为泛型参数:

 
   
   
 
  1.  var someBaseClassFoo = Foo<SomeBaseClass>();

  2.  var extenderFoo = Foo<Extender>();

也可以不指定泛型参数:

 
   
   
 
  1.  var foo = Foo();

  2.  print(foo); // Instance of 'Foo<SomeBaseClass>'

指定非SomeBaseClass会导致错误:

 
   
   
 
  1.  var foo = Foo<Object>(); // 错误

使用泛型方法

起初,Dart的泛型支持被限制在类。一个更新的语法,叫做泛型方法,允许类型参数在方法或者函数中:

 
   
   
 
  1.  T first<T>(List<T> ts) {

  2.    // 先做一些初始化工作或者错误检查

  3.    T tmp = ts[0];

  4.    //

  5.  }

这里在first 中泛型类型参数,允许你在一些地方使用类型参数T

  • 在函数的返回类型(T)

  • 在参数类型(List )

  • 在局部变量(T tmp)

了解关于泛型的更多信息,参考 使用泛型方法

库与能见度

import和library指令可以帮助你创建一个模块化的、可分享的代码库。库不仅仅提供API,还有隐私单元:以下划线开头的标识符只在库内可见。每一个Dart APP都是一个库,即使它没有使用library指令。

可以使用包来分发库。在发布包和内容管理中了解关于pub有关的信息,在SDK中包含包管理。

使用库

使用import指定如何从一个库中的命名空间在另一个库的范围内使用。

举个例子,Dart web应用通常使用 dart:html库。

 
   
   
 
  1.  import 'dart:html';

import需要的唯一参数是指定库的URI。对于内置的库,URI有特殊的 dart: 方案。对于其他的库,你可以使用文件系统路径或者 package: 方案。package方案指定由类似于pub tool的包管理提供的库。

 
   
   
 
  1.  import 'package:test/test.dart';

URI代表了统一资源标识符。URL(uniform resource locators)(统一资源定位符)是一种常见的URI。

指定一个库前缀

如果你输入两个含有冲突的标识符的库,然后你可以为一个或者所有的库指定前缀。举个例子,如果库1和库2都有Element类:

 
   
   
 
  1.  import 'package:lib1/lib1.dart';

  2.  import 'package:lib2/lib2.dart' as lib2;


  3.  // 使用来自于库1的Element

  4.  Element element1 = Element();


  5.  // 使用来自于库2的Element

  6.  lib2.Element element2 = lib2.Element();

输入库的一部分

如果你只想使用库的一部分,你可以有选择性的输入库。

 
   
   
 
  1.  // 只输入foo

  2.  import 'package:lib1/lib.dart' show foo;


  3.  // 输入除了foo

  4.  import 'package:lib2/lib2.dart' hide foo;

懒加载一个库

延期加载(或者称为懒加载) 允许应用在有需要的时候加载一个库。以下是您可能使用延迟加载的一些情况:

  • 减少应用程序的初次启动时间

  • 执行A/B测试——尝试算法的替代实现

  • 加载很少使用的功能,例如可选的屏幕和对话框

要懒加载一个库,你必须首先使用deferred as。

 
   
   
 
  1.  import 'package:greetings/hello.dart' deferred as hello;

当你使用库的使用,使用库的标识符调用loadLibrary()

 
   
   
 
  1.  Future greet async{

  2.    await hello.loadLibrary();

  3.    hello.printGreeting();

  4.  }

在前面的代码中,await关键字暂停了执行,直到库被加载。了解关于async和await,参考 异步支持asynchrony support。

你可以在一个库中调用loadLibrary()很多次,没有任何问题,库只被加载一次。

使用延时加载的时候,记住以下内容:

  • 延迟库的常量不是导入文件中的常量。在加载延迟库之前,这些常量不存在。

  • 不能在导入文件中使用延迟库中的类型,考虑将接口类型移动到由延迟库和导入文件导入的库。

  • 你使用deferred as namespace,Dart将loadLibrary()隐式插入到命名空间。loadLibrary()函数返回一个Future。

DartVM区别:即使在调用之前,Dart VM也允许访问延迟库的成员loadLibrary()。此行为可能会更改,因此 不要依赖于当前的VM行为

详细信息参见 issue #33118。

实现库

参阅 创建库包 需求如何去实现一个库包,包括:

  • 如何组织库源代码

  • 如何使用export指令

  • 何时使用part指令

异步支持

Dart库中包含很多返回 Future 或者 Stream对象的函数。这些函数是异步的。它们在设置可能耗时的操作后(比如I/O)返回,而不等待操作完成。

async和await关键字支持异步编程,让你写异步代码看起来更像是同步代码。

处理Futures

当你需要完成的Future结果时,你有两个选择:

  • 使用async和await

  • 使用Future API,在 库之旅 中有所描述。

代码使用async和await是异步的,但是它看起来很像是同步代码。举个例子,这有一些使用await关键字等待异步函数结果的代码:

 
   
   
 
  1.  await lookUpVersion();

去使用await,代码必须是一个异步函数——一个函数被标记为async:

 
   
   
 
  1.  Future checkVersion() async {

  2.    var version = await lookUpVersion();

  3.  }

注意:虽然异步函数可能执行耗时的操作,但它不会等待这些操作。异步函数只有在遇到第一个await表达式(详情)时才会执行等待。然后它返回一个Future对象,仅在await表达式完成后才恢复原样执行。

详情

使用try,catch,finally去处理和清除使用await代码的错误:

 
   
   
 
  1.  try {

  2.    version = await lookUpVersion();

  3.  } catch(e) {

  4.    // 无法查找版本

  5.  }

你可以在异步函数中多次使用await。举个例子,下面的代码等待了三次函数结果:

 
   
   
 
  1.  var entrypoint = await findEntryPoint();

  2.  var exitCode = await runExecutable(entrypoint, args);

  3.  await flushThenExit(exitCode);

在await表达式中,通常结果都是Future;如果不是,那么值自动包含在了Future中。这个Future对象表示返回一个对象的承诺,await表达式的值是返回一个对象。await表达式使执行暂停直到对象可用。

如果使用await的时候出现了编译时错误,请确保await是在一个异步函数之中(async function)。举个例子,在app中的main()函数中使用await,main()的函数体必须被标记为async:

 
   
   
 
  1.  Future main() async {

  2.    checkVersion();

  3.    print('In main: version is ${await lookUpVersion()}');

  4.  }

声明异步函数

异步函数是通过标记async修改函数体的函数。

将async关键字加到函数上,使它返回一个Future。举个例子,考虑一个返回String的同步函数:

 
   
   
 
  1.  String lookUpVersion() => '1.0.0';

如果将它改为异步函数——举个例子,future表现执行起来会非常耗时——返回值是一个Future。

 
   
   
 
  1.  Future<String> lookUpVersion() async => '1.0.0';

`

注意这个函数体不需要使用Future的API。在必要的情况下,Dart创造一个Future对象。

如果你的函数不返回任何有用的值,让它的返回类型为Future

处理流(Streams)

如果你想从流中获取值,你有两种方法:

  • 使用async和一个异步for循环(await for)。

  • 使用Stream API,在 库之旅 中有阐述。

注意:在使用await for之前,请确保它使代码更清晰并且你确实想去等待流的所有的值。例如,你通常不应该为UI事件侦听器使用await for,因为UI框架发送无穷尽的事件流。

异步for循环有以下的形式:

 
   
   
 
  1.  await for (var或者类型 标识符 in 表达式) {

  2.    // 在每一次流发射值的时候执行

  3.  }

表达式的值必须有流类型。执行过程如下:

  1. 等待直到流发射一个值

  2. 执行函数体中的for循环,变量值为被射出的值

  3. 重复1和2直到流被关闭

停止对流的监听,你可以使用一个break或者return语句,退出for循环和从流中退订。

如果在表现异步for循环的时候出现编译时错误,确保await for在asyn函数内。 举个例子,你在你的app的main()函数使用异步for循环,main()的函数体必须被标记为async。

 
   
   
 
  1.  Future main() async {

  2.    // ...

  3.    await for (var request in requestServer) {

  4.      handleRequest(requset);

  5.    }

  6.    // ...

  7.  }

获取更多关于异步编程的信息,通常上,在库之旅中参考dart:async章节。

也可以看文章Dart异步支持:1、Dart异步支持:2 和 Dart语言指向 。

发生器Generators

当你需要懒生成一连串的值时,考虑使用发生器函数。Dart内置两种发生器函数:

  • 同步发生器:返回一个可迭代对象

  • 异步发生器:返回一个流对象

表现一个同步发生器,用sync*标记函数体,用yield传递值:

 
   
   
 
  1.  Iterable<int> naturalsTo(int n) sync* {

  2.    int k = 0;

  3.    while (k < n) yield k++;

  4.  }

表现一个异步发生器,用async*标记函数体,用yield传递值:

 
   
   
 
  1.  Stream<int> asynchronousNaturalsTo(int n) async* {

  2.    int k = 0;

  3.    while (k < n) yield k++;

  4.  }

如果你的生成器是递归,你可以使用yield*提高其性能:

 
   
   
 
  1.  Iterable<int> naturalsDownFrom(int n) sync* {

  2.    if (n > 0){

  3.      yield n;

  4.      yield* naturalsDownFrom(n - 1);

  5.    }

  6.  }

了解更多关于发生器,参考文章Dart异步支持:2

可调用的类

允许你的类像函数一样被调用,使用call()方法。

在下面的示例中,WannabeFunction类定义了一个call()函数,接受三个字符串并连接它们,用空格分隔每个字符串,并附加一个感叹号。

 
   
   
 
  1.  class WannabeFunction {

  2.    call(String a, String b, String c) => '$a $b $c!';

  3.  }

  4.  main() {

  5.    var wf = WannabeFunction();

  6.    var out = wf("Hi", "there", "gang");

  7.    print('$out');

  8.  }

了解更多关于将类如同函数一样对待的信息,请参考在Dart中模拟函数 。

分离Isolates

大多的计算机,甚至一些移动设备上,都有多核CPU。为了利用所有的核心,开发人员传统上使用并发运行的共享内存线程。但是,共享状态并发容易出错,并且可能导致代码复杂化。所有Dart代码都在隔离区内运行,而不是线程。每个隔离区都有自己的内存堆,确保不会从任何其他隔离区访问隔离区的状态。有关更多信息,请参阅 dart:isolate库文档

类型定义Typedefs

在Dart中,函数是对象,就如同字符串和数字是对象那样。一个类型定义,或者一个功能型的别名,给予了函数一个名字,你可以在声明的时候和返回类型的时候使用它。当函数类型分配给变量时,typedef会保留类型信息。

下面是没有使用typedef的代码:

 
   
   
 
  1.  class SortedCollection {

  2.    Function compare;


  3.    SortedCollection(int f(Object a, Object b)) {

  4.      compare = f;

  5.    }

  6.  }


  7.  // 初始化


  8.  int sort(Object a, Object b) => 0;


  9.  void main() {

  10.    SortedCollection coll = SortedCollection(sort);


  11.    // 我们都知道compare是一个函数,但是什么类型的函数呢?


  12.    assert(coll.compare is Function);

  13.  }

当把f的值赋给compare时,类型信息就丢失了。f的类型是(Object, Object) -> int(其中 -> 表示返回),但是compare的类型是Function。如果我们将代码更改为使用显式名称并保留类型信息,则开发人员和工具都可以使用该信息。

 
   
   
 
  1.  typedef Compare = int Function(Object a, Object b);


  2.  class SortedCollection {

  3.    Compare compare;


  4.    SortedCollection(this.compare);

  5.  }


  6.  // 初始化


  7.  int sort(Object a, Object b) => 0;


  8.  void main() {

  9.    SortedCollection coll = SortedCollection(sort);

  10.    assert(coll.compare is Function);

  11.    assert(coll.compare is Compare);

  12.  }

目前,typedef仅限于函数类型,开发者希望改变这一点。

因为类型定义只是别名,它们提供了检查函数类型的方法。

 
   
   
 
  1.  typedef Compare<T> = int Function(T a, T b);


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

  3.  void main() {

  4.    assert(sort is Compare<int>);       // True

  5.  }

元数据Metadata

使用元数据给关于你的代码额外的信息。元数据注释以字符@开头,后跟对编译时常量(如deprecated)的引用或对常量构造函数的调用。

所有的Dart代码都有两个注释:@deprecated和@override,有关使用@override的示例,参阅扩展类,以下是使用@deprecated的示例:

 
   
   
 
  1.  class Television {

  2.    /// _Deprecated:使用[turnOn]代替_

  3.    @deprecated

  4.    void actvate() {

  5.      turnOn();

  6.    }


  7.    /// 打开TV的电源

  8.    void turnOn(){...}

  9.  }

你可以定义你自己的元数据注释。下面的例子中定义了一个@todo注释并带有两个参数。

 
   
   
 
  1.  library todo;

  2.  class Todo {

  3.    final String who;

  4.    final String what;


  5.    const Todo(this.who, this.what);

  6.  }

下面是使用@todo的一个示例:

 
   
   
 
  1.  import 'todo.dart';


  2.  @Todo('seth', 'make this do something')

  3.  void doSomething() {

  4.    print('do something');

  5.  }

元数据可以在库、类、类型定义、类型参数、工厂、字段、参数、或变量声明以及导入或导出指令之前出现,可以使用反射在运行时检索元数据。

评论(代码注释)

Dart支持单行、多行、文档评论。

单行(以"//"开头,忽略后面内容)

多行(介于/* */之间,可以嵌套)

文档

文档评论是以///或者/**开头的多行或者单行评论,在连续的行使用///的效果与多行评论的效果相同。

在文档评论中,Dart编译器忽略所有的文本,除非它在括号之中。使用括号,可以引用类、方法、字段、顶级变量、函数和参数。括号中的名称在已记录的程序元素的词法范围内得到解析。

以下是文档注释的示例,包含对其他的类和参数的引用:

 
   
   
 
  1.  /// A domesticated South American camelid (Lama glama).

  2.  ///

  3.  /// Andean cultures have used llamas as meat and pack

  4.  /// animals since pre-Hispanic times.

  5.  class Llama {

  6.    String name;


  7.    /// Feeds your llama [Food].

  8.    ///

  9.    /// The typical llama eats one bale of hay per week.

  10.    void feed(Food food) {

  11.      // ...

  12.    }


  13.    /// Exercises your llama with an [activity] for

  14.    /// [timeLimit] minutes.

  15.    void exercise(Activity activity, int timeLimit) {

  16.      // ...

  17.    }

  18.  }

`

在生成的文档之中,[Food]成为了Food类API文档的链接。

要解析并生成HTML文档,你可以使用SDK的 文档生成工具 。有关生成文档的示例,参阅 Dart API文档。有关如何构建评论的建议,请参阅 Dart Doc评论指南 。

总结

本教程概述了Dart语言中的常用功能。正在实施更多功能,设计者不希望破坏现有的代码体系。更多信息参阅 Dart语言规范 和 有效Dart 。

学习更多Dart的核心库,参见 Dart库之旅 。


以上是关于Dart语言之旅翻译完整版的主要内容,如果未能解决你的问题,请参考以下文章

40道python二级考试真题火爆互联网,完整版答案解析为你保驾护航

HMailServer 5.6版本完整版汉化包

(完整版)c语言初学必背代码

C语言游戏超详解扫雷游戏完整版,细节满满!!

C语言游戏超详解扫雷游戏完整版,细节满满!!

Dart学习-- Dart之消息循环机制[翻译]