★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/ )
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/10598328.html
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
此页面向您展示如何使用每个主要Dart功能,从变量和运算符到类和库,假设您已经知道如何使用其他语言编程。
要了解有关Dart核心库的更多信息,请参阅 Dart Libraries之旅。无论何时需要有关语言功能的更多详细信息,请参阅Dart语言规范。
提示: 您可以使用DartPad播放Dart的大部分语言功能(了解更多信息)。
打开DartPad
一个基本的Dart项目
以下代码使用了Dart的许多基本功能:
1 // Define a function. 2 printInteger(int aNumber) { 3 print('The number is $aNumber.'); // Print to console. 4 } 5 6 // This is where the app starts executing. 7 main() { 8 var number = 42; // Declare and initialize a variable. 9 printInteger(number); // Call a function. 10 }
以下是此程序使用的适用于所有(或几乎所有)Dart应用程序的内容:
// This is a comment.
-
单行注释。Dart还支持多行和文档注释。有关详情,请参阅注释。
int
-
一种类型。一些其他的内置类型 是
String
,List
和bool
。
42
-
一个字面数字。数字文字是一种编译时常量。
print()
-
一种方便的显示输出方式。
'...'(或"...")
-
字符串文字。
$variableName(或)${expression}
-
字符串插值:包括字符串文字内部的变量或表达式的字符串。有关更多信息,请参阅 字符串。
main()
-
应用程序执行开始的特殊,必需的顶级功能。有关更多信息,请参阅 main()函数。
var
-
一种声明变量而不指定其类型的方法。
注意: 本网站的代码遵循Dart风格指南中的约定 。
重要的概念
当您了解Dart语言时,请记住以下事实和概念:
-
您可以放在变量中的所有内容都是一个对象,每个对象都是一个类的实例。偶数,函数和
null
对象。所有对象都继承自Object类。 -
尽管Dart是强类型的,但类型注释是可选的,因为Dart可以推断类型。在上面的代码中,
number
推断为类型int
。如果要明确说明不需要任何类型,请 使用特殊类型dynamic
。 -
Dart支持泛型类型,如
List
(整数列表)或List
(任何类型的对象列表)。 -
Dart支持顶级函数(例如
main()
),以及绑定到类或对象的函数(分别是静态和实例方法)。您还可以在函数内创建函数(嵌套函数或本地函数)。 -
类似地,Dart支持顶级变量,以及绑定到类或对象的变量(静态和实例变量)。实例变量有时称为字段或属性。
-
与Java,Dart不具备关键字
public
,protected
和private
。如果标识符以下划线(_)开头,则它对其库是私有的。有关详细信息,请参阅 库和可见性。 -
标识符可以以字母或下划线(_)开头,后跟这些字符加数字的任意组合。
-
Dart有两个表达式(具有运行时值)和 语句(不具有)。例如,条件表达式
condition ? expr1 : expr2
的值为expr1
或expr2
。将其与if-else语句进行比较,该语句没有任何值。语句通常包含一个或多个表达式,但表达式不能直接包含语句。 -
Dart工具可以报告两种问题:警告和错误。警告只是表明您的代码可能无法正常工作,但它们不会阻止您的程序执行。错误可以是编译时或运行时。编译时错误会阻止代码执行; 运行时错误导致 代码执行时引发异常。
关键词
下表列出了Dart语言专门处理的单词。
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 | extends | is | sync 1 |
break | external 2 | 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的单词是内置标识符。为了简化将JavaScript代码移植到Dart的任务,这些关键字在大多数地方都是有效的标识符,但它们不能用作类或类型名称,也不能用作导入前缀。
-
带有上标3的单词是与Dart 1.0发布后添加的异步支持相关的更新,有限的保留字。不能使用
await
或yield
作为任何函数体中的标识符标记async
,async*
或sync*
。
表中的所有其他单词都是保留字,不能是标识符。
变量
这是创建变量并初始化它的示例:
var name = 'Bob';
变量存储引用。调用的变量name
包含对String
值为“Bob” 的对象的引用。
name
推断变量的类型String
,但您可以通过指定它来更改该类型。如果对象不限于单一类型,请按照设计准则指定Object
或dynamic
类型 。
dynamic name = 'Bob';
另一种选择是显式声明将推断出的类型:
String name = 'Bob';
注意: 此页面遵循 样式指南建议 使用var
,而不是键入注释,用于局部变量。
默认值
未初始化的变量的初始值为null
。即使是具有数字类型的变量最初也是null,因为数字 - 就像Dart中的其他所有 - 都是对象。
1 int lineCount; 2 assert(lineCount == null);
注意:assert()
生产代码中将忽略 该调用。在开发期间, 除非条件为真,否则抛出异常。有关详细信息,请参阅断言。assert(condition)
常量:Final and const
如果您从未打算更改变量,请使用final
或const
代替var
或替代类型。最终变量只能设置一次; const变量是编译时常量。(Const变量是隐式最终的。)最终的顶级或类变量在第一次使用时被初始化。
注意: 实例变量可以是final
但不是const
。必须在构造函数体启动之前初始化最终实例变量 - 在变量声明,构造函数参数或构造函数的初始化列表中。
以下是创建和设置最终变量的示例:
1 final name = 'Bob'; // Without a type annotation 2 final String nickname = 'Bobby';
您无法更改最终变量的值:
name = 'Alice'; // Error: a final variable can only be set once.
使用const
为您要为变量的编译时间常数。如果const变量在类级别,请标记它static const
。在声明变量的地方,将值设置为编译时常量,例如数字或字符串文字,const变量或对常数进行算术运算的结果:
1 const bar = 1000000; // Unit of pressure (dynes/cm2) 2 const double atm = 1.01325 * bar; // Standard atmosphere
该const
关键字不只是声明常数变量。您还可以使用它来创建常量值,以及声明创建常量值的构造函数。任何变量都可以具有常量值。
1 var foo = const []; 2 final bar = const []; 3 const baz = []; // Equivalent to `const []`
您可以省略声明const
的初始化表达式,如上所述。有关详细信息,请参阅不要冗余地使用const。const
baz
您可以更改非final,非const变量的值,即使它曾经有一个const值:
foo = [1, 2, 3]; // Was const []
您无法更改const变量的值:
baz = [42]; // Error: Constant variables can't be assigned a value.
有关使用const
创建常量值的更多信息,请参阅 列表,Hash集合和类。
内置类型
Dart语言特别支持以下类型:
- 数字
- 字符串
- 布尔
- 列表(也称为数组)
- 套
- Hash集合
- 符文(用于表示字符串中的Unicode字符)
- 符号
您可以使用文字初始化任何这些特殊类型的对象。例如,'this is a string'
是一个字符串文字,true
是一个布尔文字。
因为Dart中的每个变量都引用一个对象 - 一个类的实例 - 您通常可以使用构造函数来初始化变量。一些内置类型有自己的构造函数。例如,您可以使用Map()
构造函数来创建Hash集合。
数字
Dart号有两种形式:
int
-
整数值不大于64位,具体取决于平台。在Dart VM上,值可以是-2 63到2 63 - 1.编译为JavaScript的Dart使用JavaScript编号, 允许从-2 53到2 53 - 1的值。
double
-
64位(双精度)浮点数,由IEEE 754标准规定。
这两个int
和double
的亚型num
。 号码类型包括基本的运算符,如+, - ,/和*,也是在那里你会发现abs()
,ceil()
和floor()
其他方法中。(按位运算符,例如>>,在int
类中定义。)如果num及其子类型没有您要查找的内容,则 dart:math库可能会。
整数是没有小数点的数字。以下是定义整数文字的一些示例:
1 var x = 1; 2 var hex = 0xDEADBEEF;
如果数字包含小数,则为双精度数。以下是定义双重文字的一些示例:
1 var y = 1.1; 2 var exponents = 1.42e5;
从Dart 2.1开始,必要时整数文字会自动转换为双精度数:
double z = 1; // Equivalent to double z = 1.0.
版本说明: 在Dart 2.1之前,在双上下文中使用整数文字是错误的。
以下是将字符串转换为数字的方法,反之亦然:
1 // String -> int 2 var one = int.parse('1'); 3 assert(one == 1); 4 5 // String -> double 6 var onePointOne = double.parse('1.1'); 7 assert(onePointOne == 1.1); 8 9 // int -> String 10 String oneAsString = 1.toString(); 11 assert(oneAsString == '1'); 12 13 // double -> String 14 String piAsString = 3.14159.toStringAsFixed(2); 15 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
文字数字是编译时常量。许多算术表达式也是编译时常量,只要它们的操作数是编译为数字的编译时常量。
1 const msPerSecond = 1000; 2 const secondsUntilRetry = 5; 3 const msUntilRetry = secondsUntilRetry * msPerSecond;
字符串
Dart字符串是一系列UTF-16代码单元。您可以使用单引号或双引号来创建字符串:
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.";
您可以使用表达式将表达式的值放在字符串中 。如果表达式是标识符,则可以跳过{}。要获取与对象相对应的字符串,Dart会调用该对象的方法。${
expression
}
.
1 var s = 'string interpolation'; 2 3 assert('Dart has $s, which is very handy.' == 4 'Dart has string interpolation, ' + 5 'which is very handy.'); 6 assert('That deserves all caps. ' + 7 '${s.toUpperCase()} is very handy!' == 8 'That deserves all caps. ' + 9 'STRING INTERPOLATION is very handy!');
注: 在==
两个物体操作测试是否是等价的。如果两个字符串包含相同的代码单元序列,则它们是等效的。
您可以使用相邻的字符串文字或+
运算符来连接字符串:
1 var s1 = 'String ' 2 'concatenation' 3 " works even over line breaks."; 4 assert(s1 == 5 'String concatenation works even over ' 6 'line breaks.'); 7 8 var s2 = 'The + operator ' + 'works, as well.'; 9 assert(s2 == 'The + operator works, as well.');
创建多行字符串的另一种方法:使用带有单引号或双引号的三引号:
1 var s1 = ''' 2 You can create 3 multi-line strings like this one. 4 '''; 5 6 var s2 = """This is also a 7 multi-line string.""";
您可以通过为其添加前缀来创建“原始”字符串r
:
var s = r'In a raw string, not even \n gets special treatment.';
有关如何在字符串中表示Unicode字符的详细信息,请参阅Runes。
文字字符串是编译时常量,只要任何插值表达式是一个编译时常量,其值为null或数值,字符串或布尔值。
1 // These work in a const string. 2 const aConstNum = 0; 3 const aConstBool = true; 4 const aConstString = 'a constant string'; 5 6 // These do NOT work in a const string. 7 var aNum = 0; 8 var aBool = true; 9 var aString = 'a string'; 10 const aConstList = [1, 2, 3]; 11 12 const validConstString = '$aConstNum $aConstBool $aConstString'; 13 // const invalidConstString = '$aNum $aBool $aString $aConstList';
有关使用字符串的更多信息,请参阅 字符串和正则表达式。
布尔
为了表示布尔值,Dart具有一个名为的类型bool
。只有两个对象具有bool类型:boolean literals true
和false
,它们都是编译时常量。
Dart的类型安全意味着你不能使用像或 那样的代码 。相反,明确检查值,如下所示:if (nonbooleanValue)
assert (nonbooleanValue)
1 // Check for an empty string. 2 var fullName = ''; 3 assert(fullName.isEmpty); 4 5 // Check for zero. 6 var hitPoints = 0; 7 assert(hitPoints <= 0); 8 9 // Check for null. 10 var unicorn; 11 assert(unicorn == null); 12 13 // Check for NaN. 14 var iMeantToDoThis = 0 / 0; 15 assert(iMeantToDoThis.isNaN);
Lists
也许几乎每种编程语言中最常见的集合是数组或有序的对象组。在Dart中,数组是 List对象,因此大多数人只是将它们称为列表。
Dart列表文字看起来像JavaScript数组文字。这是一个简单的Dart列表:
var list = [1, 2, 3];
注意: Dart推断出list
有类型List
。如果尝试将非整数对象添加到此列表,则分析器或运行时会引发错误。有关更多信息,请阅读 类型推断。
列表使用从零开始的索引,其中0是第一个元素list.length - 1
的索引,并且是最后一个元素的索引。您可以获得列表的长度并像在JavaScript中一样引用列表元素:
1 var list = [1, 2, 3]; 2 assert(list.length == 3); 3 assert(list[1] == 2); 4 5 list[1] = 1; 6 assert(list[1] == 1);
要创建一个编译时常量const
的列表,请在列表文字前添加:
1 var constantList = const [1, 2, 3]; 2 // constantList[1] = 1; // Uncommenting this causes an error.
List类型有许多方便的方法来操作列表。有关列表的更多信息,请参阅泛型和 集合。
集:Sets
Dart中的一组是一组无序的独特物品。对集合的Dart支持由set literals和Set类型提供。
版本说明:尽管Set 类型一直是Dart的核心部分,但在Dart 2.2中引入了set literals。
这是一个简单的Dart集,使用set literal创建:
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
注意: Dart推断出halogens
具有该类型 Set
。如果您尝试向集合中添加错误类型的值,则分析器或运行时会引发错误。有关更多信息,请阅读 类型推断。
要创建一个空集,请使用{}
前面带有类型参数,或者指定{}
给类型的变量Set
:
1 var names ={}; 2 // Set names = {}; // This works, too. 3 // var names = {}; // Creates a map, not a set.
设置还是映射? 映射文字的语法类似于集合文字的语法。由于Hash集合文字是第一个,因此{}
默认为该Map
类型。如果您忘记了类型注释{}
或它所分配的变量,则Dart会创建一个类型的对象Map
。
使用add()
或addAll()
方法将项添加到现有集:
1 var elements ={}; 2 elements.add('fluorine'); 3 elements.addAll(halogens);
使用.length
得到的一组项目的数量:
1 var elements ={}; 2 elements.add('fluorine'); 3 elements.addAll(halogens); 4 assert(elements.length == 5);
要创建一个编译时常量const
的集合,请在set literal之前添加:
1 final constantSet = const { 2 'fluorine', 3 'chlorine', 4 'bromine', 5 'iodine', 6 'astatine', 7 }; 8 // constantSet.add('helium'); // Uncommenting this causes an error.
有关集的更多信息,请参阅 泛型和 集。
Hash集合:Maps
通常,映射是关联键和值的对象。键和值都可以是任何类型的对象。每个键只出现一次,但您可以多次使用相同的值。Hash集合的Dart支持由Hash集合文字和Map类型提供。
这里有几个简单的Dart贴图,使用贴图文字创建:
1 var gifts = { 2 // Key: Value 3 'first': 'partridge', 4 'second': 'turtledoves', 5 'fifth': 'golden rings' 6 }; 7 8 var nobleGases = { 9 2: 'helium', 10 10: 'neon', 11 18: 'argon', 12 };
注意: Dart推断出gifts
具有类型 Map
和nobleGases
类型的 Dart Map
。如果您尝试将错误类型的值添加到任一映射,则分析器或运行时会引发错误。有关更多信息,请阅读 类型推断。
您可以使用Map构造函数创建相同的对象:
1 var gifts = Map(); 2 gifts['first'] = 'partridge'; 3 gifts['second'] = 'turtledoves'; 4 gifts['fifth'] = 'golden rings'; 5 6 var nobleGases = Map(); 7 nobleGases[2] = 'helium'; 8 nobleGases[10] = 'neon'; 9 nobleGases[18] = 'argon';
注意: 您可能希望看到new Map()
而不仅仅是Map()
。从Dart 2开始,new
关键字是可选的。有关详细信息,请参阅使用构造函数。
像在JavaScript中一样,将新的键值对添加到现有Hash集合中:
1 var gifts = {'first': 'partridge'}; 2 gifts['fourth'] = 'calling birds'; // Add a key-value pair
以与在JavaScript中相同的方式从Hash集合中检索值:
1 var gifts = {'first': 'partridge'}; 2 assert(gifts['first'] == 'partridge');
如果您查找不在Hash集合中的键,则会得到null作为回报:
var gifts = { 'first' :'partridge' }; 断言(gifts [ 'fifth' ] == null );
使用.length
得到的映射中的键值对的数量:
1 var gifts = {'first': 'partridge'}; 2 gifts['fourth'] = 'calling birds'; 3 assert(gifts.length == 2);
要创建一个编译时常量const
的Hash集合,请在Hash集合文字之前添加:
1 final constantMap = const { 2 2: 'helium', 3 10: 'neon', 4 18: 'argon', 5 }; 6 7 // constantMap[2] = 'Helium'; // Uncommenting this causes an error.
有关Hash集合的更多信息,请参阅 泛型和 Hash集合。
符文:Runes
在Dart中,符文是字符串的UTF-32代码点。
Unicode为世界上所有书写系统中使用的每个字母,数字和符号定义唯一的数值。由于Dart字符串是UTF-16代码单元的序列,因此在字符串中表示32位Unicode值需要特殊语法。
表达Unicode代码点的常用方法是 \uXXXX
,XXXX是4位十六进制值。例如,心脏角色(♥)是\u2665
。要指定多于或少于4个十六进制数字,请将值放在大括号中。例如,笑的表情符号(?)是\u{1f600}
。
该字符串 类有几个属性,你可以用它来提取符文信息。在codeUnitAt
和codeUnit
属性返回16位编码单元。使用该runes
属性获取字符串的符文。
以下示例说明了符文,16位代码单元和32位代码点之间的关系。单击“运行”按钮 以查看运行中的符文。
注意: 使用列表操作操作符文时要小心。这种方法很容易分解,具体取决于特定的语言,字符集和操作。有关更多信息,请参阅 如何在Dart中反转字符串?在Stack Overflow上。
符号
甲符号对象表示达特程序声明的操作者或标识符。您可能永远不需要使用符号,但它们对于按名称引用标识符的API非常有用,因为缩小会更改标识符名称而不会更改标识符符号。
要获取标识符的符号,请使用符号文字, #
后面跟着标识符:
1 #radix 2 #bar
符号文字是编译时常量。
函数
Dart是一种真正的面向对象语言,因此即使是函数也是对象并且具有类型Function。 这意味着函数可以分配给变量或作为参数传递给其他函数。您也可以调用Dart类的实例,就好像它是一个函数一样。有关详细信息,请参阅可调用类。
以下是实现函数的示例:
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语句,但可以使用条件表达式。
函数可以有两种类型的参数:必需和可选。首先列出所需参数,然后列出任何可选参数。命名的可选参数也可以标记为@required
。有关详细信息,请参阅下一节。
可选参数
可选参数可以是位置参数,也可以是命名参数,但不能同时包
可选的命名参数
调用函数时,可以使用指定命名参数 。例如:paramName: value
enableFlags(bold: true, hidden: false);
定义函数时,用于 指定命名参数:{param1, param2, …}
1 /// Sets the [bold] and [hidden] flags ... 2 void enableFlags({bool bold, bool hidden}) {...}
Flutter实例创建表达式可能变得复杂,因此窗口小部件构造函数仅使用命名参数。这使得实例创建表达式更易于阅读。
您可以使用@required在任何Dart代码(不仅仅是Flutter)中注释命名参数, 以指示它是必需参数。例如:
const Scrollbar({Key key, @required Widget child})
当一个Scrollbar
构造,分析仪时,报告一个问题 child
的说法是不存在的。
必需在元包中定义。可以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 }
这是一个在没有可选参数的情况下调用此函数的示例:
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
以下是使用第三个参数调用此函数的示例:
1 assert(say('Bob', 'Howdy', 'smoke signal') == 2 'Bob says Howdy with a smoke signal');
默认参数值
您的函数可用于=
定义命名和位置参数的默认值。默认值必须是编译时常量。如果未提供默认值,则默认值为null
。
以下是为命名参数设置默认值的示例:
1 /// Sets the [bold] and [hidden] flags ... 2 void enableFlags({bool bold = false, bool hidden = false}) {...} 3 4 // bold will be true; hidden will be false. 5 enableFlags(bold: true);
弃用注释: 旧代码可能使用冒号(:
)而不是=
设置命名参数的默认值。原因是最初只:
支持命名参数。该支持可能已被弃用,因此我们建议您 使用=
指定默认值。
下一个示例显示如何设置位置参数的默认值:
1 String say(String from, String msg, 2 [String device = 'carrier pigeon', String mood]) { 3 var result = '$from says $msg'; 4 if (device != null) { 5 result = '$result with a $device'; 6 } 7 if (mood != null) { 8 result = '$result (in a $mood mood)'; 9 } 10 return result; 11 } 12 13 assert(say('Bob', 'Howdy') == 14 'Bob says Howdy with a carrier pigeon');
您还可以将列表或Hash集合作为默认值传递。以下示例定义了一个函数,该函数doStuff()
指定参数的默认列表和list
参数的默认映射gifts
。
1 void doStuff( 2 {List<int> list = const [1, 2, 3], 3 Mapgifts = const { 4 'first': 'paper', 5 'second': 'cotton', 6 'third': 'leather' 7 }}) { 8 print('list: $list'); 9 print('gifts: $gifts'); 10 }
main()函数
每个应用程序都必须具有顶级main()
功能,该功能用作应用程序的入口点。该main()
函数返回void
并具有List
参数的可选参数。
以下main()
是Web应用程序功能的示例:
1 void main() { 2 querySelector('#sample_text_id') 3 ..text = 'Click me!' 4 ..onClick.listen(reverseText); 5 }
注意:..
前面代码中 的语法称为级联。使用级联,您可以对单个对象的成员执行多个操作。
以下main()
是带参数的命令行应用程序的函数示例:
1 // Run the app like this: dart args.dart 1 test 2 void main(Listarguments) { 3 print(arguments); 4 5 assert(arguments.length == 2); 6 assert(int.parse(arguments[0]) == 1); 7 assert(arguments[1] == 'test'); 8 }
您可以使用args库来定义和解析命令行参数。
作为第一类对象的功能
您可以将函数作为参数传递给另一个函数。例如:
1 void printElement(int element) { 2 print(element); 3 } 4 5 var list = [1, 2, 3]; 6 7 // Pass printElement as a parameter. 8 list.forEach(printElement);
您还可以为变量分配函数,例如:
1 var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!'; 2 assert(loudify('hello') == '!!! HELLO !!!');
此示例使用匿名函数。更多关于下一节的内容。
匿名函数
大多数函数都被命名,例如main()
或printElement()
。您还可以创建一个名为匿名函数的无名函数,有时也可以创建一个lambda或闭包。您可以为变量分配匿名函数,以便例如可以在集合中添加或删除它。
匿名函数看起来类似于命名函数 - 零个或多个参数,在逗号和括号之间用逗号和可选类型注释分隔。
后面的代码块包含函数的主体:
1 ([[Type] param1[, …]]) { 2 codeBlock; 3 };
以下示例使用无类型参数定义匿名函数item
。为列表中的每个项调用的函数将打印一个包含指定索引处的值的字符串。
1 var list = ['apples', 'bananas', 'oranges']; 2 list.forEach((item) { 3 print('${list.indexOf(item)}: $item'); 4 });
单击运行按钮以执行代码。
如果函数只包含一个语句,则可以使用箭头表示法缩短它。将以下行粘贴到DartPad中,然后单击“运行”以验证它是否在功能上等效。
1 list.forEach( 2 (item) => print('${list.indexOf(item)}: $item'));
词汇范围
Dart是一种词法范围的语言,这意味着变量的范围是静态确定的,只需通过代码的布局。您可以“向外跟随花括号”以查看变量是否在范围内。
以下是每个范围级别包含变量的嵌套函数示例:
1 bool topLevel = true; 2 3 void main() { 4 var insideMain = true; 5 6 void myFunction() { 7 var insideFunction = true; 8 9 void nestedFunction() { 10 var insideNestedFunction = true; 11 12 assert(topLevel); 13 assert(insideMain); 14 assert(insideFunction); 15 assert(insideNestedFunction); 16 } 17 } 18 }
请注意如何nestedFunction()
使用每个级别的变量,一直到顶级。
词汇封闭
甲闭合是能够访问在其词法范围的变量的函数的对象,即使当函数用于其原来的范围之外。
函数可以关闭周围范围中定义的变量。在以下示例中,makeAdder()
捕获变量addBy
。无论返回的函数在哪里,它都会记住addBy
。
1 /// Returns a function that adds [addBy] to the 2 /// function's argument. 3 Function makeAdder(num addBy) { 4 return (num i) => addBy + i; 5 } 6 7 void main() { 8 // Create a function that adds 2. 9 var add2 = makeAdder(2); 10 11 // Create a function that adds 4. 12 var add4 = makeAdder(4); 13 14 assert(add2(3) == 5); 15 assert(add4(3) == 7);
测试函数是否相等
以下是测试顶级函数,静态方法和实例方法的相等性的示例:
1 void foo() {} // A top-level function 2 3 class A { 4 static void bar() {} // A static method 5 void baz() {} // An instance method 6 } 7 8 void main() { 9 var x; 10 11 // Comparing top-level functions. 12 x = foo; 13 assert(foo == x); 14 15 // Comparing static methods. 16 x = A.bar; 17 assert(A.bar == x); 18 19 // Comparing instance methods. 20 var v = A(); // Instance #1 of A 21 var w = A(); // Instance #2 of A 22 var y = w; 23 x = w.baz; 24 25 // These closures refer to the same instance (#2), 26 // so they're equal. 27 assert(y.baz == x); 28 29 // These closures refer to different instances, 30 // so they're unequal. 31 assert(v.baz != w.baz); 32 }
返回值
所有函数都返回一个值。如果未指定返回值,则将语句return null;
隐式附加到函数体。
1 foo() {} 2 3 assert(foo() == null);
运算符
Dart定义下表中显示的运算符。您可以覆盖许多运营商,如在 可重写运营商。
描述 | 操作者 |
---|---|
一元后缀 | expr++ expr-- () [] . ?. |
一元前缀 | -expr !expr ~expr ++expr --expr |
乘 | * / % ~/ |
添加剂 | + - |
转移 | << >> >>> |
按位AND | & |
按位异或 | ^ |
按位OR | | |
关系和类型测试 | >= > <= < as is is! |
平等 | == != |
逻辑AND | && |
逻辑或 | || |
如果为null | ?? |
有条件的 | expr1 ? expr2 : expr3 |
级联 | .. |
分配 | = *= /= += -= &= ^= 等等 |
警告: 运算符优先级是Dart解析器行为的近似值。有关明确的答案,请参阅Dart语言规范中的语法 。
使用运算符时,可以创建表达式。以下是运算符表达式的一些示例:
1 a++ 2 a + b 3 a = b 4 a == b 5 c ? a : b 6 a is T
在运算符表中,每个运算符的优先级高于其后的行中的运算符。例如,乘法运算符的%
优先级高于(因此之前执行)等于运算符==
,它的优先级高于逻辑AND运算符&&
。该优先级意味着以下两行代码执行相同的方式:
1 // Parentheses improve readability. 2 if ((n % i == 0) && (d % i == 0)) ... 3 4 // Harder to read, but equivalent. 5 if (n % i == 0 && d % i == 0) ...
警告: 对于处理两个操作数的运算符,最左边的操作数确定使用哪个版本的运算符。例如,如果您有Vector对象和Point对象,则aVector + aPoint
使用Vector版本的+。
算术运算符
Dart支持通常的算术运算符,如下表所示。
操作者 | 含义 |
---|---|
+ |
加 |
– |
减去 |
-expr |
一元减号,也称为否定(反转表达式的符号) |
* |
乘 |
/ |
划分 |
~/ |
除以,返回整数结果 |
% |
获取整数除法的余数(模数) |
例:
1 assert(2 + 3 == 5); 2 assert(2 - 3 == -1); 3 assert(2 * 3 == 6); 4 assert(5 / 2 == 2.5); // Result is a double 5 assert(5 ~/ 2 == 2); // Result is an int 6 assert(5 % 2 == 1); // Remainder 7 8 assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');
Dart还支持前缀和后缀增量和减量运算符。
操作者 | 含义 |
---|---|
++var |
var = var + 1 (表达式值是var + 1 ) |
var++ |
var = var + 1 (表达式值是var ) |
--var |
var = var – 1 (表达式值是var – 1 ) |
var-- |
var = var – 1 (表达式值是var ) |
例:
1 var a, b; 2 3 a = 0; 4 b = ++a; // Increment a before b gets its value. 5 assert(a == b); // 1 == 1 6 7 a = 0; 8 b = a++; // Increment a AFTER b gets its value. 9 assert(a != b); // 1 != 0 10 11 a = 0; 12 b = --a; // Decrement a before b gets its value. 13 assert(a == b); // -1 == -1 14 15 a = 0; 16 b = a--; // Decrement a AFTER b gets its value. 17 assert(a != b); // -1 != 0
平等和关系运营商
下表列出了相等运算符和关系运算符的含义。
操作者 | 含义 |
---|---|
== |
等于; 见下面的讨论 |
!= |
不相等 |
> |
比...更棒 |
< |
少于 |
>= |
大于或等于 |
<= |
小于或等于 |
要测试两个对象x和y是否表示相同的事物,请使用 ==
运算符。(在极少数情况下,您需要知道两个对象是否是完全相同的对象,请使用相同的() 函数。)以下是==
运算符的工作方式:
-
如果x或y为null,则如果两者都为null则返回true;如果只有一个为null,则返回false。
-
返回方法调用的结果 。(这是正确的,运算符,例如在第一个操作数上调用的方法。您甚至可以覆盖许多运算符,包括,正如您在Overridable运算符中看到的那样 。)
x.==(y)
==
==
这是使用每个相等和关系运算符的示例:
1 assert(2 == 2); 2 assert(2 != 3); 3 assert(3 > 2); 4 assert(2 < 3); 5 assert(3 >= 3); 6 assert(2 <= 3);
键入测试运算符
使用as
,is
和is!
运算符可以方便地在运行时检查类型。
操作者 | 含义 |
---|---|
as |
Typecast(也用于指定库前缀) |
is |
如果对象具有指定的类型,则为True |
is! |
如果对象具有指定的类型,则返回false |
obj is T
如果obj
实现了指定的接口,则结果为true T
。例如,obj is Object
总是如此。
使用as
运算符将对象强制转换为特定类型。通常,您应该使用它作为is
对使用该对象的表达式后跟对象的测试的简写。例如,请考虑以下代码:
1 if (emp is Person) { 2 // Type check 3 emp.firstName = 'Bob'; 4 }
您可以使用as
运算符缩短代码:
(emp as Person).firstName = 'Bob';
注意: 代码不相同。如果emp
为null或不是Person,则第一个示例(with is
)不执行任何操作; 第二个(带as
)抛出一个异常。
分配运营商
如您所见,您可以使用=
运算符分配值。要仅在assign-to变量为null时分配,请使用??=
运算符。
1 // Assign value to a 2 a = value; 3 // Assign value to b if b is null; otherwise, b stays the same 4 b ??= value;
复合赋值运算符,例如+=
将操作与赋值组合在一起。
= |
–= |
/= |
%= |
>>= |
^= |
+= |
*= |
~/= |
<<= |
&= |
|= |
以下是复合赋值运算符的工作原理:
复合赋值 | 等价表达 | |
---|---|---|
对于运营商op: | a op= b |
a = a op b |
例: | a += b |
a = a + b |
以下示例使用赋值和复合赋值运算符:
1 var a = 2; // Assign using = 2 a *= 3; // Assign and multiply: a = a * 3 3 assert(a == 6);
逻辑运算符
您可以使用逻辑运算符反转或组合布尔表达式。
操作者 | 含义 |
---|---|
!expr |
反转以下表达式(将false更改为true,反之亦然) |
|| |
逻辑或 |
&& |
逻辑AND |
以下是使用逻辑运算符的示例:
1 if (!done && (col == 0 || col == 3)) { 2 // ...Do something... 3 }
按位和移位运算符
您可以在Dart中操纵数字的各个位。通常,您将使用这些按位和移位运算符和整数。
操作者 | 含义 |
---|---|
& |
和 |
| |
要么 |
^ |
XOR |
~expr |
一元逐位补码(0s变为1s; 1s变为0s) |
<< |
向左转 |
>> |
向右转 |
这是使用按位和移位运算符的示例:
1 final value = 0x22; 2 final bitmask = 0x0f; 3 4 assert((value & bitmask) == 0x02); // AND 5 assert((value & ~bitmask) == 0x20); // AND NOT 6 assert((value | bitmask) == 0x2f); // OR 7 assert((value ^ bitmask) == 0x2d); // XOR 8 assert((value << 4) == 0x220); // Shift left 9 assert((value >> 4) == 0x02); // Shift right
条件表达式
Dart有两个运算符,可以让您简明地计算可能需要if-else语句的表达式:
condition ? expr1 : expr2
- 如果condition为true,则计算expr1(并返回其值); 否则,计算并返回expr2的值。
expr1 ?? expr2
- 如果expr1为非null,则返回其值; 否则,计算并返回expr2的值。
当您需要根据布尔表达式分配值时,请考虑使用?:
。
var visibility = isPublic ? 'public' : 'private';
如果布尔表达式测试为null,请考虑使用??
。
String playerName(String name) => name ?? 'Guest';
前面的例子至少可以用其他两种方式编写,但不能简洁:
1 // Slightly longer version uses ?: operator. 2 String playerName(String name) => name != null ? name : 'Guest'; 3 4 // Very long version uses if-else statement. 5 String playerName(String name) { 6 if (name != null) { 7 return name; 8 } else { 9 return 'Guest'; 10 } 11 }
级联符号(..)
Cascades(..
)允许您对同一对象进行一系列操作。除了函数调用,您还可以访问同一对象上的字段。这通常可以为您节省创建临时变量的步骤,并允许您编写更多流畅的代码。
请考虑以下代码:
1 querySelector('#confirm') // Get an object. 2 ..text = 'Confirm' // Use its members. 3 ..classes.add('important') 4 ..onClick.listen((e) => window.alert('Confirmed!'));
第一个方法调用,querySelector()
返回一个选择器对象。级联表示法后面的代码对此选择器对象进行操作,忽略可能返回的任何后续值。
前面的例子相当于:
1 var button = querySelector('#confirm'); 2 button.text = 'Confirm'; 3 button.classes.add('important'); 4 button.onClick.listen((e) => window.alert('Confirmed!'));
您也可以嵌套您的级联。例如:
1 final addressBook = (AddressBookBuilder() 2 ..name = 'jenny' 3 ..email = '[email protected]' 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: method 'write' isn't defined for 'void'.
该sb.write()
调用返回无效的,并不能构造上的级联void
。
注意: 严格来说,级联的“双点”符号不是运算符。它只是Dart语法的一部分。
其他运营商
在其他示例中,您已经看到了大多数剩余的运算符:
操作者 | 名称 | 含义 |
---|---|---|
() |
功能应用 | 表示函数调用 |
[] |
列表访问 | 引用列表中指定索引处的值 |
. |
会员访问权限 | 指表达式的属性; 示例:从表达式中foo.bar 选择属性bar foo |
?. |
有条件的成员访问权限 | 比如. ,但最左边的操作数可以为null; 示例:从表达式中foo?.bar 选择属性bar ,foo 除非foo 为null(在这种情况下,值为foo?.bar null) |
有关详细信息.
,?.
以及..
运营商,见 类。
控制流程语句
您可以使用以下任一方法控制Dart代码的流程:
if
和else
for
循环while
和do
-while
循环break
和continue
switch
和case
assert
您还可以使用try-catch
和影响控制流throw
,如异常中所述。
如果是,否则
Dart支持if
带有可选else
语句的语句,如下一个示例所示。另见条件表达式。
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
,正如预期的那样输出。相反,该示例将打印2
,然后2
在JavaScript中。
如果要迭代的对象是Iterable,则可以使用 forEach()方法。forEach()
如果您不需要知道当前的迭代计数器,则使用是一个不错的选择:
candidates.forEach((candidate) => candidate.interview());
像List和Set这样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 }
A 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 }
如果您使用Iterable(如列表或集合),则可能会以不同的方式编写该示例 :
1 candidates 2 .where((c) => c.yearsExperience >= 5) 3 .forEach((c) => c.interview());
siwtch 和 case
Dart中的switch语句使用比较整数,字符串或编译时常量==
。比较对象必须都是同一个类的实例(而不是其任何子类型),并且该类不能覆盖==
。 枚举类型在switch
语句中运行良好。
注意: Dart中的Switch语句适用于有限的情况,例如解释器或扫描仪。
每个非空case
子句break
通常以语句结束。其他有效的方式来结束一个非空的case
条款是continue
, throw
或return
声明。
default
当没有case
子句匹配时,使用子句执行代码:
1 var command = 'OPEN'; 2 switch (command) { 3 case 'CLOSED': 4 executeClosed(); 5 break; 6 case 'PENDING': 7 executePending(); 8 break; 9 case 'APPROVED': 10 executeApproved(); 11 break; 12 case 'DENIED': 13 executeDenied(); 14 break; 15 case 'OPEN': 16 executeOpen(); 17 break; 18 default: 19 executeUnknown(); 20 }
以下示例省略break
了case
子句中的语句,从而生成错误:
1 var command = 'OPEN'; 2 switch (command) { 3 case 'OPEN': 4 executeOpen(); 5 // ERROR: Missing break 6 7 case 'CLOSED': 8 executeClosed(); 9 break; 10 }
但是,Dart确实支持空case
句子,允许一种形式的落空:
1 var command = 'CLOSED'; 2 switch (command) { 3 case 'CLOSED': // Empty case falls through. 4 case 'NOW_CLOSED': 5 // Runs for both CLOSED and NOW_CLOSED. 6 executeNowClosed(); 7 break; 8 }
如果你真的想要fall-through,你可以使用一个continue
声明和一个标签:
1 var command = 'CLOSED'; 2 switch (command) { 3 case 'CLOSED': 4 executeClosed(); 5 continue nowClosed; 6 // Continues executing at the nowClosed label. 7 8 nowClosed: 9 case 'NOW_CLOSED': 10 // Runs for both CLOSED and NOW_CLOSED. 11 executeNowClosed(); 12 break; 13 }
一个case
条款可以有局部变量,只有内部的条款的范围是可见的。
断言:Assert
assert
如果布尔条件为false,则使用语句来中断正常执行。您可以在本导览中找到断言语句的示例。这里还有一些:
1 // Make sure the variable has a non-null value. 2 assert(text != null); 3 4 // Make sure the value is less than 100. 5 assert(number < 100); 6 7 // Make sure this is an https URL. 8 assert(urlString.startsWith('https'));
注意: 断言语句对生产代码没有影响; 他们只是为了发展。Flutter在调试模式下启用断言。 仅限开发的工具(如dartdevc) 通常默认支持断言。一些工具,如dart和dart2js, 通过命令行标志支持断言:--enable-asserts
。
要将消息附加到断言,请添加一个字符串作为第二个参数。
1 assert(urlString.startsWith('https'), 2 'URL ($urlString) should start with "https".');
第一个参数assert
可以是任何解析为布尔值的表达式。如果表达式的值为true,则断言成功并继续执行。如果为false,则断言失败并抛出异常( AssertionError)。
异常:Exceptions
您的Dart代码可以抛出并捕获异常。例外是指示发生意外事件的错误。如果未捕获异常,则会引发引发异常的隔离,并且通常会隔离隔离及其程序。
与Java相比,Dart的所有异常都是未经检查的异常。方法不会声明它们可能引发的异常,并且您不需要捕获任何异常。
Dart提供了Exception和Error 类型,以及许多预定义的子类型。当然,您可以定义自己的例外情况。但是,Dart程序可以抛出任何非null对象 - 不仅仅是Exception和Error对象 - 作为例外。
throw
以下是抛出或引发异常的示例:
throw FormatException('Expected at least 1 section');
你也可以抛出任意对象:
throw 'Out of llamas!';
注意:生产质量代码通常会抛出实现错误或异常的类型 。
因为抛出异常是一个表达式,所以可以在=>语句中以及允许表达式的任何其他地方抛出异常:
void distanceTo(Point other) => throw UnimplementedError();
Catch
捕获或捕获异常会阻止异常传播(除非您重新抛出异常)。捕获异常使您有机会处理它:
1 try { 2 breedMoreLlamas(); 3 } on OutOfLlamasException { 4 buyMoreLlamas(); 5 }
要处理可能抛出多种类型异常的代码,可以指定多个catch子句。与抛出对象的类型匹配的第一个catch子句处理异常。如果catch子句未指定类型,则该子句可以处理任何类型的抛出对象:
1 try { 2 breedMoreLlamas(); 3 } on OutOfLlamasException { 4 // A specific exception 5 buyMoreLlamas(); 6 } on Exception catch (e) { 7 // Anything else that is an exception 8 print('Unknown exception: $e'); 9 } catch (e) { 10 // No specified type, handles all 11 print('Something really unknown: $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 error 5 } catch (e) { 6 print('misbehave() partially handled ${e.runtimeType}.'); 7 rethrow; // Allow callers to see the exception. 8 } 9 } 10 11 void main() { 12 try { 13 misbehave(); 14 } catch (e) { 15 print('main() finished handling ${e.runtimeType}.'); 16 } 17 }
finally
无论是否抛出异常,要确保某些代码运行,请使用finally
子句。如果没有catch
子句匹配该异常,则在finally
子句运行后传播异常:
1 try { 2 breedMoreLlamas(); 3 } finally { 4 // Always clean up, even if an exception is thrown. 5 cleanLlamaStalls(); 6 }
该finally
子句在任何匹配的catch
子句之后运行:
1 try { 2 breedMoreLlamas(); 3 } catch (e) { 4 print('Error: $e'); // Handle the exception first. 5 } finally { 6 cleanLlamaStalls(); // Then clean up. 7 }
阅读 Libraries之旅的例外部分,了解更多信息 。
类
Dart是一种面向对象的语言,具有类和基于mixin的继承。每个对象都是一个类的实例,所有类都来自Object。 基于Mixin的继承意味着虽然每个类(除了Object)只有一个超类,但是类体可以在多个类层次结构中重用。
使用班级成员
对象具有由函数和数据(分别为方法和 实例变量)组成的成员。调用方法时,可以 在对象上调用它:该方法可以访问该对象的函数和数据。
使用点(.
)来引用实例变量或方法:
1 var p = Point(2, 2); 2 3 // Set the value of the instance variable y. 4 p.y = 3; 5 6 // Get the value of y. 7 assert(p.y == 3); 8 9 // Invoke distanceTo() on p. 10 num distance = p.distanceTo(Point(4, 4));
当最左边的操作数为null时,使用?.
而不是.
避免异常:
1 // If p is non-null, set its y value to 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});
版本注:该new
关键字Dart2成为可选项。
有些类提供常量构造函数。要使用常量构造函数创建编译时常量,请将const
关键字放在构造函数名称之前:
var p = const ImmutablePoint(2, 2);
构造两个相同的编译时常量会产生一个规范的实例:
1 var a = const ImmutablePoint(1, 1); 2 var b = const ImmutablePoint(1, 1); 3 4 assert(identical(a, b)); // They are the same instance!
在常量上下文中,您可以省略const
构造函数或文字之前的内容。例如,查看此代码,该代码创建一个const映射:
//这里有很多const关键字。常量pointAndLine = const的{ '点' :常量[ 常量ImmutablePoint (0 ,0 )],'线' :常数[ 常量ImmutablePoint (1 ,10 ),常量ImmutablePoint ( - 2 ,11 )],};
您可以省略除const
关键字的第一次使用之外的所有内容:
1 // Lots of const keywords here. 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 var a = const ImmutablePoint(1, 1); // Creates a constant 2 var b = ImmutablePoint(1, 1); // Does NOT create a constant 3 4 assert(!identical(a, b)); // NOT the same instance!
版本注:该const
关键字成为Dart2的恒定范围内可选。
获取对象的类型
要在运行时获取对象的类型,可以使用Object的runtimeType
属性,该属性返回Type对象。
print ('a的类型是$ {a.runtimeType}' );
到目前为止,您已经了解了如何使用类。本节的其余部分将介绍如何实现类。
实例变量
以下是声明实例变量的方法:
print('The type of a is ${a.runtimeType}');
所有未初始化的实例变量都具有该值null
。
所有实例变量都生成一个隐式getter方法。非最终实例变量也会生成隐式setter方法。有关详细信息,请参阅Getters和setter。
1 class Point { 2 num x; // Declare instance variable x, initially null. 3 num y; // Declare y, initially null. 4 num z = 0; // Declare z, initially 0. 5 }
如果初始化声明它的实例变量(而不是构造函数或方法),则在创建实例时设置该值,该实例在构造函数及其初始化列表执行之前。
构造函数
通过创建与其类同名的函数来声明构造函数(另外,可选地,如命名构造函数中所述的附加标识符 )。最常见的构造函数形式,即生成构造函数,创建一个类的新实例:
1 class Point { 2 num x; 3 num y; 4 } 5 6 void main() { 7 var point = Point(); 8 point.x = 4; // Use the setter method for x. 9 assert(point.x == 4); // Use the getter method for x. 10 assert(point.y == null); // Values default to null. 11 }
该this
关键字是指当前实例。
注意: 使用this
时,才会有一个名称冲突。否则,Dart风格省略了this
。
将构造函数参数赋值给实例变量的模式是如此常见,Dart具有语法糖,使其变得简单:
1 class Point { 2 num x, y; 3 4 Point(num x, num y) { 5 // There's a better way to do this, stay tuned. 6 this.x = x; 7 this.y = y; 8 } 9 }
默认构造函数
如果您未声明构造函数,则会为您提供默认构造函数。默认构造函数没有参数,并在超类中调用无参数构造函数。
构造函数不是继承的
子类不从其超类继承构造函数。声明没有构造函数的子类只有默认(无参数,无名称)构造函数。
命名构造函数
使用命名构造函数为类实现多个构造函数或提供额外的清晰度:
1 class Point { 2 num x, y; 3 4 Point(this.x, this.y); 5 6 // Named constructor 7 Point.origin() { 8 x = 0; 9 y = 0; 10 } 11 }
请记住,构造函数不是继承的,这意味着超类的命名构造函数不会被子类继承。如果希望使用超类中定义的命名构造函数创建子类,则必须在子类中实现该构造函数。
调用非默认的超类构造函数
默认情况下,子类中的构造函数调用超类的未命名的无参数构造函数。超类的构造函数在构造函数体的开头被调用。如果 还使用初始化列表,则在调用超类之前执行。总之,执行顺序如下:
- 初始化列表
- 超类的无参数构造函数
- 主类的无参数构造函数
如果超类没有未命名的无参数构造函数,则必须手动调用超类中的一个构造函数。在冒号(:
)之后,在构造函数体(如果有)之前指定超类构造函数。
在下面的示例中,Employee类的构造函数为其超类Person调用命名构造函数。单击运行按钮以执行代码。
因为在调用构造函数之前会计算超类构造函数的参数,所以参数可以是一个表达式,例如函数调用:
1 class Employee extends Person { 2 Employee() : super.fromJson(getDefaultData()); 3 // ··· 4 }
警告: 超类构造函数的参数无权访问this
。例如,参数可以调用静态方法,但不能调用实例方法。
初始化list
除了调用超类构造函数之外,还可以在构造函数体运行之前初始化实例变量。用逗号分隔初始化程序。
1 // Initializer list sets instance variables before 2 // the constructor body runs. 3 Point.fromJson(Mapjson) 4 : x = json['x'], 5 y = json['y'] { 6 print('In Point.fromJson(): ($x, $y)'); 7 }
警告: 初始化程序的右侧无权访问this
。
在开发期间,您可以通过assert
在初始化列表中使用来验证输入。
1 Point.withAssert(this.x, this.y) : assert(x >= 0) { 2 print('In Point.withAssert(): ($x, $y)'); 3 }
设置最终字段时,初始化程序列表很方便。以下示例初始化初始化列表中的三个最终字段。单击运行按钮以执行代码。
重定向构造函数
有时构造函数的唯一目的是重定向到同一个类中的另一个构造函数。重定向构造函数的主体是空的,构造函数调用出现在冒号(:)之后。
1 class Point { 2 num x, y; 3 4 // The main constructor for this class. 5 Point(this.x, this.y); 6 7 // Delegates to the main constructor. 8 Point.alongXAxis(num x) : this(x, 0); 9 }
常量构造函数
如果您的类生成永远不会更改的对象,则可以使这些对象成为编译时常量。为此,请定义const
构造函数并确保所有实例变量都是final
。
1 class ImmutablePoint { 2 static final ImmutablePoint origin = 3 const ImmutablePoint(0, 0); 4 5 final num x, y; 6 7 const ImmutablePoint(this.x, this.y); 8 }
常量构造函数并不总是创建常量。有关详细信息,请参阅有关使用构造函数的部分 。
工厂构造函数:Factory constructors
factory
在实现不总是创建其类的新实例的构造函数时使用关键字。例如,工厂构造函数可能从缓存中返回实例,或者它可能返回子类型的实例。
以下示例演示了从缓存中返回对象的工厂构造函数:
1 class Logger { 2 final String name; 3 bool mute = false; 4 5 // _cache is library-private, thanks to 6 // the _ in front of its name. 7 static final Map_cache = 8 {}; 9 10 factory Logger(String name) { 11 if (_cache.containsKey(name)) { 12 return _cache[name]; 13 } else { 14 final logger = Logger._internal(name); 15 _cache[name] = logger; 16 return logger; 17 } 18 } 19 20 Logger._internal(this.name); 21 22 void log(String msg) { 23 if (!mute) print(msg); 24 } 25 }
注意: 工厂构造函数无权访问this
。
像调用任何其他构造函数一样调用工厂构造函数:
1 var logger = Logger('UI'); 2 logger.log('Button clicked');
方法
方法是为对象提供行为的函数。
实例方法
对象的实例方法可以访问实例变量和this
。在distanceTo()
下面的示例中方法是一个实例方法的一个例子:
1 import 'dart:math'; 2 3 class Point { 4 num x, y; 5 6 Point(this.x, this.y); 7 8 num distanceTo(Point other) { 9 var dx = x - other.x; 10 var dy = y - other.y; 11 return sqrt(dx * dx + dy * dy); 12 } 13 }
Getters 和 setters
getter和setter是提供对象属性的读写访问权限的特殊方法。回想一下,每个实例变量都有一个隐式getter,如果合适的话还有一个setter。您可以使用get
和set
关键字通过实现getter和setter来创建其他属性 :
1 class Rectangle { 2 num left, top, width, height; 3 4 Rectangle(this.left, this.top, this.width, this.height); 5 6 // Define two calculated properties: right and bottom. 7 num get right => left + width; 8 set right(num value) => left = value - width; 9 num get bottom => top + height; 10 set bottom(num value) => top = value - height; 11 } 12 13 void main() { 14 var rect = Rectangle(3, 4, 20, 15); 15 assert(rect.left == 3); 16 rect.right = 12; 17 assert(rect.left == -8); 18 }
使用getter和setter,您可以从实例变量开始,稍后使用方法包装它们,而无需更改客户端代码。
注意: 无论是否明确定义了getter,增量(++)等运算符都以预期的方式工作。为避免任何意外的副作用,操作员只需调用一次getter,将其值保存在临时变量中。
抽象方法
实例,getter和setter方法可以是抽象的,定义一个接口,但将其实现留给其他类。抽象方法只能存在于抽象类中。
要使方法成为抽象,请使用分号(;)而不是方法体:
1 abstract class Doer { 2 // Define instance variables and methods... 3 4 void doSomething(); // Define an abstract method. 5 } 6 7 class EffectiveDoer extends Doer { 8 void doSomething() { 9 // Provide an implementation, so the method is not abstract here... 10 } 11 }
抽象类
使用abstract
修饰符定义抽象类 - 无法实例化的类。抽象类对于定义接口非常有用,通常还有一些实现。如果希望抽象类看起来是可实例化的,请定义工厂构造函数。
抽象类通常有抽象方法。这是一个声明具有抽象方法的抽象类的示例:
1 // This class is declared abstract and thus 2 // can't be instantiated. 3 abstract class AbstractContainer { 4 // Define constructors, fields, methods... 5 6 void updateChildren(); // Abstract method. 7 }
隐式接口
每个类都隐式定义一个接口,该接口包含该类的所有实例成员及其实现的任何接口。如果要在不继承B实现的情况下创建支持B类API的A类,则A类应实现B接口。
类通过在implements
子句中声明它们然后提供接口所需的API来实现一个或多个 接口。例如:
1 // A person. The implicit interface contains greet(). 2 class Person { 3 // In the interface, but visible only in this library. 4 final _name; 5 6 // Not in the interface, since this is a constructor. 7 Person(this._name); 8 9 // In the interface. 10 String greet(String who) => 'Hello, $who. I am $_name.'; 11 } 12 13 // An implementation of the Person interface. 14 class Impostor implements Person { 15 get _name => ''; 16 17 String greet(String who) => 'Hi $who. Do you know who I am?'; 18 } 19 20 String greetBob(Person person) => person.greet('Bob'); 21 22 void main() { 23 print(greetBob(Person('Kathy'))); 24 print(greetBob(Impostor())); 25 }
这是一个指定类实现多个接口的示例:
class Point implements Comparable, Location {...}
扩展类
使用extends
创建一个子类,并super
指超:
1 class Television { 2 void turnOn() { 3 _illuminateDisplay(); 4 _activateIrSensor(); 5 } 6 // ··· 7 } 8 9 class SmartTelevision extends Television { 10 void turnOn() { 11 super.turnOn(); 12 _bootNetworkInterface(); 13 _initializeMemory(); 14 _upgradeApps(); 15 } 16 // ··· 17 }
覆盖成员:Overriding members
子类可以覆盖实例方法,getter和setter。您可以使用@override
注释来指示您有意覆盖成员:
1 class SmartTelevision extends Television { 2 @override 3 void turnOn() {...} 4 // ··· 5 }
要在类型安全的代码中缩小方法参数或实例变量的 类型,可以使用covariant
关键字。
可覆盖的运算符
您可以覆盖下表中显示的运算符。例如,如果定义Vector类,则可以定义+
添加两个向量的方法。
< |
+ |
| |
[] |
> |
/ |
^ |
[]= |
<= |
~/ |
& |
~ |
>= |
* |
<< |
== |
– |
% |
>> |
注意:您可能已经注意到,这!=
不是可覆盖的运算符。表达e1 != e2
只是语法糖!(e1 == e2)
。
这是一个覆盖+
和-
运算符的类的示例:
1 class Vector { 2 final int x, y; 3 4 Vector(this.x, this.y); 5 6 Vector operator +(Vector v) => Vector(x + v.x, y + v.y); 7 Vector operator -(Vector v) => Vector(x - v.x, y - v.y); 8 9 // Operator == and hashCode not shown. For details, see note below. 10 // ··· 11 } 12 13 void main() { 14 final v = Vector(2, 3); 15 final w = Vector(2, 2); 16 17 assert(v + w == Vector(4, 5)); 18 assert(v - w == Vector(0, 1)); 19 }
如果覆盖==
,则还应覆盖Object的hashCode
getter。用于覆盖的一个例子==
和hashCode
,请参见 实施映射键。
有关覆盖的更多信息,请参阅 扩展类。
noSuchMethod()
要在代码尝试使用不存在的方法或实例变量时检测或做出反应,您可以覆盖noSuchMethod()
:
1 class A { 2 // Unless you override noSuchMethod, using a 3 // non-existent member results in a NoSuchMethodError. 4 @override 5 void noSuchMethod(Invocation invocation) { 6 print('You tried to use a non-existent member: ' + 7 '${invocation.memberName}'); 8 } 9 }
你不能调用,除非未实现的方法, 一个以下是真实的:
-
接收器具有静态类型
dynamic
。 -
接收器有一个静态类型,它定义了未实现的方法(抽象是OK),接收器的动态类型的实现与类
noSuchMethod()
中的实现不同Object
。
有关更多信息,请参阅非正式 noSuchMethod转发规范。
枚举类型
枚举类型(通常称为枚举或枚举)是一种特殊类,用于表示固定数量的常量值。
使用枚举
使用enum
关键字声明枚举类型:
enum Color { red, green, blue }
枚举中的每个值都有一个index
getter,它返回枚举声明中值的从零开始的位置。例如,第一个值具有索引0,第二个值具有索引1。
1 assert(Color.red.index == 0); 2 assert(Color.green.index == 1); 3 assert(Color.blue.index == 2);
要获取枚举中所有值的列表,请使用枚举values
常量。
1 Listcolors = Color.values; 2 assert(colors[2] == Color.blue);
您可以在switch语句中使用枚举,如果您不处理所有枚举值,您将收到警告:
1 var aColor = Color.blue; 2 3 switch (aColor) { 4 case Color.red: 5 print('Red as roses!'); 6 break; 7 case Color.green: 8 print('Green as grass!'); 9 break; 10 default: // Without this, you see a WARNING. 11 print(aColor); // 'Color.blue' 12 }
枚举类型具有以下限制:
- 您不能子类化,混合或实现枚举。
- 您无法显式实例化枚举。
有关更多信息,请参阅Dart语言规范。
向类添加功能:mixins
Mixins是一种在多个类层次结构中重用类代码的方法。
要使用 mixin,请使用with
关键字后跟一个或多个mixin名称。以下示例显示了两个使用mixins的类:
1 class Musician extends Performer with Musical { 2 // ··· 3 } 4 5 class Maestro extends Person 6 with Musical, Aggressive, Demented { 7 Maestro(String maestroName) { 8 name = maestroName; 9 canConduct = true; 10 } 11 }
要实现 mixin,请创建一个扩展Object的类,并且不声明构造函数。除非您希望mixin可用作常规类,否则请使用mixin
关键字而不是class
。例如:
1 mixin Musical { 2 bool canPlayPiano = false; 3 bool canCompose = false; 4 bool canConduct = false; 5 6 void entertainMe() { 7 if (canPlayPiano) { 8 print('Playing piano'); 9 } else if (canConduct) { 10 print('Waving hands'); 11 } else { 12 print('Humming to self'); 13 } 14 } 15 }
要指定只有某些类型可以使用mixin - 例如,所以你的mixin可以调用它没有定义的方法 - 用于on
指定所需的超类:
1 mixin MusicalPerformer on Musician { 2 // ··· 3 }
版本说明:mixin
Dart 2.1中引入了对关键字的支持。通常使用早期版本中的代码abstract class
。有关2.1 mixin更改的更多信息,请参阅 Dart SDK changelog和2.1 mixin规范。
类变量和方法
使用static
关键字实现类范围的变量和方法。
静态变量
静态变量(类变量)对于类范围的状态和常量很有用:
1 class Queue { 2 static const initialCapacity = 16; 3 // ··· 4 } 5 6 void main() { 7 assert(Queue.initialCapacity == 16); 8 }
静态变量在使用之前不会初始化。
注意: 此页面遵循 首选的常量名称的样式指南建议lowerCamelCase
。
静态方法
静态方法(类方法)不对实例进行操作,因此无权访问this
。例如:
1 import 'dart:math'; 2 3 class Point { 4 num x, y; 5 Point(this.x, this.y); 6 7 static num distanceBetween(Point a, Point b) { 8 var dx = a.x - b.x; 9 var dy = a.y - b.y; 10 return sqrt(dx * dx + dy * dy); 11 } 12 } 13 14 void main() { 15 var a = Point(2, 2); 16 var b = Point(4, 4); 17 var distance = Point.distanceBetween(a, b); 18 assert(2.8 < distance && distance < 2.9); 19 print(distance); 20 }
注意: 对于常用或广泛使用的实用程序和功能,请考虑使用顶级函数而不是静态方法。
您可以使用静态方法作为编译时常量。例如,您可以将静态方法作为参数传递给常量构造函数。
泛型
如果您查看基本数组类型的API文档 List,您会看到该类型实际上是List
。<...>表示法将List标记为 通用(或参数化)类型 - 具有正式类型参数的类型。按照惯例,大多数类型变量都有单字母名称,例如E,T,S,K和V.
为什么要使用泛型?
类型安全通常需要泛型,但它们比仅允许代码运行有更多好处:
- 正确指定泛型类型会产生更好的生成代码。
- 您可以使用泛型来减少代码重复。
如果您希望列表只包含字符串,则可以将其声明为List
(将其读作“字符串列表”)。这样,您,您的程序员和您的工具可以检测到将非字符串分配给列表可能是一个错误。这是一个例子:
1 var names = List(); 2 names.addAll(['Seth', 'Kathy', 'Lars']); 3 names.add(42); // Error
使用泛型的另一个原因是减少代码重复。泛型允许您在多种类型之间共享单个接口和实现,同时仍然利用静态分析。例如,假设您创建了一个用于缓存对象的接口:
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{ 2 T getByKey(String key); 3 void setByKey(String key, T value); 4 }
在此代码中,T是替身类型。它是一个占位符,您可以将其视为开发人员稍后定义的类型。
使用集合文字
可以参数化列表,集和Hash集合文字。参数化文字就像你已经看到的文字一样,除了你在开始括号之前添加 (对于列表和集合)或 (对于Hash集合)。以下是使用类型文字的示例:
1 var names =['Seth', 'Kathy', 'Lars']; 2 var uniqueNames = {'Seth', 'Kathy', 'Lars'}; 3 var pages = { 4 'index.html': 'Homepage', 5 'robots.txt': 'Hints for web robots', 6 'humans.txt': 'We are people, not machines' 7 };
使用带有构造函数的参数化类型
要在使用构造函数时指定一个或多个类型,请将类型放在<...>
类名称后面的尖括号()中。例如:
var nameSet = Set.from(names);
以下代码创建一个具有整数键和View类型值的映射:
var views = Map<int, View>();
通用集合及其包含的类型
Dart泛型类型的具体化,这意味着他们随身携带的类型信息在运行时。例如,您可以测试集合的类型:
1 var names = List(); 2 names.addAll(['Seth', 'Kathy', 'Lars']); 3 print(names is List ); // true
注意: 相反,Java中的泛型使用擦除,这意味着在运行时删除泛型类型参数。在Java中,您可以测试对象是否为List,但您无法测试它是否为a List
。
限制参数化类型
实现泛型类型时,您可能希望限制其参数的类型。你可以使用extends
。
1 class Foo{ 2 // Implementation goes here... 3 String toString() => "Instance of 'Foo<$T>'"; 4 } 5 6 class Extender extends SomeBaseClass {...}
使用SomeBaseClass
或其任何子类作为通用参数是可以的:
1 var someBaseClassFoo = Foo(); 2 var extenderFoo = Foo ();
也可以不指定泛型参数:
1 var foo = Foo(); 2 print(foo); // Instance of 'Foo'
指定任何非SomeBaseClass
类型会导致错误:
var foo = Foo
使用通用方法
最初,Dart的通用支持仅限于课程。一种称为泛型方法的新语法允许在方法和函数上使用类型参数:
1 T first(List ts) { 2 // Do some initial work or error checking, then... 3 T tmp = ts[0]; 4 // Do some additional checking or processing... 5 return tmp; 6 }
这里on first
(
)的泛型类型参数允许你T
在几个地方使用type参数:
- 在函数的返回类型(
T
)中。 - 在参数类型(
List
)中。 - 在局部变量的类型(
T tmp
)。
有关泛型的更多信息,请参阅 使用泛型方法。
Libraries和visibility
该import
和library
指令可以帮助您创建一个模块化的,可共享的代码库。库不仅提供API,还是隐私单元:以下划线(_)开头的标识符仅在库内可见。每个Dart应用程序都是一个库,即使它不使用library
指令。
可以使用包来分发库。 有关pub(包含在SDK中的包管理器)的信息,请参阅 Pub Package和Asset Manager。
使用库
使用import
指定如何从一个库中的命名空间在另一个库的范围内使用。
例如,Dart Web应用程序通常使用dart:html 库,它们可以像这样导入:
import 'dart:html';
唯一需要的参数import
是指定库的URI。对于内置库,URI具有特殊dart:
方案。对于其他库,您可以使用文件系统路径或package:
方案。该package:
方案指定由包管理器(如pub工具)提供的库。例如:
import 'package:test/test.dart';
注意: URI代表统一资源标识符。 URL(统一资源定位符)是一种常见的URI。
指定库前缀
如果导入两个具有冲突标识符的库,则可以为一个或两个库指定前缀。例如,如果library1和library2都有一个Element类,那么你可能有这样的代码:
1 import 'package:lib1/lib1.dart'; 2 import 'package:lib2/lib2.dart' as lib2; 3 4 // Uses Element from lib1. 5 Element element1 = Element(); 6 7 // Uses Element from lib2. 8 lib2.Element element2 = lib2.Element();
仅导入库的一部分
如果只想使用库的一部分,则可以有选择地导入库。例如:
1 // Import only foo. 2 import 'package:lib1/lib1.dart' show foo; 3 4 // Import all names EXCEPT foo. 5 import 'package:lib2/lib2.dart' hide foo;
懒惰地加载一个库
延迟加载(也称为延迟加载)允许应用程序根据需要加载库,如果需要的话。以下是您可能使用延迟加载的一些情况:
- 减少应用程序的初始启动时间。
- 例如,执行A / B测试 - 尝试算法的替代实现。
- 加载很少使用的功能,例如可选的屏幕和对话框。
要懒洋洋地加载库,必须先使用它导入它deferred as
。
import 'package:greetings/hello.dart' deferred as hello;
当您需要库时,loadLibrary()
使用库的标识符进行调用 。
1 Future greet() async { 2 await hello.loadLibrary(); 3 hello.printGreeting(); 4 }
在前面的代码中,await
关键字暂停执行,直到加载库。有关详细信息async
,并await
请参阅异步支持。
您可以loadLibrary()
在库上多次调用而不会出现问题。该库只加载一次。
使用延迟加载时请记住以下内容:
- 延迟库的常量不是导入文件中的常量。请记住,在加载延迟库之前,这些常量不存在。
- 您不能在导入文件中使用延迟库中的类型。相反,请考虑将接口类型移动到由延迟库和导入文件导入的库。
- Dart隐式插入
loadLibrary()
您使用的命名空间。该函数返回Future。deferred asnamespace
loadLibrary()
Dart VM差异: 即使在调用之前,Dart VM也允许访问延迟库的成员loadLibrary()
。此行为可能会更改,因此 不要依赖于当前的VM行为。 有关详细信息,请参阅问题#33118。
实现库
有关 如何实现库包的建议,请参阅 创建库包,包括:
- 如何组织库源代码。
- 如何使用该
export
指令。 - 何时使用该
part
指令。 - 何时使用该
library
指令。
异步支持
Dart库中包含许多返回Future或Stream对象的函数。这些函数是异步的:它们在设置可能耗时的操作(例如I / O)后返回,而不等待该操作完成。
在async
和await
关键字支持异步编程,让你写异步代码看起来类似于同步代码。
处理期货
当您需要完成Future的结果时,您有两个选择:
- 使用
async
和await
。 - 使用Future API,如 库浏览中所述。
使用async
和await
异步的代码,但它看起来很像同步代码。例如,这里有一些代码await
用于等待异步函数的结果:
await lookUpVersion();
要使用await
,代码必须在异步函数中 - 标记为的函数async
:
1 Future checkVersion() async { 2 var version = await lookUpVersion(); 3 // Do something with version 4 }
注意: 虽然异步函数可能会执行耗时的操作,但它不会等待这些操作。相反,异步函数只在遇到第一个await
表达式(详细信息)时执行。然后它返回一个Future对象,仅在await
表达式完成后才恢复执行。
使用try
,catch
和finally
处理使用await
以下代码的错误和清理:
1 try { 2 version = await lookUpVersion(); 3 } catch (e) { 4 // React to inability to look up the version 5 }
您可以await
在异步功能中多次使用。例如,以下代码等待三次函数结果:
1 var entrypoint = await findEntrypoint(); 2 var exitCode = await runExecutable(entrypoint, args); 3 await flushThenExit(exitCode);
在,价值通常是未来; 如果不是,那么该值将自动包装在Future中。此Future对象表示返回对象的承诺。值是返回的对象。await表达式使执行暂停,直到该对象可用。await expression
expression
await expression
如果在使用时出现编译时错误await
,请确保await
处于异步功能中。 例如,要await
在您的应用程序的main()
功能中使用,main()
必须将正文标记为async
:
1 Future main() async { 2 checkVersion(); 3 print('In main: version is ${await lookUpVersion()}'); 4 }
声明异步函数
一个异步函数是一个函数体标有async
修改。
将async
关键字添加到函数使其返回Future。例如,考虑这个同步函数,它返回一个String:
String lookUpVersion() => '1.0.0';
如果将其更改为异步函数 - 例如,因为将来的实现将非常耗时 - 返回的值是Future:
FuturelookUpVersion() async => '1.0.0';
请注意,函数的主体不需要使用Future API。如有必要,Dart会创建Future对象。
如果您的函数没有返回有用的值,请设置其返回类型Future
。
处理流
当您需要从Stream获取值时,您有两个选择:
- 使用
async
和异步for循环(await for
)。 - 使用Stream API,如 库浏览中所述。
注意: 在使用之前await for
,请确保它使代码更清晰,并且您确实希望等待所有流的结果。例如,你通常应该不使用await for
的UI事件侦听器,因为UI框架发送事件的人流如潮。
异步for循环具有以下形式:
await for (varOrType identifier in expression) { // Executes each time the stream emits a value. }
值expression
必须具有Stream类型。执行过程如下:
- 等到流发出一个值。
- 执行for循环的主体,将变量设置为该发出的值。
- 重复1和2,直到关闭流。
要停止侦听流,可以使用break
or return
语句,该for语句会从for循环中断开并从流中取消取消。
如果在实现异步for循环时遇到编译时错误,请确保await for
它处于异步函数中。 例如,要在应用程序的main()
函数中使用异步for循环,main()
必须将正文标记为async
:
1 Future main() async { 2 // ... 3 await for (var request in requestServer) { 4 handleRequest(request); 5 } 6 // ... 7 }
有关异步编程的更多信息,请参阅 库浏览的 dart:async部分。另请参阅文章 Dart语言异步支持:阶段1 和Dart语言异步支持:阶段2和Dart语言规范。
Generators
当您需要懒惰地生成一系列值时,请考虑使用生成器函数。Dart内置支持两种Generators功能:
- 同步生成器:返回一个Iterable对象。
- 异步生成器:返回Stream对象。
要实现同步生成器函数,请将函数体标记为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。
可调用的类
要允许像函数一样调用Dart类,请实现该call()
方法。
在下面的示例中,WannabeFunction
该类定义了一个call()函数,它接受三个字符串并连接它们,用空格分隔每个字符串,并附加一个感叹号。单击运行按钮以执行代码。
有关处理函数类的更多信息,请参阅 在Dart中模拟函数。
分离
大多数计算机,即使在移动平台上,也有多核CPU。为了利用所有这些核心,开发人员传统上使用并发运行的共享内存线程。但是,共享状态并发容易出错,并且可能导致代码复杂化。
所有Dart代码都在隔离区内运行,而不是线程。每个隔离区都有自己的内存堆,确保不会从任何其他隔离区访问隔离区的状态。
有关更多信息,请参阅 dart:isolate库文档。
类型定义
在Dart中,函数是对象,就像字符串一样,数字是对象。一个类型定义,或功能型的别名,给出了一个函数类型声明字段时,您可以使用和返回类型的名称。当函数类型分配给变量时,typedef会保留类型信息。
请考虑以下代码,它不使用typedef:
1 class SortedCollection { 2 Function compare; 3 4 SortedCollection(int f(Object a, Object b)) { 5 compare = f; 6 } 7 } 8 9 // Initial, broken implementation. 10 int sort(Object a, Object b) => 0; 11 12 void main() { 13 SortedCollection coll = SortedCollection(sort); 14 15 // All we know is that compare is a function, 16 // but what type of function? 17 assert(coll.compare is Function); 18 }
当分配类型信息丢失f
到compare
。类型 f
是(Object,
Object)
→ int
(其中→表示返回),但类型compare
是功能。如果我们将代码更改为使用显式名称并保留类型信息,则开发人员和工具都可以使用该信息。
1 typedef Compare = int Function(Object a, Object b); 2 3 class SortedCollection { 4 Compare compare; 5 6 SortedCollection(this.compare); 7 } 8 9 // Initial, broken implementation. 10 int sort(Object a, Object b) => 0; 11 12 void main() { 13 SortedCollection coll = SortedCollection(sort); 14 assert(coll.compare is Function); 15 assert(coll.compare is Compare); 16 }
注意: 目前,typedef仅限于函数类型。我们希望这会改变。
因为typedef只是别名,所以它们提供了一种检查任何函数类型的方法。例如:
1 typedef Compare= int Function(T a, T b); 2 3 int sort(int a, int b) => a - b; 4 5 void main() { 6 assert(sort is Compare<int>); // True! 7 }
元数据
使用元数据提供有关代码的其他信息。元数据注释以字符开头@
,后跟对编译时常量的引用(如deprecated
)或对常量构造函数的调用。
所有Dart代码都有两个注释:@deprecated
和 @override
。有关使用的示例@override
,请参阅扩展类。以下是使用@deprecated
注释的示例:
1 class Television { 2 /// _Deprecated: Use [turnOn] instead._ 3 @deprecated 4 void activate() { 5 turnOn(); 6 } 7 8 /// Turns the TV's power on. 9 void turnOn() {...} 10 }
您可以定义自己的元数据注释。这是一个定义带有两个参数的@todo注释的示例:
1 library todo; 2 3 class Todo { 4 final String who; 5 final String what; 6 7 const Todo(this.who, this.what); 8 }
以下是使用@todo注释的示例:
1 import 'todo.dart'; 2 3 @Todo('seth', 'make this do something') 4 void doSomething() { 5 print('do something'); 6 }
元数据可以出现在库,类,typedef,类型参数,构造函数,工厂,函数,字段,参数或变量声明之前以及导入或导出指令之前。您可以使用反射在运行时检索元数据。
注释
Dart支持单行注释,多行注释和文档注释。
单行注释
单行注释以//
。开头。//
Dart编译器会忽略行之间和行尾的所有内容。
1 void main() { 2 // TODO: refactor into an AbstractLlamaGreetingFactory? 3 print('Welcome to my Llama farm!'); 4 }
多行注释
多行注释以... /*
结尾*/
。介于两者之间的/*
,并*/
用Dart编译器忽略(除非该注释是一个文档注释;见下一节)。多行注释可以嵌套。
1 void main() { 2 /* 3 * This is a lot of work. Consider raising chickens. 4 5 Llama larry = Llama(); 6 larry.feed(); 7 larry.exercise(); 8 larry.clean(); 9 */ 10 }
文档注释
文档注释是首先多行或单行注释///
或/**
。使用///
连续的行上有一个多行文档注释同样的效果。
在文档注释中,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 8 /// Feeds your llama [Food]. 9 /// 10 /// The typical llama eats one bale of hay per week. 11 void feed(Food food) { 12 // ... 13 } 14 15 /// Exercises your llama with an [activity] for 16 /// [timeLimit] minutes. 17 void exercise(Activity activity, int timeLimit) { 18 // ... 19 } 20 }
在生成的文档中,[Food]
成为Food类的API文档的链接。
要解析Dart代码并生成HTML文档,您可以使用SDK的 文档生成工具。 有关生成的文档的示例,请参阅Dart API文档。有关如何构建注释的建议,请参阅 Dart Doc注释指南。