Contents

A tour of the Dart language

在假定您已经知道如何使用另一种语言进行编程的情况下,此页面将向您展示如何使用Dart的每个主要功能,从变量和运算符到类和库. 有关该语言的简要介绍,不够完整,请参见语言示例页面 .

要了解有关Dart核心库的更多信息,请参见库导览 . 每当您需要有关语言功能的更多详细信息时,请查阅Dart语言规范 .

A basic Dart program

以下代码使用了Dart的许多最基本的功能:

// Define a function.
printInteger(int aNumber) {
  print('The number is $aNumber.'); // Print to console.
}

// This is where the app starts executing.
main() {
  var number = 42; // Declare and initialize a variable.
  printInteger(number); // Call a function.
}

Here’s what this program uses that applies to all (or almost all) Dart apps:

// This is a comment.
单行注释. Dart还支持多行和文档注释. 有关详细信息,请参见注释 .
int
一种. 其他一些内置类型StringListbool .
42
数字文字. 数字文字是一种编译时常量.
print()
A handy way to display output.
'...' (or "...")
字符串文字.
$variableName (or ${expression})
字符串插值:在字符串文字中包含变量或表达式的等效字符串. 有关更多信息,请参见Strings .
main()
特殊的, 必需的 ,顶层功能,在此开始执行应用程序. 有关更多信息,请参见main()函数 .
var
在不指定变量类型的情况下声明变量的方法.

Important concepts

在学习Dart语言时,请牢记以下事实和概念:

  • 您可以在变量中放置的所有内容都是一个对象 ,每个对象都是一个的实例. 偶数,函数和null都是对象. 所有对象都从Object类继承.

  • Although Dart is strongly typed, type annotations are optional because Dart can infer types. In the code above, number is inferred to be of type int. When you want to explicitly say that no type is expected, use the special type dynamic.

  • Dart支持通用类型,例如List<int> (整数列表)或List<dynamic> (任何类型的对象列表).

  • Dart支持顶级功能(例如main() )以及与类或对象绑定的功能(分别为静态 方法实例方法 ). 您也可以在函数内创建函数( 嵌套 函数局部函数 ).

  • 同样,Dart支持顶级变量 ,以及与类或对象绑定的变量(静态变量和实例变量). 实例变量有时称为字段或属性.

  • 与Java不同,Dart没有关键字publicprotectedprivate . 如果标识符以下划线(_)开头,则该标识符专用于其库. 有关详细信息,请参见库和可见性 .

  • 标识符可以以字母或下划线(_)开头,然后是这些字符加数字的任意组合.

  • Dart同时具有表达式 (具有运行时值)和语句 (没有). 例如, 条件表达式 condition ? expr1 : expr2 condition ? expr1 : expr2的值为expr1expr2 . 将其与没有价值的if-else语句进行比较. 一条语句通常包含一个或多个表达式,但是一个表达式不能直接包含一条语句.

  • Dart工具可以报告两种问题: 警告错误 . 警告只是表明您的代码可能无法运行,但它们并不阻止您的程序执行. 错误可以是编译时或运行时. 编译时错误完全阻止了代码执行. 运行时错误导致代码执行时引发异常 .

Keywords

下表列出了Dart语言特别对待的单词.

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

避免将这些单词用作标识符. 但是,如有必要,标有上标的关键字可以是标识符:

  • 带有上标1的单词是上下文关键字 ,仅在特定位置具有含义. 它们在任何地方都是有效的标识符.

  • 带有上标2的单词是内置标识符 . 为了简化将JavaScript代码移植到Dart的任务,这些关键字在大多数地方都是有效的标识符,但不能用作类或类型名,也不能用作导入前缀.

  • 带有上标3的单词是较新的,有限的保留单词,与Dart 1.0版本之后添加的异步支持有关. 在任何标有asyncasync*sync*函数体中,不能使用awaityield作为标识符.

表格中的所有其他字都是保留字 ,不能是标识符.

Variables

这是创建变量并对其进行初始化的示例:

var name = 'Bob';

变量存储引用. 名为name的变量包含对值为" Bob"的String对象的引用.

name变量的类型推断为String ,但是您可以通过指定它来更改类型. 如果对象不限于单一类型,请遵循设计准则指定Objectdynamic类型.

dynamic name = 'Bob';

另一个选择是显式声明将要推断的类型:

String name = 'Bob';

Default value

未初始化的变量的初始值为null . 甚至具有数字类型的变量最初都为null,因为数字(与Dart中的所有其他内容一样)都是对象.

int lineCount;
assert(lineCount == null);

Final and const

如果您从未打算更改变量,请使用finalconst ,而不是var或除了类型之外. 最终变量只能设置一次; const变量是编译时常量. (const变量隐式为final.)最终的顶层或类变量在首次使用时被初始化.

这是创建和设置最终变量的示例:

final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';

您不能更改最终变量的值:

name = 'Alice'; // Error: a final variable can only be set once.

对要用作编译时常量的变量使用const . 如果const变量位于类级别,则将其标记为static const . 在声明变量的地方,将值设置为编译时常量,例如数字或字符串文字,const变量或对常量进行算术运算的结果:

const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere

const关键字不仅用于声明常量变量. 您还可以使用它来创建常量值 ,以及声明创建常量值的构造函数. 任何变量都可以具有恒定值.

var foo = const [];
final bar = const [];
const baz = []; // Equivalent to `const []`

You can omit const from the initializing expression of a const declaration, like for baz above. For details, see DON’T use const redundantly.

您可以更改非最终非常量变量的值,即使该变量曾经具有const值也是如此:

foo = [1, 2, 3]; // Was const []

您不能更改const变量的值:

baz = [42]; // Error: Constant variables can't be assigned a value.

从Dart 2.5开始,您可以定义使用类型检查和类型转换isas ), 集合if散布运算符......? )的常量:

// Valid compile-time constants as of Dart 2.5.
const Object i = 3; // Where i is a const Object with an int value...
const list = [i as int]; // Use a typecast.
const map = {if (i is int) i: "int"}; // Use is and collection if.
const set = {if (list is List<int>) ...list}; // ...and a spread.

有关使用const创建常量值的更多信息,请参见ListsMapsClasses .

Built-in types

