tour

major feature, from variables and operators to classes and libraries

basic program

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

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

important concepts

Variables

创建并初始化变量:

var name = 'Bob';

变量存储引用。name变量存储了值为“Bob”的String对象的引用

name变量的类型被推断为String,如果不被限制为单一类型,可以指定为 Objectdynamic

dynamic name = 'Bob';

或者明确声明类型:

String name = 'Bob';

对于局部变量,优先使用 var 而不是明确声明

Default value

未经初始化的变量,初始化值为 null,甚至数值类型也是这样,因为它们是对象。

int lineCount;
assert(lineCount == null);

生产中,assert() 代码被忽略、不执行,然而,在开发中,如果 condation 为 false,执行 assert(condation) 时将抛出异常

Final and const

如果从未打算更改变量的值,使用 finalconst,而不是 var

Build-in types

Numbers

int

不大于 64 bits,取决于平台:

double

64位(双精度)浮点数

intdouble 都是 num 的子类,num 包含基本算术操作符和数学计算方法,位操作定义在 int 中,dart:math 库中定义更多数学计算

数字与字符串相互转换:

// String -> int
var one = int.parse('1');

// String -> double
var onePointOne = double.parse('1.1');

// int -> String
String oneAsString = 1.toString();

// double -> String
String piAsString = 3.14159.toStringAsFixed(2);

数字字面量是编译时常量,算术表达式的操作数都为编译时常量时整个表达式也为编译时常量:

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

Strings

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} 将表达式的值放到字符串中,如果表达式是变量标识符可以去掉 {},调用对象的 toString 方法来获取该对象对应的字符串值。

== 判断两个字符串内容是否相等,+ 连接两个字符串(或者两个字符串字面量相邻),三引号创建多行字符串,r 前缀创建“原生”字符串。

字符串字面量是编译时常量,当插值表达式为编译时常量时,要求其中操作数也为编译时常量且类型为 bool / num / String / null

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

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

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

Booleans

布尔类型为 bool,字面量为 truefalse(编译时常量)。

Lists

数组即 List,其字面量示例:

var list = [1, 2, 3];

推断类型为 List<int>,如果试图添加非 int 对象,分析器或运行时将会报错。

在 List 字面量前添加const,创建其编译时常量:

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

使用 ... 将一个 List 中所有元素插入到另一个 List 中:

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

使用 ...? 避免插入为 null 的 List 时报错:

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

创建 List 时使用条件if

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

创建 List 时使用循环for

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

Sets

元素不重复的无序的集合,使用 Set 字面量示例:

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

推断类型为Set<String>

创建空的 Set:

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

Maps

键和值可以是任意类型的对象,使用 Map 字面量示例:

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

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

使用 Map 构造器示例:

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

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

Operators

作用与两个操作数的操作符取决于最左侧的操作数

== 判断两个对象的内容是否一样,identical() 判断两个对象是否为同一个

x == y 运行方式如下:

  1. 如果 x、y 都是 null 表达式的值为 true,如果只有一个为 null,表达式的值则为 false
  2. 表达式的值等于 x.==(y),操作符 == 其实是由第一个操作数所调用的方法,甚至很多操作符都可以被重写

Type test

Operator Meaning
as Typecast (also used to specify library prefixes)
is True if the object has the specified type
is! False if the object has the specified type
if (emp is Person) {
  // Type check
  emp.firstName = 'Bob';
}

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

如果 empnull 或者不是 Person 类型时,is 示例代码将不执行赋值操作,然而,as 示例代码将会抛出异常。

Assignment

??= 只在被赋值变量为 null 时才执行赋值操作:

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

Conditional expressions

expr1 ?? expr2 如果 expr1 不为 null 返回其值,否者,返回 expr2 的值。

Cascade notation (..)

在同一对象上执行一系列操作,除了方法调用,还能访问属性,这样就节省了创建临时变量的步骤、使代码更流畅。

querySelector('#confirm') // Get an object.
  ..text = 'Confirm' // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));
  
var sb = StringBuffer();
sb.write('foo')
  ..write('bar'); // Error: method 'write' isn't defined for 'void'.

严格讲,.. 级联符号不是一种操作符而是 Dart 的一种语法

Other

?. (Conditional member access): Like ., but the leftmost operand can be null; for example, foo?.bar selects property bar from expression foo unless foo is null (in which case the value of foo?.bar is null)

Control flow

for 循环中的闭包可以获取到索引值:

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());
// The output is 0 and then 1, as expected.
// In contrast, the example would print 2 and then 2 in JavaScript.

在 switch-case 中使用 continue标签

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

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

