Dart快速入门 语法篇

入门

1
2
3
4
5
6
7
8
9
10
// 定义个方法。
printNumber(num aNumber) {
print('The number is $aNumber.'); // 在控制台打印内容。
}
// 这是程序执行的入口。
main() {
var number = 42; // 定义并初始化一个变量。
printNumber(number); // 调用一个方法。
}
  • 注释:///* ... */,同其他主流语言
  • 类型:num、String、int、bool等
  • 字面量:42,’Hello world!’
  • 函数:类似print()的形式
  • 字符串插值
  • 入口方法:main

基本理念

  • 所有可以用变量引用的都是对象,每个对象都是一个类的实例,例如数字、方法、null,所有对象都继承Object类
  • Dart是强类型语言。但是不强制使用类型标注,因为它可以通过推导得到变量类型。在你明确不希望有类型时,使用dynamic关键字表示动态类型
  • Dart支持泛型,比如List<int>
  • Dart支持顶级方法main(),支持类的静态方法、实例方法,也可以在函数内使用函数
  • 类似地,Dart支持全局变量、局部变量和在类中定义的成员变量
  • Dart没有public、protected、private的区分,如果标识符以_开头,那么该标识符就是私有的
  • Dart的变量名只能以下划线和字母开头,后跟字符或数字
  • Dart区分语句块和表达式,只有表达式有值。

关键字

分为三类:

  • 对于只在特定上下文环境下生效的上下文关键字,可以用作标识符
  • 对于内置标识符,为了便于移植JavaScript代码到Dart,这些关键字不可用作类或类型名或import的前缀
  • 其他关键字为保留字

变量

1
2
3
4
var name = 'Dart';
String name = 'Dart';
Dynamic name = 'Dart';

根据基本理念,变量都是存储值的引用。使用var修饰时,变量类型会自动推导;也可以显示声明变量类型,或者使用dynamic关键字表示变量可能有多种类型。

任何没有初始化的变量默认值都为null。

常量使用finalconst(实例变量只能用final)。

1
2
3
4
5
6
7
8
final name = 'foo';
final String title = 'FE';
const foo = 10000;
const double percent = 0.314;
final bar = const[];
const baz = []; // 和上面一个效果
  • final变量只能赋值一次,const变量是编译时常量。
  • const除了用来定义不变量,还可以用来创建不变的值,以及定义创建常量的构造函数。在这么用时可以省略构造过程,像上面的baz变量一样

内置类型

  • numbers
  • strings
  • booleans
  • lists (也被称之为 arrays)
  • maps
  • runes (用于在字符串中表示 Unicode 字符)
  • symbols

再次重申,Dart中变量都是一个对象,所以你都可以使用构造函数来初始化。

Number

intdouble两种类型。提供了原生操作符和abs()等常用函数,整数和浮点数的字面量初始化类似js。

字符串和数字互转:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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');

String

Dart的字符串是UTF-16编码的字符序列。可以使用单引号或双引号创建。字符串中用${expr}的语法使用表达式,如果表达式是一个标识符,可以省去{},对{}内的表达式,Dart使用toString()方法转成字符串使用。

