导览
Dart编程语言导览
本文展示如何使用各个主要Dart功能,从变量、运算符到类和库,这里假定你已经至少了解如何使用一种其它编程语言。
要了解更多有关 Dart核心库的知识,请参见核心库导览。在想要了解更多有关语言功能的时候,请查询Dart语言规范。
小贴士: 你可以借助DartPad来使用Dart语言的大部分功能 (了解更多)。
一个Dart基础程序
以下代码使用了Dart的大量最基础的功能:
// 定义一个函数
printInteger(int aNumber) {
print('The number is $aNumber.'); // 打印到控制台
}
// 这是应用开始执行的地方。
main() {
var number = 42; // 声明和初始化一个变量
printInteger(number); // 调用函数
}
以下是这个程序所使用到的可用于所有(或几乎所有)的Dart应用的部分:
// 这是一条注释
单行注释。Dart还支持多行注释和文件注释。更多详情,请参见 注释.
int
一种类型。其它的一些 内置类型 有 String
, List
和 bool
。
42
>
一个数字字面量。数字字面量是一种编译时常量。
print()
一种显示输出的方便的方式。
'...'
(or "..."
)
一个字符串字面量。
$*variableName*
(或 ${*expression*}
)
插值字符串:包含等价于字符串字面量的变量或表达式的字符串。更多相关信息,请参见 字符串.
main()
在应用执行开始处的特殊的、必须的顶级函数。更多相关信息,请参见 main()函数.
var
一种无需指定类型声明变量的方式。
Note: 本站的代码遵循 Dart 样式指南中的准则。
重要概念
在学习Dart编程语言的过程中,请在心中保持以下事实和概念:
- 在变量中放置的所有内容都是对象,并且每个对象都是一个类的实例。包括数字、函数和
null
都是对象。所有的对象都继承自 Object 类。 - 虽然Dart是强类型语言,类型标注却是可选的,因为Dart可以推导类型。在以上的代码中,
number
所推导的类型是int
。在你想要显式地说明无需设定类型, 使用特殊类型dynamic
. - Dart支持泛型,如
List<int>
(整型列表) 或List<dynamic>
(任意类型对象的列表)。 - Dart支持顶级函数(如
main()
),以及与类或对象绑定的函数 (分别为静态和实例方法)。你也可以在函数内创建函数(嵌套或局部函数)。 - 类型地,Dart支持顶级变量,以及与类或对象绑定的变量(静态和实例变量)。实例变量有时也称为字段或属性。
- 不同于Java,Dart没有
public
,protected
和private
这些关键字。 如果一个标识符以下划线(_)开头,它是对库私有的。详情请参见 库和可见性. - 标识符可以字母或下划线 (_)开头,后接这些字符和数字的任意组合。
- Dart既有表达式(拥有运行时值)也有语句(没有运行时值)。例如, 条件表达式
condition ? expr1 : expr2
有一个值expr1
或expr2
。对比 if-else 语句则没有值。一条语句通常包含一个或多个表达式,但一个表达式不能直接包含一条语句。 - Dart工具可以报出两类问题:警告(warning)和错误(error) 。警告只是表明你的代码可能无法运行,但并不会阻止程序的执行。错误可以是编译时或运行时的。编译时错误会完全阻止代码的执行,运行时错误会在代码执行时导致 异常 的抛出。
关键字
下表中列出了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版本之后添加的与异步支持相关的更新的、有限的保留词。你无法使用
async
,async*
或sync*
标记的函数体内使用await
或yield
作为标识符。
表中的其它词都是保留词,无法用作标识符。
变量
以下是一个创建变量并初始化的示例:
var name = 'Bob';
变量存储引用。名为name
的变量包含一个对值为”Bob”的String
对象的引用。
变量name
的类型推导为String
,但你可以通过指定类型来进行修改。如果对象不仅限于单个类型,按照设计指南将其指定为Object
或dynamic
类型。
dynamic name = 'Bob';
另一个选项是显式地将其声明为所要推导的类型:
String name = 'Bob';
Note: 本页中对局部变量按照 样式指南推荐 使用 var
,而非类型注解。
默认值
未初始过的值有一个初始值null
。即使是带有数值类型的变量初始值也是null,因为数值类型和Dart中的所有内容一样,都是对象。
int lineCount;
assert(lineCount == null);
Note: 生产代码会忽略 assert()
调用。而在开发期间,当 if 条件为假时 assert(*condition*)
会抛出一个异常。详情请参见 Assert。
Final和const
如果你不想要修改一个变量,使用 final
或 const
来替代 var
或加前类型前。final变量只能设置一次,const变量是编译时常量。(const变量是隐式的final)。final顶级或类变量在初次使用时进行初始化。
Note: 实例变量可以是 final
,但不能为 const
。final实例变量必须在构造函数体开始之前进行初始化,通过构造函数参数在变量声明中或在构造函数的初始化程序列表中。
以下是一个创建和设置final变量的示例:
final name = 'Bob'; // 无类型标注
final String nickname = 'Bobby';
你无法修改final变量的值:
name = 'Alice'; // Error: a final variable can only be set once.
对你希望是编译时常量的变量使用const
。如果const变量是类级别的,将其标记为 static const
。在声明变量之处,设置值为编译时常量,如数值或字符串字面量、const变量或对常数的算术运算结果:
const bar = 1000000; // 单位压强 (dynes/cm2)
const double atm = 1.01325 * bar; // 标准大气
const
关键字不只是为声明常变量的。你还可以使用它来创建常量值,以及声明创建常量值的构造函数。任意变量可拥有常量值。
var foo = const [];
final bar = const [];
const baz = []; // 等价于 const []
你可以在const
声明的初始化表达式中省略 const
,像上面的 baz
。详情请参见 不要重复使用const.
你可以修改一个非final、非const变量的值,即使它曾经是一个const值:
foo = [1, 2, 3]; // 曾经是const []
但是不能修改const变量的值:
baz = [42]; // Error: Constant variables can't be assigned a value.
更多有关使用 const
创建常量值的内容,请参见 列表, 映射 和 类。
内置类型
Dart语言拥有对如下类型的特别支持:
- 数值
- 字符串
- 布尔型
- 列表 (也称为数组)
- 集
- 映射
- rune (用于在字符串表示Unicode字符串)
- 符号
你可以使用字面量初始化任意这些特殊类型的对象。例如, 'this is a string'
是一个字符串字面量, true
是一个布尔型字面量。
因为Dart中的每个变量都引用一个对象 – 一个类的实例,通常可以使用构造函数来初始化变量。一些内置类型有它们自己的构造函数。例如,你可以使用 Map()
构造函数来创建一个映射。
数值
Dart有两种数值类型:
整型值根据平台不大于64位。在 Dart VM中,值可以为 -263 到 263 – 1。编译为JavaScript的Dart使用 JavaScript数值, 允许的值为 -253 到 253 – 1。
64位(双精度)浮点数值,如IEEE 754 标准中所描述。
int
和 double
是 num
的子类型。num类型包含基本运算符如 +, -, / 和 *,也包含其它方法中的 abs()
,ceil()
和 floor()
。(位相关的运算符,如 >>,在 int
类中定义)。 如果num及其子类型中没有你所要寻找的,可以使用 dart:math 库。
整型是不包含小数点的数字。以下是一些定义整型字面量的示例:
var x = 1;
var hex = 0xDEADBEEF;
如果数值中包含小数点,则为double类型。以下是一些定义double字面量的示例:
var y = 1.1;
var exponents = 1.42e5;
在 Dart 2.1中,整型字面在需要时会自动转化为double值:
double z = 1; // 等价于 z = 1.0.
Version note: 在Dart 2.1之前,在double上下文中使用整型字面量会报错。
以下是如何将字符串转化为数字及其反向操作:
// 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类型指定了传统的按钮移动 (<<, >>)、与 (&)和 或 (|)运算符。例如:
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;
字符串
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!');
Note: ==
运算符用于测试两个对象是否相等。如果两个字符串拥有相同序列的代码单元则相等。
可以使用相邻字符串字面量或者了 +
运算符来拼接字符串:
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.';
参见 Rune 来获取如何在字符串中表达Unicode字符的详情。
字符串字面量是编译时常量,仅需编译时常量中的插值表达式运行结果为null或数值、字符串或布尔值。
// 这些可以在一个const字符串中使用
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';
// 这些不可以在一个const字符串中使用
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';
更多有关使用字符串的信息,参见 字符串和正则表达式。
布尔型
要表现布尔值,Dart中有一个名为bool
的类型。仅有两个对象的类型为bool:布尔型字面量true
和 false
,它们都是编译时常量。
Dart的类型安全意味着你不能使用if (*nonbooleanValue*)
或 assert (*nonbooleanValue*)
这样的代码。而是要显式的检查值,类似这样:
// 检查空字符串
var fullName = '';
assert(fullName.isEmpty);
// 检查0值
var hitPoints = 0;
assert(hitPoints <= 0);
// 检查null值
var unicorn;
assert(unicorn == null);
// 检查NaN
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);
列表
或许在几乎所有编程语言中最常见的集合(collection)都是数组,或有序的对象组。在Dart,数组是List对象,因此大部分会称其为列表。
Dart的列表字面量和JavaScript中的数组字面量很像。以下是一个简单的Dart列表:
var list = [1, 2, 3];
Note: Dart推导 list
的类型为 List<int>
。如果你尝试向这个列表中添加非整型对象,分析器或运行时会抛出错误。更多信息请阅读 类型推导。
列表使用基于0的索引,即0是第1个元素的索引,并且 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; // 取消这行的注释会导致报错
Dart 2.3 引入了展开运算符(...
) 及判空展开运算符 (...?
),提供一种向集合插入多个元素的简洁方式。
例如,你可以使用展开运算符 (...
) 来将列表中的所有元素插入到另一个列表中:
var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);
如果展开运算符右侧的表达式有可能为null,可通过使用可判空展开运算符(...?
)来避免异常:
var list;
var list2 = [0, ...?list];
assert(list2.length == 1);
更多有关使用展开运算符的详情和示例,参见展开运算符提议。
Dart 2.3 还引入了collection if 和 collection for,可以使用条件(if
)和循环 (for
)来构建使用条件(if
)和循环 (for
)的集合。
以下是使用collection if来创建一个包含3项或4项的列表::
var nav = [
'Home',
'Furniture',
'Plants',
if (promoActive) 'Outlet'
];
以下是使用collection for来在将它们添加到另一个列表之前操作列表项的示例:
var listOfInts = [1, 2, 3];
var listOfStrings = [
'#0',
for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');
更多有关使用collection if和for的示例,参见 控制流collection建议。
列表类型有很多操作列表的便捷的方法。有关列表更多的信息,请参见泛型 和 集合.
集
Dart中的集(set)是一个包含独立项的无序集合。Dart对集的支持由集字面量和Set类型所提供。
Version note: 虽然Set类型一直是Dart的核心部分,集字面量在Dart 2.2中才引入。
以下是一个简单的Dart集,使用集字面量创建:
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
Note: Dart推导 halogens
的类型为 Set<String>
。 如果你尝试向集添加错误类型的值,分析器或运行时会抛出错误。更多信息,请阅读类型推导部分。
要创建空集,使用带有类型前缀的{}
或向类型为Set
的变量赋值{}
:
var names = <String>{};
// Set<String> names = {}; // 这也同样起作用
// var names = {}; // 创建一个映射,而非集
集或映射? 映射字面量的语法类型集字面量。因为映射字面量的优先级更高, {}
的默认为 Map
类型。如果你忘记对 {}
进行类型标注,或者它所赋值的变量,那么Dart会创建一个类型为 Map<dynamic, dynamic>
的对象。
使用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'); // 取消本行注释会导致报错
在Dart 2.3中,集也像列表一样支持展开运算符 (...
和 ...?
) 以及 collection if 和 for。更多信息,参见 列表展开运算符 和 列表集合运算符 部分的讨论。
映射
总的来说,映射是一个关联键和值的对象。键和值都可以为任意对象类型。每个键仅出现一次,但相同值可出现多次。Dart对映射的支持由映射字面量和 Map 类型所提供。
以下是一些简单的Dart映射,使用映射字面量创建:
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
Note: Dart推导 gifts
的类型为 Map<String, String>
,而nobleGases
的类型为 Map<int, String>
。如果你尝试对任一映射添加错误类型的值,分析器或运行时会抛出错误。更多相关信息,请参阅 类型推导。
可以使用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';
Note: 你可能会希望看到 new Map()
而非仅仅是 Map()
。从Dart 2中,关键字 new
为可选部分。更多详情,参见使用构造函数。
和JavaScript中一样向已有映射添加键值对:
var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds'; // 添加一个键值对
以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'; // 取消本行注释会导致报错
在Dart 2.3中,映射像列表一样支持展开运算符 (...
和 ...?
) 及collection if 和 for。有关详情及示例,参见 展开运算符提议 和 控制流集合提议。
Runes
Dart中,runes是字符串的UTF-32代码点。
Unicode为全球书写系统中的每个字母、数字和符号定义了一个独立的数值。因为Dart字符串是一个UTF-16代码单元的序列,在字符串中表达32位Unicode值要求有特殊的语法。
通常表达一个Unicode代码点的方式为 \uXXXX
,其中XXXX是一个4位数的16进制值。例如,心形字符串 (♥) 为 \u2665
。要指定比4位16进制更多或更少的位数,需将值放在花括号中。例如,笑脸emoji (😆) 为 \u{1f600}
。
String 类有多个可用于提取rune信息的属性。 codeUnitAt
和 codeUnit
属性返回16-位代码单元。使用 runes
属性来获取字符串的rune。
以下示例描述runes、16-位代码单元以及32-位代码单元之间的关系。点击 Run 来实时查看rune。
Note: 在使用列表运算符操作runes时要小心。这种方法根据具体语言、字符集和操作可能会很容易崩溃。更多相关信息,参见Stack Overflow上的 如何倒排Dart中的字符串? 。
符号
符号(Symbol) 对象表示在Dart程序中声明的一个运算符或标识符。你可能永远都不会需要用到符号,但它们对于通过名称引用标识符的 API 有无限的价值,因为最小化会修改标识符的名称但不修改标识符符号。
使用符号字面量获取一个标识符的符号,它只是#
后接标识符。
#radix
#bar<code class="language-nocode">
符号字面量是编译时常量。
函数
Dart是一个真面向对象语言,因此即使是函数也是对象,并且拥有一个类型Function。这表示函数可赋值给变量或作为参数传递给其它函数。也可以调用将Dart类的实例看作函数来进行调用。更多详情,参见 可调用类。
以下是实现一个函数的示例:
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
虽然高效Dart推荐 为公有 API 进行类型标注,函数则省略类型时依然有效:
isNoble(atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
对于仅包含一个表达式的函数,可以使用简写语法:
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
=> *expr*
语法是{ return *expr*; }
的简写。 =>
标记有时被称为箭头语法。
Note: 仅有一个表达式 – 而不是一条语句 – 可以出现在箭头(=>)和分号(;)之间。例如,你不能在这里放置一条 if语句, 条件表达式。
函数可以有两种类型的参数:必选和可选。必选参数放在前面,后接可选参数。可选参数可为命名参数或位置参数。
Note: 有些API – 尤其是Flutter widget构造函数 – 即使用对改造参数也仅使用命名参数。参见下面一节了解更多详情。
可选参数
可选参数可以为命名参数或位置参数,或者是两者都有。
命名参数
在调用一个函数时,你可以使用*paramName*: *value*
指定命名参数。例如:
enableFlags(bold: true, hidden: false);
在定义函数时,使用 {*param1*, *param2*, …}
来指定命名参数:
/// 设置bold 和 hidden标记 ...
void enableFlags({bool bold, bool hidden}) {...}
虽然命名参数是一种可选参数,通过可以@required 来标记它们以表示该参数是必须 – 即用户必须为该参数提供一个值。例如:
const Scrollbar({Key key, @required Widget child})
如果有人尝试不指定child
参数就创建一个Scrollbar
,那么分析器会报出问题。
使用@required 标注,需要依赖meta 包并导入 package:meta/meta.dart
。
位置参数
将一组函数参数封装在 []
中,会将它们标记为可选位置参数:
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');
以下是使用了第3个参数调用该函数的示例:
assert(say('Bob', 'Howdy', 'smoke signal') ==
'Bob says Howdy with a smoke signal');
默认参数值
函数可以使用 =
来为命名参数和位置参数定义默认值。默认值必须为编译时常量。如未提供默认值,则默认值为 null
。
以下是一个为命名参数设置默认值的示例:
/// 设置bold 和 hidden标记 ...
void enableFlags({bool bold = false, bool hidden = false}) {...}
// bold将为true; hidden将为false
enableFlags(bold: true);
Deprecation note: 老代码中可能会使用冒号 (:
) 代替 =
来设置命名函数的默认值。原因是原来命名函数中仅支持 :
。该支持可能会被淘汰,所以我们推荐使 用=
来指定默认值。
下面的示例展示如何为位置参数设置默认值:
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');
也可以传递列表或映射作为默认值。以下示例定义了一个函数doStuff()
,它为 list
参数指定了一个默认列表并为 gifts
参数指定了一个默认映射。
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');
}
main()函数
每个应用必须有一个顶级的 main()
函数,它作为应用的入口。 main()
函数返回 void
并有一个可选参数l List<String>
。
以下是一个针对web应用的 main()
函数示例:
void main() {
querySelector('#sample_text_id')
..text = 'Click me!'
..onClick.listen(reverseText);
}
Note: 以下代码中 ..
语法称作一个 级联。通过级联,你可以对单个对象的成员执行多个操作。
以下是一个针对接收参数的命令行应用的main()
函数示例:
// 像这样运行它: dart args.dart 1 test
void main(List<String> arguments) {
print(arguments);
assert(arguments.length == 2);
assert(int.parse(arguments[0]) == 1);
assert(arguments[1] == 'test');
}
可以使用 args库 来定义和解析命令行参数。
函数作为一等对象
你可以传递函数来作为另一个函数的参数。例如:
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
// 传递 printElement作为参数
list.forEach(printElement);
可将函数赋值给变量,如:
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');
该例使用了匿名函数,更多相关内容请见下一节。
匿名函数
大部分函数都是命名函数,如 main()
或 printElement()
。你也可创建一个无名称函数,称为匿名函数,有时也称为lambda 或闭包。可以将匿名函数赋值给变量,那么例如就可以从集合中添加或删除它。
匿名函数类似于命名函数 – 0或多个参数,在括号中由逗号和可选选类型标注分隔。
后面的代码块中包含函数体:
([[*Type*] *param1*[, …]]) { *codeBlock*; };
以下示例定义了一个带有无类型参数 item
的匿名函数。对列表中的第一项所调用的函数,打印包含在指定索引处的值的字符串。
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
点击 Run 执行下面的代码。
如果函数仅包含一条语句,可将其简化为使用箭头标记。将如下行拷贝到DartPad中并点击Run来验证它的功能是相同的。
list.forEach(
(item) => print('${list.indexOf(item)}: $item'));
词法作用域
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()
是如何使用每个级别的变量的,一路到顶级的变量。
词法闭包
闭包是一个即使函数位置原作用域之外也可在词法作用域中访问变量的函数对象。
函数可封闭周围作用域中所定义的变量。在以下示例中, makeAdder()
捕获变量 addBy
。不论返回的函数放在哪里,它都能记住 addBy
。
/// 返回一个将addBy添加到函数参数中的函数
Function makeAdder(num addBy) {
return (num i) => addBy + i;
}
void main() {
// 创建一个加2的函数
var add2 = makeAdder(2);
// 创建一个加4的函数
var add4 = makeAdder(4);
assert(add2(3) == 5);
assert(add4(3) == 7);
}
测试函数是否相等
下面是一个测试顶级函数、静态方法和实例方法是否相等的示例:
void foo() {} // 一个顶级函数
class A {
static void bar() {} // 一个静态方法
void baz() {} // 一个实例方法
}
void main() {
var x;
// 对比顶级函数
x = foo;
assert(foo == x);
// 对比静态方法
x = A.bar;
assert(A.bar == x);
// 对比实例方法
var v = A(); // A 的实例 #1
var w = A(); // A 的实例 #2
var y = w;
x = w.baz;
// 这些闭包引用相同的实例 (#2),
// 因此它们相等。
assert(y.baz == x);
// 这些闭包引用不同的实例,
// 因此它们不相等。
assert(v.baz != w.baz);
}
返回值
所有的函数都会返回值。如果未指定返回值,语句 return null;
会隐式地附加到函数体中。
foo() {}
assert(foo() == null);
运算符
Dart定义了下表中显示的运算符。你可以重载很多运算符,在重载运算符中进行了描述。
描述 | 运算符 | ||
---|---|---|---|
一元后置 |
*expr*++ *expr*-- () [] . ?.
|
||
一元前置 |
-*expr* !*expr* ~*expr* ++*expr* --*expr*
|
||
乘除 |
* / % ~/
|
||
加减 |
+ -
|
||
按位移 |
<< >> >>>
|
||
按位与 | & |
||
按位异或 | ^ |
||
按位或 | ` | ` | |
关系和类型测试 |
>= > <= < as is is!
|
||
等于 |
== !=
|
||
逻辑与 | && |
||
逻辑或 | ` | ` | |
判空(null) | ?? |
||
条件 | *expr1* ? *expr2* : *expr3* |
||
级联 | .. |
||
赋值 |
= *= /= += -= &= ^= etc.
|
Warning: 运算符优先级是对Dart解析器行为的估计。要获取确定性的答案,参见Dart语言规范中的语法。
在使用运算符时,可创建表达式。以下是一些运算符表达式的示例:
a++
a + b
a = b
a == b
c ? a : b
a is T
在运算符表格中, 每个运算符都比下面一行的运算符优先级要高。比如乘法运算符 %
的优先级高于(因此先执行)等号运算符 ==
,而它的优先级又高于逻辑与运算符 &&
。这个优先级表示以下两行代码的执行方式相同:
// 括号提升可读性
if ((n % i == 0) && (d % i == 0)) ...
// 更验阅读,但是等价的
if (n % i == 0 && d % i == 0) ...
Warning: 对于使用两个运算项的运算符,最左侧的运算项决定使用哪个版本的运算符。例如,如果有一个Vector对象和一个Point对象, aVector + aPoint
使用Vector版本的+。
算术运算符
Dart支持常见算术运算符,如下表所示:
运算符 | 含义 |
---|---|
+ |
加 |
– |
减 |
-*expr* |
一元减,也称为否定(反转表达式的符号) |
* |
乘 |
/ |
除 |
~/ |
整除,返回一个整数结果 |
% |
获取整数相除的余数(模) |
示例:
assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // 结果是双精度类型
assert(5 ~/ 2 == 2); // 结果是整型
assert(5 % 2 == 1); // 余数
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* ) |
示例:
var a, b;
a = 0;
b = ++a; // 在b获取值之前对a递增
assert(a == b); // 1 == 1
a = 0;
b = a++; // 在b获取值之后对a递增
assert(a != b); // 1 != 0
a = 0;
b = --a; // 在b获取值之前对a递减
assert(a == b); // -1 == -1
a = 0;
b = a--; // 在b获取值之后对a递减
assert(a != b); // -1 != 0
比较和关系运算符
下表中列出了比较和关系运算符的含义。
运算符 | 含义 |
---|---|
== |
等于,参见下面的讨论 |
!= |
不等于 |
> |
大于 |
< |
小于 |
>= |
大于等于 |
<= |
小于等于 |
测试两个对象x 和 y 是否表示相同的内容,使用 ==
运算符。(在极少的情况中,需要知道两个对象是否是完全相同的对象,转而使用 identical()函数。) 以下展示 ==
运算符如何运行:
- 如果x 或 y 为null,如果两者都为null则返回true,如果只一个为null则返回false。
- 返回
*x*.==(*y*)
方法运行的结果 。(没错,==
这样的运算符是对它们的第一项所调用的方法。你甚至可以重载很多运算符,包括==
, 可参见 重载运算符。)
以下是使用每个比较和关系运算符的示例:
assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(2 <= 3);
类型测试运算符
as
, is
和 is!
运算符对于在运行时检查类型非常方便。
运算符 | 含义 |
---|---|
as |
对象类型转换 (也用于指定 库前缀) |
is |
如果对象有指定的类型时为true |
is! |
如果对象有指定的类型时为false |
如果obj
实现了T
所指定的接口的话则 obj is T
的结果为 true。例如,obj is Object
永远为true。
使用 as
运算符将对象转换为指定类型。通常,应使用它作为对象is
测试的简写,后接使用该对象的表达式。例如,考虑使用如下代码:
if (emp is Person) {
// 类型检查
emp.firstName = 'Bob';
}
可以使用 as
运算符让代码更精简:
(emp as Person).firstName = 'Bob';
Note: 两种代码并不等同。如果 emp
为null 或者不是Person,第1个例子(带is
)不会做任何事,第二个(带as
)抛出异常。
赋值运算符
如你所见,可以使用=
运算符进行赋值。仅为值为null的变量赋值时,使用 ??=
运算符。
// 为a赋值
a = value;
// 若b为null时为 b 赋值,否则,b 保持不变
b ??= value;
复合赋值运算符如 +=
将运算和赋值合并在一起。
| =
| –=
| /=
| %=
| >>=
| ^=
|
| +=
| *=
| ~/=
| <<=
| &=
| |=
|
以下是复合赋值运算符运行的方式:
复合赋值 | 比较表达式 | |
---|---|---|
对于运算符 op: | a *op*= b |
a = a *op* b |
示例: | a += b |
a = a + b |
下例中使用赋值和复合赋值运算符:
var a = 2; // 使用 =赋值
a *= 3; // 赋值和乘法: a = a * 3
assert(a == 6);
逻辑运算符
可以使用逻辑运算符取反或合并布尔表达式。
运算符 | 含义 | ||
---|---|---|---|
!*expr* |
对紧接着的表达式进行取反(修改false 为 true,反之亦然) | ||
` | ` | 逻辑或 | |
&& |
逻辑与 |
以下是一个使用逻辑运算符的示例:
if (!done && (col == 0 || col == 3)) {
// ...Do something...
}
按位及左移右移运算符
可以在Dart中操作数字的单个位。通常,会对整型使用这些按位和移动运算符。
运算符 | 含义 | |
---|---|---|
& |
与 | |
` | ` | 或 |
^ |
异或 | |
~*expr* |
一元按位取反(0变成1,1变成0) | |
<< |
左移 | |
>> |
右移 |
以下是使用按位和移动运算符的示例:
final value = 0x22;
final bitmask = 0x0f;
assert((value & bitmask) == 0x02); // 与
assert((value & ~bitmask) == 0x20); // 与 取反
assert((value | bitmask) == 0x2f); // 或
assert((value ^ bitmask) == 0x2d); // 异或
assert((value << 4) == 0x220); // 左移
assert((value >> 4) == 0x02); // 右移
条件表达式
Dart有两个运算符可以让我们简洁地运行可能会要求使用 if-else 语句的表达式:
*condition* ? *expr1* : *expr2*
若if条件为true, 运行 expr1 (并返回它的值),否则运行并返回 expr2的值。
*expr1* ?? *expr2*
右 expr1 为非空,返回其值,否则运行并返回 expr2的值。
在需要根据布尔表达式进行赋值时,考虑使用 ?:
。
var visibility = isPublic ? 'public' : 'private';
如果要测试布尔表达式是否为null,考虑使用 ??
。
String playerName(String name) => name ?? 'Guest';
前例至少可通过另外两种方式进行编写,但都没有它简洁:
// 使用?: 运算符的稍长版本
String playerName(String name) => name != null ? name : 'Guest';
// 使用if-else语句的非常长的版本
String playerName(String name) {
if (name != null) {
return name;
} else {
return 'Guest';
}
}
级联标记 (..)
级联 (..
) 允许我们对同一对象进行一系列操作。除调用函数外,你还可以对相同的对象访问字段。这通常可节省创建临时变量的步骤并让我们能编写列流畅的代码。
思考如下代码:
querySelector('#confirm') // 获取一个对象
..text = 'Confirm' // 使用它的成员
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
第1个方法调用querySelector()
,返回一个选择器对象。紧接级联标记的代码对选择器对象进行操作,它忽略所有后续可能返回的值。
前例等同于:T
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
构建级联。
Note: 严格的说,对级联使用的“双点号”标记并非运算符。它只是Dart语法的一部分。
其它运算符
我们已经在其它示例中看到过大部分剩余的运算符:
运算符 | 名称 | 含义 |
---|---|---|
() |
函数应用 | 表示一个函数调用 |
[] |
列表访问 | 引用列表中所指定索引处的值 |
. |
成员访问 | 引用一个表达式的属性;例如:foo.bar 从表达式foo 中选择属性 bar 。 |
?. |
条件成员访问 | 类似 . , 但最左侧的运算项可以为null;例如: foo?.bar 从foo 中选择属性 bar ,除非 foo 为 null (这时 foo?.bar 的值为 null) |
更多有关.
, ?.
和 ..
运算符的信息,参见 类。
流程控制语句
可以使用以下方式来控制Dart 代码中的流程
-
if
和else
-
for
循环 -
while
和do
–while
循环 -
break
和continue
-
switch
和case
assert
也可以使用try-catch
和 throw
来影响控制流程,在 异常中有进行讲解。
if和else
Dart 支持带有可选的else
语句的 if
语句,如下例中所示。同时请参见 条件表达式。
if (isRaining()) {
you.bringRainCoat();
} else if (isSnowing()) {
you.wearJacket();
} else {
car.putTopDown();
}
不同于JavaScript,条件必须使用布尔值,其它的都不行。参见 布尔型 获取更多信息。
for循环
你可以使用标准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
。但对比在JavaScript中则会先打印 2
再打印 2
。
如果所迭代的对象是一个 Iterable,可以使用forEach() 方法。如果无需知道当前的迭代计数器的话使用 forEach()
是一个很好的选择:
candidates.forEach((candidate) => candidate.interview());
Iterable类如List 和 Set 还把持迭代的 for-in
形式:
var collection = [0, 1, 2];
for (var x in collection) {
print(x); // 0 1 2
}
while和do-while
while
循环在循环之前运行条件:
while (!isDone()) {
doSomething();
}
do
–while
在循环之后运行条件:
do {
printLine();
} while (!atEndOfPage());
break和continue
使用 break
来停止循环:
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和case
Dart中的switch 语句使用==
比较整型、字符串或编译时常量。 比较的对象必须都是相同类的实例(而非其子类型的),并且该类不能重载 ==
.。枚举类型 在 switch
语句中可以良好运行。
Note: Dart中的switch语句针对受限的环境中,如解释器或扫描器。
每个非空 case
从句按照规则以break
语句结束。其它结束非空 case
从名的有效方式有 continue
, throw
或 return
语句。
在没有匹配的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': // 空的case直接越过
case 'NOW_CLOSED':
// 同时对CLOSED 和 NOW_CLOSED运行
executeNowClosed();
break;
}
如果你真的希望越过,可以使用continue
语句及一个标签:
var command = 'CLOSED';
switch (command) {
case 'CLOSED':
executeClosed();
continue nowClosed;
// 继续在nowClosed标签处执行
nowClosed:
case 'NOW_CLOSED':
// 对CLOSED 和 NOW_CLOSED同时运行
executeNowClosed();
break;
}
case
从句可以有局部变量,仅在从句作用域内部可见。
断言
在开发过程中,可以使用断言语句 assert(*condition*, *optionalMessage*)
; 来在布尔条件为false时打断正常的执行。可以通过本导览找到断言语句的很多示例。以下是另外一些示例:
// 确保变量值为非null
assert(text != null);
// 确保值小于100
assert(number < 100);
// 确保这是一个https链接
assert(urlString.startsWith('https'));
要为断言关联消息,对assert
添加一个字符串作为第2个参数。
assert(urlString.startsWith('https'),
'URL ($urlString) should start with "https".');
assert
的第1个参数可以是解析为布尔值的任意表达式。如果表达式的值为true,断言成功且执行继续。如果它为false,断言失败且会抛出异常(一个AssertionError)。
断言到底做了什么呢?这取决于你所使用的工具和框架:
- Flutter在调试模式中启用断言。
- 仅针对开发的工具如 dartdevc 通常默认启用断言。
- 一些工具,如 dart 和 dart2js,支持通过命令行标记的断言:
--enable-asserts
.
在生产模式下,会忽略断言, assert
的参数不会被执行。
异常
Dart代码可以抛出和捕获异常。异常是表示预期外的事情发生时的报错。如未捕获到异常,抛出异常的 隔离(isolate)被挂起,并且通常隔离及其程序会被终止。
不同睛Java,Dart的异常都是非检查型异常。方法未声明它们所要抛出的异常,那么也不要求你捕获任何异常。
Dart提供 Exception 和 Error 类型,以及很多预定义的子类型。当然也可以定义自己的异常。但是Dart程序可抛出任意非空对象(不只是Exception和Error对象)来作为异常。
Throw
以下是一个抛出异常的示例:
throw FormatException('Expected at least 1 section');
你也可以抛出自己的对象:
throw 'Out of llamas!';
Note: 生产质量的代码通常抛出实现Error 或 Exception的类型。
因为抛出异常是一个表达式,你可以在=>及其它允许使用表达式的任何地方抛出异常:
try {
breedMoreLlamas();
} on OutOfLlamasException {
buyMoreLlamas();
}
处理会抛出一个类型以上异常的代码,可以指定多个捕获分分钟。第一个匹配所抛出对象类型的catch从句会处理该异常。如果catch从句未指定类型,该分分钟可处理任意类型抛出的对象:
try {
breedMoreLlamas();
} on OutOfLlamasException {
// 一个具体异常
buyMoreLlamas();
} on Exception catch (e) {
// 任何其它异常
print('Unknown exception: $e');
} catch (e) {
// 无具体类型,处理所有类型
print('Something really unknown: $e');
}
如以上代表所示,可以使用 on
或 catch
或者同时使用。在需要指定异常类型时使用 on
。在你的异常处理器需要异常对象时使用 catch
。
可以为catch()
指定一个或两个参数。第1个是抛出的异常,第二是栈跟踪 (一个 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++); // 运行时错误
} catch (e) {
print('misbehave() partially handled ${e.runtimeType}.');
rethrow; // 允许调用者查看该异常
}
}
void main() {
try {
misbehave();
} catch (e) {
print('main() finished handling ${e.runtimeType}.');
}
}
Finally
要确保一些代码不管是否抛出异常时都执行,使用 finally
从句。如果没有 catch
从句匹配该异常,在finally
从句执行后会推出异常。
try {
breedMoreLlamas();
} finally {
// 即使在没有抛出异常时也会清理
cleanLlamaStalls();
}
finally
从任意匹配的catch
从句之后执行:
try {
breedMoreLlamas();
} catch (e) {
print('Error: $e'); // 首先处理异常
} finally {
cleanLlamaStalls(); // 然后进行清理
}
阅读库导览的 异常 的一节了解更多信息。
类
Dart是一个带有类和基于mixin继承的面向对象语言。每个对象都是类的实例,所有类都继承自 Object.。* 基于mixin的继承* 表示虽然每个类(除Object外)都只有一个超类,一个类主体可在多个类级别中复用。
使用类成员
对象有由函数和数据(分别为方法和实例变量)组成的成员。在调用方法时,对一个对象进行调用:该方法可访问对象的函数和数据。
使用点号 (.
) 来引用于实例变量或方法:
var p = Point(2, 2);
// 设置实例变量y的值
p.y = 3;
// 获取 y 的值
assert(p.y == 3);
// 对 p 调用 distanceTo()
num distance = p.distanceTo(Point(4, 4));
使用 ?.
代替 .
来避免最左侧操作项为null时的异常:
// 如果p 不为null, 设置它的y 值为 4
p?.y = 4;
使用构造函数
可以使用构造函数创建对象。构造函数名可为 *ClassName*
或 *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});
Version note: 关键字 new
Dart 2中成为可选项。
一些类提供 常量构造函数。要使用常量构造函数创建编译时常量,在构造函数名前放置关键字 const
:
var p = const ImmutablePoint(2, 2);
构造两个相同的编译时常量会产生单个相同的实例:
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // 它们是相同的实例!
在常量上下文中,可以在构造函数或字面量前省略 const
。例如,查看如下创建一个const映射的代码:
// 这里有很多const 关键字
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
可以省略掉第一次使用之外的所有 const
关键字:
// 仅一个const, 它创建常量上下文
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
如果常量构造函数在常量上下文之外且未使用 const
调用,它创建一个非常量对象。
var a = const ImmutablePoint(1, 1); // 创建一个常量
var b = ImmutablePoint(1, 1); // 不创建常量
assert(!identical(a, b)); // 不是相同的实例!
Version note: 关键字 const
在Dart 2的常量上下文成为可选。
获取一个对象的类型
要在运行时获取对象的类型,可以使用Object的 runtimeType
属性,它返回一个 Type 对象。
print('The type of a is ${a.runtimeType}');
到这里,你已学习了如何使用类。本节后面的部分展示如何实现类。
实例变量
以下是如何声明实例变量:
class Point {
num x; // 声明实例变量 x, 初始值为 null
num y; // 声明 y, 初始值为 null
num z = 0; // 声明 z, 初始值为 0
}
所有未初始化的实例变量的值为 null
。
所有的实例变量生成一个getter 方法。非final实例变量也生成一个隐式setter 方法。更多详情,参见getters和setters。
class Point {
num x;
num y;
}
void main() {
var point = Point();
point.x = 4; // 使用针对x 的setter方法
assert(point.x == 4); // 使用针对x 的getter方法
assert(point.y == null); // 默认值为null
}
如果在其声明处实例化实例变量(而非在构造函数或方法中进行),在实例创建时设置值,即在构造函数及期初始化程序列表执行前。
本文首发地址:Alan Hou的个人博客