dart
dart
变量
声明变量
- 不指定类型:
var name = 'Bob'
- 明确指定类型(Optional types):
String name = 'Bob'
- 在变量类型并不明确的情况下,可以使用dynamic关键字:
dynamic name = 'Bob'
- 可空类型:在类型后面添加
?
,指可以赋值为 null 的类型。确定一个可空变量不为 null 时,使用!
运算符来强制将其转换为非空类型。
默认值
未初始化的变量默认值是 null。即使变量是数字类型默认值也是 null,因为在 Dart 中一切都是对象,数字类型也不例外。
final 和 const
- final 变量的值只能被设置一次。必须在构造函数体执行之前初始化 final 实例变量 —— 在变量声明中,参数构造函数中或构造函数的初始化列表中进行初始化。
- const 变量在编译时就已经固定
final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';
const bar = 1000000; // 压力单位 (dynes/cm2)
const double atm = 1.01325 * bar; // 标准气压
// 如果我们预期变量不能为空,但在定义时不能确定其初始值,则可以加上late关键字,
// 表示会稍后初始化,但是在正式使用它之前必须得保证初始化过了,否则会报错
late int k;
数据类型
Number
int:整数值不大于64位, 具体取决于平台。 在 Dart VM 上, 值的范围从 -263 到 263 - 1. Dart 被编译为 JavaScript 时,使用 JavaScript numbers, 值的范围从 -253 到 253 - 1。
double:64位(双精度)浮点数,依据 IEEE 754 标准。
从 Dart 2.1 开始,必要的时候 int 字面量会自动转换成 double 类型。
String
Dart 字符串是一组 UTF-16 单元序列。 字符串通过单引号或者双引号创建。 字符串可以通过 ${expression}
的方式内嵌表达式。 如果表达式是一个标识符,则 {}
可以省略。 在 Dart 中通过调用就对象的 toString()
方法来得到对象相应的字符串。
提示
可以使用 +
运算符来把多个字符串连接为一个,也可以把多个字面量字符串写在一起来实现字符串连接。
使用连续三个单引号或者三个双引号实现多行字符串对象的创建。
使用 r
前缀,可以创建 “原始 raw” 字符串。
Boolean
Dart 只有字面量 true and false 是布尔类型, 这两个对象都是编译时常量。
Dart 的类型安全意味着不能使用 if (nonbooleanValue) 或者 assert (nonbooleanValue)。 而是应该像下面这样,明确的进行值检查:
// 检查空字符串。
var fullName = '';
assert(fullName.isEmpty);
// 检查 0 值。
var hitPoints = 0;
assert(hitPoints <= 0);
// 检查 null 值。
var unicorn;
assert(unicorn == null);
// 检查 NaN 。
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);
List
在 Dart 中的 Array 就是 List 对象, List 字面量非常像 JavaScript 中的 array 字面量。
/*Dart 推断 list 的类型为 List<int> 。
如果尝试将非整数对象添加到此 List 中, 则分析器或运行时会引发错误。*/
var list = [1, 2, 3];
在 List 字面量之前添加 const 关键字,可以定义 List 类型的编译时常量
var constantList = const [1, 2, 3];
// constantList[1] = 1; // 取消注释会引起错误。
Set
在 Dart 中 Set 是一个元素唯一且无序的集合。 Dart 为 Set 提供了 Set 字面量和 Set 类型。
/* Dart 推断 halogens 类型为 Set<String> 。
如果尝试为它添加一个 错误类型的值,分析器或执行时会抛出错误。*/
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
要创建一个空集,使用前面带有类型参数的 {} ,或者将 {} 赋值给 Set 类型的变量
var names = <String>{};
// Set<String> names = {}; // 这样也是可以的。
// var names = {}; // 这样会创建一个 Map ,而不是 Set 。
Map
Map 是用来关联 keys 和 values 的对象。 keys 和 values 可以是任何类型的对象。
在一个 Map 对象中一个 key 只能出现一次。 但是 value 可以出现多次。
Dart 中 Map 通过 Map 字面量 和 Map 类型来实现。
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
var gifts = Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
var nobleGases = Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';
Rune
Runes 用于表示字符串中的 UTF-32 编码字符。由于 Dart 字符串是 UTF-16 编码的字符序列,因此在处理一些特殊字符(如表情符号)时,需要使用 Runes 来正确处理这些字符。
Symbol
Symbol 是一个表示 Dart 代码中的标识符(如变量名、函数名、类名等)的特殊类型。
主要用于反射(Reflection)和元编程(Metaprogramming),允许你在运行时动态地访问和操作代码中的标识符。
Iterable 与 Stream
Iterable 对象是一个元素集合,可以是可迭代的或是数组。
Stream 是一系列异步事件的序列。其类似于一个异步的 Iterable,不同的是当你向 Iterable 获取下一个事件时它会立即给你,但是 Stream 则不会立即给你而是在它准备好时告诉你。
函数
函数是一等对象。一个函数可以作为另一个函数的参数。同样可以将一个函数赋值给一个变量。
var say = (str){
print(str);
};
say("hi world");
可选参数
可选参数可以是命名参数或者位置参数,但一个参数只能选择其中一种方式修饰。
命名可选参数
定义函数是,使用 {param1, param2, …}
来指定命名参数。
调用函数时,可以使用指定命名参数 paramName: value
。 例如:
void enableFlags({bool bold, bool hidden}) {...}
enableFlags(bold: true, hidden: false);
使用 @required
注释表示参数是 required 性质的命名参数
const Scrollbar({Key key, @required Widget child})
//此时 Scrollbar 是一个构造函数, 当 child 参数缺少时,分析器会提示错误。
位置可选参数
将参数放到 []
中来标记参数是可选的
String say(String from, String msg, [String device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
默认参数值
在定义方法的时候,可以使用 =
来定义可选参数的默认值。 默认值只能是编译时常量。 如果没有提供默认值,则默认值为 null。
/// 设置 [bold] 和 [hidden] 标志 ...
void enableFlags({bool bold = false, bool hidden = false}) {...}
// bold 值为 true; hidden 值为 false.
enableFlags(bold: true);
main()
任何应用都必须有一个顶级 main()
函数,作为应用服务的入口。 main()
函数返回值为空,参数为一个可选的 List<String>
。
匿名函数
匿名函数和命名函数看起来类似— 在括号之间可以定义一些参数或可选参数,参数使用逗号分割。
后面大括号中的代码为函数体:([[Type] param1[, …]]) { codeBlock; };
如果函数只有一条语句, 可以使用箭头简写。
list.forEach(
(item) => print('${list.indexOf(item)}: $item'));
返回值
Dart函数声明如果没有显式声明返回值类型时会默认当做dynamic
处理,注意,函数返回值没有类型推断.
运算符
Dart 支持常用的运算运算符和关系运算符。
提示
assert(5 / 2 == 2.5); // 结果是双浮点型
assert(5 ~/ 2 == 2); // 结果是整型
assert(5 % 2 == 1); // 余数
类型判断运算符
as
, is
, 和 is!
运算符用于在运行时处理类型检查
赋值运算符
// 将值赋值给变量a
a = value;
// 如果b为空时,将变量赋值给b,否则,b的值保持不变。
b ??= value;
条件表达式
ondition ? expr1 : expr2
:如果条件为 true, 执行 expr1 (并返回它的值): 否则, 执行并返回 expr2 的值。expr1 ?? expr2
:如果 expr1 是 non-null, 返回 expr1 的值; 否则, 执行并返回 expr2 的值。
级联运算符
级联运算符 ..
可以实现对同一个对像进行一系列的操作。 除了调用函数, 还可以访问同一对象上的字段属性。
querySelector('#confirm') // 获取对象。
..text = 'Confirm' // 调用成员变量。
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
//等价于
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
控制流程语句
if - else
for
while
和 do-while
switch case
同 C 语言类似。
提示
Dart 的判断条件必须是布尔值,不能是其他类型。
在 case 语句中,每个非空的 case 语句结尾需要跟一个 break 语句。
闭包在 Dart 的 for 循环中会捕获循环的 index 索引值, 来避免 JavaScript 中常见的陷阱。
实现了 Iterable 的类(比如, List 和 Set)同样也支持使用 for-in 进行迭代操作 iteration
异常
Dart 代码可以抛出和捕获异常。 异常表示一些未知的错误情况。 如果异常没有被捕获, 则异常会抛出, 导致抛出异常的代码终止执行。
和 Java 有所不同, Dart 中的所有异常是非检查异常。 方法不会声明它们抛出的异常, 也不要求捕获任何异常。
Dart 提供了 Exception 和 Error 类型, 以及一些子类型。 当然也可以定义自己的异常类型。 但是,此外 Dart 程序可以抛出任何非 null 对象, 不仅限 Exception 和 Error 对象。
示例:
throw FormatException('Expected at least 1 section');
throw 'Out of llamas!';
void distanceTo(Point other) => throw UnimplementedError();
捕获异常可以避免异常继续传递(除非重新抛出( rethrow )异常)。
try {
breedMoreLlamas();
} on OutOfLlamasException {
// 一个特殊的异常
buyMoreLlamas();
} on Exception catch (e) {
// 其他任何异常
print('Unknown exception: $e');
} catch (e) {
// 没有指定的类型,处理所有异常
print('Something really unknown: $e');
}
提示
如上述代码所示,捕获语句中可以同时使用 on 和 catch ,也可以单独分开使用。 使用 on 来指定异常类型, 使用 catch 来 捕获异常对象。
catch() 函数可以指定1到2个参数, 第一个参数为抛出的异常对象, 第二个为堆栈信息 ( 一个 StackTrace 对象 )。
不管是否抛出异常, finally 中的代码都会被执行。 如果 catch 没有匹配到异常, 异常会在 finally 执行完成后,再次被抛出。
try {
breedMoreLlamas();
} catch (e) {
print('Error: $e'); // Handle the exception first.
} finally {
cleanLlamaStalls(); // Then clean up.
}
类
Dart 是一种基于类和 mixin 继承机制的面向对象的语言。 每个对象都是一个类的实例,所有的类都继承于 Object. 。 基于 Mixin 继承 意味着每个类(除 Object 外) 都只有一个超类, 一个类中的代码可以在其他多个继承类中重复使用。
类的声明与构造
类的属性或者方法以下划线开头表示库内私有。
class Point {
num x; // 声明示例变量 x,初始值为 null 。
num y; // 声明示例变量 y,初始值为 null 。
num z = 1; // 声明示例变量 z,初始值为 0 。
num distanceTo(num a) {
return a+z;
}
}
void main() {
var point = Point();
point.x = 4; // Use the setter method for x.
assert(point.x == 4); // Use the getter method for x.
assert(point.y == null); // Values default to null.
}
构造函数
通过创建一个与其类同名的函数来声明构造函数。
class Point {
num x, y;
Point(num x, num y) {
this.x = x;
this.y = y;//使用 this 关键字引用当前实例
//近当存在命名冲突时,使用 this 关键字。 否则,按照 Dart 风格应该省略 this 。
}
// 在构造函数体执行前,
// 语法糖已经设置了变量 x 和 y。
Point(this.x, this.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;
Point(this.x, this.y);
// 命名构造函数
Point.origin() {
x = 0;
y = 0;
}
}
调用父类非默认构造函数
默认情况下,子类的构造函数会自动调用父类的默认构造函数(匿名,无参数)。 父类的构造函数在子类构造函数体开始执行的位置被调用。 如果提供了一个 initializer list (初始化参数列表), 则初始化参数列表在父类构造函数执行之前执行。 总之,执行顺序如下:
- initializer list (初始化参数列表)
- superclass’s no-arg constructor (父类的无名构造函数)
- main class’s no-arg constructor (主类的无名构造函数)
如果父类中没有匿名无参的构造函数, 则需要手工调用父类的其他构造函数。 在当前构造函数冒号
:
之后,函数体之前,声明调用父类构造函数。
重定向构造函数
class Point {
num x, y;
// 类的主构造函数。
Point(this.x, this.y);
// 指向主构造函数
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 关键字。 例如,一个工厂构造函数可能会返回一个 cache 中的实例, 或者可能返回一个子类的实例。一般用来构建单例模式。
使用构造函数
通过 构造函数 创建对象。 构造函数的名字可以是 ClassName 或者 ClassName.identifier。
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // 它们是同一个实例。
相关信息
在 Dart 2 中 new 关键字变成了可选的。
在构造函数名之前加 const 关键字,来创建编译时常量.
构造两个相同的编译时常量会产生一个唯一的, 标准的实例。
在没有声明构造函数的情况下, Dart 会提供一个默认的构造函数。 默认构造函数没有参数并会调用父类的无参构造函数。
使用类的成员变量和方法
使用 .
来引用实例对象的变量和方法。
使用 ?.
来代替 .
, 可以避免因为左边对象可能为 null , 导致的异常。
获取对象类型
使用对象的 untimeType
属性, 可以在运行时获取对象的类型, runtimeType
属性回返回一个 Type
对象。print('The type of a is ${a.runtimeType}');
Getter 和 Setter
Getter 和 Setter 是用于对象属性读和写的特殊方法。
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// 定义两个计算属性: right 和 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);
}
抽象类与抽象方法
使用 abstract
修饰符来定义 抽象类 — 抽象类不能实例化。
抽象类通常用来定义接口,以及部分实现。如果希望抽象类能够被实例化,那么可以通过定义一个 工厂构造函数 来实现。
抽象类中可以定义抽象方法,抽象方法只定义接口不进行实现,而是留给其他类去实现。
定义一个抽象方法,使用分号 ;
来代替函数体
abstract class TeacherInterface {
void teaching();
}
class Teacher implements TeacherInterface {}
扩展类(继承)
子类继承父类后,可以直接使用父类中定义的属性和方法,并且子类可以对父类的方法进行重写以实现定制化的功能。 使用 extends
关键字来创建子类, 使用 super
关键字来引用父类。
注意
Dart 只支持单继承,即子类只能够有一个父类。
运算符重载
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);
}
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 {
// 如果不重写 noSuchMethod,访问
// 不存在的实例变量时会导致 NoSuchMethodError 错误。
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: ' +
'${invocation.memberName}');
}
}
枚举类型
枚举类型也称为 enumerations 或 enums , 是一种特殊的类,用于表示数量固定的常量值。
使用枚举类型
enum Color { red, green, blue }
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
//使用枚举的 values 常量, 获取所有枚举值列表( list )。
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);
Mixin
Mixin 是复用类代码的一种途径, 复用的类可以在不同层级,之间可以不存在继承关系。
通过 with 后面跟一个或多个混入的名称,来 使用 Mixin。
能够进行混合的类被称为 Mixin 类,Mixin 类中不能实现构造方法,否则不能够被其他类进行 混合。使用 with 关键字来进行 Mixin 混合,Mixin 支持多混合
main() {
var bird = Bird("鸟类");
bird.desc();//Instance of 'Bird'
}
//动物类作为基类
class Animal {
String name;
Animal(this.name);
}
// 用来进行混合的描述类
class Descript {
desc(){
print(this);
}
}
// 动物鸟类
class Bird extends Animal with Descript {
Bird(name):super(name);
}
如果不想使 Mixin 类实例化,那么可以使用 mixin 关键字代替 class 关键字来定义 Mixin 类 如果使用 mixin 关键字进行定义,就使用 on 关键字进行继承。
类的静态变量与方法
class Queue {
static const initialCapacity = 16;
// ···
}
class Point {
num x, y;
Point(this.x, this.y);
static num distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}
void main() {
assert(Queue.initialCapacity == 16);
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(2.8 < distance && distance < 2.9);
print(distance);
}
静态变量只到它们被使用的时候才会初始化。 静态方法(类方法)不能在实例上使用,因此它们不能访问 this 。
可调用类
main() {
var cls = MyClass();
var res = cls("Hello","World");
print(res);//[Hello, World]
}
class MyClass {
call(a,b){
return [a,b];
}
}
泛型
类泛型
class MyClass<T> {
T data;
}
T 只是一个标识符,用来作为泛型进行占位,其实际类型会在运行时确定。
class MyClass<T extends People> {}
函数泛型
T getData<T extends People>(MyClass<T> people){}
函数名后面的尖括号中用来指定泛型的类型,这个类型可以在函数的返回值、参数类型,甚至参数类型的泛型以及函数体中使用。
异步支持
Future
Future 表示一个不会立即完成的计算过程。与普通函数直接返回结果不同的是异步函数返回一个将会包含结果的 Future。该 Future 会在结果准备好时通知调用者。
Future.delayed(Duration(seconds: 2),(){
//return "hi world!";
throw AssertionError("Error");
}).then((data){
//执行成功会走到这里
print(data);
}).catchError((e){
//执行失败会走到这里
print(e);
}).whenComplete((){
//无论成功或失败都会走到这里
});
Future.wait 它接受一个 Future 数组参数,只有数组中所有 Future 都执行成功后,才会触发 then 的成功回调,只要有一个 Future 执行失败,就会触发错误回调。
Future.wait([
// 2秒后返回结果
Future.delayed(Duration(seconds: 2), () {
return "hello";
}),
// 4秒后返回结果
Future.delayed(Duration(seconds: 4), () {
return " world";
})
]).then((results){
print(results[0]+results[1]);
}).catchError((e){
print(e);
});
async/await
async 用来表示函数是异步的,定义的函数会返回一个 Future 对象,可以使用 then 方法添加回调函数。
main() {
var future = getData();
print(future);//Instance of 'Future'
print("继续执行...");
}
getData() async{
var data = await "HelloWorld";
}
//表达式 返回的值必须是 Stream 类型
await for (var request in requestServer) {
handleRequest(request);
}
使用 try, catch, 和 finally 来处理代码中使用 await 导致的错误。
try {
version = await lookUpVersion();
} catch (e) {
// React to inability to look up the version
}
模块
//系统库
import 'dart:io';
//自定义库
import "./lib/pri2.dart" as pri2;//模块命名
import "./lib/pri.dart"; //全部导入
import "./lib/pri.dart" show other //部分导入
//第三方库,需要在pubspec.yaml先声明
import 'package:other_package/other_library.dart';
其中,show 的作用是选择部分内容进行导入,上面的代码中只导入了 pri.dart 文件中的 other方法,如果要导入多个内容,那么使用逗号进行分割即可。
Typedefs
函数别名 typedef Compare = int Function(Object a, Object b);
生成器
同步生成器
同步生成器的返回类型通常是 Iterable,因为它们生成的值可以被迭代器逐个消耗。 适用于在迭代过程中逐步产生数据,并且不需要进行异步操作的情况。比如生成数字序列、字母序列等。
Iterable<int> countUpTo(int n) sync* {
for (int i = 1; i <= n; i++) {
yield i;
}
}
void main() {
final numbers = countUpTo(5);
for (var number in numbers) {
print(number);
}
}
异步生成器
异步生成器的返回类型通常是 Stream,因为它们生成的值是异步的,需要通过订阅器来消耗。适用于需要异步处理的情况,比如从网络或文件中读取数据,或者执行一些耗时的操作并逐步返回结果。
Stream<int> countUpToAsync(int n) async* {
for (int i = 1; i <= n; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
void main() async {
await for (var number in countUpToAsync(5)) {
print(number);
}
}