使用'''"""表示多行字符串。使用r''表示纯字符串。

Boolean

布尔类型有两个字面量值,truefalse。和JavaScript不同的是,在if语句等使用bool类型的地方,只有true被认为是true,其余所有值都是false。这也是为了避免JavaScript中判断true、false时坑爹的地方。

1
2
3
4
5
6
7
if (1) {
print('JS prints this line.');
} else {
print('Dart in production mode prints this line.');
// However, in checked mode, if (1) throws an
// exception because 1 is not boolean.
}

List

List的字面量写法和JavaScript一样。Dart会做类型推导,在元素类型不一致时报错。你可以使用const语句定义一个不变的List对象。

1
2
const list = [1, 2, 3];
var list = const [1, 2, 3];

2.3后,Dart支持...解构操作符,以及对空列表兼容的...?。同时支持collection if和collection for语法。

1
2
3
4
5
6
7
8
9
10
11
var nav = [
'Home',
'Furniture',
'Plants',
if (promoActive) 'Outlet'
];
var listOfInts = [1, 2, 3];
var listOfStrings = [
'#0',
for (var i in listOfInts) '#$i'
];

Set

2.2版本后支持

一组元素唯一的无序列表。字面量写法类似数学中集合的定义方法。

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

也可以使用构造函数的方式创建。

1
2
3
var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);

类似List,2.3之后有......?的语法支持。

Map

表达键值对数据,每个键只出现一次,且可以是任意类型。类似Set,可以使用字面量和构造函数两种方式构造。使用字面量时,Dart会做类型推导。

Map的设置和JavaScript类似,另外类似List,2.3之后有......?的语法支持。

Rune

Dart用Rune类型表示UTF-32的字符,如emoji等。

Symbol

用来代表Dart中声明的操作符或标识符,可以在标识符前添加#获取标识符的Symbol对象。

方法

类似JavaScript,Dart中的Function也是对象并具有Function类型。推荐使用显式类型声明方法。

1
2
3
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}

Dart支持箭头函数。

可选参数

可选参数分两种:命名参数、位置参数。

命名参数使用param: value指定,在调用时使用{param1, param2}的形式传递参数。支持在参数前添加@required表示参数必选。

位置参数使用[]包裹方法参数,使用时不传参数即可。

1
2
3
4
5
6
7
8
9
enableFlags(bold: true, hidden: false);
enableFlags({bool bold, bool hidden}) {
// ...
}
String say(String from, String msg, [String device]) {
// ...
}
assert(say('Bob', 'Howdy') == 'Bob says Howdy');

定义方法时,可以使用=定义可选参数的默认值。否则默认值为null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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');

main函数

每个应用都需要有顶级的main()函数作为入口,返回值void类型,并且有可选的List<String>参数(用于解析命令行输入的参数数据)。如

1
2
3
4
5
void main() {
querySelector("#sample_text_id")
..text = "Click me!"
..onClick.listen(reverseText);
}

上面的..语法为级联调用,表示在一个对象上执行多个操作。

第一公民

类似JavaScript,Dart中Function可以作为参数、返回值、变量、对象使用。同样也有匿名函数可以使用,区别是箭头后是语句块时,不使用箭头,只在之后是表达式时使用箭头。

作用域与闭包

Dart是静态作用域,即变量的作用域在写代码时就确定了,作用域层级即大括号的层级。

类似JavaScript,Dart的闭包意味着方法可以封闭其作用域内的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// 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);
}

返回值

所有函数必须返回一个值,否则默认return null

操作符

  • ~/返回取整截断的商
  • 使用==判断相等性
    • 会调用左侧对象的==方法,和后面的对象对比
  • 类型转换:
    • as,类型转换,类似typescript中的as
    • is 判断对象是否是指定类型
    • is! 判断对象是否不是指定类型
  • ??=在value不是null时赋值给变量
  • expr1 ?? expr2表示如果expr1是非null则返回其值,否则执行expr2并返回
  • .. 级联操作符,表示在一个对象上连续调用多个函数以及访问成员变量,可以嵌套
  • ?..类似,但是在左侧操作对象为null时返回null
    1
    2
    3
    4
    5
    6
    7
    8
    final addressBook = (new AddressBookBuilder()
    ..name = 'jenny'
    ..email = 'jenny@example.com'
    ..phone = (new PhoneNumberBuilder()
    ..number = '415-555-0100'
    ..label = 'home')
    .build())
    .build();

流程控制

  • for循环中,Dart会自动捕获当时的index索引值,避免JavaScript中问题。对interable的对象可以使用forEach()方法遍历,对List、Set还支持for-in形式的遍历
  • switch中的每个case(除了空case)都必须有break
  • assert在检查模式下会被跳过

异常

和JavaScript中的异常类似。

不一样的是,可以使用oncatch捕获异常,可以通过rethrow在其中重新抛出异常。

  • 构造方式类似ES6中引入JavaScript Class。
  • 用成员方式声明的类变量在定义时初始化,也就是在构造函数前
  • 可以使用Object的runtimeType属性来判断实例的类型
  • 使用const关键字结合构造函数可以构造出不可变的对象实例

构造函数

使用和类名同名的方法作为构造函数(或者使用命名构造函数)。因为把构造函数参数赋值给实例变量的场景太常见了,Dart提供了下面的语法糖。

1
2
3
4
5
6
7
8
class Point {
num x;
num y;
// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}

可以使用命名构造函数实现多个构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
class Point {
num x;
num y;
Point(this.x, this.y);
// Named constructor
Point.fromJson(Map json) {
x = json['x'];
y = json['y'];
}
}

子类不会从父类继承构造函数,在未定义构造函数时,会有一个默认构造函数,这个函数没有参数,且会调起父类的没有参数的构造函数。

在有初始化参数列表(initializer list)的情况下,初始化参数列表在父类构造函数前执行。

  1. 初始化参数列表
  2. 父类无参构造函数
  3. 子类无参构造函数

父类没有无参构造函数时,需要手动调用父类的其他构造函数。

初始化列表

在执行父类构造函数前,可以初始化实例参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Point {
num x;
num y;
Point(this.x, this.y);
// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map jsonMap)
: x = jsonMap['x'],
y = jsonMap['y'] {
print('In Point.fromJson(): ($x, $y)');
}
}

在冒号右边用逗号隔开初始化表达式。注意:等号右边无法访问this

重定向构造函数

重定向构造函数没有代码,在构造函数声明后,用冒号调用其他构造函数

1
2
3
4
5
6
7
8
9
10
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);
}

常量构造函数

如果类支持提供状态不变的对象,需要定义一个const构造函数,且所有类变量都要是final

1
2
3
4
5
6
7
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
}

工厂构造函数

当你的构造函数不需要返回新对象,而从其他地方获取时(如缓存),使用工厂构造函数。工厂构造函数内无法访问this。调用时方式和普通构造函数等同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
}
var logger = new Logger('UI');

方法

类方法可以访问this,另外对于类对象的每个属性都有隐含的getter和setter(final除外)。也可以显式使用getset定义getter和setter的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Rectangle {
num left;
num top;
num width;
num 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;
}
  • Dart使用extends继承,用super指代父类,用@overide注解重载操作。
  • Dart中有抽象类/抽象方法,设计和使用类似Java的抽象类/抽象方法。如果你希望抽象类可实例化,可以定义一个工厂工造函数。
  • 每个类都隐式的定义了一个包含所有实例成员的接口,通过使用implement实现若干其他类的API(不包括构造函数)
  • 可以重载一些操作符,如+, -, [], >>等,实现在特定类上的特定表现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Vector {
final int x;
final int y;
const Vector(this.x, this.y);
/// Overrides + (a + b).
Vector operator +(Vector v) {
return new Vector(x + v.x, y + v.y);
}
/// Overrides - (a - b).
Vector operator -(Vector v) {
return new Vector(x - v.x, y - v.y);
}
}

有意思的是,Dart提供noSuchMethod()方法,在访问不存在的类实例或方法时被调用。如果没有填写,默认使用Object的同名方法。

1
2
3
4
5
6
7
@proxy
class A {
void noSuchMethod(Invocation inv) {
print('You tried to use a non-existent member: ' +
'${inv.memberName}');
}
}

枚举

枚举是特殊的类,使用enum关键字定义。每个枚举值都有index属性的getter函数,枚举的values常量可以返回所有枚举值。

1
2
3
4
enum Color { red, green, blue }
assert(Color.red.index == 0);
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

mixin

Dart中提供了多类继承中重用类代码的mixin,用with结合mixin类实现,这种类没有构造函数。除非你想像正常类一样使用mixin,否则使用mixin关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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只在特定类中使用时,结合on让mixin也能调用父类方法。

1
2
3
mixin MusicalPerformer on Musician {
// ···
}

类变量、函数

使用static前缀修饰,表示类级别的变量、函数。类变量只在第一次使用时初始化。静态方法无法访问this。

泛型

使用泛型的两个动机:

  • 有助于IDE、环境、同事帮你定位问题和代码自动生成
  • 减少重复代码

List和Map的泛型定义类似C++风格。

1
2
3
4
5
6
7
8
9
10
var names = <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 = new List<String>();
var views = new Map<int, View>();
print(names is List<String>); // true

在泛型中使用extends可以限制泛型的具体类型。在1.21之后,Dart支持泛型函数。

1
2
3
4
5
6
T first<T>(List<T> ts) {
// ...Do some initial work or error checking, then...
T tmp ?= ts[0];
// ...Do some additional checking or processing...
return tmp;
}

包管理

使用importlibrary引入和导出模块。_开头的标识符只在库内部可见。

1
2
3
4
5
6
7
import 'dart:html';
import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';
import 'package:lib2/lib2.dart' as lib2; // 指定库前缀,避免重名
import 'package:lib1/lib1.dart' show foo; // 部分导入

dart:开头代表内置库,package:开头代表外部库。外部库使用pub包管理器管理。

懒加载库

懒加载即在使用时再加载库,如优化app启动时间,加载很可能用不到的功能。

加载时使用deferred as导入,使用loadLibrary()方法加载。

1
2
3
4
5
6
import 'package:deferred/hello.dart' deferred as hello;
greet() async {
await hello.loadLibrary();
hello.printGreeting();
}

异步支持

Dart中返回FutureStream的方法都是异步的,意味着设置好耗时操作(I/O)后就返回。类似ES7中的awaitasync,你也可以像组织同步代码一样组织你的异步代码。

Dart中声明异步方法是在函数名后加入async,这类方法返回一个Future对象,了解JS中Promise的同学可以很快理解Future是做什么的。

1
2
3
4
5
6
7
8
checkVersion() async {
var version = await lookUpVersion();
if (version == expectedVersion) {
// Do something.
} else {
// Do something else.
}
}

在返回值是Stream时,使用await for的形式接收Stream中的数据。另外别忘了用async修饰外界函数。

1
2
3
4
5
6
7
Future main() async {
// ...
await for (var request in requestServer) {
handleRequest(request);
}
// ...
}

生成器函数

惰性生产数据,类似ES6中的function*。Dart提供两种类型:

  • 同步:返回Iterator
  • 异步:返回Stream
1
2
3
4
5
6
7
8
9
10
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}

可调用的类

类中实现了call()方法时,类实例可以当做方法调用。

1
2
3
4
5
class WannabeFunction {
call(int a, int b) => a + b;
}
var wf = new WannabeFunction();
wf(3, 4); // 7

类型别名

类似typescript中的interface定义,Dart可以借助typedef进行一些更复杂的类型判断。typedef只是类型别名的一种说法。

1
2
3
4
5
6
7
typedef int Compare(int a, int b);
int sort(int a, int b) => a - b;
main() {
assert(sort is Compare); // True!
}

元数据

使用元数据给代码添加额外信息,也能便于文档自动生成。

  • @deprecated
  • @override
  • @proxy

你还可以自定义自己的元数据注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
library todo;
class todo {
final String who;
final String what;
const todo(this.who, this.what);
}
// another file
import 'todo.dart';
@todo('seth', 'make this do something')
void doSomething() {
print('do something');
}

注释

  • 单行,//
  • 多行,/**/
  • 文档注释, ///开头,或/**开头,*/结束

遵守规范的注释风格会有助于文档自动生成。