目录
初识Dart
Dart中强制使用分号,javaScript 可以使用或者不适用分号,但是 Dart 必须使用分号和java 类似。 Dart 是强类型语言,但是支持类型推断, 所以实际开发中,可以不用写 void ;
Dart 可以使用如下语法 [var|<Type>] variableId 声明一个变量,如果一个变量没有指定初始值,那么 它的初始值是 null,int 类型的变量初始值也是 null,这是因为Dart中数字类型也是对象
一些重要的概念
a:Dart没有访问控制符(即public、protected、private),但是如果一个标识符以_开头,则表示它在 库内 是私有的
b:标识符可以以字母或者_开头,后面可以跟其他字符或者数字(注意,Dart不支持$符,也不支持Unicode作为变量名)
c:Dart中所有能够使用变量引用的都是对象,每个对象都是一个类的实例,数字、方法、null都是对象,所有对象都继承自Object类
d:Dart中函数是可以嵌套声明的
变量
final 和 const
final只能在初始化时赋值一次,后续值不可以修改
const同时也是final,区别在于const更加严格,它是编译时常量,也就是说如果一个变量声明为了const,那么应该是在非运行时就能得到这个值。
numbers
Dart支持两种类型的数值:int和double,它们都是num类的实例。num类型定义了基本的操作符,如 +/-/*//,以及abs()、ceil()、floor()等函数,而int子类中还定义了位操作符
int x = 1;
int hex = 0xA;
double y = 1.1;
double z = 1.42e5;
int 类型支持位操作
assert((3 << 1) == 6);
assert((3 >> 1) == 1);
assert((3 | 4) == 7);
字符串可以和数值互转,如:
// String -> init
var one = int.parse('1');
assert(one == 1);
// String -> double
var oneDotOne = double.parse('1.1');
assert(oneDotOne == 1.1);
// int -> String
String strOne = 1.toString();
assert(strOne == '1');
// double -> String
String strOneDotOne = 1.1.toStringAsFixed(1);
assert(strOneDotOne == '1.1');
strings
Dart字符串是UTF-16编码的字符序列,可以用单引号也可以用双引号:
var s1 = 'single quotes';
var s2 = "double quotes";
Dart同样支持模板字符串,语法为:${expression},如果 expression 是一个变量,那么可以省略{},即为 $varibale。如果表达式的结果是一个对象,那么会调用对象的toString()方法:
var s = 'World';
print('Hello, $s'); // 输出:Hello, World
var s2 = 100;
print('$s2 * 10 = ${s2 * 10}'); // 输出:100 * 10 = 1000
如果要创建多行字符串,可以使用 '''或者""",如
var s = '''
This is
a multi-line string
''';
还可以使用r前缀来创建一个原始(raw)字符串,如下:
var s = r'This a raw string, even \n is not special';
// 返回:This a raw string, even \n is not special
字符串字面量是编译时常量。如果一个字符串带有插值,那么如果字符串插值内引用的表达式或者变量计算结果也是编译时常量,那么这个字符串就也是编译时常量,如:
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = const [1, 2, 3];
// 以下字符串是合法的,因为它所引用的也都是编译时常量
const validConstString = '$aConstNum $aConstBool $aConstString';
// 但是下面这个就是非法的,因为所引用的不全是编译时常量
const invalidConstString = '$aNum $aBool $aString $aConstList';
booleans
Dart中有名字为bool的类型,它有两个实例对象:true和false,这两个对象都是编译时常量。当Dart需要一个布尔值的时候,只有true对象才是true,其他的都是false。
var name = 'Ruphi';
if (name) {
print('Hello, $name');
}
以上代码在生产模式下什么都不会输出,在检查模式下则会抛出一个异常,表示name不是一个布尔值,所以需要特别注意和JavaScript的差异:因此,在Dart中,我们应该 显式地 判断一个变量,确保其计算结果是布尔型的
if (1) {
print('JS prints this line');
} else {
print('Dart in production prints this line.');
// But in checked mode it throws an exception
}
list
var list = [1, 2, 3];
int i;
List list = [1, 2, 3, 4, 5];
for (i = 0; i < list.length; ++i) {
print(list[i]);
}
如果在list字面量前加一个const关键字,那么可以定义为一个不变的list对象(也就是编译时常量),如:
const constantList = const [1, 2, 3];
constantList[1] = 1; // 报错
maps
Dart中用Map来表示键值对相关的对象,键和值可以是任何类型的对象,但是 每个键只能出现一次。Dart中使用map可以通过map字面量和Map类型
var fruits = {
'apple': '苹果',
'banana': '香蕉'
};
var numMap = {
1: '壹',
2: '贰'
};
var fruits = new Map();
fruits['apple'] = '苹果';
fruits['banana'] = '香蕉';
const numMap = new Map();
numMap[1] = '壹';
numMap[2] = '贰';
fruits['pear'] = '梨子';
fruits['apple']; // 返回 '苹果'
runes(用于在字符串中表示Unicode字符)
在Dart中,runes代表字符串的UTF-32 Unicode,Unicode为每一个字符、标点符号、表情符号等都定义了一个唯一的数值,但是Dart字符串是UTF-16的字符序列,所以如果要表达UTF-32的字符,那么就需要用Runes
通常而言,表示Unicode字符的语法是:\uXXXX,XXXX表示的是4个16进制数,而1个16进制数需要用4位来表示,4个XXXX则是16位,即为UTF-16,那么对于UTF-32而言,它需要32位来表示来表示一个Unicode,而对于这些字符,所采用的语法则是:\u{XXXX...},如笑脸的emoji是\u{1f600}
而包含UTF-32字符的字符串,就要采用Runes了
main() {
var clapping = '\u{1f44f}';
print(clapping);
print(clapping.codeUnits);
print(clapping.runes.toList());
Runes input = new Runes(
'\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}'
);
print(new String.fromCharCodes(input));
}
方法
Dart中,方法也是对象并且是Function类的实例,因此,方法可以赋值给变量,也可以当做其他方法的参数,还可以是把Dart类的实例当做方法来调用.
箭头语法
bool greaterThanTen(n) => n > 10;
// 它相当于:
bool greaterThanTen(n) {
return n > 10;
}
必选参数与可选参数
方法的参数类型分为必选参数和可选参数,必选参数必须在参数列表的最前面,而可选参数则需要放在必选参数的后面
//命名参数
void greet({ String action, String what }) {
print('$action $what');
}
// 调用方法如下:
greet(what: 'Hello', action: 'say');
//可选参数
void greet(String from, String msg, [String device]) {
print('$from says $msg');
if (device != null) {
print('via $device');
}
}
// 调用方法如下:
greet('Ruphi', 'hello'); // 输出: Ruphi says Hello
greet('Ruphi', 'hello', 'iPhone 8 Plus');
/*
输出:
Ruphi says Hello
via iPhone 8 Plus
*/
//默认参数
void greet(String from, String msg, [String device = 'iPhone']) {
print('$from says $msg');
if (device != null) {
print('via $device');
}
}
greet('Ruphi', 'Hello');
方法对象
printElement(element) {
print(element);
}
var list = [1, 2, 3];
list.forEach(printElement);
//方法也可以赋值给一个 变量
var showList = (list) => list.forEach((i) => print(i));
匿名方法
var list = ['apple', 'orange', 'grape', 'banana'];
list.forEach((item) {
print(list.indexOf(item).toString() + ': ' + item);
});
两者等价
list.forEach((item) => print(list.indexOf(item).toString() + ': ' + item));
静态作用域
Dart变量的作用域在写代码的时候就已经确定了,因此,大括号里定义的变量就只能在大括号里访问
var topLevel = true;
main() {
var insideMain = true;
myFunction() {
var insideFunction = true;
nestedFunction() {
var insideNestedFunction = true;
}
}
}
语法闭包
一个闭包是一个方法对象,不论该对象在何处被调用,该对象都能访问自己作用域内的变量(即捕获了的变量)。
Function uid() {
int id = 0;
return () => id++;
}
main() {
var uidGenerator = uid();
print(uidGenerator());
print(uidGenerator());
print(uidGenerator());
}
/*
输出:
0
1
2
*/
函数相等
foo() {} // 顶级函数
class A {
static void bar() {} // 静态方法
void baz() {} // 实例方法
}
main() {
var x;
// 比较顶级方法
x = foo;
assert(foo == x); // 通过
// 比较静态方法
x = A.bar;
assert(A.bar == x); // 通过
// 比较实例方法
var v = new A(); // A的实例 #1
var w = new A(); // A的实例 #2
var y = w;
x = w.baz;
// 因为 y.baz 和 x 都是指向实例#2的baz方法,因此它们是相等的
assert(y.baz == x); // 通过
// v.baz指向的是#1的baz方法,w.baz指向的是#2的baz方法,因此它们不相等
assert(v.baz != w.baz); // 通过
}
Dart中不像JavaScript有===,它只有==相等运算符,用以测试两个对象代表的是否为同样的内容,x == y的语义为:
如果x或者y是null,如果两个都是null则返回true,如果只有一个是null则返回false
返回如下函数的返回值:x.==(y)(也就是说,==是x的一个方法,测试相等性时,是将y传入==方法测试的,如果需要,甚至还可以覆写这些操作符
class A {
bool operator ==(dynamic y) {
return y == 1;
}
}
main() {
var a = new A();
print(a == 1); // true
print(a == 2); // false
}
类型判定运算符
Dart中有如下的类型判定运算符:
as:类型转换
is:如果对象是指定的类型,则返回true
is!:如果对象是指定的类型,返回false
只有当obj实现了T的接口时,obj is T才是true,如:obj is Object总是true,此外,可以用as运算符把对象转化为特定的类型
(emp as Person).firstName = 'Bob';
??=运算符
对于x ??= y,具有如下的行为:
如果x是null,则赋值y给x
如果x不是null,则不赋值y给x
不能用于变量初始化赋值
var x = null;
x ??= 10;
print(x); // 输出10
var y = 10;
y ??= 20;
print(y); // 输出10
??条件表达式
Dart中除了常规的大多数语言都支持的?:条件表达式之外,还有??条件表达式,即对于expr1 ?? expr2,执行如下运算:
如果expr1不是null,则返回expr1
如果expr1是null,则执行expr2并返回
也就是说expr1 ?? expr2操作符等价于:expr1 == null ? expr2 : expr1
级联操作符
级联操作符..可以在同一个对象上连续调用多个函数以及访问成员变量,使用级联操作符可以避免创建临时变量
querySelector('#button')
..text = 'Confirm'
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
//和上面代码相同
var button = querySelector('#button');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
级联调用是可以嵌套的
final addressBook = (
new AddressBookBuilder()
..name = 'aaaaa'
..email = 'xxxxxx@rr.cn'
..phone = (
new PhoneNumberBuilder()
..number = '12345678910'
..label = 'Home'
).build()
).build();
在方法上使用级联操作符需要小心,如以下代码是错误的
var str = new StringBuffer();
str.write('foo')..write('bar');
write()方法返回的是void,无法在void上使用级联运算符,正确的写法应该是:
str..write('foo')..write('bar');
条件成员访问 ?.
条件成员访问符?.,要求对于 obj?.propertyOrMethod操作满足:
obj不能是null
如果obj是null,则返回null;如果不是null,则返回 obj.propertyOrMethod
异常处理
在Dart里,异常表示一些未知的错误情况,如果异常没有被捕获,则异常会抛出,导致抛出异常的代码终止执行。在Dart里有两个类型表示异常,即为:Exeception和Error,其他的子类型和自定义异常类型均继承自这两个基础异常类型。此外,还可以抛出任意的非null的对象作为异常,而不一定是Exception或者Error及其子类型。
throw
throw new FormatException('Expected at least 1 section'); // 抛出 Exception 子类型
throw 'Out ot memory'; // 抛出非`null`对象
// throw是表达式,可以在可以使用表达式的地方使用
distanceTo(Point other) => throw new UnimplementedError();
catch
捕获异常可以避免异常继续传递(除非重新throw异常),因此捕获异常可以获得处理异常的机会。和Javascript不同的是,由于Dart有类型系统,所以可以针对特定类型的异常做处理
try {
aFunctionThatMayCauseException();
} on SomeException {
// 处理异常
}
try {
aFunctionThatMayCauseException();
} on AException {
// 处理异常
} on BException catch (e) {
// 捕获异常对象e
} catch (e) {
// 默认情况下的异常处理
}
Dart中的catch提供了两个参数,即为:catch(errorObject, errorStack),其中第一个参数提供了抛出的异常对象,而第二个参数则是StackTrace类的实例,提供了异常的堆栈信息
try {
aFunctionThatMayCauseException();
} catch (e, s) {
// ...
}
使用rethrow关键字,重新抛出异常,
final foo = '';
void misbehave() {
try {
foo = 'You can\'t change a final variable\'s value.';
} catch (e) {
print('misbehave() partially handled ${e.runtimeType}.');
rethrow;
}
}
void main() {
try {
misbehave();
} catch (e) {
print('main() finished handling ${e.runtimeType}.');
}
}
finally
finally语句的作用,则在于无论是否捕获异常,都必须要执行,如下:
try {
aFunctionThatMayCauseException();
} finally {
// 无论有没有异常抛出,这里的语句都会执行
}
类
Dart是面向对象的语言,所有对象都是一个类的实例,所有类都继承自Object。此外,Dart支持基于mixin的继承机制,这意味着,每个类(除了Object)都只有一个父类,一个类的代码可以在其他多个类继承中重复使用。
类的定义
使用 class 关键字声明一个类
class Point {
num x;
num y;
Point(num _x, num _y) {
x = _x;
y = _y;
}
}
也可以直接在构造函数中使用初始化赋值的语法糖
class Point {
num x;
num y;
Point(this.x, this.y);
}
命名构造函数
class Point {
num x;
num y;
Point(this.x, this.y);
Point.fromJson(Map json) {
x = json['x'];
y = json['y'];
}
}
调用父类的构造函数
默认情况下,子类的构造函数会自动调用父类的无名无参的默认构造函数,父类的构造函数在子类构造函数的函数体开头位置调用。但是如果提供了初始化参数列表,则初始化参数列表会在父类构造函数之前执行,也就是说,执行顺序 1、初始化参数列表 2、父类无名构造函数 3、子类无名构造函数
如果超类中没有无名无参构造函数,那么就需要手动调用了,调用方法为在构造函数之后使用:调用,如:
class Point {
num x;
num y;
Point(this.x, this.y);
}
class Point3D extends Point {
num z;
Point3D(num x, num y, num z): super(x, y) {
this.z = z;
}
}
重定向构造函数
有时候一个构造函数会调用类中的其他构造函数,这种构造函数称之为重定向构造函数
class Point {
num x;
num y;
Point(this.x, this.y);
Point.alongXAxis(num x): this(x, 0);
}
常量构造函数
如果类提供的是状态不变的对象,那么可以把这些对象定义为编译时常量,实现这种功能可以定义const构造函数,且声明所有类的变量为final
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin = const Immutable(0, 0);
}
工厂方法构造函数
如果一个构造函数并不总是返回一个新的对象,那么可以在构造函数前面加上factory关键字,来表示它是一个工厂方法构造函数。但是需要注意的是,工厂构造函数中不能访问this。
class Logger {
final String name;
bool mute = false;
// 缓存实例
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);
}
}
}
setter/getter
在一个方法前面加上setter或getter关键字,那么这个方法就成为了setter和getter,就可以对 instance.methodName 进行赋值或者取值操作,而无需使用()来调用
class Person {
String firstName;
String lastName;
Person(this.firstName, this.lastName);
String get fullName {
return '$firstName $lastName';
}
}
final me = new Person('aaa', 'bbbb');
print(me.fullName); // 输出:Ruphi Lau
getter里不能带(),即get name()是错误的,get name才是正确的,而setter里则可以接受一个参数,即为赋值传入的值:set name(String name)。
操作符重写
Dart中支持操作符重写,可被重写的操作符有:
比较运算符:>,<,<=,>=,==
算数运算符:+,-,*,/,%,~/
位运算符:|,&,^,~,<<,>>
方括号运算符:[],[]=
重写运算符的语法为使用operator关键字紧接运算符,以下例子为实现向量的运算
class Vector {
final int x;
final int y;
const Vector(this.x, this.y);
Vector operator +(Vector v) {
return new Vector(x + v.x, y + v.y);
}
Vector operator -(Vector v) {
return new Vector(x - v.x, y - v.y);
}
}
main() {
final v = new Vector(2, 3);
final w = new Vector(2, 2);
final addRes = v + w;
print('(${addRes.x}, ${addRes.y})'); // 输出:(4, 5)
final minusRes = v - w;
print('(${minusRes.x}, ${minusRes.y})'); // 输出:(0, 1)
}
Mixins
Mixins是一种在多类继承中重用一个类代码的手段,可以为类添加新的功能。使用Mixins的方法为使用with关键字,
class Person {
final name;
Person(this.name);
}
class Program {
program() => print('aaaa');
}
class Reading {
reading() => print('bbbb');
}
class Tom extends Person with Program, Reading {
Tom(): super('cccc') {
print('$name :');
program();
reading();
}
}
main() {
new Tom();
}
输出:
cccc :
aaaa
bbbb
如果一个类继承Object,但是该类没有构造函数,那么就不能调用super,这个类就是一个mixin
abstract class Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
if (canPlayPiano) {
print('first');
} else if (canConduct) {
print('two');
} else {
print('three');
}
}
}
库
在Dart中,可以使用import和library创建模块化的可分享代码。Dart库具有如下一些基本概念:
a:在库里面,以_开头的标识符仅在库内部可见
b:每个Dart程序都是一个库,即使没有使用library命名,也是一个库
c:库可以使用Dart package工具部署。
d:import必须传入参数,参数为库的URI
e:对于内置的库,使用dart:开头作为库的URI,如dart:io
f:对于其他的库,可以使用文件系统路径,或者使用package:开头的方式作为库的URI
// 只导入foo
import 'package:lib1/lib1.dart' show foo;
// 导入全部(除了foo)
import 'package:lib2/lib2.dart' hide foo;
import 'package:deferred/hello.dart' deferred as hello;
greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
异步支持与async/await
Dart中有类似于Javascript的async/await语法,用以支持异步场景。返回Future或者Stream对象。
async关键字
等待直到stream返回一个数据
使用stream返回的参数执行for循环代码,直到stream数据返回完毕
如果遇到break或者return语句,可以提前停止接收stream的数据
Dart代码并不运行在线程模型中,而是隔离的isolates模型,每个isolate都有自己的堆内存,并且确保每个isolate的状态都不能被其他isolate所访问
checkVersion() async {
var version = await lookUpVersion();
if (version == expectedVersion) {
// ...
} else {
// ...
}
}
Future<String> lookUpVersion() async => '1.0.0'
main() async {
// ...
await for (var request in requestServer) {
handleRequest(request);
}
// ...
}
Typedef
在Dart中,方法也是对象,可以使用typedef或function-type alias来为方法类型指定别名。
typedef int Compare(Object a, Object b);
class SortedCollection {
Compare compare;
SortedCollection(this.compare);
}
int sort(Object a, Object b) {
// ...
}
main() {
SortedCollection collection = new SortedCollection(sort);
assert(coll.compare is Function);
assert(coll.compare is Compare);
}