Dart语言对以下类型提供特殊支持:

  • numbers
  • strings
  • booleans
  • 列表(也称为数组
  • sets
  • maps
  • 符文(用于在字符串中表示Unicode字符)
  • symbols

您可以使用文字初始化任何这些特殊类型的对象. 例如, 'this is a string'是字符串文字,而true是布尔文字.

因为在每达特变量是指一的物体的实例-你通常可以使用构造初始化变量. 一些内置类型具有自己的构造函数. 例如,您可以使用Map()构造函数来创建地图.

Numbers

飞镖数字有两种形式:

int

整数值不大于64位,具体取决于平台. 上达特VM,值可以是从-2 63 2 63 - 1达特该真实编译为JavaScript使用JavaScript的号码,允许从-2 53到2 53的值- 1.

double

IEEE 754标准指定的64位(双精度)浮点数.

intdouble都是num子类型. num类型包括基本的运算符,例如+,-,/和*,并且在其他方​​法中也可以找到abs()ceil()floor() . (在int类中定义了按位运算符,例如>>.)如果num及其子类型没有您要查找的内容,则dart:math库可能会.

整数是不带小数点的数字. 以下是一些定义整数文字的示例:

var x = 1;
var hex = 0xDEADBEEF;

如果数字包含小数,则为双精度. 以下是一些定义双字面量的示例:

var y = 1.1;
var exponents = 1.42e5;

从Dart 2.1开始,整数文字会在必要时自动转换为双精度:

double z = 1; // Equivalent to double z = 1.0.

这是将字符串转换为数字的方法,反之亦然:

// 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(|)运算符. 例如:

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

文字数是编译时常量. 许多算术表达式也都是编译时常量,只要它们的操作数是计算为数字的编译时常量即可.

const msPerSecond = 1000;
const secondsUntilRetry = 5;
const msUntilRetry = secondsUntilRetry * msPerSecond;

Strings

Dart字符串是UTF-16代码单元的序列. 您可以使用单引号或双引号创建一个字符串:

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 use the other delimiter.";

您可以使用${ expression }将表达式的值放在字符串中. 如果表达式是标识符,则可以跳过{}. 为了获取与对象相对应的字符串,Dart调用对象的toString()方法.

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!');

You can concatenate strings using adjacent string literals or the + operator:

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

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

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

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

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

您可以通过在字符串前添加r来创建"原始"字符串:

var s = r'In a raw string, not even \n gets special treatment.';

有关如何在字符串中表达Unicode字符的详细信息,请参见符文和字形群集 .

文字字符串是编译时常量,只要任何插值表达式都是计算时为null或数字,字符串或布尔值的编译时常量.

// These work in a const string.
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';

// These do NOT work in a const string.
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = [1, 2, 3];

const validConstString = '$aConstNum $aConstBool $aConstString';
// const invalidConstString = '$aNum $aBool $aString $aConstList';

有关使用字符串的更多信息,请参见字符串和正则表达式 .

Booleans

为了表示布尔值,Dart具有一个名为bool的类型. 只有两个对象具有布尔类型:布尔文字truefalse ,它们都是编译时常量.

Dart的类型安全性意味着您不能使用if ( nonbooleanValue )assert ( nonbooleanValue ) . 而是显式检查值,如下所示:

// Check for an empty string.
var fullName = '';
assert(fullName.isEmpty);

// Check for zero.
var hitPoints = 0;
assert(hitPoints <= 0);

// Check for null.
var unicorn;
assert(unicorn == null);

// Check for NaN.
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);

Lists

几乎每种编程语言中最常见的集合也许是array或对象的有序组. 在Dart中,数组是List对象,因此大多数人都将它们称为list .

Dart列表文字看起来像JavaScript数组文字. 这是一个简单的Dart列表:

var list = [1, 2, 3];

列表使用基于零的索引,其中0是第一个元素的索引,而list.length - 1是最后一个元素的索引. 您可以获取列表的长度并引用列表元素,就像在JavaScript中一样:

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

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

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

var constantList = const [1, 2, 3];
// constantList[1] = 1; // Uncommenting this causes an error.

Dart 2.3引入了散布运算符... )和空感知的散布运算符...? ),它们提供了一种将多个元素插入到集合中的简洁方法.

例如,您可以使用传播运算符( ... )将列表的所有元素插入另一个列表:

var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);

如果扩展运算符右边的表达式可能为null,则可以通过使用可识别null的扩展运算符( ...? )来避免出现异常:

var list;
var list2 = [0, ...?list];
assert(list2.length == 1);

有关使用传播运算符的更多详细信息和示例,请参阅传播运算符建议.

Dart 2.3还引入了collection ifcollection for ,您可以使用它们使用条件( if )和重复( for )来构建集合.

这是使用collection创建包含三个或四个项目的列表的示例:

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

这是一个在将项目添加到另一个列表之前使用collection操纵列表项目的示例:

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

有关是否使用收集的更多详细信息和示例,请参阅控制流收集建议.

列表类型有许多方便的方法来处理列表. 有关列表的更多信息,请参见泛型集合 .

Sets

Dart中的集合是无序的唯一项集合. 集文字和类型为集提供Dart支持.

这是一个使用集合文字创建的简单Dart集:

var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};

要创建一个空集,请在类型参数之前使用{} ,或将{}分配给Set类型的变量:

var names = <String>{};
// Set<String> names = {}; // This works, too.
// var names = {}; // Creates a map, not a set.

使用add()addAll()方法将项目添加到现有集中:

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);

使用.length获取集合中的项目数:

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
assert(elements.length == 5);

要创建一个为编译时常量的集合,请在集合文字之前添加const

final constantSet = const {
  'fluorine',
  'chlorine',
  'bromine',
  'iodine',
  'astatine',
};
// constantSet.add('helium'); // Uncommenting this causes an error.

从Dart 2.3开始,集合像列表一样支持散布运算符( ......? )和集合ifs和fors. 有关更多信息,请参见列表传播运算符列表集合运算符讨论.

有关集合的更多信息,请参见泛型集合 .

Maps

通常,映射是关联键和值的对象. 键和值都可以是任何类型的对象. 每个仅出现一次,但是您可以多次使用相同的 . 地图文字和地图类型提供了对地图的Dart支持.

以下是使用地图文字创建的几个简单的Dart地图:

var gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
};

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

您可以使用Map构造函数创建相同的对象:

var gifts = Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

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

就像在JavaScript中一样,将新的键值对添加到现有地图中:

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

从地图中检索值的方式与在JavaScript中相同:

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

如果您查找的键不在地图中,则返回null:

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

使用.length来获取映射中的键值对的数量:

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

要创建一个编译时常量的映射,请在映射文字之前添加const

