Default value(默认值)
没有初始化的变量自动获取一个默认值为 null
。类型为数字的 变量如何没有初始化其值也是 null
,不要忘记了 数字类型也是对象。
int lineCount;
assert(lineCount == null);
// Variables (even if they will be numbers) are initially null.
Final and const
如果你以后不打算修改一个变量,使用 final
或者 const
。 一个 final
变量只能赋值一次;一个 const
变量是编译时常量。 (Const
变量同时也是 final
变量。) 顶级的 final
变量或者类中的 final
变量在 第一次使用的时候初始化。
注意: 实例变量可以为 final 但是不能是 const 。
const
关键字不仅仅只用来定义常量。 还可以用来创建不变的值, 还能定义构造函数为 const
类型的,这种类型 的构造函数创建的对象是不可改变的。任何变量都可以有一个不变的值。
Strings(字符串)
通过提供一个 r
前缀可以创建一个 “原始 raw” 字符串:
var s = r"In a raw string, even \n isn't special.";
原始 raw 字符串:字符串里的所有字符都会被当成字符输出。上面的字符串 s,输出如下:In a raw string, even \n isn’t special.,里面的换行符 “\n” 也会被输出,而不是输出一个空行。
Lists(列表)
在 list
字面量之前添加 const
关键字,可以 定义一个不变的 list
对象(编译时常量):
var constantList = const [1, 2, 3];
// constantList[1] = 1; // Uncommenting this causes an error.
Runes
在 Dart
中,runes
代表字符串的 UTF-32 code points
。
Unicode
为每一个字符、标点符号、表情符号等都定义了 一个唯一的数值。 由于 Dart
字符串是 UTF-16 code units
字符序列, 所以在字符串中表达 32-bit Unicode
值就需要 新的语法了。
通常使用 \uXXXX
的方式来表示 Unicode code point
, 这里的 XXXX
是4个 16 进制的数。 例如,心形符号 (♥)
是 \u2665
。 对于非 4 个数值的情况, 把编码值放到大括号中即可。 例如,笑脸 emoji (?)
是 \u{1f600}
。
String
类 有一些属性可以提取 rune
信息。 codeUnitAt
和 codeUnit
属性返回 16-bit code
units
, 使用 runes
属性来获取字符串的 runes
信息。
Optional parameters(可选参数)
可选参数可以是命名参数或者基于位置的参数,但是这两种参数不能同时当做可选参数。
Optional named parameters(可选命名参数)
调用方法的时候,你可以使用这种形式 paramName: value
来指定命名参数。例如:
enableFlags(bold: true, hidden: false);
在定义方法的时候,使用 {param1, param2, …}
的形式来指定命名参数:
/// Sets the [bold] and [hidden] flags to the values
/// you specify.
enableFlags({bool bold, bool hidden}) {
// ...
}
在定义方法的时候,可以使用 =
来定义可选参数的默认值。 默认值只能是编译时常量。 如果没有提供默认值,则默认值为 null
。
/// Sets the [bold] and [hidden] flags to the values you
/// specify, defaulting to false.
void enableFlags({bool bold = false, bool hidden = false}) {
// ...
}
// bold will be true; hidden will be false.
enableFlags(bold: true);
Optional positional parameters(可选位置参数)
把一些方法的参数放到 []
中就变成可选 位置参数了:
String say(String from, String msg, [String device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
对于可选命名参数和可选位置参数的区别,目前来看仅仅是调用的时候可选命名参数需要写上名字,而可选位置参数是不需要的。
Cascade notation (..
)(级联操作符)
级联操作符 (..
) 可以在同一个对象上 连续调用多个函数以及访问成员变量。 使用级联操作符可以避免创建 临时变量, 并且写出来的代码看起来 更加流畅:
例如下面的代码:
querySelector('#button') // Get an object.
..text = 'Confirm' // Use its members.
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
第一个方法 querySelector()
返回了一个 selector
对象。 后面的级联操作符都是调用这个对象的成员, 并忽略每个操作 所返回的值。
Note:
1、方法的返回如果为void
,使用级联不起作用;
2、String
的相关方法,使用级联不起作用;
Constructors
Default constructors(默认构造函数)
如果你没有定义构造函数,则会有个默认构造函数。 默认构造函数没有参数,并且会调用超类的 没有参数的构造函数。
Constructors aren’t inherited(构造函数不会继承)
子类不会继承超类的构造函数。 子类如果没有定义构造函数,则只有一个默认构造函数 (没有名字没有参数)。
Named constructors(命名构造函数)
使用命名构造函数可以为一个类实现多个构造函数, 或者使用命名构造函数来更清晰的表明你的意图:
class Point {
num x;
num y;
Point(this.x, this.y);
// Named constructor
Point.fromJson(Map json) {
x = json['x'];
y = json['y'];
}
}
注意:构造函数不能继承,所以超类的命名构造函数 也不会被继承。如果你希望 子类也有超类一样的命名构造函数,你必须在子类中自己实现该构造函数。
Redirecting constructors(重定向构造函数)
有时候一个构造函数会调动类中的其他构造函数。 一个重定向构造函数是没有代码的,在构造函数声明后,使用 冒号调用其他构造函数。
class Point {
num x;
num y;
// The main constructor for this class.
Point(this.x, this.y);
// Delegates to the main constructor.
Point.alongXAxis(num x) : this(x, 0);
}
Constant constructors(常量构造函数)
如果你的类提供一个状态不变的对象,你可以把这些对象 定义为编译时常量。要实现这个功能,需要定义一个 const 构造函数, 并且声明所有类的变量为 final。
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
}
Factory constructors(工厂方法构造函数)
如果一个构造函数并不总是返回一个新的对象,则使用 factory 来定义 这个构造函数。例如,一个工厂构造函数 可能从缓存中获取一个实例并返回,或者 返回一个子类型的实例。
下面代码演示工厂构造函数 如何从缓存中返回对象。
class Logger {
final String name;
bool mute = false;
// _cache is library-private, thanks to the _ in front
// of its name.
static final Map<String, Logger> _cache =
<String, Logger>{};
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = new Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) {
print(msg);
}
}
}
注意: 工厂构造函数无法访问 this。
使用 new 关键字来调用工厂构造函数。
var logger = new Logger('UI');
logger.log('Button clicked');
Invoking a non-default superclass constructor(调用超类构造函数)
默认情况下,子类的构造函数会自动调用超类的 无名无参数 的默认构造函数。 超类的构造函数在子类构造函数体开始执行的位置调用。 如果提供了一个 initializer list(初始化参数列表) ,则初始化参数列表在超类构造函数执行之前执行。 下面是构造函数执行顺序:
- initializer list(初始化参数列表)
- superclass’s no-arg constructor(超类的无名构造函数)
- main class’s no-arg constructor(主类的无名构造函数)
如果超类没有无名无参数构造函数, 则你需要手工的调用超类的其他构造函数。 在构造函数参数后使用冒号 “:” 可以调用 超类构造函数。
初始化列表非常适合用来设置 final 变量的值。 下面示例代码中初始化列表设置了三个 final 变量的值。
import 'dart:math';
class Point {
final num x;
final num y;
final num distanceFromOrigin;
Point(x, y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
main() {
var p = new Point(2, 3);
print(p.distanceFromOrigin);
}
Getters and setters
Getters and setters are special methods that provide read and write access to an object’s properties. Recall that each instance variable has an implicit getter, plus a setter if appropriate. You can create additional properties by implementing getters and setters, using the get and set keywords:
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// Define two calculated properties: right and bottom.
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}
void main() {
var rect = Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
With getters and setters, you can start with instance variables, later wrapping them with methods, all without changing client code.
Implicit interfaces
Every class implicitly defines an interface containing all the instance members of the class and of any interfaces it implements. If you want to create a class A that supports class B’s API without inheriting B’s implementation, class A should implement the B interface.
A class implements one or more interfaces by declaring them in an implements clause and then providing the APIs required by the interfaces. For example:
// 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()));
}
Overriding members
Subclasses can override instance methods, getters, and setters. You can use the @override
annotation to indicate that you are intentionally overriding a member:
class SmartTelevision extends Television {
@override
void turnOn() {...}
// ···
}
The covariant keyword
Some (rarely used) coding patterns rely on tightening a type by overriding a parameter’s type with a subtype, which is invalid. In this case, you can use the covariant keyword to tell the analyzer that you are doing this intentionally. This removes the static error and instead checks for an invalid argument type at runtime.
Version note: The covariant keyword was introduced in 1.22. It replaces the @checked annotation.
The following shows how you might use covariant:
class Animal {
void chase(Animal x) { ... }
}
class Mouse extends Animal { ... }
class Cat extends Animal {
void chase(covariant Mouse x) { ... }
}
Although this example shows using covariant in the subtype, the covariant keyword can be placed in either the superclass or the subclass method. Usually the superclass method is the best place to put it. The covariant keyword applies to a single parameter and is also supported on setters and fields.
noSuchMethod()
To detect or react whenever code attempts to use a non-existent method or instance variable, you can override noSuchMethod():
class Foo {
foo(x) {}
}
class MockFoo implements Foo {
// PS: Make sure that a tear-off of `_mockFoo` has the same type
// as a tear-off of `Foo.foo`.
_mockFoo(x) {
// ... implement mock behavior for `foo` here.
}
noSuchMethod(Invocation i) {
if (i.memberName == #foo) {
if (i.isMethod &&
i.positionalArguments.length == 1 &&
i.namedArguments.isEmpty) {
return _mockFoo(i.positionalArguments[0]);
} else if (i.isGetter) {
return _mockFoo;
}
}
return super.noSuchMethod(i);
}
}
You can’t invoke an unimplemented method unless one of the following is true:
- The receiver has the static type
dynamic
. - The receiver has a static type that defines the unimplemented method
(abstract
is OK), and the dynamic type of the receiver has an
implemention ofnoSuchMethod()
that’s different from the one in
classObject
.
For more information, see the informal noSuchMethod forwarding specification.
Adding features to a class: mixins
Mixins are a way of reusing a class’s code in multiple class hierarchies.
To use a mixin, use the with keyword followed by one or more mixin names. The following example shows two classes that use mixins:
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
To implement a mixin, create a class that extends Object
and declares no constructors
. Unless you want your mixin to be usable as a regular class, use the mixin
keyword instead of class
. For example:
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');
}
}
}
To specify that only certain types can use the mixin — for example, so your mixin can invoke a method that it doesn’t define — use on
to specify the required superclass:
mixin MusicalPerformer on Musician {
// ···
}
Version note: Support for the mixin keyword was introduced in Dart 2.1. Code in earlier releases usually used abstract class instead. For more information on 2.1 mixin changes, see the Dart SDK changelog and 2.1 mixin specification.
Generic collections and the types they contain
Dart generic types are reified, which means that they carry their type information around at runtime. For example, you can test the type of a collection:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
Note: In contrast, generics in Java use erasure, which means that generic type parameters are removed at runtime. In Java, you can test whether an object is a List, but you can’t test whether it’s a List.
Asynchrony support
Handling Futures
When you need the result of a completed Future, you have two options:
- Use async and await.
- Use the Future API, as described in the library tour.
Code that uses async
and await
is asynchronous, but it looks a lot like synchronous code. For example, here’s some code that uses await to wait for the result of an asynchronous function:
await lookUpVersion();
To use await
, code must be in an async
function—a function marked as async:
Future checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
Note: Although an async function might perform time-consuming operations, it doesn’t wait for those operations. Instead, the async function executes only until it encounters its first await expression (details). Then it returns a Future object, resuming execution only after the await expression completes.
Callable classes
To allow your Dart class to be called like a function, implement the call() method.
void main() {
var demo = CallableClass();
print(demo(1, 2));
}
class CallableClass {
int call(int a, int b) => a + b;
}
Async and await
The following diagram shows the flow of execution through the code. Each number corresponds to a step below.
diagram showing flow of control through the main() and printDailyNewsDigest functions
- The app begins executing.
- The
main()
function calls the async functionprintDailyNewsDigest()
,
which begins executing synchronously. printDailyNewsDigest()
uses await to call the function
gatherNewsReports(),
which begins executing.- The
gatherNewsReports()
function returns an uncompleted future (an
instance ofFuture<String>
). - Because
printDailyNewsDigest()
is an async function and is awaiting
a value, it pauses its execution and returns an uncompleted future
(in this case, an instance ofFuture<void>
) to its caller (main()
). - The remaining print functions execute. Because they’re synchronous,
each function executes fully before moving on to the next print
function. For example, the winning lottery numbers are all printed
before the weather forecast is printed. - When
main()
has finished executing, the asynchronous functions can
resume execution. First, the future returned bygatherNewsReports()
completes. ThenprintDailyNewsDigest()
continues executing, printing
the news. - When the
printDailyNewsDigest()
function body finishes executing,
the future that it originally returned completes, and the app exits.
Note that an async function starts executing right away (synchronously). The function suspends execution and returns an uncompleted future when it reaches the first occurrence of any of the following:
- The function’s first await expression (after the function gets the uncompleted future from that expression).
- Any return statement in the function.
- The end of the function body.
只有执行完main方法下面的代码后才会执行那些await的操作!
async、async*、sync*
async与await配合使用;
sync*、async*与yield配合使用;
DO annotate with Object instead of dynamic to indicate any object is allowed.
Some operations work with any possible object. For example, a log() method could take any object and call toString() on it. Two types in Dart permit all values: Object and dynamic. However, they convey different things. If you simply want to state that you allow all objects, use Object, as you would in Java or C#.
Using dynamic sends a more complex signal. It may mean that Dart’s type system isn’t sophisticated enough to represent the set of types that are allowed, or that the values are coming from interop or otherwise outside of the purview of the static type system, or that you explicitly want runtime dynamism at that point in the program.
void log(Object object) {
print(object.toString());
}
/// Returns a Boolean representation for [arg], which must
/// be a String or bool.
bool convertToBool(dynamic arg) {
if (arg is bool) return arg;
if (arg is String) return arg == 'true';
throw ArgumentError('Cannot convert $arg to a bool.');
}