我在之前的一篇Flutter安装教程中提到了Flutter采用的语言是Dart,所以要学习Flutter必须要学习Dart。这语言非常新,由Chrome在2011年10月份推出第一版,目前生态也相对较小,所以Dart的存在感一直比较低,但我相信Flutter会越来越火热,必将反过来推动Dart生态的发展!在大致学了一遍Dart的语法后,我发现它很像是JavaScript和Java的结合体(TypeScript?😂):Dart是一门面向对象的强类型语言,拥有可运行的class、元数据(注解)、泛型、抽象、反射、异步支持...等各种机制。接下来我将列出Dart(version: 2.2.0)的部分语法糖(异常处理、流程控制语句等和C、JS等都几乎一致,所以不再介绍)。
注:如果你想跑本文中的示例,需要安装Dart SDK,本文所用Dart版本为2.1.0
Dart 语法列表
语法概述[1]
新建一个demo.dart,写入一个基本的dart程序:
// 这是程序执行的入口。
main() {
add(1, 2);
}
add(firstNum, secondNum) {
num result = firstNum + secondNum;
print('$firstNum + $secondNum = $result');
}
可以在终端输入dart demo.dart
执行程序。
- main函数:这是所有Dart程序执行的入口,有且仅有一个,和C语言中的main一样;
- add方法:一个普通的接收两个形参的函数;
- num:定义result的类型为数字类型,如果确定该数字类型是浮点型或者是整型,也可以更具体地指定为
double
或int
; - print:用来打印内容的内置方法;
- $vars:字符串插值,在字符串字面量中引用变量或者表达式;
- 所有能够使用变量引用的都是对象,每个对象都是一个类的实例。所有的对象都继承于 Object 类。
- 和Java 不同的是,Dart 没有 public、 protected、private 关键字。如果一个标识符以 _ 开头,则该标志符在库内是私有的。
关键字列表[2]
- 带有上标 1 的关键字是内置关键字。避免把内置关键字当做标识符使用。 也不要把内置关键字用作类名字和类型名字。
- 带有上标 2 的关键字,是在Dart 1.0发布以后又新加的,用于支持异步相关的特性。 你不能在标记为
async
、async*
、或者sync*
的方法体内使用async
、await
、或者yield
作为标识符。
变量[3]
1. 变量声明及初始化
一个简单的变量声明如下:
var name = '张三';
其中var关键字表示接收任意类型的变量,但是和JavaScript不同的是,Dart中的var一旦接收了一次赋值,其声明的变量类型就确定下来了,不能再更改,且在同作用域中不能再次声明。
如下所示的代码在Dart中会报错:
testVariable() {
var name = '张三';
name = 22;
print(name);
}
// 执行将报错:Error: A value of type 'dart.core::int' can't be assigned to a variable of type 'dart.core::String'.
testVariable() {
var name = '张三';
var name = '李四';
print(name);
}
// 执行将报错:Error: 'name' is already declared in this scope.
2. 默认值:没有初始化的变量自动获取一个默认值为null
int a;
print(a == null); // true
3. final & const
- 使用final或const定义的变量不能被更改:
final int a = 2;
a = 3; // 报错:'a', a final variable, can only be set once.
const int b = 4;
b = 5; // 报错:Constant variables can't be assigned a value.
- final 或者 const 都不能和 var 同时使用。
- final和const不同在于,const变量为编译时常量,需要在编译期间就确定值,const声明必须用const类型的值初始化。如:
int a = 2;
final int b = a * 3; // 6
const int c = a * 3; // 报错:Const variables must be initialized with a constant value.
- const 变量同时也是final变量。
- 如果 const 变量在类中,必须定义为 static const。static 表示成员在类本身上可用,而不是在类的实例上。如:
class TestConst {
const int a = 3; // 报错:Only static fields can be declared as const.
static const int b = 3; // 正确写法
}
- const 关键字不仅仅只用来定义常量,还可以用来创建不变的值,还能定义const类型的构造函数,这种类型的构造函数创建的对象是不可改变的。任何变量都可以有一个不变的值:
testConst() {
List foo = const [2, 4];
foo.add(6); // 报错:Unsupported operation: Cannot add to an unmodifiable list
print(foo);
}
内置类型[4]
Dart 内置支持下面这些类型:
- numbers
- strings
- booleans
- lists
- maps
- runes
- symbols
1. numbers
如前面所说,Dart一共可以用四种方式声明一个数字变量:var,num,int,double。
其中num是int和double的父类,用来声明不确定浮点型还是整型的数字,且变量可以重新赋值浮点或整型的变量;而当用var声明数字变量时,初始化时就会确定其为整型还是浮点型,若为整型,则不能重新赋值一个浮点数字了,若为浮点型,再重新赋值整型数字时,会自动转换成浮点型。如下示例一目了然:
var a = 2;
a = 3.14;
// 报错:A value of type 'double' can't be assigned to a variable of type 'int'.
而下面这样是可以的,但是a输出为浮点型:
var a = 3.14;
a = 2;
print(a) ; // 2.0
double和int声明的情况和var一致,只是var初始化时可选择类型,double和int初始化时就必须明确:
int a = 3.14;
// 报错:A value of type 'double' can't be assigned to a variable of type 'int'.
double b = 2;
print(b); // 2.0
2. strings
- 可以在字符串中使用表达式,用法是这样的: ${expression}。如果表达式是一个比赛服,可以省略 {}。 如果表达式的结果为一个对象,则 Dart 会调用对象的 toString() 函数来获取一个字符串。
String name = 'Tony';
List<String> subjects = ['math', 'physics'];
print('$name is boy');
// Tony is boy
print('$name is good at ${subjects.map((item) => item.toUpperCase())}');
// Tony is good at (MATH, PHYSICS)
- 可以使用 + 操作符来把多个字符串链接为一个,也可以把多个字符串放到一起来实现同样的功能:
String run = 'Tony ' + 'is ' + 'running';
String eat = 'Tony ' 'is ' 'eating';
print(run); // Tony is running
print(eat); // Tony is eating
- 使用三个单引号或者双引号也可以创建多行字符串对象:
String work = '''
Tony is working in the office;
He is tired;
He needs a good sleep;
''';
print(work);
/*
Tony is working in the office;
He is tired;
He needs a good sleep;
*/
3.booleans
Dart在需要一个布尔值的时候,只有值为true的变量才被认为是true,其他所有情况都被认为是false,这一点和JavaScript是完全不同的,如以下代码,如果是JavaScript解释器执行的话将输出:how are u! 而在Dart中将会报错:
var name = 'Tony';
if (name) { // 报错:Conditions must have a static type of 'bool'.
print('how are u!');
} else {
print('anybody here?');
}
4. lists
Dart中的list可被理解为JS中的Array,两种数据结构几乎一模一样,只是用法上有所不同。
testList() {
var list = [];
print(list.isEmpty); // true
list.add(3);
list.addAll([4,6,8]);
print(list); // [3, 4, 6, 8]
var list2 = new List(4);
print(list2); // [null, null, null, null]
List list3 = [1,2,3];
print(list3.map((item) => item * 2)); // (2, 4, 6)
List<int> list4 = [5,2,7,9,1];
list4.sort();
print(list4); // [1, 2, 5, 7, 9]
}
更多的list方法可参考https://api.dartlang.org/stable/dart-core/List-class.html
5. maps
Dart中的Map和ES6中的Map类似,实例方法有所不同,下面是简单的一些例子:
testMap() {
var cities = new Map();
var list = ['NanJin', 'SuZhou'];
cities['HangZhou'] = 'ZheJiang';
cities['ChengDu'] = 'SiChuan';
cities.addAll({
'XiaMen': 'FuJian',
'GuiYang': 'GuiZhou'
});
cities[list] = 'JiangSu';
print(cities); // {HangZhou: ZheJiang, XiaMen: FuJian, GuiYang: GuiZhou, [NanJin, SuZhou]: JiangSu}
print(cities['HangZhou']); // ZheJiang
print(cities.length); // 4
cities.clear();
print(cities.isEmpty); // true
}
6. runes
这玩意儿不常用。在 Dart 中,runes 代表字符串的 UTF-32 code points,而普通的Dart字符串是UTF-16 code units序列,所以在Dart中,使用\uXXXX的形式表示32-bit Unicode,其中XXXX是4个16进制的数,如果是非4个数值的情况,把编码值放到大括号中即可。 例如, emoji表情 (😀) 是 \u{1f600}。
String 类有一些属性可以提取 rune 信息。 codeUnitAt
和 codeUnit
属性返回 16-bit code units。 使用 runes
属性来获取字符串的 runes 信息。
testUnicode() {
var smile = '\u{1f600}';
print(smile);
Runes emoji = new Runes('\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}');
print(new String.fromCharCodes(emoji));
}
输出结果为:
7. symbols
这玩意儿更不常用,甚至可能永远都用不上。做JS的人可能熟悉ES6中的symbol,但是这两者却是大大滴不同。ES6中的symbol用来定义一个独一无二的值,作为替代易混淆的字符串的一种标识。而在Dart中,symbol用来反射库中的元数据,这里需要超前接触一些概念了,比如class、库、反射等;但现在不做详细研究,大概知道用法和用途就好。
反射是在运行时获取某类元数据的一种机制,比如类中的方法数量、该类拥有的构造函数数量或函数中的参数数量。
新建Animal.dart,输入以下内容:
library animal_lib;
class Animal {
walk() {
print('animals can walk');
}
sleep() {
print('animals need to sleep');
}
eat() {
print('animals need to eat');
}
}
class Person extends Animal{
work() {
print('a person need to work for money');
}
}
新建AnimalSymbol.dart,输入以下内容:
import 'dart:core';
import 'dart:mirrors';
import 'Animal.dart';
main() {
Symbol lib = new Symbol('animal_lib');
Symbol className = new Symbol('Animal');
// 像是上面的编译时常量也可以写成:Symbol className = #Animal;
// print(#s == new Symbol('s')); //true
testSymbolReflect(lib, className);
}
void testSymbolReflect(Symbol lib, Symbol className) {
MirrorSystem mirrorSystem = currentMirrorSystem();
LibraryMirror libMirror = mirrorSystem.findLibrary(lib);
if (libMirror != null) {
print('there are ${libMirror.declarations.length} classes found in the library:');
// 获取库中所有的类
libMirror.declarations.forEach((s, d) => print(s));
// containsKey用来判断库中是否存在某一个类
if (libMirror.declarations.containsKey(className)) print('found class');
// 获取一个指定类的反射
ClassMirror classMirror = libMirror.declarations[className];
print('there are ${classMirror.instanceMembers.length} instance methods in the ${MirrorSystem.getName(className)}: ');
// 获取该类中所有的实例方法
classMirror.instanceMembers.forEach((s, v) => print(MirrorSystem.getName(s)));
}
}
执行dart AnimalSymbol.dart
,期望输出结果:
操作符[5]
这里仅对一些比较有意思的操作符进行举例,很多操作符是大部分语言共通的,就不再介绍了。
-
?.
:条件成员访问,避免出现类似JS中Cannot read property 'name' of undefined
的异常。
var a;
return a?.name; // 如过不用 ?. 而使用 . 会抛出异常
-
??
:是否为null,类似JS中的||
var a;
var b = a ?? 4;
var c = b ?? 6;
print(b); // 4
print(c); // 4
-
??=
:是否为null,如果是则赋值
var a = 2;
var b = 6;
var c;
b ??= a;
c ??= a;
print(b); // 6
print(c); // 2
-
..
:级联操作符,可以在同一个对象上连续调用多个函数以及访问成员变量。 使用级联操作符可以避免创建临时变量。
main() {
var animal = new Animal();
animal
..walk()
..sleep()
..eat();
}
class Animal {
walk() {
print('animals can walk');
}
sleep() {
print('animals need to sleep');
}
eat() {
print('animals need to eat');
}
}
-
~/
:除号,但是返回值为整数
var a = 25;
print(a ~/ 6); // 4
-
as
:把对象转换为特定的类型。 一般情况下,你可以把它当做用 is 判定类型然后调用所判定对象的函数的缩写形式。例如下面的 示例:
if (emp is Person) { // Type check
emp.firstName = 'Bob';
}
使用as简化上述操作:
(emp as Person).firstName = 'Bob';
注意: 上面这两个代码效果是有区别的。如果 emp 是 null 或者不是 Person 类型, 则第一个示例使用 is 则不会执行条件里面的代码,而第二个情况使用 as 则会抛出一个异常。
-
is
:如果对象是指定的类型返回 true -
is!
:如果对象是指定的类型返回 false
int a = 2;
print(a is int); // true
print(a is! int); // false
function(方法)[6]
1. 基础用法
和JS不一样,Dart中没有关键字function声明,且可以指定方法的返回类型,当然也可以不用指定。如下定义一个返回布尔类型的函数:
bool isEvenNumber(int number) {
return number % 2 == 0;
}
对于只有一个表达式的方法,可以像ES6中的胖箭头那样简写,如上所示代码可以简写为:
bool isEvenNumber(int number) => number % 2 == 0;
方法可以有两种类型的参数:必需的和可选的。 必需的参数在参数列表前面, 后面是可选参数;可选参数可以是命名参数或者基于位置的参数,但是这两种参数不能同时当做可选参数。举个栗子:
- 可选命名参数:需要把可选命名的参数放在{}中,调用的时候使用param: value的形式传入:
main() {
print(add(n1: 2, n2: 9, n3: 4)); // 15
}
int add({int n3, int n2, int n1}) { // {}内参数顺序可以变
return n1 + n2 + n3;
}
- 可选位置参数:需要把可选位置的参数放在[]中:
main() {
print(add(2, 9)); // 11
print(add(2, 9, 4)); // 15
}
int add(int n1, int n2, [int n3]) {
int sum = n1 + n2;
if (n3 != null) {
sum += n3;
}
return sum;
}
- 默认参数值:默认参数值仅适用于可选参数,且默认值只能是编译时常量,如果没有提供默认值,则默认值为 null:
main() {
print(add(2, 9)); // 15
}
int add(int n1, int n2, [int n3 = 4]) { // 默认n3的值为4
int sum = n1 + n2;
if (n3 != null) {
sum += n3;
}
return sum;
}
2.匿名函数
和JS差不多的使用方法:
var list = ['apples', 'oranges', 'grapes', 'bananas', 'plums'];
list.forEach((i) { // forEach中就是一个匿名方法
print(list.indexOf(i).toString() + ': ' + i);
});
3.函数是一等公民
一等公民指的是,函数可以像任何变量一样,赋值给另一个变量,或者作为参数传给另一个函数,或者作为返回值被一个函数return,它可以去任何值可以去的地方。举个栗子:
main() {
testFunction()(3, 7); // 10
}
testFunction() {
var foo = (int n1, int n2) {
return n1 + n2;
};
return foo;
}
class(类)[7]
Dart 是一个面向对象编程语言,同时支持基于 mixin 的继承机制。每个对象都是一个类的实例,所有的类都继承于 Object。 基于 Mixin 的继承意味着每个类(Object 除外)都只有一个超类,一个类的代码可以在其他多个类继承中重复使用。
一个普通的类:
main() {
var ins = new TestClass(); // 获取实例
print(ins.a); // 9;
print(ins.add()); // 11
// 使用 ?. 来替代 . 可以避免当左边对象为 null 时候 抛出异常:
var ins2;
print(ins2?.add()); // null
}
class TestClass {
num a = 9;
num b = 2;
add() {
return a + b;
}
}
1. 构造函数
定义一个和类名字一样的方法就定义了一个构造函数,类似于ES6中Class内部的constructor:
main() {
var ins = new TestClass(6, 7);
print(ins.a); // 6;
print(ins.add()); //13
}
class TestClass {
num a = 9;
num b = 2;
// 定义构造函数
TestClass(int a, int b) {
this.a = a;
this.b = b;
}
// 上面的构造函数参数赋值给实例变量的操作可以简写成以下形式:
// TestClass(this.a, this.b);
add() {
return this.a + this.b;
}
}
2.命名构造函数
使用命名构造函数可以为一个类实现多个构造函数:
main(){
var ins = new TestClass.doMath(6, 7);
var ins2 = new TestClass.doPrint(); // I am printing something
print(ins.add()); // 13
}
class TestClass {
num a;
num b;
// 命名的构造函数
TestClass.doMath(this.a, this.b);
TestClass.doPrint(){
print('I am printing something');
}
add() {
return this.a + this.b;
}
}
注意以下几点:
- 如果你没有定义构造函数,则会有个默认构造函数。 默认构造函数没有参数,并且会调用超类的无名无参数的构造函数。
- 构造函数不能继承。 子类如果没有定义构造函数,则只有一个默认构造函数 (没有名字没有参数)。
- 如果超类没有无名无参数构造函数, 则你需要手工地调用超类的其他构造函数。 在构造函数参数后使用冒号 (:) 可以调用 超类构造函数。
main() {
var ins = new TestClass();
print(ins.add());
//输出:
// parent construtor
// 7
}
class ParentClass {
ParentClass() {
print('parent construtor');
}
}
class TestClass extends ParentClass {
num a = 3;
num b = 4;
add() {
return this.a + this.b;
}
}
main() {
var ins = new TestClass();
print(ins.add());
//输出:
// parent construtor
// child constructor
// 7
}
class ParentClass {
ParentClass.doPrint() {
print('parent construtor');
}
}
class TestClass extends ParentClass {
num a = 3;
num b = 4;
TestClass(): super.doPrint() {
print('child constructor');
}
add() {
return this.a + this.b;
}
}
3.初始化列表
在构造函数体执行之前除了可以调用超类构造函数之外,还可以初始化实例参数,初始化列表非常适合用来设置 final 变量的值。使用逗号分隔初始化表达式:
main() {
var ins = new TestClass({
'a': 6,
'b': 7
});
print(ins.add());
//输出:
// parent construtor
// child constructor
// 13
}
class ParentClass {
ParentClass.doPrint() {
print('parent construtor');
}
}
class TestClass extends ParentClass {
final num a;
final num b;
TestClass(Map numMap)
: a = numMap['a'],
b = numMap['b'],
super.doPrint() {
print('child constructor');
}
add() {
return this.a + this.b;
}
}
构造函数执行顺序为:
- 初始化参数列表
- 超类的无名构造函数
- 主类的无名构造函数
4.重定向构造函数
有时候一个构造函数会调动类中的其他构造函数,用冒号连接调用的构造函数:
main() {
new TestClass.doMath(); // sum: 13
}
class TestClass {
num a;
num b;
TestClass(this.a, this.b) {
print('sum: ${add()}');
}
TestClass.doMath() : this(6, 7);
int add() {
return this.a + this.b;
}
}
5.工厂方法构造函数
工厂构造函数是一种使用factory
声明的构造函数,与普通构造函数不同,它不会自动生成一个实例对象,而是通过代码来决定要返回什么实例对象。比如,你希望每次新生成一个实例对象时就将其缓存起来,下次直接取缓存中的实例对象:
main() {
var ins1 = new Symbol('s');
var ins2 = new Symbol('s');
print(identical(ins1, ins1)); // true
}
class Symbol {
final String name;
static Map<String, Symbol> _cache;
factory Symbol(String name) {
if (_cache == null) {
_cache = {};
}
// 从缓存中取实例对象,若缓存中没有,则新生成一个实例放入缓存中
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final symbol = new Symbol._internal(name);
_cache[name] = symbol;
return symbol;
}
}
Symbol._internal(this.name);
}
6. getter and setter
getter和setter是用来设置和访问对象属性的特殊函数,每个实例变量都隐含的具有一个 getter, 如果变量不是 final 的则还有一个 setter。
main() {
var ins = new TestClass(4, 5, 6);
print(ins.area); // 20
ins.area = 10;
print(ins.width); // 2.0
print(ins.volume); // 60.0
ins.volume = 240;
print(ins.height); // 24.0
}
class TestClass {
num width;
num length;
num height;
TestClass(this.width, this.length, this.height);
num get area => width * length;
void set area(num value) => width = value / length;
num get volume => area * height;
void set volume(num value) => height = value / area;
}
7.隐式接口
Dart没有interface,但是每个类都隐式地定义了一个包含所有实例成员的接口,并且这个类实现了这个接口。如果你想创建类 A 来支持类 B 的 api,但又不希望拥有类 B 的实现,则类 A 应该implements 类B。所以在Dart中:
- class即是interface;
- 当class被当做interface用时,class中的方法就是接口的方法,需要在子类里重新实现,否则抛出异常;
- 当class被当做interface用时,class中的成员变量也需要在子类里重新实现,否则抛出异常;
来看个例子:
main() {
Person p = new Person();
p.walk(); // a person has 2 legs for walking
}
class Animal {
final int eyes;
final int legs;
Animal(this.eyes, this.legs);
walk() {}
eat() {}
}
class Person implements Animal {
final int eyes = 2;
final int legs = 2;
eat() {
print('a person needs to eat for living');
}
walk() {
print('a person has $legs legs for walking');
}
}
8. 继承
前面的例子中已经有了一些继承的例子,继承很简单,和java没什么区别,所以不再详细介绍了。
我们来看下,extends和implements的区别:
- 继承不需要在子类中全部重写超类中的实例变量和实例方法
- 继承中子类会拥有超类的实现
- Dart中是单继承,但是可以多实现
如下是继承的模式:
main() {
Person p = new Person(2, 2);
p.eat(); // animals need to eat for living
}
class Animal {
final int eyes;
final int legs;
Animal(this.eyes, this.legs);
walk() {}
eat() {
print('animals need to eat for living');
}
}
class Person extends Animal {
final int eyes;
final int legs;
Person(this.eyes, this.legs) : super(eyes, legs);
walk() {
print('a person has $legs legs for walking');
}
}
9.Mixins
mixins是给类添加新的特性的方式,也是一种重用类代码的一种方式。mixins的关键字是with。但是有了继承,为什么还要Mixins呢? 主要是因为继承是单一的,而比如有些情况,两个子类继承不同的父类,但是两个字类又需要一些共同的方法,这时候mixins就发挥作用了。
比如下列例子中,人和麻雀分别继承不同的类,但是这两个类都需要一个唱歌的功能,现在就可以把唱歌通过混入的方式加入这两个类中:
main() {
Sparrow sparrow = new Sparrow();
sparrow.singASong(); // sing a song
Person person = new Person();
person.singASong(); // sing a song
}
abstract class Animal {
void walk();
void eat();
}
class Sing {
void singASong() {
print('sing a song');
}
}
// 哺乳类继承动物类
class Mammal extends Animal {
void walk() {
print('mammals can walk');
}
void eat() {
print('mammals need to eat');
}
}
// 鸟类继承动物类
class Bird extends Animal {
void walk() {
print('birds can walk');
}
void eat() {
print('birds need to eat for living');
}
}
// 人继承自哺乳类,同时拥有唱歌的功能
class Person extends Mammal with Sing {}
// 麻雀继承自鸟类,也拥有唱歌的功能
class Sparrow extends Bird with Sing {}
ok,现在我们知道了Dart中的class有三种关系:extends、implements、with;
这三种关系可以同时使用,但是有先后顺序:extends -> mixins -> implements,extends在前,mixins在中间,implements最后。
抽象[8]
Java程序猿肯定很熟悉抽象了,但JS中没有抽象这一概念。抽象可以修饰类和方法。可以这样解释抽象类:有一个类A,它知道需要实现一些功能,但它不知道具体实现逻辑,也不知道要写多少个功能合适,所以就交给子类来具体实现。抽象类通常具有抽象函数。
抽象的意义:抽象类的存在意义就是为了被子类继承,抽象方法存在的意义就是为了被子类重写。
抽象类有以下几个特征:
- 一个类如果继承了抽象类,这个类必须重写抽象类的所有抽象方法,只要有一个抽象方法没有被重写,子类也必须定义为抽象类。
- 一个类如果拥有了抽象方法,这个类必须定义成抽象类。
- 一个类要变成抽象类,在类名前面加
abstract
关键字即可。要定义一个抽象方法,不要用 { } 即可。抽象类不能被实例化;
举个栗子:
// 定义一个抽象类,里面有两个抽象方法
// 如果类里面有抽象方法,但是类没有被abstract修饰,则抛出异常
abstract class Animal {
void walk();
void eat();
}
// 子类继承抽象类,需要全部重写抽象方法
class Person extends Animal {
void eat() {
print('a person needs to eat for living');
}
void walk() {
print('a person has 2 legs for walking');
}
}
// 子类继承抽象类,如果有任何一个抽象方法没重写,则子类也必须定义为抽象类
abstract class Bird extends Animal {
void eat() {
print('birds need to eat for living');
}
}
枚举[9]
枚举其实是一种特殊的类,用来表现一个固定数目的常量。使用enum
关键字来定义枚举类型:
enum Color {
red,
green,
blue
}
枚举类型中的每个值都有一个 index getter 函数, 该函数返回该值在枚举类型定义中的位置(从 0 开始)。 例如,第一个枚举值的位置为 0, 第二个为 1:
main () {
print('red position: ${Color.red.index}'); // 0
print('green position: ${Color.green.index}'); // 1
print('blue position: ${Color.blue.index}'); // 2
}
枚举的 values 常量可以返回所有的枚举值。
List<Color> colors = Color.values;
print(colors); // [Color.red, Color.green, Color.blue]
枚举类型具有如下的限制:
- 无法继承枚举类型、无法使用 mixin、无法implements一个枚举类型
- 无法显示的初始化一个枚举类型
泛型[10]
泛型是任何强类型语言都拥有的一种机制,用<>来标注类型。
使用泛型有两个优点:
- 必须指定数据的类型,类型安全,更易解读;
- 避免代码重复,使代码更简洁;
如:定义一个成员类型为String的List:
var names = new List<String>();
names.add('Tom'); // ok
names.add(34);
// 报错:The argument type 'int' can't be assigned to the parameter type 'String'.
使用集合字面量:
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
泛型可以让单一类适配多种类型的数据操作,比如:
main() {
var k = new IntCache();
k.setByKey('Tom', 22); // 如果是k.setByKey('Tom', '22');则会报错
print(k.getByKey('Tom'));
var j = new StringCache();
j.setByKey('Jack', 'math');
print(j.getByKey('Jack'));
}
class IntCache {
Map<String, int> map = new Map();
int getByKey(String key) {
return map[key];
}
void setByKey(String key, int value) {
map.addAll({key: value});
}
}
// 上面代码是对int类型操作,在没用泛型的情况下,你想对String类型操作,就得重新定义一个类
class StringCache {
Map<String, String> map = new Map();
String getByKey(String key) {
return map[key];
}
void setByKey(String key, String value) {
map.addAll({key: value});
}
}
泛型通过将类型参数化,实现一个类可以适配多种数据类型操作,这样写就可以完美解决上述的问题:
class Cache<T> {
Map<String, T> map = new Map();
T getByKey(String key) {
return map[key];
}
void setByKey(String key, T value) {
map.addAll({key: value});
}
}
在构造函数中使用泛型:
var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = new Set<String>.from(names);
在运行时也可以判断具体的类型:
var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
限制泛型类型:当使用泛型类型的时候,你可能想限制泛型的具体类型。 使用 extends 可以实现这个功能:
main() {
var z = new Cache();
z.setByKey('Jack', 'math'); // 报错
print(z.getByKey('Jack'));
}
class Cache<T extends int> {
Map<String, T> map = new Map();
T getByKey(String key) {
return map[key];
}
void setByKey(String key, T value) {
map.addAll({key: value});
}
}
异步机制[11]
Dart 有一些语言特性来支持异步编程。最常见的特性是async
方法和await
表达式。
Dart 库中有很多返回Future
或者Stream
对象的方法。这些方法是异步的: 这些函数在设置完基本的操作后就返回了,而无需等待操作执行完成。 例如读取一个文件,在打开文件后就返回了。
1. Future
Future和ES6中的Promise别无二致,来看个关于Future的例子:
main() {
testFuture();
// 4秒后输出:
// hello world!
// future done
}
testFuture() {
// 延迟4秒
Future.delayed(new Duration(seconds: 4), () {
return 'hello world!';
}).then((data) {
print(data);
}).catchError((e) {
print(e);
}).whenComplete(() {
//无论成功或失败都会走到这里
print('future done');
});
}
Future提供了wait
方法来实现多个异步操作的等待,这看起来很像是ES6中的Promise.all
,还提供了any
方法实现第一个异步操作完成后的处理,类似Promise.race。具体可看API:https://api.dartlang.org/stable/2.3.0/dart-async/Future-class.html
main() {
testFuture();
// 3秒后输出:
// future wait
}
testFuture() {
Future.wait([
Future.delayed(new Duration(seconds: 3), () {
return 'future';
}),
Future.delayed(new Duration(seconds: 1), () {
return 'wait';
})
]).then((results) {
print('${results[0]} ${results[1]}');
}).catchError((e) {
print(e);
});
}
再来看看async
和await
的用法,和es6一样,只不过async修饰在方法名后,es6中修饰在方法名前:
Future<String> delayPrint() {
return Future.delayed(new Duration(seconds: 4), () {
return 'test async await';
});
}
testAsyncAwait() async {
String str = await delayPrint();
print(str);
}
2. Stream
Stream也是用于接收异步事件数据,和Future不同的是,它可以接收多个异步操作的结果(成功或失败)。也就是说,在执行异步任务时,可以通过多次触发成功或失败事件来传递结果数据或错误异常。Stream 常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。举个例子:
main() {
testStream();
// 依次输出以下内容(在各自延迟时间之后输出):
// second Error
// first Error
// hello 1
// hello 3
// Stream done
}
testStream() {
Stream.fromFutures([
// 1秒后返回结果
Future.delayed(new Duration(seconds: 2), () {
return 'hello 1';
}),
// 抛出一个异常
Future.delayed(new Duration(seconds: 1), () {
throw AssertionError('first Error');
}),
// 3秒后返回结果
Future.delayed(new Duration(seconds: 3), () {
return 'hello 3';
}),
Future.delayed(new Duration(milliseconds: 300), () {
throw AssertionError('second Error');
})
]).listen((data) {
print(data);
}, onError: (e) {
print(e.message);
}, onDone: () {
print('Stream done');
});
}
元数据(注解)[12]
这玩意儿在Java中叫“注解”,在JS中叫“装饰器”(目前还在ES提案中)。使用注解可以给你的代码添加一些额外信息和功能,Dart提供了三个可直接使用的注解: @deprecated
、 @override
。
先简单介绍一下这俩:
-
@deprecated:如果一个方法或类上面被这个注解修饰,则代表这个方法或类不再建议使用。如图中所示,调用一个被@deprecated修饰的实例方法,编辑器会提示你不要使用该方法:
-
@override:使用这个注解修饰方法,代表这个方法是对父类方法的重写。如果父类中没有这个方法,编辑器会给你提示:
你也可以定义自己的注解:
class Todo {
final String who;
final String what;
const Todo(this.who, this.what);
}
@Todo('seth', 'make this do something')
class TestMetaData {
void doSomething() {
print('do something');
}
}
使用反射可以获取元数据信息:
main() {
ClassMirror classMirror = reflectClass(TestMetaData);
// 获取 class 上的元数据
classMirror.metadata.forEach((metadata) {
print(metadata.reflectee.who + ' ==> ' + metadata.reflectee.what);
});
}
执行后输出:seth ==> make this do something
小结
要学习Dart,你也可以参考Dart的官方文档:https://dart.dev/guides/language/language-tour,本文主要参考的也是官方文档,但对官网的一些说明进行了更详细的解释。Dart中很多的特性可以深入研究,但是如果只是想快速上手开发,浅尝辄止便够了。如果本文中有任何问题,欢迎指出。
-
http://dart.goodev.org/guides/language/language-tour#a-basic-dart-program%E4%B8%80%E4%B8%AA%E6%9C%80%E5%9F%BA%E6%9C%AC%E7%9A%84-dart-%E7%A8%8B%E5%BA%8F ↩
-
http://dart.goodev.org/guides/language/language-tour#keywords%E5%85%B3%E9%94%AE%E5%AD%97 ↩
-
http://dart.goodev.org/guides/language/language-tour#variables%E5%8F%98%E9%87%8F ↩
-
http://dart.goodev.org/guides/language/language-tour#built-in-types%E5%86%85%E7%BD%AE%E7%9A%84%E7%B1%BB%E5%9E%8B ↩
-
http://dart.goodev.org/guides/language/language-tour#operators%E6%93%8D%E4%BD%9C%E7%AC%A6 ↩
-
http://dart.goodev.org/guides/language/language-tour#functions%E6%96%B9%E6%B3%95 ↩
-
http://dart.goodev.org/guides/language/language-tour#classes ↩
-
http://dart.goodev.org/guides/language/language-tour#abstract-classes ↩
-
http://dart.goodev.org/guides/language/language-tour#generics%E6%B3%9B%E5%9E%8B ↩
-
http://dart.goodev.org/guides/language/language-tour#asynchrony-support%E5%BC%82%E6%AD%A5%E6%94%AF%E6%8C%81 ↩
-
http://dart.goodev.org/guides/language/language-tour#metadata%E5%85%83%E6%95%B0%E6%8D%AE ↩