final constantMap = const {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

// constantMap[2] = 'Helium'; // Uncommenting this causes an error.

从Dart 2.3开始,地图支持扩展运算符( ......? )和集合if和for,就像列表一样. 有关详细信息和示例,请参阅传播操作员建议控制流收集建议.

有关地图的更多信息,请参见泛型地图 .

Runes and grapheme clusters

在Dart中, 符文公开字符串的Unicode代码点. 从Dart 2.6开始,使用字符包查看或操作用户感知的字符,也称为Unicode(扩展)字素簇.

Unicode为世界上所有书写系统中使用的每个字母,数字和符号定义了唯一的数值. 由于Dart字符串是UTF-16代码单元的序列,因此在字符串中表示Unicode代码点需要特殊的语法. 表示Unicode代码点的常用方法是\uXXXX ,其中XXXX是4位十六进制值. 例如,心脏字符(♥)是\u2665 . 要指定多于或少于4个十六进制数字,请将值放在大括号中. 例如,笑的表情符号()是\u{1f606} .

如果您需要读取或编写单个Unicode字符,请使用由字符包在String上定义的characters getter. 返回的Characters对象是作为Characters簇序列的字符串. 这是使用字符API的示例:

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

输出取决于您的环境,如下所示:

$ dart bin/main.dart
Hi 🇩🇰
The end of the string: ???
The last character: 🇩🇰

有关使用character包操作字符串的详细信息,请参见character包的示例API参考 .

Symbols

Symbol对象代表在Dart程序中声明的运算符或标识符. 您可能永远不需要使用符号,但是它们对于按名称引用标识符的API来说是无价的,因为最小化会更改标识符名称,但不会更改标识符符号.

要获取标识符的符号,请使用符号文字,该符号文字是#后跟标识符:

#radix
#bar

符号文字是编译时常量.

Functions

Dart是一种真正的面向对象的语言,因此函数甚至都是对象,并且具有类型Function. 这意味着可以将函数分配给变量或作为参数传递给其他函数. 您也可以将Dart类的实例称为函数. 有关详细信息,请参见Callable类 .

这是实现函数的示例:

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

尽管Effective Dart建议为公共API使用类型注释 ,但是如果您省略类型,则该功能仍然有效:

isNoble(atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

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

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

=> expr语法是{ return expr ; } { return expr ; } . =>表示法有时称为箭头语法.

函数可以具有两种类型的参数: requiredoptional . 首先列出必需的参数,然后列出所有可选参数. 可选参数可以命名或为position .

Optional parameters

可选参数可以是命名参数,也可以是位置参数,但不能同时使用.

Named parameters

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

enableFlags(bold: true, hidden: false);

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

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold, bool hidden}) {...}

尽管命名参数是一种可选参数,但是您可以使用@required对其进行注释,以指示该参数是强制性的-用户必须为该参数提供一个值. 例如:

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

如果有人尝试创建Scrollbar而不指定child参数,则分析器将报告问题.

要使用@required批注,请取决于meta包并导入package:meta/meta.dart .

Positional parameters

[]包装一组功能参数会将其标记为可选的位置参数:

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

这是不带可选参数调用此函数的示例:

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

这是一个使用第三个参数调用此函数的示例:

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

Default parameter values

您的函数可以使用=来定义命名参数和位置参数的默认值. 默认值必须是编译时常量. 如果未提供默认值,则默认值为null .

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

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold = false, bool hidden = false}) {...}

// bold will be true; hidden will be false.
enableFlags(bold: true);

下一个示例显示如何为位置参数设置默认值:

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');

You can also pass lists or maps as default values. The following example defines a function, doStuff(), that specifies a default list for the list parameter and a default map for the gifts parameter.

void doStuff(
    {List<int> list = const [1, 2, 3],
    Map<String, String> gifts = const {
      'first': 'paper',
      'second': 'cotton',
      'third': 'leather'
    }}) {
  print('list:  $list');
  print('gifts: $gifts');
}

The main() function

每个应用程序都必须具有顶级main()函数,该函数充当该应用程序的入口点. main()函数返回void并具有可选的List<String>参数作为参数.

这是Web应用程序的main()函数的示例:

void main() {
  querySelector('#sample_text_id')
    ..text = 'Click me!'
    ..onClick.listen(reverseText);
}

这是一个带有参数的命令行应用程序的main()函数的示例:

// Run the app like this: 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');
}

You can use the args library to define and parse command-line arguments.

Functions as first-class objects

您可以将一个函数作为参数传递给另一个函数. 例如:

void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

// Pass printElement as a parameter.
list.forEach(printElement);

您还可以将函数分配给变量,例如:

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

本示例使用匿名函数. 下一节将详细介绍.

Anonymous functions

大多数函数都已命名,例如main()printElement() . 您还可以创建一个称为匿名函数无名函数 ,有时也可以创建一个lambda闭包 . 您可以将匿名函数分配给变量,以便例如可以从集合中添加或删除它.

匿名函数看起来类似于命名函数-零个或多个参数,括号之间用逗号和可选的类型注释分隔.

后面的代码块包含函数的主体:

([[Type] param1[, …]]) {
  codeBlock;
};

以下示例定义了一个匿名函数,该函数具有未类型化的参数item . 为列表中的每个项目调用的函数将打印一个字符串,其中包括指定索引处的值.

var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

单击运行以执行代码.

如果该函数仅包含一个语句,则可以使用箭头符号将其缩短. 将以下行粘贴到DartPad中,然后单击"运行"以验证其功能是否等效.

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

Lexical scope

Dart是一种词法范围的语言,这意味着变量的范围是静态确定的,只需通过代码的布局即可. 您可以"向外跟随花括号"以查看变量是否在范围内.

这是在每个作用域级别具有变量的嵌套函数的示例:

bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;

    void nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}

注意nestedFunction()如何使用每个级别的变量,一直到最高级别.

Lexical closures

闭包是可以在其词法范围内访问变量的函数对象,即使该函数在其原始范围之外使用也是如此.

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

/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(int addBy) {
  return (int i) => addBy + i;
}

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

  // Create a function that adds 4.
  var add4 = makeAdder(4);

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

Testing functions for equality

这是测试顶级函数,静态方法和实例方法是否相等的示例:

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

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

void main() {
  var x;

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

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

  // Comparing instance methods.
  var v = A(); // Instance #1 of A
  var w = A(); // Instance #2 of A
  var y = w;
  x = w.baz;

  // These closures refer to the same instance (#2),
  // so they're equal.
  assert(y.baz == x);

  // These closures refer to different instances,
  // so they're unequal.
  assert(v.baz != w.baz);
}

Return values

所有函数都返回一个值. 如果未指定返回值,则语句return null;否则, return null; 隐式附加到函数主体.

foo() {}

assert(foo() == null);

Operators

Dart定义了下表中显示的运算符. 您可以覆盖许多这些运算符,如Overridable operator中所述 .

Description Operator
一元后缀 expr++    expr--    ()    []    .    ?.
一元前缀 -expr    !expr    ~expr    ++expr    --expr      await expr   
multiplicative *    /    %  ~/
additive +    -
shift <<    >>    >>>
按位与 &
按位异或 ^
按位或 |
关系和类型测试 >=    >    <=    <    as    is    is!
equality ==    !=   
逻辑与 &&
逻辑或 ||
如果为空 ??
conditional expr1 ? expr2 : expr3
cascade ..
assignment =    *=    /=   +=   -=   &=   ^=   etc.

使用运算符时,将创建表达式. 以下是一些运算符表达式的示例:

a++
a + b
a = b
a == b
c ? a : b
a is T

运算符表中 ,每个运算符的优先级都比其后一行中的运算符高. 例如,乘法运算符%优先级高于(然后在执行之前)相等运算符== ,其优先级高于逻辑AND运算符&& . 该优先级意味着以下两行代码以相同的方式执行:

// Parentheses improve readability.
if ((n % i == 0) && (d % i == 0)) ...

