代码块控制流
你可以使用下面的控制流来控制你的代码:
- if … else …
- for 循环
- while 和 do-while 循环
- break 和 continue
- switch 和 case
- assert
if … else …
Dart支持 if 代码块,配上可选的else 代码块,下面是个小例子:
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());
如果你在一些可迭代类型的对象中进行迭代,并且你并不需要知道迭代的索引,你可以使用for-in来进行迭代:
for (var candidate in candidates) {
candidate.interview();
}
一些可迭代类型有forEach()方法可以作为另一种迭代的方式:
var collection = [1, 2, 3];
collection.forEach(print); // 1 2 3
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();
}
如果你在可迭代对象上操作,你可以使用对应的高阶函数来达到相同的效果
candidates
.where((c) => c.yearsExperience >= 5)
.forEach((c) => c.interview());
Switch和case
Dart中Switch代码块可以使用整型、字符串或者其他编译时常量来进行比较,case中的变量必须是和比较值相同类型的对象
每一个非空的case都会以break结尾,也可以使用continue、throw或者return来结束一个非空的case
使用default来执行没有匹配到case的项
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结束,会引发一个error
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配合label
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分支内可以有自己的局部变量,这个局部变量仅在当前分支作用域内有效
断言
在开发中,使用断言代码块可以中止代码的执行,如果代码执行中断言结果为false,下面是一些例子:
// 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的第二个参数添加一个message,用于断言执行时显示
assert(urlString.startsWith('https'), 'URL ($urlString) should start with "https".');
第一个参数就是断言内用于判断的表达式,如果表达式为true,则断言成功,并会继续执行接下来的代码,如果表达式为false,则会抛出exception,并显示第二个参数中的message
Exceptions
Dart中可以抛出并捕获异常。异常是代码没有按照预期执行的错误。如果异常没有被捕获,引发异常的程序将会停止,通常这会导致整个程序中断
与Java相比,Dart中所有的异常都是未检查异常,函数不会声明任何它们抛出的异常,它们也不必捕获任何异常
Dart提供了Exception和Error类,同时提供了许多对应的子类。你当然可以定义自己的异常类。而且Dart程序可以将任何非空的对象作为异常抛出
Throw
下面是抛出引发Exception的一个例子
throw FormatException('Expected at least 1 section');
你也可以抛出一个随意的对象
throw 'Out of llamas!';
生产环境质量的代码,通常要求抛出的异常类型实现了Error或者Exception
Catch
捕获将会中断异常的传播,捕获异常将会给你一个机会去解决它:
try {
breedMoreLlamas();
} on OutOfLlamasException {
buyMoreLlamas();
}
在代码中可以抛出不止一种类型的异常,你可以指定多种类型的Exception,如果有捕获类型与异常类型相匹配,则异常会被这个捕获处理掉,如果没有指定对应的异常类型,那么任何异常都会被捕获
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');
}
你可以使用on 也可以使用 catch,如果你使用on 那么你必须要指定一种异常类型,当你要获取对应的异常对象时,使用catch
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关键字。如果异常没有被捕获,那么将会在finally代码块执行完之后继续向外抛出
try {
breedMoreLlamas();
} finally {
// Always clean up, even if an exception is thrown.
cleanLlamaStalls();
}
finally代码块在任意捕获到异常的代码之后执行
try {
breedMoreLlamas();
} catch (e) {
print('Error: $e'); // Handle the exception first.
} finally {
cleanLlamaStalls(); // Then clean up.
}
Classes
Dart是面向对象的语言,每一个对象都是类的实例,所有的类除了Null之外都继承自Object。基于Mixin的继承意味着尽管所有的class(顶级类、Object?除外)都只能有一个父类的情况下可以更多的复用类内部的成员。扩展函数可以在不改变类内容和创建新的子类的情况下给类添加新的功能
使用类成员
类成员对象由函数和数据组成。你可以在该类的实例对象上调用类成员函数和类成员变量
使用 . 来引用实例上的变量和函数
var p = Point(2, 2);
// Get the value of y.
assert(p.y == 2);
// Invoke distanceTo() on p.
double distance = p.distanceTo(Point(4, 4));
使用 ?. 来替换 . 来避免在调用null实例时引发异常
// If p is non-null, set a variable equal to its y value.
var a = p?.y;
使用构造函数
你可以使用构造函数来创建一个对象,构造函数的名字可以是 类名或者类名.identifier。举个例子,创建一个Point对象可以使用Point()或者Point.fromJson()构造函数:
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});
new关键字在Dart2中成为可选关键字
许多类提供了常量构造函数。要创建一个编译时常量,可以使用常量构造函数,只需要在构造函数名前面加上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就可以忽略,举个例子:
// 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外的所有const
// Only one const, which establishes the constant context.
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
使用同样的构造函数创建的两个对象,如果其中一个使用 const 关键字创建编译时常量,这两个对象是不一样的
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!
获取对象类型
想要获取一个对象的类型,可以使用Object的一个属性runtimeType,返回一个Type类型的值
print('The type of a is ${a.runtimeType}');
实例变量
下面将会展示如何去创建一个实例变量
class Point {
double? x; // Declare instance variable x, initially null.
double? y; // Declare y, initially null.
double z = 0; // Declare z, initially 0.
}
所有没有初始化的属性的值都会被赋予null
所有实例内部的变量都会有一个隐式的gettter,所有不是final或者late final的变量如果没有初始化的话,都会有一个隐式的setter
你初始化的非延迟初始化的实例变量,都会在构造函数被调用的时候进行对应的初始化
class Point {
double? x; // Declare instance variable x, initially null.
double? y; // Declare y, initially null.
}
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.
}
实例变量可以设置为final,但是必须进行初始化。你可以使用构造函数参数或者构造函数初始化列表进行初始化。如果你想在构造函数执行之后再进行初始化,你可以使用late final关键字,但是使用时记得小心
class ProfileMark {
final String name;
final DateTime start = DateTime.now();
ProfileMark(this.name);
ProfileMark.unnamed() : name = '';
}
构造函数
使用跟类名一样的名字作为函数名来创建一个构造函数,当然也可以在函数名后面加上特定的标识名来创建命名构造函数。使用构造函数就可以创建类的实例
class Point {
double x = 0;
double y = 0;
Point(double x, double y) {
// There's a better way to do this, stay tuned.
this.x = x;
this.y = y;
}
}
这里的this关键字指向当前的实例
当出现名称冲突时,才使用this关键。否则的话,省略this关键字
Dart有将构造函数参数分配到对应的实例变量上的语法糖
class Point {
double x = 0;
double y = 0;
// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}
默认构造参数
如果没有创建构造函数,Dart将会创建一个不带参数的默认构造函数,并在其中调用父类的无参构造函数
构造函数不会继承
子类不会从父类继承构造函数,如果子类没有声明构造函数,只会创建默认构造函数(无参数,无构造体)
命名构造函数
创建命名函数可以为类添加更多创建实例的方式
const double xOrigin = 0;
const double yOrigin = 0;
class Point {
double x = 0;
double y = 0;
Point(this.x, this.y);
// Named constructor
Point.origin()
: x = xOrigin,
y = yOrigin;
}
记住构造函数是不能够被继承的,这意味着父类的命名构造函数也无法被子类继承,如果你想要子类也拥有一个父类中定义的构造函数,需要你自己实现
父类非默认构造函数执行
默认情况下,子类会去执行父类非命名的无参构造函数,父类的构造函数在子类的构造函数体开头被执行。如果此时刚好也使用了初始化列表(initial list),初始化列表将在父类构造函数之前调用,总的来说执行顺序如下:
- 初始化列表(initial list)
- 父类构造函数
- 子类构造函数
如果父类没有非命名的无参构造函数,那么必须指定指定父类的一个构造函数。在函数构造体之前使用 : 来指定对应的父类构造函数
class Person {
String? firstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {
// Person does not have a default constructor;
// you must call super.fromJson(data).
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}
void main() {
var employee = Employee.fromJson({});
print(employee);
// Prints:
// in Person
// in Employee
// Instance of 'Employee'
}
因为父类的构造函数参数会在构造函数调用之前计算完成,所以构造参数可以是一个表达式,如参数是一个函数调用:
class Employee extends Person {
Employee() : super.fromJson(fetchDefaultData());
// ···
}
注意: 父类的构造函数参数不能访问this,所以父类的构造函数中只能调用静态函数而不能调用实例方法
初始化列表
除了调用父类的构造函数,你还可以在构造函数执行前执行初始化列表,各个初始化语句之间使用 逗号 分割:
// 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)');
}
初始化列表每一项的右边无法访问this
在开发环境下,你可以在初始化列表中使用asset对传入的值进行校验
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
使用初始化列表对final字段进行初始化非常方便:
import 'dart:math';
class Point {
final double x;
final double y;
final double distanceFromOrigin;
Point(double x, double y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
void main() {
var p = Point(2, 3);
print(p.distanceFromOrigin);
}
重定向构造函数
有些时候,一些构造函数的目的就是调用同一个类中的另一个构造函数。重定向构造函数没有构造体,想要调用的构造放置在 : 之后
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);
}
const构造函数
如果你的类创建的实例无法修改,你可以在构造函数前使用const,这样创建的实例就是一个编译时常量。如果你想要这样做,那么你要确保实例变量都是final的
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
工厂构造函数
当你想使用构造函数,但是并不想再创建当前类新的实例,此时可以使用factory关键字。比如你想从缓存中返回当前类的实例,或者想返回子类型的实例。另一种使用情况是,想要初始化无法在 initializer list 进行逻辑处理的final变量
下面的例子中,Logger的工厂构造函数从缓存中返回了一个实例,Logger.fromJson 工厂构造函数从JSON数据中初始化了 final 变量
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));
}
factory Logger.fromJson(Map<String, Object> json) {
return Logger(json['name'].toString());
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
注意: 工厂构造函数不能访问this关键字
调用工厂构造函数和别的构造函数是一样的
var logger = Logger('UI');
logger.log('Button clicked');
var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);
方法
对象里提供的行为的函数叫做方法
实例方法
实例方法可以访问this关键字,下面例子中的 distanceTo方法就是一个实例方法:
import 'dart:math';
class Point {
double x = 0;
double y = 0;
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);
}
}
操作符
操作符是一些特殊名称的实例方法,Dart允许你使用下面这些名称来定义操作符
< | + | | | [] |
---|---|---|---|
> | / | ^ | []= |
<= | ~/ | & | ~ |
>= | * | << | == |
- | % | >> |
注意: 你可以发现有一些操作符,像 != 并不在上面的表格中,因为这些操作符仅仅是语法糖。举个例子,表达式 e1 != e2 就是 !(e1 == e2) 的语法糖
使用内置的标识符 operator 来定义运算符,下面的例子就是定义运算符 + 和 - :
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.
// ···
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
Getters 和 Setters
Getters和Setters是提供读写对象属性功能的特殊方法。回想一下,每个实例变量都有一个隐式的getter,某些情况还会有一个setter。你可以通过get和set关键字实现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一次,并将返回值保存在一个临时变量中
抽象方法
实例,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关键字来定义一个抽象类,抽象类不能被初始化。抽象类在定义接口时非常有用,如果你想要抽象类可以被初始化,你可以定义一个工厂构造函数
抽象类通常有抽象方法,下面的例子就是一个包含抽象方法的抽象类:
// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
// Define constructors, fields, methods...
void updateChildren(); // Abstract method.
}
隐式接口
每一个类都隐式的定义了一个接口,这个接口包含类和所有实现接口的所有实例成员。如果你想要创建一个类A,在没有继承类B的情况下支持所有类B的API,那么你需要让A去实现B的接口
类在 implements 关键字后面跟上对应要实现的接口名称,然后在类内部实现接口规定的那些方法,下面是一个例子:
// 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()));
}
下面的例子是指定类实现多个接口:
class Point implements Comparable, Location {...}
继承类
在子类上使用 extends 关键字来指定继承的父类,使用 super 关键字来指向父类:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
重写成员
子类可以重写实例方法(包括操作符), getters和setters。你可以使用 @override 注解来表明你想重写的成员
class SmartTelevision extends Television {
@override
void turnOn() {...}
// ···
}
注意: 如果你重写了 == , 那么你也需要去重写对象的 hashCode getter
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方法
扩展方法
扩展方法是给已有的类库添加新功能的方法。你甚至可以在不了解某个类的情况下给其添加扩展方法,下面就是给定义在 string_apis.dart中的 String 类添加名为 parseInt 的扩展方法的例子:
import 'string_apis.dart';
...
print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.
枚举类型
枚举类型,通常称之为 enumerations 或者 enums , 这是一种包含固定数量常量值的特殊类型
使用枚举
使用 enum 关键字定义枚举类型:
enum Color { red, green, blue }
枚举中的每一个值都有一个对应的索引getter,这个索引getter会返回这个值在枚举中的位置(从0开始计数):
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
想要获取枚举中的值List,可以使用 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'
}
枚举类型有以下的限制:
- 你不能继承、混入或者实现一个枚举
- 你不能实例化一个枚举
mixins: 给类添加特性
Mixins是一种在多类层次中复用class代码的方式
使用 with 关键字后面接多个要混入的Mixin来使用 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 依赖于某个 mixin 中没有定义的方法。下面的例子中展示了你可以通过使用 on 关键字的方式来限制mixin的使用:
class Musician {
// ...
}
mixin MusicalPerformer on Musician {
// ...
}
class SingerDancer extends Musician with MusicalPerformer {
// ...
}
在上面的代码中,只有继承或者实现了Musician的类可以使用 mixin MusicalPerformer。因为 SingerDancer 继承了 Musician,所以 SingerDancer可以混入 MusicalPerformer
类变量和类方法
使用 static 关键字来实现类级别的变量和方法
静态变量
静态变量(又叫做类变量)通常用于存储类级别的状态或者常量:
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
静态变量在它们使用之前是不会初始化的
注意: 代码标准推荐使用 小驼峰风格 来命名常量名
静态方法
静态方法(又叫类方法)不由类的实例操作,而是由类本身来执行,因此它们无法访问 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);
}
注意: Dart中有top-level的函数,请考虑使用top-level函数来替代静态方法