在开发过程中,使用 assert(condition, optionalMessage); 在 condition 为 false 时抛出异常、阻断程序继续执行。生产环境中忽略断言。

assert 起作用的情况:

Functions

Dart 是完全面向对象的语言,甚至连函数都是对象、类型为 Function,这意味着函数可以赋值给变量或者作为参数传递给其他函数,{ return expr; } 可简写为 => expr;

参数类型

  1. 必需的
  2. 可选的
    • 命名的
    • 位置的

首先列出必需参数,随后列出可选参数;可选参数可以是命名参数或者位置参数,但是不能同时存在。

void enableFlags({bool bold, bool hidden}) {...}

enableFlags(bold: true, hidden: false);

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

使用 @required 表示该参数强制需要,如果调用时没有提供,分析器会报问题,依赖 meta 包(import 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');

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

使用 = 为可选的命名或位置参数设置默认值(必须是编译时常量),如果不提供则默认为 null

void enableFlags({bool bold = false, bool hidden = false}) {...}

enableFlags(bold: true);

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

assert(say('Bob', 'Howdy') ==
    'Bob says Howdy with a carrier pigeon');

返回值

所有函数都有返回值,如果没有指定返回值,return null 将会被隐式地追加到函数体。

foo() {}

assert(foo() == null);

main()

每个应用必须有一个顶级/全局的 main 函数,以作为整个应用的入口点。返回 void,参数为一个可选的 List<String>

as first-class objects

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

var list = [1, 2, 3];

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

// Assign a (anonymous) function to a variable
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

Anonymous

没有名字的函数被称为匿名函数lambda闭包() 之间零或多个参数,类型声明可选,逗号分隔;跟随 {},其中代码块即为函数体。

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

// If the function contains only one statement, shorten it using arrow notation
([[Type] param1[, …]]) => statement

scope

变量的作用范围仅仅通过代码布局即可静态得知:

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

closures

闭包 是函数对象,可以访问语法范围内的变量,即使这个函数在定义它的范围之外去使用:

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

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

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

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

Classes

use classes

using constructors

using members

get an object’s type

在运行时获取一个对象的类型,使用其 runtimeType 属性(继承自 Object,为 Type 对象/实例)。

implement classes

constructors

class Point {
  num x, y;

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

将构造器参数赋值给实例变量的操作十分常用,语法糖使该操作变得更加简单:

class Point {
  num x, y;

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

named constructors

class Point {
  num x, y;

  Point(this.x, this.y);

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

initializer list

初始化器的右侧(如 json['x'])不能访问实例成员

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

开发阶段,在初始化列表中使用 assert 来验证输入参数:

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

invoking a superclass constructor

默认情况下,子类构造器会调用父类的无名称、无参数的(默认)构造器,父类构造器在子类构造器体执行前被调用;如果父类中没有默认构造器,则必须手动在子类构造器体前用 : 来指定父类中已经定义好的某个构造器。

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

传递给父类构造器的参数可以是一个表达式,例如一个方法调用,但是,不能是实例(this)相关、可以是类(static)相关的

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

在调用父类构造器的同时使用到初始化列表的话,初始化列表要位于父类构造器调用之前,具体执行顺序如下:

  1. 初始化列表
  2. 父类构造器
  3. 子类构造器体

redirecting constructors

有些构造器只是重定向到其他构造器:

class Point {
  num x, 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, y;

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

factory constructors

当构造器并不总是创建/返回当前类的新实例对象时,例如从缓存中返回已经存在的实例对象或者创建子类对象,可以在构造器前添加 factory 关键字实现,同时,该构造器不能再访问实例成员。

class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }

  Logger._internal(this.name);

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

instance variables and methods

implicit interfaces

任何一个类都++隐式地定义了++一个接口,这个接口包含了对应的类以及该类所实现接口的所有实例成员。如果类 A 想要支持类 B 的接口而不想继承它的实现,那么,类 A 可以实现类 B 对应的接口。

// 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()));
}
//  A class implements multiple interfaces
class Point implements Comparable, Location {...}

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

只有满足如下条件才能调用尚不存在的实例成员:

void main() {
  dynamic f1 = Foo1();
  f1.method1();
  print(f1.v1);
  
  var f2 = Foo2();
  f2.method2();
}

class Foo1 {
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

class Foo2 {
  void method2();
  
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

mixins

在多类层次结构中复用一个类的代码,使用 with 关键字、在其后添加 mixin 名称。

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

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

只继承 Object 且未声明构造器的类即可作为 mixin,如果不想使用常规类,可以将关键字 class 替换为 mixin

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

使用 on 关键字指定在使用该 mixin 前必须集成的父类。

mixin MusicalPerformer on Musician {
  // ···
}

Libraries and visibility

Libraries 不仅提供 APIs,还是隐私单元,以 _ 开头的标识符只在当前 Library 中可见。

  1. library
  2. visibility
  3. package
  4. https://dart.dev/guides/packages

Generics

集合字面量可以被参数化:

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

泛型类型规范化,在运行时携带其类型信息:

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

Exceptions

异常表示发生意外事情,如果异常没有被捕获,所抛出异常的 isolate 会被暂停,并且通常其对应的程序也会被终止。

Dart 提供 ExceptionError 类型,以及众多子类,也可以自定义异常类型,甚至抛出异常非空对象作为异常。

Throw

// an example of throwing, or raising, an exception
throw FormatException('Expected at least 1 section');

// throw arbitrary objects
throw 'Out of llamas!';

生产质量的代码通常抛出实现 ExceptionError 的异常类型对象

Catch

需要异常类型时使用 on 关键字,需要异常对象时使用 catch 关键字:

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

可以为 catch 制定一个或两个参数,第一个是所抛出的异常对象,第二个是 StackTrace 对象:

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

如果只是部分处理一个异常,然后让它继续传递,则使用 rethrow 关键字:

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

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

Asynchrony

异步函数/方法:设置完可能耗时操作(如 I/O)后,不等待其完成、直接返回 Future (异步操作终将返回处理结果的一种承诺)或 Stream (获取数据/事件序列的一种方式)对象。

声明

在函数/方法上添加 async 关键字,使之返回 Future 对象:

// String lookUpVersion() => '1.0.0';

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

函数/方法体内不需要使用 Future API,Dart 会自动为其按需创建 Future 对象;如果函数/方法没有返回值,则其返回值类型声明为 Future<void>

处理 Future

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

async 函数/方法在执行到其中的第一个 await 表达式时直接返回 Future 对象,然后在 await 表达式处理完成时(异步操作返回处理结果值)继续执行函数/方法剩余部分。

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

async 函数/方法中,可以使用 await 多次。

确保 awaitasync 函数/方法体内

处理 Stream

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

expressionStream 类型,执行流程:

  1. 等待着 stream 发射出一个值
  2. 使用该值执行一遍循环体
  3. 重复 1、2 步骤,直到 stream 关闭

使用 breakreturn 可以打断 for 循环的执行,同时取消对当前 stream 的订阅、停止对其监听。

使用 await for 时,要确保这样会使代码更清晰,以及,想要等待 stream 的所有值

Generators

使用 generator function 实现延迟(懒惰)生产序列值。

synchronous generator function

标记函数体为 sync*,并使用 yield 传递值,返回 Iterable 对象:

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

asynchronous generator function

标记函数体为 sync*,并使用 yield 传递值,返回 Stream 对象:

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

Metadata

提供关于代码的额外信息,以 @ 符号开头,后面紧跟一个运行时常量的引用或者常量构造器的调用。@deprecated@override 在所有 Dart 代码中都可用。

自定义实现:

library todo;

class Todo {
  final String who;
  final String what;

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

使用示例:

import 'todo.dart';

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

Typedefs

函数也是对象,在作为变量或返回值时会丢失其类型信息,使用 typedef(也叫函数类型别名)给函数类型起一个名字来避免这种情况。

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

class SortedCollection {
//  Function compare;
  Compare compare;

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

int sort(Object a, Object b) => 0;

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

  assert(coll.compare is Function);
  
  assert(coll.compare is Compare);
}

Callable classes

在类中实现 call() 后,就可以像调用函数一样使用其实例/对象:

class WannabeFunction {
  call(String a, String b, String c) => '$a $b $c!';
}

main() {
  var wf = WannabeFunction();
  var out = wf("Hi","there,","gang");
  print('$out');
}

Isolates

为了利用好多核 CPU,通常会使用共享内存的多线程来同时执行,这种共享状态的并发容易产生错误且导致代码复杂。Dart 使用 isolates 取代线程,每个 isolate 都拥有它自己的内存堆,以确保其中的状态信息不被其他 isolate 所访问。

Comments

文档注释可以使用 /// 开头:

/// A domesticated South American camelid (Lama glama).
///
/// Andean cultures have used llamas as meat and pack
/// animals since pre-Hispanic times.
class Llama {
  String name;

  /// Feeds your llama [Food].
  ///
  /// The typical llama eats one bale of hay per week.
  void feed(Food food) {
    // ...
  }

  /// Exercises your llama with an [activity] for
  /// [timeLimit] minutes.
  void exercise(Activity activity, int timeLimit) {
    // ...
  }
}

在注释中,使用 [] 指向其他元素(类、方法、属性、全局变量、函数或参数)。

官方原文