// Harder to read, but equivalent.
if (n % i == 0 && d % i == 0) ...

Arithmetic operators

Dart支持常用的算术运算符,如下表所示.

Operator Meaning
+ Add
Subtract
-expr 一元减号,也称为负数(反转表达式的符号)
* Multiply
/ Divide
~/ 除法,返回整数结果
% 获取整数除法的余数(取模)

Example:

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 int
assert(5 % 2 == 1); // Remainder

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

Dart还支持前缀和后缀增量和减量运算符.

Operator Meaning
++var var = var + 1 (表达式值为var + 1
var++ var = var + 1 (表达式值为var
--var var = var – 1 (表达式值为var – 1
var-- var = var – 1 (expression value is var)

Example:

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

Equality and relational operators

下表列出了相等运算符和关系运算符的含义.

Operator Meaning
== 等于; 见下面的讨论
!= 不相等
> 比...更棒
< 少于
>= 大于或等于
<= 小于或等于

要测试两个对象x和y是否表示同一事物,请使用==运算符. (在极少数情况下,您需要知道两个对象是否是完全相同的对象,请改用same ()函数.) ==运算符的工作方式如下:

  1. 如果xy为null,则如果两个均为null,则返回true;如果只有一个为null,则返回false.

  2. 返回方法调用x .==( y ) . (是的, ==类的运算符是在其第一个操作数上调用的方法.您甚至可以覆盖许多运算符,包括== ,如您在Overridable运算符中所见.)

这是使用每个相等和关系运算符的示例:

assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(2 <= 3);

Type test operators

asisis! 操作员在运行时检查类型很方便.

Operator Meaning
as Typecast(也用于指定库前缀
is 如果对象具有指定的类型,则为True
is! 如果对象具有指定的类型,则为False

如果obj实现T指定的接口,则obj的结果obj is T为true. 例如, obj is Object始终为true.

当且仅当您确定对象属于该类型时,才使用as运算符将对象转换为特定类型. 例:

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

如果不确定对象的类型为T ,则在使用对象之前使用is T来检查类型.

if (emp is Person) {
  // Type check
  emp.firstName = 'Bob';
}

Assignment operators

如您所见,可以使用=运算符分配值. 要仅在分配给变量为null的情况下进行分配,请使用??=运算符.

// Assign value to a
a = value;
// Assign value to b if b is null; otherwise, b stays the same
b ??= value;

复合赋值运算符(例如+=将运算与赋值结合在一起.

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

复合赋值运算符的工作方式如下:

  复合分配 等价表达
对于操作员op a op= b a = a op b
Example: a += b a = a + b

The following example uses assignment and compound assignment operators:

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

Logical operators

您可以使用逻辑运算符反转或合并布尔表达式.

Operator Meaning
!expr 反转以下表达式(将false更改为true,反之亦然)
|| 逻辑或
&& 逻辑与

这是使用逻辑运算符的示例:

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

Bitwise and shift operators

您可以在Dart中操纵数字的各个位. 通常,您可以将这些按位和移位运算符与整数一起使用.

Operator Meaning
& AND
| OR
^ XOR
~expr Unary bitwise complement (0s become 1s; 1s become 0s)
<< 左移
>> 右移

这是使用按位和移位运算符的示例:

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

Conditional expressions

Dart有两个运算符,可让您简洁地评估可能需要if-else语句的表达式:

condition ? expr1 : expr2
如果条件为true,则求值expr1 (并返回其值); 否则为false. 否则,求值并返回expr2的值.
expr1 ?? expr2
如果expr1不为null,则返回其值; 否则 ,返回0. 否则,求值并返回expr2的值.

当您需要基于布尔表达式分配值时,请考虑使用?: .

var visibility = isPublic ? 'public' : 'private';

如果布尔表达式测试是否为null,请考虑使用?? .

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

前面的示例可以用至少两种其他方式编写,但不那么简洁:

// Slightly longer version uses ?: operator.
String playerName(String name) => name != null ? name : 'Guest';

// Very long version uses if-else statement.
String playerName(String name) {
  if (name != null) {
    return name;
  } else {
    return 'Guest';
  }
}

Cascade notation (..)

级联( .. )允许您对同一对象执行一系列操作. 除了函数调用,您还可以访问同一对象上的字段. 这通常可以节省创建临时变量的步骤,并允许您编写更多流畅的代码.

考虑以下代码:

querySelector('#confirm') // Get an object.
  ..text = 'Confirm' // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

第一个方法调用querySelector()返回选择器对象. 跟随级联符号的代码在此选择器对象上运行,而忽略了可能返回的任何后续值.

前面的示例等效于:

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

您也可以嵌套级联. 例如:

final addressBook = (AddressBookBuilder()
      ..name = 'jenny'
      ..email = 'jenny@example.com'
      ..phone = (PhoneNumberBuilder()
            ..number = '415-555-0100'
            ..label = 'home')
          .build())
    .build();

小心在返回实际对象的函数上构造级联. 例如,以下代码失败:

var sb = StringBuffer();
sb.write('foo')
  ..write('bar'); // Error: method 'write' isn't defined for 'void'.

sb.write()调用返回void,并且您不能在void上构造一个层叠.

Other operators

在其他示例中,您已经看到了大多数剩余的运算符:

Operator Name Meaning
() 功能应用 代表一个函数调用
[] 列表访问 引用列表中指定索引处的值
. 会员访问 引用表达式的属性; 示例: foo.bar从表达式foo选择属性bar
?. 有条件的成员访问 . ,但最左边的操作数可以为null; 示例: foo?.bar从表达式foo选择属性bar ,除非foo为null(在这种情况下foo?.bar值为null)

有关的更多信息.?...运算符,请参见 .

Control flow statements

您可以使用以下任意一种方式控制Dart代码的流程:

  • if and else
  • for loops
  • while and do-while loops
  • break and continue
  • switch and case
  • assert

您还可以使用try-catchthrow来影响控制流,如Exceptions中所述 .

If and else

Dart支持if语句和可选的else语句,如下例所示. 另请参阅条件表达式 .

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

与JavaScript不同,条件必须使用布尔值,而不能使用其他值. 有关更多信息,请参见布尔值.

For loops

您可以使用标准的for循环进行迭代. 例如:

var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
  message.write('!');
}

Dart for循环内的闭包捕获索引的 ,避免了JavaScript中常见的陷阱. 例如,考虑:

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

如预期的那样,输出为0 ,然后为1 . 相比之下,例如将打印2 ,然后2中的JavaScript.

如果要迭代的对象是Iterable,则可以使用forEach()方法. 如果您不需要了解当前的迭代计数器,则使用forEach()是一个不错的选择:

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

List和Set之类的可迭代类也支持for-in形式的迭代

var collection = [0, 1, 2];
for (var x in collection) {
  print(x); // 0 1 2
}

While and do-while

while循环评估循环之前的条件:

while (!isDone()) {
  doSomething();
}

do - while循环评估循环的条件:

do {
  printLine();
} while (!atEndOfPage());

Break and continue

Use break to stop looping:

while (true) {
  if (shutDownRequested()) break;
  processIncomingRequests();
}

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

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

如果您使用的是Iterable(例如列表或集合),则可以用不同的方式编写该示例:

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

Switch and case

Dart中的switch语句使用==比较整数,字符串或编译时常量. 被比较的对象必须全部是同一类的实例(而不是其任何子类型的实例),并且该类不得覆盖== . 枚举类型switch语句中工作良好.

通常,每个非空case子句都以break语句结尾. 结束非空case子句的其他有效方法是continuethrowreturn语句.

当没有case子句匹配时,使用default子句执行代码:

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语句,从而生成错误:

var command = 'OPEN';
switch (command) {
  case 'OPEN':
    executeOpen();
    // ERROR: Missing break

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

但是,Dart确实支持空case子句,允许使用以下形式:

var command = 'CLOSED';
switch (command) {
  case 'CLOSED': // Empty case falls through.
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

如果您真的想要失败,可以使用continue语句和标签:

var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed;
  // Continues executing at the nowClosed label.

  nowClosed:
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

case子句可以具有局部变量,这些局部变量仅在该子句的范围内可见.

Assert

在开发过程中,使用断言语句— assert( condition , optionalMessage ) ; —如果布尔条件为假,则中断正常执行. 在整个浏览过程中,您可以找到断言语句的示例. 这里还有一些:

// Make sure the variable has a non-null value.
assert(text != null);

// Make sure the value is less than 100.
assert(number < 100);

// Make sure this is an https URL.
assert(urlString.startsWith('https'));

要将消息附加到断言,请将字符串添加为assert的第二个参数.

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

assert的第一个参数可以是任何解析为布尔值的表达式. 如果表达式的值为true,则断言成功并继续执行. 如果为假,则断言失败,并引发异常( AssertionError ).

断言何时确切起作用? 这取决于您使用的工具和框架:

在生产代码中,断言将被忽略,并且不会评估assert的参数.

Exceptions

您的Dart代码可以引发并捕获异常. 异常是错误,指示发生了意外情况. 如果该异常没有被捕获,该分离物引起异常被暂停,并且通常该分离物和其程序被终止.

与Java相反,Dart的所有异常都是未经检查的异常. 方法不会声明它们可能引发哪些异常,并且不需要捕获任何异常.

Dart提供了ExceptionError类型,以及许多预定义的子类型. 当然,您可以定义自己的异常. 但是,Dart程序可以将任何非null对象(不仅仅是Exception和Error对象)作为异常抛出.

Throw

这是引发或引发异常的示例:

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

您还可以抛出任意对象:

throw 'Out of llamas!';

因为抛出异常是一个表达式,所以可以在=>语句以及允许表达式的其他任何地方抛出异常:

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

Catch

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

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}

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

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');
}

如前面的代码所示,您可以同时使用oncatch . on需要指定异常类型时使用. 当异常处理程序需要异常对象时,请使用catch .

您可以为catch()指定一个或两个参数. 第一个是引发的异常,第二个是堆栈跟踪( StackTrace对象).

try {
  // ···
} on Exception catch (e) {
  print('Exception details:\n $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

要部分处理异常,同时允许其传播,请使用rethrow关键字.

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // Runtime error
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // Allow callers to see the exception.
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}

Finally

为确保某些代码无论是否引发异常都可以运行,请使用finally子句. 如果没有catch子句与该异常匹配,则在finally子句运行后传播该异常:

try {
  breedMoreLlamas();
} finally {
  // Always clean up, even if an exception is thrown.
  cleanLlamaStalls();
}

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

try {
  breedMoreLlamas();
} catch (e) {
  print('Error: $e'); // Handle the exception first.
} finally {
  cleanLlamaStalls(); // Then clean up.
}

通过阅读图书馆之旅的" 例外"部分了解更多信息.

Classes

Dart是一种具有类和基于Mixin的继承的面向对象语言. 每个对象都是一个类的实例,并且所有类都来自Object. 基于Mixin的继承意味着尽管每个类(对象除外)都只有一个超类,但是一个类主体可以在多个类层次结构中重用. 扩展方法是一种在不更改类或创建子类的情况下向类添加功能的方法.

Using class members

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

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

var p = Point(2, 2);

// Set the value of the instance variable y.
p.y = 3;

// Get the value of y.
assert(p.y == 3);

// Invoke distanceTo() on p.
double distance = p.distanceTo(Point(4, 4));

使用?. 代替. 为了避免在最左边的操作数为null时发生异常:

// If p is non-null, set its y value to 4.
p?.y = 4;

Using constructors

您可以使用构造函数创建对象. 构造函数名称可以是ClassNameClassName . identifier ClassName . identifier . 例如,以下代码使用Point()Point.fromJson()构造函数创建Point对象:

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

以下代码具有相同的效果,但是在构造函数名称之前使用可选的new关键字:

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

一些类提供常量构造函数 . 要使用常量构造函数创建编译时常量,请将const关键字放在构造函数名称之前:

var p = const ImmutablePoint(2, 2);

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

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

assert(identical(a, b)); // They are the same instance!

常量上下文中 ,可以在构造函数或文字之前省略const . 例如,看下面的代码,它创建一个const映射:

// Lots of const keywords here.
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

您可以省略除首次使用const关键字外的所有内容:

// Only one const, which establishes the constant context.
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

If a constant constructor is outside of a constant context and is invoked without const, it creates a 非恒定对象:

var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant

assert(!identical(a, b)); // NOT the same instance!

Getting an object’s type

要在运行时获取对象的类型,可以使用Object的runtimeType属性,该属性返回Type对象.

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

到目前为止,您已经了解了如何使用类. 本节的其余部分显示如何实现类.

Instance variables

声明实例变量的方法如下:

class Point {
  double x; // Declare instance variable x, initially null.
  double y; // Declare y, initially null.
  double z = 0; // Declare z, initially 0.
}

所有未初始化的实例变量的值都为null .

所有实例变量都会生成一个隐式的getter方法. 非最终实例变量也会生成隐式的setter方法. 有关详细信息,请参见Getter和setter .

class Point {
  double x;
  double y;
}

void main() {
  var point = Point();
  point.x = 4; // Use the setter method for x.
  assert(point.x == 4); // Use the getter method for x.
  assert(point.y == null); // Values default to null.
}

如果在声明了实例变量的位置(而不是在构造函数或方法中)初始化实例变量,则在创建实例时(即在构造函数及其初始化程序列表执行之前)设置值.

Constructors

通过创建一个与其类具有相同名称的函数来声明一个构造函数(另加一个可选的标识符,如Named构造函数中所述 ). 生成器的最常见形式是生成器,它创建类的新实例:

class Point {
  double x, y;

  Point(double x, double y) {
    // There's a better way to do this, stay tuned.
    this.x = x;
    this.y = y;
  }
}

this关键字引用当前实例.

将构造函数自变量分配给实例变量的模式非常普遍,Dart拥有语法糖使其变得容易:

class Point {
  double x, y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}

Default constructors

如果不声明构造函数,则会为您提供默认的构造函数. 默认构造函数没有参数,并在超类中调用无参数构造函数.

Constructors aren’t inherited

子类不从其超类继承构造函数. 声明没有构造函数的子类仅具有默认(没有参数,没有名称)构造函数.

Named constructors

使用命名构造函数可为一个类实现多个构造函数或提供额外的清晰度:

class Point {
  double x, y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin() {
    x = 0;
    y = 0;
  }
}

请记住,构造函数不是继承的,这意味着子类不会继承超类的命名构造函数. 如果要使用超类中定义的命名构造函数创建子类,则必须在子类中实现该构造函数.

Invoking a non-default superclass constructor

默认情况下,子类中的构造函数调用超类的未命名,无参数的构造函数. 在构造函数主体的开头调用超类的构造函数. 如果还使用了初始化程序列表 ,它将在调用超类之前执行. 总而言之,执行顺序如下:

  1. 初始化列表
  2. 超类的无参数构造函数
  3. 主类的无参数构造函数

如果超类没有未命名的无参数构造函数,则必须手动调用超类中的构造函数之一. 一个冒号后指定超类构造函数( :只是在构造函数体(如果有的话)之前,).

在下面的示例中,Employee类的构造函数为其超类Person调用命名的构造函数. 单击运行以执行代码.

因为超类构造函数的参数是在调用构造函数之前求值的,所以参数可以是表达式,例如函数调用:

class Employee extends Person {
  Employee() : super.fromJson(defaultData);
  // ···
}

Initializer list

除了调用超类构造函数外,您还可以在构造函数主体运行之前初始化实例变量. 用逗号分隔初始化程序.

// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, double> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

在开发过程中,您可以通过使用初始化列表中的assert来验证输入.

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}

设置最终字段时,初始化列表很方便. 下面的示例在初始化列表中初始化三个最终字段. 单击运行以执行代码.

Redirecting constructors

有时,构造函数的唯一目的是重定向到同一类中的另一个构造函数. 重定向构造函数的主体为空,构造函数调用出现在冒号(:)之后.

class Point {
  double x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(double x) : this(x, 0);
}

Constant constructors

如果您的类产生的对象永不改变,则可以使这些对象具有编译时常数. 为此,定义一个const构造函数,并确保所有实例变量都是final .

class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final double x, y;

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

常量构造函数并不总是创建常量. 有关详细信息,请参见有关使用构造函数的部分.

Factory constructors

在实现并非总是创建其类的新实例的构造函数时,请使用factory关键字. 例如,工厂构造函数可能会从缓存返回一个实例,或者可能会返回一个子类型的实例.

以下示例演示了工厂构造函数从缓存中返回对象:

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) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

就像调用其他构造函数一样,调用工厂构造函数:

var logger = Logger('UI');
logger.log('Button clicked');

Methods

方法是提供对象行为的函数.

Instance methods

对象上的实例方法可以访问实例变量和this . 以下示例中的distanceTo()方法是一个实例方法的示例:

import 'dart:math';

class Point {
  double x, y;

  Point(this.x, this.y);

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

Getters and setters

获取器和设置器是特殊的方法,提供对对象属性的读写访问. 回想一下,每个实例变量都有一个隐式的getter,如果合适的话还有一个setter. 您可以通过使用getset关键字实现getter和setter来创建其他属性:

class Rectangle {
  double left, top, width, height;

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

  // Define two calculated properties: right and bottom.
  double get right => left + width;
  set right(double value) => left = value - width;
  double get bottom => top + height;
  set bottom(double value) => top = value - height;
}

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

使用getter和setter方法,您可以从实例变量开始,然后使用方法包装它们,而无需更改客户端代码.

Abstract methods

实例,getter和setter方法可以是抽象的,定义一个接口,但将其实现留给其他类使用. 抽象方法只能存在于抽象类中 .

要使方法抽象,请使用分号(;)代替方法主体:

abstract class Doer {
  // Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

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

Abstract classes

使用abstract修饰符定义一个抽象类 -一个无法实例化的类. 抽象类通常用于某些实现中,用于定义接口. 如果您希望抽象类看起来可实例化,请定义一个工厂构造函数 .

抽象类通常具有抽象方法 . 这是一个声明具有抽象方法的抽象类的示例:

// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
  // Define constructors, fields, methods...

  void updateChildren(); // Abstract method.
}

Implicit interfaces

每个类都隐式定义一个接口,其中包含该类及其实现的所有接口的所有实例成员. 如果要创建支持类B的API的类A而又不继承类B的实现,则类A应该实现B接口.

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

// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

  // In the interface.
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// An implementation of the Person interface.
class Impostor implements Person {
  get _name => '';

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

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

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}

Here’s an example of specifying that a class implements multiple interfaces:

class Point implements Comparable, Location {...}

Extending a class

使用extends来创建子类,使用super来引用超类:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

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

Overriding members

子类可以覆盖实例方法,getter和setter. 您可以使用@override批注指示您有意覆盖成员:

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}

要缩小安全类型的代码中方法参数或实例变量的类型 ,可以使用covariant关键字 .

Overridable operators

您可以覆盖下表中显示的运算符. 例如,如果定义一个Vector类,则可以定义一个+方法来添加两个向量.

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

这是一个覆盖+-运算符的类的示例:

class Vector {
  final int x, y;

  Vector(this.x, this.y);

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

  // Operator == and hashCode not shown. For details, see note below.
  // ···
}

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

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

如果覆盖== ,则还应该覆盖Object的hashCode getter. 有关重写==hashCode的示例,请参见实现映射键 .

有关覆盖的更多信息,通常,请参见扩展类 .

noSuchMethod()

要在代码尝试使用不存在的方法或实例变量时进行检测或作出反应,可以重写noSuchMethod()

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

除非满足以下条件之一 ,否则您不能调用未实现的方法:

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

  • 接收器具有定义未实现方法的静态类型(抽象是可以的),而接收器的动态类型具有noSuchMethod()的实现,该实现与Object类中的实现不同.

有关更多信息,请参见非正式的noSuchMethod转发规范.

Extension methods

Dart 2.7中引入的扩展方法是一种向现有库添加功能的方法. 您可能甚至不知道使用扩展方法. 例如,当您在IDE中使用代码完成功能时,它建议将扩展方法与常规方法一起使用.

这是在string_apis.dart定义的名为parseInt() String上使用扩展方法的示例:

import 'string_apis.dart';
...
print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.

有关使用和实现扩展方法的详细信息,请参见扩展方法页面 .

Enumerated types

枚举类型(通常称为枚举枚举 )是一种特殊的类,用于表示固定数量的常量值.

Using enums

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

enum Color { red, green, blue }

枚举中的每个值都有一个index ,它返回该值在枚举声明中从零开始的位置. 例如,第一个值的索引为0,第二个值的索引为1.

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

要获取枚举中所有值的列表,请使用枚举的values常量.

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

您可以在switch语句中使用枚举,如果不处理所有枚举的值,则会收到警告:

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // Without this, you see a WARNING.
    print(aColor); // 'Color.blue'
}

枚举类型具有以下限制:

  • 您不能继承,混入或实现枚举.
  • 您不能显式实例化一个枚举.

For more information, see the Dart language specification.

Adding features to a class: mixins

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

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

class Musician extends Performer with Musical {
  // ···
}

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

实现 mixin,请创建一个扩展Object且不声明构造函数的类. 除非您希望mixin可用作常规类,否则请使用mixin关键字而不是class . 例如:

mixin 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');
    }
  }
}

