Dart语言学习需要明确的重要概念:
- Dart语言,一切实例皆对象,所有对象都继承自
object
。即便数字,函数和null
也不例外。 - Dart是强类型语言,支持类型推断,故类型注释是可选的。明确不需要类型推断时,可以使用
dynamic
关键字。
Dart中的两种类型允许所有值:
object
和dynamic
。
使用object
类型注释,而不用dynamic
,代表变量允许any object
。
使用dynamic
发送更复杂的信号。这可能意味着Dart的类型系统尚不足够复杂和精准,无法表示允许的类型集。或明确希望在运行时变量具有动态性时可以使用dynamic
。
- Dart支持泛型,如
List
,List - Dart支持顶级函数如:
main()
;以及与类或对象绑定的静态方法和实例方法;支持嵌套或局部函数。 - Dart支持顶级变量以及与类或对象关联的静态变量和实例变量。
- 与Java不同,Dart没有关键字
public
,protected
和private
。如果变量标识符以下划线_
开头,则该标识符对于库来说便是私有的。
变量的声明:
正确:
var name = 'Bob'; name存储引用。
dynamic name = 'Bob';
String name = 'Bob';
错误:
var String name = "你好"//变量不能同时使用var 和 类型名称进行声明。
final
和const
final
修饰的变量只会被设置一次,final
修饰的顶级变量和类变量在首次使用时才会被初始化,即final
修饰的变量运行时初始化。const
修饰的变量是编译时常量(属于隐式的final
变量)
注意:实例变量可以是
final
变量,但不能是const
。
字符串:
Dart中的字符串是UTF-16
码元的序列。可以使用单引号或双引号来创建。
- 字符串的表示:
'...'或者"..."
- 字符串插值:
插入表达式,使用{}
:${ expression }
;插入变量$variable
。
var s = "123";
var s1 = "你好" + "${567}";//你好567
var s2 = "再见$s";//再见123
var s3 = "$s${789}";//123789
- 多行字符串
使用`'''`书写
var s4 = '''这是
多行字符串,
多行书写即可。
''';
使用转义符`\n`
var s4 = '这是多行字符串,\n 多行书写即可';
- 原始字符串前缀前书写
r
,会使特殊字符不生效:
var s4 = r'这是多行字符串,\n 多行书写即可';
//log: '这是多行字符串,\n 多行书写即可'
数组:
- 创建可变数组
var mutableList = [4,5,6];
mutableList[0] = 99;
mutableList.add(88);
mutableList.insert(0, 77);//[77, 99, 5, 6, 88]
- 创建编译时常量数组
var constantList = const [1, 2, 3];
constantList[1] = 10;//会触发错误
var constantList = const [1, 2, 3];
constantList = [3,4,5];//经此一步,可使用为可变数组
constantList[1] = 10;//[3, 10, 5]
3.创建不可变数组
const constantList = [1, 2, 3];
//constantList = [3,4,5];//`constant`本身不允许
//constantList[1] = 10;//不允许会触发错误
4.Dart 2.3中引入了展开运算符(spread operator):....
和空感知展开运算符(null-aware spread operator):...?
//使用`...`,将数组中的所有元素插入到另一个数组中.
var list = [2,3,4];
var list1 = [1,...list,9];//[1, 2, 3, 4, 9]
//使用`...?`避免插入null时异常
var list;//未赋值,默认为`null`
var list1 = [1,...?list,9];//[1, 9]
5.Dart 2.3 提供了collection if
和collection for
, 允许我们使用if
条件和for
循环构建集合。
collection if
使用
var show = true;
var list = [3,4,100,if(show) 300, 0];//[3, 4, 100, 300, 0]
collection for
使用
var temp = [1,2,3];
var result = ["@1",for( var i in temp) "@${i + 2}" ,"@3"];//[@1, @3, @4, @5, @3]
Set
- Dart中的
Set
var set = {"cat",'dog','cow'};//1
//注意使用`var para = {}`创建的是`map`
var set1 = {};//2
Set set2 = {};//3
set1.add("动物园");
set1.addAll(set);//{动物园, cat, dog, cow}
- 创建编译时是常量的
Set
final set2 = const {1,2,3,4};//常量
3.final
和var
变量对比
var set2 = {1,2,3,4};
set2 = {2};
set2.add(5);
final set2 = {1,2,3,4};
set2 = {2};//报错,final只允许被设置一次。
set2.add(5);//不会报错。
var set2 = const {1,2,3,4};
//set2 = {2};//经此一步,才能操作set2,否则不能。
//set2.add(5);
- Set也支持
...
和...?
运算符(Dart 2.3)
var set2 = {"dd"};
set2.add("cc");
var set1 = {"ggg",...set2,""};//{ggg, dd, cc, }
- Set支持
collection if
和collection for
(Dart 2.3)和list
的做法一样。
Maps
1.Dart中的Map
var map = {}//默认创建为`Map`与`var map = Map()`一致;.
Map map2 = {};
map2[2] = "你好";//添加键值对
var map3 = {
"str" : 9,
3 : "number"
};
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
-
Map
也支持...和...?运算符(Dart 2.3)
var map = {1: "你",2: "好"};
var map1 = {3 : "2020",...map,4:"come on"};//{3: 2020, 1: 你, 2: 好, 4: come}
-
Map
支持collection if
和collection for
(Dart 2.3)
var show = true;
var map = {1: "你",2: "好", if(show) 6 : "dart"};
var map2 = { 8: "collection",for( var i in [4,3] ) i+1 : "value_$i" };
//{8: collection, 5: value_4, 4: value_3}
关于Unicode字符集的表示
Dart中字符串使用UTF-16码元的序列表示,因此表示Unicode时使用特定的语法:
若Unicode标量(16进制数)的位数等于4位时:
\uXXXX
;若大于或少于4位时:\u{标量值}
函数
Dart函数的示例
String _dartFunction(String para1, int para2) {
return para1 + "$para2";
}
建议公共API使用类型注释,但是省略也依然是有效的
_dartFunction( para1, para2) {
return para1 + "$para2";
}
对于仅包含一个表达式的函数,可以使用简写语法:
=> expresion
(箭头语法),它是{ return expresion }
的简写语法
String _dartFunction( String para1, int para2) => para1 + "$para2";
注意:在简写语法中=>
和;
之间只能出现一个表达式,而不是一个语句。例如,不可使用if
语句,但是可以使用条件表达式。
以上三种方法示例,在函数调用时,不会有参数标签,并且参数不可省略。否则会提示:
Error: Too few positional arguments: 2 required, 0 given.
Dart中函数的参数被称为位置参数。
可选参数
Dart函数定义可选参数,有两种方式:
- 通过
{ param1, param2, … }
实现参数命名(即:设置参数标签)。
//Case1 :参数全部可选
String _dartFunction({String para1, int para2}) {
String temp = '';
if (para1 != null) {
temp += para1;
}
if (para2 != null) {
temp += "$para2";
}
if (para1 == null && para2 == null) {
temp = "默认值";
}
return temp;
}
//调用
_dartFunction()
_dartFunction(para1 : "你好", para2: 647)
_dartFunction(para1 : "你好")
_dartFunction(para2 : 647)
_dartFunction(para2 : 647, para1: "你好")
//Case 2 : 参数2可选
String _dartFunction(String para1,{ int para2}) {
......
}
//Case 2.1 : 使用Case1的方式实现Case 2的效果
String _dartFunction({ @required String para1, int para2}) {
/*文档说`@required`可以强制函数提供此参数,否则报错,然而_dartFunction()并没有报错。*/
....
}
- 通过
[ param1, param2, … ]
实现可选的位置参数,不会有参数标签。
//Case1 参数全部可选
String _dartFunction([String para1, int para2]) {
......
}
//调用
_dartFunction()
_dartFunction("你好")
_dartFunction("你好",1)
_dartFunction(1)//不允许 实测`一个参数时匹配参数1`
//Case2:参数2可选
String _dartFunction(String para1, [int para2]) {
......
}
//调用
_dartFunction("你好")
_dartFunction("你好",1)
_dartFunction()//不允许
使用[ ]
实现可选参数,Dart语法不允许的方式(文档未描述)。
// Case 3
String _dartFunction({String para1}, int para2) {
......
}
String _dartFunction([String para1], int para2) {
......
}
//编译报错:`'para2' isn't defined`,感觉像语法缺陷。
使用=
为可选参数提供初始值,旧版本可能使用:
提供初始值。
String _dartFunction({String para1 = "hello", int para2 = 112}) {....}
String _dartFunction([String para1 = "hello", int para2 = 112]) {....}
函数实例
可以将一个函数作为参数传递给另一个函数,也可以将函数分配给变量。
//case 1
var printElement = (int item) {
print(item);
};
//case 2
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
list.forEach(printElement);
匿名函数也称闭包:
([[Type] param1[, …]]) {
codeBlock;
};
示例
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item)=>print(item));//Case 1
list.forEach((item){
print(item);
});//Case 2
函数支持嵌套。所有函数都返回一个值。如果未指定返回值,默认return null
。
操作符
描述 | 符号 |
---|---|
一元后缀 | expr++ , expr-- , () [] . ?. |
一元前缀 | - expr , ! expr ,~ expr , ++ expr, -- expr , await expr |
算术运算符 | * , / , % , ~/ (整除,向下取整) , + , - |
位运算符 | << (左移) , >> (右移), >>> ,& (按位与),^ (按位异或), | (按位或) |
关系与类型运算符 | >= , > , <= , < , as (type cast), is (if type return true) , is! (if type return false) |
相等运算符 | == ,!= |
逻辑与 | && |
逻辑或 | || |
判空 | ?? |
三目运算符 | expr1 ? expr2 : expr3 |
级联 | .. |
赋值(含复合)运算符 | = , *= , /= , += , -= , &= ,^= |
使用注意:
as
当且仅当我们确定该对象属于该类型时,才使用运算符将其转换为特定类型。在不确定时,需要先使用is
运算符进行检查。
(emp as Person).firstName = 'Bob';
expr1 ?? expr2
: expr1
有值时便返回,否则返回expr2
。
a ??= b
: 如果a
为null
,则为a
分配值b
;否则,a
保持不变。
级联:..
严格来说级联..
并不是运算符,它只是Dart中的语法。允许我们对同一对象执行一系列操作,包括函数调用,访问同一对象上的字段。使用级联可以不用创建临时变量,更加快速和流畅。
class Person {
String name;
int age;
personDescription() {
print((name ?? "匿名者") + "年芳${age ?? 3}岁");
}
String personWork(){
age ??= -1;
if (age < 0){
return "未知";
} else if (age < 23&&age>0) {
return "学习";
} else {
return "工作";
}
}
}
final person = Person();
person
..personDescription() //匿名者年芳3岁
..personWork()
..age = 18
..name = "QiShare"
..personWork()//注意:需小心使用级联处理返回对象的函数。比如此处返回值并没有被用到。
..personDescription(); //QiShare年芳18岁
//等价于
final person = Person();
person.personDescription(); //匿名者年芳3岁
person.personWork();
person.age = 18;
person.name = "QiShare";
person.personWork();
person.personDescription(); //QiShare年芳18岁
控制流
-
if
andelse
-
for
loops -
while
anddo-while
loops -
break
andcontinue
-
switch
andcase
属于自动贯穿,需要break
空case
会自动贯穿。
var command = 'CLOSED';
switch (command) {
case 'CLOSED': // Empty case falls through.
case 'NOW_CLOSED':
// Runs for both CLOSED and NOW_CLOSED.
executeNowClosed();
break;
}
如果需要贯穿可以使用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
开发代码时可以帮助我们调试代码,在生产代码中,断言会被忽略,并且assert
不评估to
的参数。
assert(text != null);
异常
Dart提供了Exception
和Error
类型,以及许多预定义的子类型。也可以定义自己的异常。Dart程序允许将任何非null
对象(不仅仅是Exception和Error对象)作为异常抛出。
Throw
抛出异常
throw FormatException('抛出异常');
throw "抛出异常";
注意:为了代码质量,通常会抛出Exception
和Error
类型的异常。
抛出异常是一个表达式,所以可以在=>
语句以及允许表达式的任何其他地方抛出异常。
Catch
捕获异常会阻止该异常的传播(除非您重新抛出该异常)
要处理可能引发不止一种类型的异常的代码,可以指定多个catch子句。使用on
指定异常类型,匹配抛出异常的类型,并进行处理;未指定时,处理任何类型的异常。
try {
throw FormatException("一个小异常");//1
throw "字符串异常";//2
} on Exception catch(e) {
print("捕获了一个异常${e.runtimeType}" );//1
} catch (e) {
print("捕获了一个异常${e.runtimeType}" );//2。
}
若需要允许捕获的异常重新传播,需要使用rethrow
关键字。只允许使用在catch
闭包中。
finally
为了确保某些代码无论是否引发异常都可以运行,请使用finally
子句。如果没有catch子句与该异常匹配,则在该finally
子句运行后传播该异常
1.不添加catch
,使用finally
,不会阻止异常的传播。
try {
throw FormatException("一个小异常");//1
throw "字符串异常";//2
} finally {
print("执行finally");
}
2.添加catch
,阻止异常传播;finally
子句会在所有catch
处理完成,再执行。
try {
throw FormatException("一个小异常");//1
throw "字符串异常";//2
} on Exception catch(e,s) {
print("捕获了一个异常${e.runtimeType}" );//1
print("捕获异常时,函数的调用堆栈+${s}");
} catch (e) {
print("捕获了一个异常${e.runtimeType}" );//2
} finally {
print("执行finally");
}
Class
Dart是一种具有类和基于Mixin
继承的面向对象语言。
构造函数
1.通过创建一个与其类具有相同名称的函数来声明一个构造函数。这是构造函数最常见的形式。
class Point {
num x,y;
Point(num x,num y) {
this.x = x;
this.y = y;
}
String toString() {
return "(${this.x},$y)";
}
}
this
引用当前对象。需要注意的是:只有当命名冲突的时候,才会使用this
,一般Dart
的风格都是忽略它。
将构造函数参数分配给实例变量的模式非常普遍,Dart使用语法糖使其变得简单:
class Point {
num x,y;
Point(this.x,this.y);
}
2.默认构造函数:如果不声明构造函数,则会提供默认的构造函数。默认构造函数没有参数,并在超类中调用无参数构造函数。(初始化的顺序,决定)
3.构造函数不会被继承:
子类不从其父类继承构造函数。声明没有构造函数的子类仅具有默认构造函数。
4.命名构造函数:使用命名构造函数可为一个类实现多个构造函数或提供额外的描述信息:
class Point {
num x,y;
Point(this.x,this.y);
//命名构造函数
Point.init(this.x,this.y);
Point.origin(x,y){
this.x = x;
this.y = y;
}
String toString() {
return "(${this.x},$y)";
}
}
记住,构造函数没有继承,这意味着子类不会继承超类的命名构造函数。如果要使用超类中定义的命名构造函数创建子类,则必须在子类中实现该构造函数。
4.子类调用父类非默认(无参,无名)构造函数:
默认情况下,子类中的构造函数会调用父类的未命名,无参数的构造函数。父类的构造函数在构造函数主体的开头被调用。如果还使用了初始化参数列表,它将在调用父类之前执行。
执行顺序如下:
a.初始化主类参数
b.父类的无参构造函数
c.主类的无参构造函数
如果父类没有未命名,无参数的构造函数,则必须在构造函数中手动调用父类的某个构造函数。
调用示例一:
父类不存在无参,未命名的构造函数时,子类不管是重新实现父类的构造函数,还是命名的构造函数,都必须调用父类的构造函数。
class Point {
num x,y;
Point(this.x,this.y);
Point.init(this.x,this.y );
Point.origin(x,y){
this.x = x;
this.y = y;
}
}
class SubPoint extends Point {
SubPoint.init(num x , num y) : super.origin(x,y);
SubPoint.subInit(x,y):super.init(x,y);
}
因为,父类的构造函数,在子类的构造函数之前被调用,故还可以写为:
class SubPoint extends Point {
SubPoint.init() : super.origin(2,6);
SubPoint.subInit(x,y):super.init(x,y+5);
}
调用示例二:
父类存在无参,未命名的构造函数时,可以省略调用父类的构造函数。
class Point {
num x,y;
Point({this.x,this.y});//参数省略
}
class SubPoint extends Point {
num z;
SubPoint.init(num x , num y) : super();//可以省略
SubPoint.subInit(this.z,x,y);
}
初始化实例变量:
可以在构造函数体之前初始化实例变量。
class Point {
num x,y;
//注意:this.x = *,this.y =* ,此处的*不能使用`this`属性。
Point.origin(x,y):this.x = x,this.y = y{
print('In Point.origin(): ($x, $y)');
}
Point.fromJson(Map json) : x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
}
class Point {
final num x;
final num y;
final num distanceFromOrigin;
Point(x, y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
重定向构造函数
类似便利初始化方法。
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);
}
常量构造函数
如果类可以产生永不改变的对象,则可以让这些对象是编译时常量。为此,我们可以定义一个const
构造函数,并确保所有实例变量均为final
。
class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
final num x, y;
const ImmutablePoint(this.x, this.y);
}
Factory 构造函数
当类实现一个并不总是创建当前类对象的构造函数时,需要使用factory
关键字。比如:factory
构造函数可能从缓存中返回一个实例,或从它的子类中返回一个实例。
需要注意的是Factory
构造函数,没有使用this
的权限。
class Logger {
final String name;
bool mute = false;
// _cache is library-private, thanks to
// the _ in front of its name.
static final Map _cache =
{};
factory Logger(String name) {
return _cache.putIfAbsent(
name, () => Logger._internal(name));
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
获取对象的类型
要在运行时获取对象的类型,可以使用Object
的runtimeType
属性,该属性返回Type
对象。
实例变量
class Point {
num x; // Declare instance variable x, initially null.
num y; // Declare y, initially null.
num z = 0; // Declare z, initially 0.
}
所有未初始化的实例变量都具有值null
。
所有实例变量都会生成一个隐式的getter
方法。非final
实例变量也会生成隐式的setter
方法。
方法
1.定义实例方法
class Point {
num x, y;
Point(this.x, this.y);
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
2.Getter
和 setter
可提供对对象属性的读写。
每个实例变量都有一个隐式的getter
,如果合适的话还有一个setter
;可以使用get
和set
关键字通过实现getter
和setter
来创建其他属性。
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);
}
3.定义抽象方法
实例方法,getter
和setter
方法可以是抽象的,定义一个接口,但将其实现留给其他类实现。抽象方法只能存在于抽象类中。
定义抽象方法:使用;
代替方法体即可。
abstract class Doer {
// Define instance variables and methods...
void doSomething(); // Define an abstract method.
}
class EffectiveDoer extends Doer {
void doSomething() {
// Provide an implementation, so the method is not abstract here...
}
}
抽象类
抽象类,一个无法被实例化的类。
使用abstract
修饰符定义一个抽象类。抽象类通常用于定义接口(有时也定义接口的实现)。如果希望抽象类可实例化,可以定义一个工厂构造函数(可返回子类的实例)。
// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
// Define constructors, fields, methods...
void updateChildren(); // Abstract method.
}
隐式接口
每个类都会隐式定义一个接口,这个接口包含所有实例变量,以及除构造函数外的所有方法接口。
如果要创建一个支持类B接口的类A,但是却不继承类B的实现。类A需要实现类B的接口。
类A实现一个或多个接口,通过在implements
子句声明它们,然后类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()));
}
这是指定一个类实现多个接口的示例:
class Point implements Comparable, Location {...}
通过implements
指定一个类实现其他类隐式接口的方式,有点像Swift中的协议(存在类型)。协议的声明—>隐式接口,协议的实现—>通过implements
约束其他类实现。这种方式让类本身抽象成了协议的定义。举个例子:
class Person {
String name;
int age;
}
class Student extends Person {
learn(){
print("学习");
}
}
class Teacher extends Person {
teach(){
print("教书");
}
}
class PartTimeStudent implements Teacher, Student {
String name = "";
int age = 12;
learn() {
print("$name兼职身份:学习");
}
teach(){
print("$name兼职身份:教书");
}
}
//定义调用的方法:
greet(Person person){
if (person is Teacher){
person.teach();
}
if (person is Student) {
person.learn();
}
if (person is PartTimeStudent) {
person.learn();
person.teach();
}
}
//调用
greet(PartTimeStudent()..name = "Qishare");
//执行结果:
Qishare兼职身份:教书//`person is Teacher`判定生效
Qishare兼职身份:学习//`person is Student`判定生效
//`person is PartTimeStudent`判定生效。
Qishare兼职身份:学习
Qishare兼职身份:教书
根据上例总结:
- 通过这种方式可实现多继承。
2.PartTimeStudent
的实例,是实现了Person
,Teacher
,Student
接口的实例。而(Person person)
允许实现了Person
接口的类型传入。后续类型判断采用接口类型,只要是实现了此接口的类型都可传入。接口类型:Swift协议类型 。
类继承
- 关键字
extends
创建子类 :class Student extends Person
。 - 关键字
super
引用父类。 - 子类可以重写父类的
getter
、setter
以及实例方法。子类使用@override
标记子类将要进行重写。
class Person {
String name;
int age;
work() {
print("person工作内容");
}
}
class Student extends Person {
@override
work(){
//super.work();
print("student工作内容:学习");
}
}
-
可以重写的操作符
!= 不能被重写,因为它是
!(*==*)
的语法糖。如果重写==
,还必须重写Object
的hashCode
的getter
方法。
class Vector {
final int x, y;
Vector(this.x, this.y);
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
// ···
}
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
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}');
}
}
方法扩展Extension
Extension methods
:Dart 2.7中引入,是一种向现有类库添加功能的方式。
使用场景:当使用其他人的API或广泛使用的库时,更改API通常是不切实际或不可能的。但是可能仍想添加一些功能。
扩展不仅可以定义方法,还可以定义其他成员变量,例如getter
,setter
和operator
。
1.创建扩展语法:
extension on {
()*
}
2.使用示例:
class Person {
String name;
int age;
}
extension Persion_extension on Person {
String get completedName => "我叫$name";
set realAge(newValue) {
print(newValue);
}
String introduce()=> "$completedName,今年$age岁";
}
//使用
Person person = Person()..name = "Qishare"..age = 2;
print(person.completedName);//我叫Qishare
print(person.introduce());//我叫Qishare,今年2岁
枚举类型
枚举类型是一种特殊的类,用于表示固定数量的常量值。
使用enum
关键字声明枚举类型。
enum Color { red, green, blue }
枚举的每个值都有index
的getter
方法。返回枚举声明从零开始的位置。
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
要获取枚举中所有值的列表,请使用枚举的values
常量。
List colors = Color.values;
assert(colors[2] == Color.blue);
枚举类型具有以下限制:
1.不能显式实例化一个枚举类型。
2.不能子类化,mixIn
,implement
一个枚举类型。
向类添加功能:mixin
mixin
:是一种在多个类的层次结构中复用类代码的一种方式。
声明一个mixin
的类与普通的类基本一致,唯一的区别,使用mixin
声明的类,没有构造方法。声明mixin
类的时候,有时会使用on
关键字,限制mixin
类只能被某些特定的类型使用,on
可以指定使用类的父类。
需要混入代码的类使用with
关键字指定一个或多个mixin
的类。
class Person {
String name;
int age;
}
//限制使用类为`Person `的子类
mixin MixinClaseName on Person {
String accessPersonName()=> "我叫$name";
}
//使用此`mixin`类
class Student extends Person with MixinClaseName {
}
//调用
Student sdu = Student()..name = "wally"..age = 12;
print(sdu.accessPersonName());
类变量和方法
使用static
关键字修饰来实现类变量和方法。
泛型
泛型通常是类型安全所必需的,使用泛型可以减少代码重复。
使用示例:
class SomeBaseClass {...}
class Foo {
void printSomeMsg(S param1,T param2) {
print("Instance of 'Foo<$param1>'_'Foo<$param2>'");
}
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
库的使用
import
指定库的URI,对于内置库,URI具有特殊的dart:schemes
,对于其他库,可以使用文件系统路径或package: shemes
(指定由包管理器(例如pub工具)提供的库)。
指定库前缀
如果导入两个标识符冲突的库,则可以为一个或两个库指定一个前缀。例如,如果library1和library2都具有Element类,那么可能具有以下代码:
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// Uses Element from lib1.
Element element1 = Element();
// Uses Element from lib2.
lib2.Element element2 = lib2.Element();
延时使用
延迟加载(也称为延迟加载)允许Web应用程序在需要库时按需加载库。减少Web应用程序的初始启动时间。
仅dart2js支持延迟加载。 Flutter,Dart VM和dartdevc不支持延迟加载。
要延迟加载库,首先导入它并使用deferred as
。
import 'package:greetings/hello.dart' deferred as hello;
使用时,请loadLibrary()
使用库的标识符进行调用 。
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
异步支持
Dart库充满了返回Future
或Stream
对象的函数。这些函数是异步的:它们的返回值可能在耗时操作(例如I / O)之后返回,而无需等待该操作完成。
async
和await
关键字支持异步编程,让我们可以编写看起来类似于同步代码的异步代码。
处理Future
当需要Future
执行完成的结果时,有两种选择:
- 使用
async
和await
。 - 使用
Future
API。
使用async
和await
的代码是异步的,但是看起来很像同步代码。
await lookUpVersion();
要使用await
,代码必须在async
标记的函数中。
Future checkVersion() async {
var version = await lookUpVersion();
}
即使
async
的函数可能执行耗时的操作,但是它不会等待这些操作。异步函数仅在遇到第一个await
表达式(details)时,才会执行。仅在await
表达式完成后,返回Future
对象,才会恢复执行。
使用 try
, catch
, 和finally
去处理用了await
代码的错误情况 。
try {
version = await lookUpVersion();
} catch (e) {
// React to inability to look up the version
}
在一个async
异步函数中可以多次使用await
。
asyncFunc() async {
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
}
在await expression
中 expression
的值通常是一个Future
对象。如果不是,也会自动包装成Future
对象。Future
对象意味着一个返回对象的承诺[promise]
。await expression
的值,便是那个返回的对象。此表达式使执行暂停,直到其返回的对象可用为止。
注意:当使用
await
的时候,出现编译错误,请确保await
出现在async
函数中。
异步函数的声明
异步函数,是一个函数体被async
关键字修饰的函数。
为一个函数添加async
关键字,可以使其返回一个Future
对象。
下例为同步函数与异步函数的使用示例,可以清楚的看到两者的区别:
//同步函数
String lookUpVersion() => '1.0.0';
//异步函数:返回值是,Future
Future lookUpVersion() async =>'1.0.0';
需要注意的是函数体不需要使用Future
的API,Dart会创建所需的Future
对象。如果函数不会返回有用的结果,可以让返回值为Future
。
处理Streams
当我们需要从一个Stream
中获取值时,我们有两种选择:
- 使用
async
和 一个异步的for
循环(await for
)。 - 使用
Stream API
, 详细 描述。
注意:使用
await for
之前, 请确保代码清晰并且我们真正需要等待所有Stream
的结果。比如,通常不应将await for
用于UI事件侦听器,因为UI框架发送无休止的事件Stream
。
一个异步的for
循环的格式:
await for (varOrType identifier in expression) {
// Executes each time the stream emits a value.
}
expression
的值必须具有Stream类型。执行过程如下:
- 等到
Stream
发出一个值。 - 执行
for
循环的方法体,并使用发出的值为变量赋值 - 重复
1
和2
直到Stream
关闭。
要停止监听流,可以使用break
或return
语句,该语句会跳出for
循环并取消对流的监听。
生成器(Generators
)
当需要延迟生成值的序列时,可以使用生成器函数。Dart内置支持两种生成器:
-
Synchronous
生成器:返回Iterable
对象 -
Asynchronous
生成器:返回Stream
对象
实现一个Synchronous
生成器, 使用sync*
标记函数体,使用yield
语句传递值。
Iterable courseList(int n) sync* {
int k = 0;
while (k < n) yield "课程${k++}";
}
print(person.courseList(8));//(课程0, 课程1, 课程2, 课程3, 课程4, 课程5, 课程6, 课程7)
for (var i in person.courseList(8)){
print(i);//课程i
}
实现一个Asynchronous
生成器, 使用async*
标记函数体,使用yield
语句传递值。
Stream courseList(int n) async* {
int k = 0;
while (k < n) yield "课程${k++}";
}
Future> getAsyncList(int n) async {
final list = [];
await for (var i in courseList(n)) {
list.add(i);
}
return list;
}
person.getAsyncList(8).then((res){
print(res);//[课程0, 课程1, 课程2, 课程3, 课程4, 课程5, 课程6, 课程7]
});
可调用的类
实现call()
方法可以让Dart类能向函数一样被调用。
class WannabeFunction {
String call(String a, String b, String c) => '$a $b $c!';
}
var wf = WannabeFunction();
var out = wf('Hi', 'there,', 'gang');
隔离(Isolates
)
许多计算机,甚至移动平台,都有多核的CPU,为了利用多核CPU,开发人员传统上使用可共享内存的线程,并发运行。但是,并发的共享状态易于出错,并且可能导致复杂的代码。
所有Dart代码都在隔离体isolates
内运行,而不是线程。每个隔离(isolate
)区都有自己的内存堆,从而确保任何其他隔离区都无法访问隔离区的状态。
隔离区和事件循环详情
Typedef
在Dart中,函数是对象,就像字符串和数字是对象一样。typedef
为类型提供别名
typedef Compare = int Function(Object a, Object b);
class SortedCollection {
Compare compare;
SortedCollection(this.compare);
}
注意:当前,
typedef
仅限于函数类型。
MetaData
使用元数据提供有关代码的其他信息。元数据的批注以字符@
开头,后跟对编译时常量的引用(例如:deprecated
)或对常量构造函数的调用。
所有Dart代码都有两个注释:@deprecated
和@override
。
使用@deprecated注释的示例:
class Television {
/// _Deprecated: Use [turnOn] instead._
@deprecated
void activate() {
turnOn();
}
/// Turns the TV's power on.
void turnOn() {...}
}
可以定义自己的元数据注释。这是定义带有两个参数的@todo注释的示例:
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');
}
参考资料:
Dart-Language-Tour