Dart之旅
Posted 人生如梦91
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dart之旅相关的知识,希望对你有一定的参考价值。
最近看见同学都这么努力,而我每天就是上班撸码,下班玩游戏,感觉自己也应该努力一点,做为一个大龄程序员,为了不必淘汰,必须要加强学习。Google出了个Flutter,像ReactNative一样,可以编写移动应用程序,于是决定学习一波,看了一天Dart官网的文档,决定写一篇博客加强记忆,这篇博文就是自己对官网文档的一个翻译。
文章目录
- 基本的Dart程序
- 重要概念
- Dart的关键字
- 变量
- 内建类型
- Functions
- Opeartor
- 流程控制
- 异常
- Classes
- 枚举类型
- 特征添加:混入
- 库
- 异步支持
- 可调用类
- Typedefs
- Metadata
- 注释
基本的Dart程序
一个基本的Dart程序如下所示:
从以上代码,我们可以知道以下几点:
- Dart的入口也是main函数,写法和C/C++类似,但是可以省略返回值,返回值由最后的返回类型进行推导。
- Dart使用var关键字定义变量,可以省略变量类型
- 在字符串中可以使用$变量名或$变量名来引用该变量
重要概念
- 一切能赋给一个变量的值都是一个对象,而每一个对象都是一个类的实例,不管是数值,函数,或者是null对象,所有对象继承自Object类
- 尽管Dart是一种强类型语言,但是类型声明是可选的,Dart语言可以自动推导变量类型,如果你的确想声明这个变量无类型,请使用dynamic
- Dart也支持泛型,如List或者List等
- Dart支持顶级的函数,如main函数,也有类函数和实例函数,还有嵌套函数
- 同样的,Dart也支持顶层的变量,也支持类变量和实例变量,就像Objc中的@property
- 不像C++或Java,Dart没有public, protected, private等关键字,如果一个函数以下划线(_)开头,那么这是一个私有函数
- 变量同样可以使用下划线(_)开头,表示这是一个私有变量
- Dart同样有表达式和变量声明,比如说三目运算符condition ? expr1 : expr2
- Dart工具能够指示警告和错误两种问题,警告只是提醒你,你的代码可能无法正常工作,错误分成编译时错误和运行时错误,编译时错误会阻止程序运行,运行时错误则会出现异常。
Dart的关键字
Dart的关键字如下所示,共有60个
其中:
- 角标为1的是上下文关键字,只在特定的地方有意义,他们在其他地方都可以用做变量
- 角标为2的是内建变量,他们可以在大多数地方被用做变量,但不能用于类或类型名称,或者在import声明
- 角标为3的是较新的关键字,这些关键字在Dart 1.0之后用于支持异步,当一个函数被标记为async或async或sync时,你不能使用await或者yield作为变量名
既然如此,还有个解决问题的更好的方法,就是不用任何关键字做变量名。
变量
变量的声明方式如下:
var name = 'Bob';
变量以引用的形式存在,变量名’name‘是一个值为’Bob’的String类型的引用,但是你可以通过指定类型来修改,如果一个变量不仅仅指定一个类型,请指定类型为Object或dynamic,如下:
dynamic name = 'Bob';
另外还有一种显式声明的方法来定义变量,如下:
String name = 'Bob';
默认值
未初始化的变量都会有一个初始化的值null,甚至是一个数值类型,因为在Dart中,就算是一个普通的数值类型,也是一个对象。
Final/Const
如果你不想去修改一个变量,请使用final或const来代替var,一个final变量只能被设置一次,一个const变量是一个编译时常量,final顶层变量或类变量将会在第一次使用时被初始化。
内建类型
Dart语言的内建类型如下所示:
- numbers: int, double
- stirngs:String 表示字符串,
- booleans:bool 表示布尔值
- lists:List 表示数组
- sets:Set 集合
- maps:Map 表示字典,键值对
- runes: 用于描述Unicode字符串
- symbols
Strings
- UTF-16字符串,可以使用单引号或双引号定义
- 使用+号连接字符串,使用’’’(三个单引号)或"""(三个双引号)可定义多行字符串
- 可以使用toString方法,创建字符串,使用类型的parse方法可以解析字符串
- 可以使用r‘’或r""定义原生字符串,原生字符串中的不会存在转义
List
list也许是几乎所有语言中最常用的集合类型,在Dart中,数组使用List对象,定义如下,类似javascript
var list = [1, 2, 3];
List支持泛型,你可以使用List,这样,如果你在列表中加入一个非整型对象,则会得到一个错误。List索引从0开始,-1表示最后一个元素,你可以使用list.length方法获得数组的长度。
Sets
Dart中的一个set是一个无序惟一容器,set的定义方式如下:
var halogens = 'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine';
Set也支持泛型,上述定义也可以使用Set
Maps
map是一个关键键值对的集合,map的键值对可以是任何类型,每个key只能出现一次,但值可以出现任意多次,其定义方式如下:
var gifts =
//Key: Value:
'first' : 'partridge',
'second' : 'turtledoves',
'fifth' : 'golden rings'
;
同样的, Map也支持泛型,上述定义也可以使用Map<String, String>,另外,以上类型也可以使用new关键字创建,如new Map(),但Dart 2 中,new关键字可以省略。通过键可以获取到值,访问方式如下:
var value = gifts['first'];
同时,你也可以直接给对应的键赋值,使用length获取长度。
Runes
runes表示UTF-32字符,比如\\u1f600代表emoji表情(?) 。String类也有一些方法使你可以获得rune信息,codeUnitAt和codeUnit属性返回一个16位编码单位,使用runes属性可以获得一个runes字符串。
Symbols
一个Symbol类型表示一个操作符或Dart程序中的一个标识符定义,你可能永远也不会使用symbols,但是对于按名称引用标识符的API来说,它们是有用的,因为缩小更改了标识符名称,而不是标识符符号。要想拿到一个标识符的symbol,只需要使用#号即可。
Functions
Dart是一门真正的面向对象的语言,所以即使是一个函数也有一个类型Function,这意味着一个函数可以被赋值或用做参数传递到另外一个函数,你也可以像调用函数一样调用一个类,类似C++中的 仿函数。函数的定义如下所示:
bool isNoble(int atomicNumber)
return _nobleGases[atomicNumber] != null;
尽管Dart推荐为公用Api声明类型,但你确实可以省略类型,如下所示:
isNoble(atomicNumber)
return _nobleGases[atomicNumber] != null;
对于那些仅包含一句表达式的函数来说,你甚至可以按如下写法:
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
这种写法就是一个闭包或Lambda函数,你可以使用@required来标记一个参数,表示这个参数必须要传值。
可选参数
可选参数可能是位置型或者是命名型,但不能同时存在
可选命名参数
当你在调用一个方法时,你可以使用paramName:value的方式给指定的参数赋值。当你定义一个函数时,使用param1, param2, …的方式定义如下所示:
enableFlags(bold : true, hidden : false);
可选位置参数
参数使用[]包裹即可被定义为一个位置参数,如下所示:
String say(String from, String msg, [String device])
var result = '$from says $msg';
if(device != null)
result = '$result with a $device';
return result;
这样一来,在调用参数的时候,你可以根据需要传递两个或三个参数。
参数默认值
你的函数可以使用=来为位置参数和命名参数定义一个默认值,如果没有指定默认值,默认值为null。如下所示:
void enableFlags(bool bold = false, bool hidden = false)...
main()函数
每一个app都必须有一个顶级的main函数,作为整个app的入口,main函数返回值为void,标准的C/C++都是返回int,main函数的参数列表可以为空,也可以是一个List类型的参数列表。
匿名函数
大多数函数都是命名函数,你也能创建一个匿名函数,你可以将匿名函数赋值给一个变量,比如说你可以将它添加到集合或者从中移除。一个匿名函数看起来和命名函数差不多,定义方法如下:
([[Type] param1[, ...]])
codeBlock;
;
从上述定义可以看出,匿名函数,可以省略返回类型和参数。
Opeartor
Dart的操作符类型如下表所示,你可以像C++一样,重写操作符。
数学运算符
Dart的数学运算符如下表所示:
Dart操作符和运算法与其他的语言几乎一模一样,~/是整除的意思,只返回一个整型,比如:
5 ~/ 2 = 2
类似于C++中的
(int)(5 / 2)
另外Dart也支持++, – 这些操作符。还有各种等于,不等于,大于,小于操作符。
类型测试
as: 用于类型转换,is 测试该实例是否是一个类的实例,is!与is结果相反。
赋值操作符
Dart中有一个赋值操作符??=,当要赋值的变量(左值)是null时,则赋值,否则保持原值。
另外还有逻辑运算符,位移运算符,条件运算符等。其中条件运算符除了?:这个三目运算符之外,还有一个expr1 ?? expr2运算符,如果expr1不为null, 返回expr1的值,否则返回expr2的值
级联运算
级联(…)允许实现类型链式的调用方法,它让你额外的访问同一个类型的一些属性,如下所示:
querySelector('#confir')
..text = 'Confirm'
..classes.add('important')
..onClick.listen((e)=>window.alert('Confirmed!'));
以上代码可以分解为以下代码
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('import');
button.onClick.listen((e) => window.alert('Confirmed!'));
其他运算符
Dart中有一个?.运算符,称做条件成员访问,例如foo?.bar,当foo为null的时候选择访问bar,否则选择访问foo。
流程控制
Dart中的流程控制和其他语言类似,共有如下几种
- if and else
- for循环
- while 和 do-while 循环
- break and continue
- switch and case
- assert
其中,大部分的使用方法和C/C++一致,只有switch有区别,区别如下:
- dart中的case 语句必须以break结尾,否则会报错,但C/C++中并未有此规定
- 当且仅当一个case 为空时,可以继续往下执行,但C++中case可包含代码块,如下所示:
Dart:
var command = 'CLOSED';
switch (command)
case 'CLOSED': // Empty case falls through.
case 'NOW_CLOSED':
// Runs for both CLOSED and NOW_CLOSED.
executeNowClosed();
break;
C++:
int command = 0;
switch(command)
case 0:
printf("0");
case 1:
printf("1");
break;
- C++中的switch类型只能为整型,但是Dart中可以为任意类型
- 如果想实现C++的方法,可以使用continue label的方式,跳转到指定标签继续执行case
异常
Dart代码可以抛出和捕获异常,异常是一种指示一些意外情况发生的错误,如果异常没有捕获,那么抛出的异常可能导致程序挂起或终止。Dart提供Exception和Error两种类型,除了预定义的异常类型,你还可以定义自己的异常类型。然而Dart程序可以抛出任何非空对象,不仅仅是Exception或Error。
Throw & Catch
Dart使用throw关键字来抛出异常,在Java中,使用catch来捕获异常,但是在Dart中,则使用on关键字来捕获,使用cache来传递异常参数,单独的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不单只有一个异常参数,还有一个CallStack参数,表示程序的调用栈,类似于Java中的printStackTrace()方法。同时,你还可以使用rethrow关键字将正在处理的异常进行再抛出,由调用的函数继续捕获进行处理。
Finally
类似于Java中的finally,不管最后发生何种异常,finally中的代码都会执行。
Classes
Dart 是一门面向对象语言,每个对象都是一个类的实例,而每一个类都是由Object类扩展下来,除了Object之外,每个类都有一个父类。每个类也包含一个this,类似C++。
访问类成员
对象由成员和函数组成,当你调用一个方法时,该方法就可以访问该对象中的函数和数据。使用点(.)来访问成员变量或成员函数。使用?.来避免空指针异常。
成员变量
成员变量的定义如下:
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方法。
构造函数
你可以通过构造函数来创建一个对象,构造函数通常以类名或类名.标识符命名,你还可以使用const 构造函数来创建一个const对象。在C++中,使用构造函数,如果不使用初始化列表,我们应该写如下代码:
C++:
Point(int x, int y) this->x = x; this->y = y;
但是在Dart中,你可以使用类似如上的写法,还有另外一种更简便的方法:
Point(this.x, this.y);
如果你不为当前类声明一个构造函数,Dart将为你提供一个默认的构造函数,默认的构造函数没有参数,并且会调用父类的无参构造方法,代码可编写如下:
Point():super()
初始化列表
我们知道,在C++中,构造函数有一个初始化列表,如下所示:
Point(int x, int y) : x(x), y(y)
在Dart中,同样可以使用初始化列表,使用方法如下:
Point(x, y) : x = x, y = y
构造重定向
有时候一个构造函数只有一个目的,就是重定向到该类的另一个构造函数中,重定义构造函数的函数体为空,定义如下:
Point.alongXAxis(num x) : this(x, 0);
常构造
如果你产生的对象不会被修改,你可以使这些对象保持编译时常量,要这样做,定义一个const构造函数,确保所有变量为final类型。
Factory构造函数
通常情况下,构造函数会创建一个新的实例,但有时候你需要复用对象而不是创建新对象,则可以使用factor构造函数,如下代码所示:
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 = Logger._internal(name);
_cache[name] = logger;
return logger;
Getter & Setter
Getter和Setter指定用于读取和写入权限的方法,每个实例变量都会有一个隐式的getter方法,你可以通过实现自己的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);
对象类型
要想获得一个对象的类型,可以使用对象的runtimeType属性,该属性返回一个Type对象,如下所示:
print('The type of a is $a.runtimeType');
抽象类
在C++中,也有抽象类的概念,我们把包含有纯虚函数的类称做抽象类,并没有任何关键字标记,C++中的定义如下所示:
class Test
virtual void test(void) = 0;
;
抽象类不能实例化,子类继承自抽象类,必须实现纯虚函数,否则该子类也不能实例化。在Dart中,使用abstract来定义一个抽象类。其中包含的方法是抽象函数,也必须在子类中实现。
隐式接口
每个类隐式的定义了一个接口,这个接口包含所有的实倒方法,如果你需要创建一个类B但不从A继承,可以使用implements关键字实现该接口,并实现其中的方法。
继承与覆写
和Java一样,Dart使用extends来继承一个类,如果需要覆盖父类的方法,需要使用@override,Dart也可以像C++一样重写操作符函数, 如下所示:
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);
// Operator == and hashCode not shown. For details, see note below.
// ···
void main()
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');
除了以下几种情况,否则你不能调用一个未实现的方法:
- 接收者有一个dynamic的类型定义
- 接收者有一个定义为未实现方法的类型,并且实现了不同于Object类中的noSuchMethod()方法
枚举类型
枚举类型是一个特别的类,通常用于表示一个固定的数值或常量。使用方式如下:
enum Color red, green, blue
每一个枚举类型都有一个称为index的getter方法,返回一个从0开始的对应的值在枚举中的索引。枚举类型可以在switch语句中使用,如果没有将所有的情况都处理完整,你将会得到一个警告。使用枚举类型还有如下限制:
- 你不能继承,混入或实现一个枚举
- 你不能显式的实例化一个枚举
特征添加:混入
混入是一种在多个类继承中重用类代码的方式,要想使用混入,在with关键字后添加一个或多个混入类名称,示例如下:
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 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');
指定一个仅有确定类型
库
import和library能帮助你创建一个模块并且分享出去,库不仅仅提供API,更是一个以下划线开头的可视类型。每一个Dart app都是一个库,即使并没有使用library指令。库可以以包的形式被使用。通常使用import来引用一个库中的命名空间。import惟一要求的参数就是一个指定库的URI,对于内建的库来说,你需要指定dart:,如果该库是由包管理器提供的,则应该使用package:。
如果你引入了两个命名有冲突的库,你可以使用as关键字为其中一个或多个同时指定一个前缀,示例如下:
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
你也可以引用库中的一部分,使用show关键字,可以指定从库中仅引入某个类,使用hide则可以引用除该类中的其他类,如下所示:
// Import only foo.
import 'package:lib1/lib1.dart' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
延迟加载允许应用在使用到一个库的时候进行加载,以下是一些你可能需要延迟加载的情况:
- 需要减少app的初动速度
- 执行A/B测试-尝试交替实现一个算法
- 加载很少使用的功能,比如一个可选的屏幕和对话框
要延迟加载一个库,你必须道先使用deferred as来引入一个库,之后在使用的时候调用loadLibrary()方法,如下所示:
import 'package:greetings/hello.dart' deferred as hello;
Future greet() async
await hello.loadLibrary();
hello.printGreeting();
你可以多次调用loadLibrary(),但是这个类仅会被加载一次。使用延迟加载时,你必须要谨记以下几点:
- 使用延迟加载的库中的常量引入时不再是常量。记住,这些常量在加载之前是不存在的。
- 你不能使用导入文件中的类型,相反,考虑将接口类型移动到由延迟库和导入文件导入的库中。
- Dart会在你使用deferred as定义的命名空间中隐藏的插入一个loadLibrary()调用,loadLibrary()返回一个Feature对象。
异步支持
Dart库是一个充满返回一个Feature或Stream方法的库。这些方法都是异步的,他们可能在设置某些耗时的操作之后就返回而不等待他们完成。async和await关键字支持异步编程,可以让你像编写同步代码一样的编写异步代码。
处理Features
当你需要使用完整的Feature时,你有两个选择:
- 使用async 和 await
- 使用Feature API,如从库中的引入
使用async和await的代码是异步的,但是它看起来会像同步的代码一样。要使用await,代码必须在一个async方法中,使用try,catch,finally来处理错误和清理操作,如下所示:
Future checkVersion() async
var version = await lookUpVersion();
// Do something with version
在一个异步方法中,你可以多次使用await,在一个await表达式中,它的值通常是一个Feature,如果不是一个Feature,这个值也是一个原子值,Feature对象指定一个promise返回一个对象。await表达式的值就是这个返回的对象,await表达式会中断执行直接这个对象可以使用。如果你使用await的时候获得一个编译时错误,确保await是在async方法当中。
处理Streams
当你需要从Stream中获取一个值,你有两个选择:
- 使用async和一个异步循环(await for)
- 使用Stream API
如下所示:
await for (varOrType identifier in expression)
这个表达式的值是一个Stream,执行进程如下所示:
- 等待直到Stream发射一个值
- 执行for循环的body,变量设置为发射的值
- 重复1和2直接到Stream关闭
如果需要停止监听Stream,你可以使用break或return来中断循环。
生成器
当你需要延迟生成一个值的序列时,可以考虑使用一个生成器函数,Dart内建支持两类生成器
- 同步生成器(Synchronous generator): 返回一个Iterable对象
- 异步生成器(Asynchronous generator): 返回一个Stream对象
要实现一个同步生成器函数,你需要使用sync*标记,然后使用yield语句传送该值。如下所示:
Iterable<int> naturalsTo(int n) sync*
int k = 0;
while (k < n) yield k++;
要实现异步生成器函数,你需要使用async*标记,然后使用yield语句传送该值,如下所示:
Stream<int> asynchronousNaturalsTo(int n) async*
int k = 0;
while (k < n) yield k++;
如果你的生成器是一个递归函数,你可以使用yield*来提高性能。
可调用类
如果你要使你的类能像函数一样被调用,类似C++中的仿函数,请实现call()方法
class WannabeFunction
call(String a, String b, String c) => '$a $b $c!';
Typedefs
在Dart中,函数是一个对象,就像String和Number对象一样。一个typedef或者函数类型别名,为函数类型提供一个在声明字段和返回类型时可以使用的名称,当一个函数类型赋值给一个变量的时候,typedef会维持原有的类型信息。使用方法如下:
typedef Compare = int Function(Object a, Object b);
Metadata
使用Metadata给你的代码添加额外的信息。一个Metadata标注以字符@开始,跟着是一个编译时常值,例如deprecated或者调用一个常量构造函数。有两个标注在所有Dart代码中都可以使用,@deprecated和@override。你可以定义自己的metadata标注,只需要类定义了const构造函数。如下所示:
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');
Metadata可以出现在库、类、typedef、类型参数、构造函数、工厂、函数、字段、参数或变量声明之前,也可以出现在导入或导出指令之前。您可以使用反射在运行时检索元数据。
注释
Dart中的注释也分单行注释//和多行注释/**/, 其中还有一种文档注释类型,以/**或///开始,同样以**/或///结束。
以上是关于Dart之旅的主要内容,如果未能解决你的问题,请参考以下文章