要指定仅某些类型可以使用mixin(例如,因此您的mixin可以调用它未定义的方法),请使用on来指定所需的超类:

mixin MusicalPerformer on Musician {
  // ···
}

Class variables and methods

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

Static variables

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

class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}

静态变量在使用之前不会初始化.

Static methods

静态方法(类方法)无法在实例上操作,因此无法访问this . 例如:

import 'dart:math';

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

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

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

您可以使用静态方法作为编译时常量. 例如,您可以将静态方法作为参数传递给常量构造函数.

Generics

如果查看基本数组类型List的API文档则会看到该类型实际上是List<E> . <…>标记将List标记为通用 (或参数化 )类型-具有正式类型参数的类型. 按照惯例 ,大多数类型变量都具有单字母名称,例如E,T,S,K和V.

Why use generics?

泛型通常是类型安全所必需的,但是它们具有的好处不仅仅在于允许您的代码运行:

  • 正确指定泛型类型可产生更好的代码.
  • 您可以使用泛型来减少代码重复.

如果打算仅包含字符串的列表,则可以将其声明为List<String> (将其称为"字符串列表"). 这样,您,其他程序员和工具可以检测到将非字符串分配给列表可能是一个错误. 这是一个例子:

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error

使用泛型的另一个原因是减少代码重复. 泛型使您可以在多种类型之间共享单个接口和实现,同时仍可以利用静态分析. 例如,假设您创建一个用于缓存对象的接口:

abstract class ObjectCache {
  Object getByKey(String key);
  void setByKey(String key, Object value);
}

您发现您想要此接口的特定于字符串的版本,因此您创建了另一个接口:

abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}

后来,您决定要该接口的数字特定版本…您就明白了.

Generic types can save you the trouble of creating all these interfaces. Instead, you can create a single interface that takes a type parameter:

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

在此代码中,T是替代类型. 您可以将其视为占位符,作为开发人员以后将定义的类型.

Using collection literals

列表,集合和映射文字可以被参数化. 参数化文字与您已经看到的文字一样,只不过您在开括号之前添加了< type > (用于列表和集合)或< keyType , valueType > (用于地图). 这是使用类型文字的示例:

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

Using parameterized types with constructors

要在使用构造函数时指定一种或多种类型,请将类型放在类名之后的尖括号( <...> )中. 例如:

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

以下代码创建一个具有整数键和View类型值的映射:

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

Generic collections and the types they contain

Dart泛型类型是经过整形的 ,这意味着它们在运行时会携带其类型信息. 例如,您可以测试集合的类型:

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

Restricting the parameterized type

在实现通用类型时,您可能希望限制其参数的类型. 您可以使用extends来执行此操作.

class Foo<T extends SomeBaseClass> {
  // Implementation goes here...
  String toString() => "Instance of 'Foo<$T>'";
}

class Extender extends SomeBaseClass {...}

可以将SomeBaseClass或其任何子类用作通用参数:

var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();

也可以不指定任何通用参数:

var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'

指定任何非SomeBaseClass类型都会导致错误:

var foo = Foo<Object>();

Using generic methods

最初,Dart的通用支持仅限于类. 一种称为通用方法的更新语法,允许在方法和函数上使用类型参数:

T first<T>(List<T> ts) {
  // Do some initial work or error checking, then...
  T tmp = ts[0];
  // Do some additional checking or processing...
  return tmp;
}

first<T> )上的泛型类型参数使您可以在多个地方使用类型参数T

  • 在函数的返回类型( T )中.
  • 在参数的类型中( List<T> ).
  • 以局部变量( T tmp )的类型.

有关泛型的更多信息,请参见使用泛型方法.

Libraries and visibility

importlibrary指令可以帮助您创建模块化且可共享的代码库. 库不仅提供API,而且是隐私的单位:以下划线(_)开头的标识符仅在库内部可见. 每个Dart应用程序都是一个库 ,即使它不使用library指令.

可以使用软件包分发库.

Using libraries

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

例如,Dart Web应用程序通常使用dart:html库,可以像这样导入:

import 'dart:html';

import的唯一必需参数是指定库的URI. 对于内置库,URI具有特殊的dart:方案. 对于其他库,可以使用文件系统路径或package:方案. package:方案指定由包管理器(例如pub工具)提供的库. 例如:

import 'package:test/test.dart';

Specifying a library prefix

如果导入两个标识符冲突的库,则可以为一个或两个库指定一个前缀. 例如,如果library1和library2都具有Element类,那么您可能具有以下代码:

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

// Uses Element from lib1.
Element element1 = Element();

// Uses Element from lib2.
lib2.Element element2 = lib2.Element();

Importing only part of a library

如果只想使用库的一部分,则可以有选择地导入库. 例如:

// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;

Lazily loading a library

延迟加载 (也称为延迟加载 )允许Web应用程序在需要库时以及在需要时加载库. 在某些情况下,您可能会使用延迟加载:

  • 减少Web应用程序的初始启动时间.
  • 执行A / B测试-例如,尝试算法的替代实现.
  • 加载很少使用的功能,例如可选的屏幕和对话框.

要延迟加载库,必须首先使用deferred as导入它.

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

当您需要该库时,请使用该库的标识符调用loadLibrary() .

Future greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}

在前面的代码中, await关键字将暂停执行,直到加载该库为止. 有关asyncawait更多信息,请参见异步支持 .

您可以在库上多次调用loadLibrary()而不会出现问题. 该库仅加载一次.

使用延迟加载时,请记住以下几点:

  • 延迟库的常量不是导入文件中的常量. 请记住,这些常量在加载延迟的库之前不存在.
  • 您不能在导入文件中使用延迟库中的类型. 而是考虑将接口类型移到由延迟库和导入文件导入的库中.
  • Dart使用deferred as namespace隐式将loadLibrary()插入到您定义的deferred as namespace . loadLibrary()函数返回Future .

Implementing libraries

请参阅创建库包以获取有关如何实现库包的建议,包括:

  • 如何组织库源代码.
  • 如何使用export指令.
  • 何时使用part指令.
  • 何时使用library指令.
  • 如何使用条件导入和导出来实现支持多个平台的库.

Asynchrony support

Dart库充满了返回FutureStream对象的函数. 这些函数是异步的 :它们在设置可能耗时的操作(例如I / O)之后返回,而无需等待该操作完成.

asyncawait关键字支持异步编程,使您可以编写看起来类似于同步代码的异步代码.

Handling Futures

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

使用asyncawait代码是异步的,但是看起来很像同步代码. 例如,以下代码使用await等待异步函数的结果:

await lookUpVersion();

要使用await ,代码必须位于async函数中-标记为async的函数:

Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}

使用trycatchfinally来处理使用await代码中的错误和清除:

try {
  version = await lookUpVersion();
} catch (e) {
  // React to inability to look up the version
}

您可以在async函数中多次使用await . 例如,下面的代码等待三遍函数结果:

var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);

await expression ,的值expression通常是未来; 如果不是,则该值将自动包装在Future中. 此Future对象指示返回一个对象的承诺. await expression的值是返回的对象. 等待表达式使执行暂停,直到该对象可用为止.

如果在使用await时收到编译时错误,请确保awaitasync函数中. 例如,要在应用程序的main()函数中使用await ,必须将main()main()标记为async

Future main() async {
  checkVersion();
  print('In main: version is ${await lookUpVersion()}');
}

Declaring async functions

async函数是一个函数,其主体带有async修饰符.

async关键字添加到函数将使其返回Future. 例如,考虑以下同步函数,该函数返回String:

String lookUpVersion() => '1.0.0';

如果将其更改为async函数(例如,由于将来的实现会很耗时),则返回的值为Future:

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

请注意,该函数的主体不需要使用Future API. Dart会在必要时创建Future对象. 如果您的函数没有返回有用的值,请使其返回类型为Future<void> .

有关使用futures, asyncawait的交互式介绍,请参见异步编程codelab .

Handling Streams

当需要从流中获取值时,有两种选择:

  • 使用async异步for循环await for ).
  • 如资料库导览所述使用Stream API.

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

await for (varOrType identifier in expression) {
  // Executes each time the stream emits a value.
}

expression的值必须具有Stream类型. 执行过程如下:

  1. 等到流发出一个值.
  2. 执行for循环的主体,并将变量设置为该发射值.
  3. 重复1和2,直到关闭流.

要停止收听流,可以使用breakreturn语句,该语句会脱离for循环并取消订阅流.

如果在实现异步for循环时遇到编译时错误,请确保await for位于async函数中. 例如,要在应用程序的main()函数中使用异步for循环,必须将main()main()标记为async

Future main() async {
  // ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  // ...
}

通常,有关异步编程的更多信息,请参见库介绍的dart:async部分.

Generators

当您需要懒散地产生值序列时,请考虑使用生成器函数 . Dart具有对两种生成器功能的内置支持:

  • 同步生成器:返回一个Iterable对象.
  • 异步生成器:返回一个Stream对象.

要实现同步生成器函数,请将函数主体标记为sync* ,并使用yield语句传递值:

Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

要实现异步生成器函数,请将函数主体标记为async* ,并使用yield语句传递值:

Stream<int> asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

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

Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

Callable classes

要允许像函数一样调用Dart类的实例,请实现call()方法.

在下面的示例中, WannabeFunction类定义了一个call()函数,该函数接受三个字符串并将它们连接起来,每个字符串之间用空格分隔,并附加一个感叹号. 单击运行以执行代码.

Isolates

大多数计算机,甚至在移动平台上,都具有多核CPU. 为了利用所有这些核心,开发人员传统上使用并发运行的共享内存线程. 但是,共享状态并发易于出错,并且可能导致复杂的代码.

所有Dart代码都在isolates内部运行,而不是线程. 每个隔离区都有其自己的内存堆,确保任何其他隔离区都无法访问隔离区的状态.

有关更多信息,请参见以下内容:

Typedefs

在Dart中,函数是对象,就像字符串和数字是对象一样. typedef函数类型别名为函数类型提供一个名称,您可以在声明字段和返回类型时使用该名称. 当将函数类型分配给变量时,typedef保留类型信息.

考虑以下代码,该代码不使用typedef:

class SortedCollection {
  Function compare;

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

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

void main() {
  SortedCollection coll = SortedCollection(sort);

  // All we know is that compare is a function,
  // but what type of function?
  assert(coll.compare is Function);
}

分配f进行compare时,类型信息丢失. f的类型是(Object, Object)int (其中→表示返回),而compare的类型是Function. 如果我们将代码更改为使用显式名称并保留类型信息,则开发人员和工具都可以使用该信息.

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

class SortedCollection {
  Compare compare;

  SortedCollection(this.compare);
}

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

void main() {
  SortedCollection coll = SortedCollection(sort);
  assert(coll.compare is Function);
  assert(coll.compare is Compare);
}

因为typedef只是别名,所以它们提供了一种检查任何函数类型的方法. 例如:

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

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

void main() {
  assert(sort is Compare<int>); // True!
}

Metadata

使用元数据提供有关您的代码的其他信息. 元数据批注以字符@开头,后跟对编译时常量的引用(例如deprecated )或对常量构造函数的调用.

所有Dart代码都可以使用两个注释: @deprecated@override . 有关使用@override示例,请参见扩展类 . 这是使用@deprecated批注的示例:

class Television {
  /// _Deprecated: Use [turnOn] instead._
  @deprecated
  void activate() {
    turnOn();
  }

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

您可以定义自己的元数据注释. 这是定义带有两个参数的@todo注释的示例:

library todo;

class Todo {
  final String who;
  final String what;

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

这是使用@todo批注的示例:

import 'todo.dart';

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

元数据可以出现在库,类,typedef,类型参数,构造函数,工厂,函数,字段,参数或变量声明之前,也可以出现在导入或导出指令之前. 您可以在运行时使用反射来检索元数据.

Comments

Dart支持单行注释,多行注释和文档注释.

Single-line comments

A single-line comment begins with //. Everything between // and the end of line is ignored by the Dart compiler.

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

Multi-line comments

多行注释以/*开头,以*/结束. Dart编译器将忽略/**/之间的所有内容(除非注释为文档注释;请参阅下一节). 多行注释可以嵌套.

void main() {
  /*
   * This is a lot of work. Consider raising chickens.

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

Documentation comments

文档注释是以////**开头的多行或单行注释. 在连续的行上使用///与多行doc注释具有相同的效果.

在文档注释中,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代码并生成HTML文档,您可以使用SDK的文档生成工具. 有关生成的文档的示例,请参阅Dart API文档. 有关如何构建评论的建议,请参阅Dart Doc评论指南.

Summary

该页面总结了Dart语言的常用功能. 正在实现更多功能,但我们希望它们不会破坏现有代码. 有关更多信息,请参见Dart语言规范Effective Dart .

要了解有关Dart核心库的更多信息,请参阅Dart库之旅 .

by  ICOPY.SITE