本文介绍了 Dart 的环境搭建以及 Dart 语法,包括变量与常量、数据类型、函数、操作符、类、泛型等等。
安装
-
在 Dart for Windows (gekorm.com) 下载安装包安装 Dart SDK。
-
在 VSCode 中安装 Dart 和 Code Runner 插件。
-
新建一个
.dart
文件,输出 "Hello dart!" :
void main() {
print('Hello dart!');
}
复制代码
Run Code:
Hello dart!
复制代码
变量和常量
变量
在Dart中变量的声明有三个关键字:var 、dynamic 和 Object 。
像 JS,Dart 中的变量可以不预先定义变量类型,也可以在变量前加类型来声明变量:
var a = 114514;
String str = 'this is string';
复制代码
使用这两种方法声明变量之后,后续不能够更改变量类型。
var a = 114514;
a = '12345'; // 报错
复制代码
使用dynamic
来声明变量可以使得在编译阶段不检查类型,这与 JavaScript 一样:
dynamic str = 'this is string';
str = 114514;
print(str);
复制代码
常量
使用 const
和 final
定义。
const PI = 3.14;
复制代码
const
需要是编译时常量,而final
不需要:
const constDateTime = DateTime.now(); // 报错
final finalDateTime = DateTime.now();
复制代码
数据类型
数值
数值类型 num
包含 int
和 double
两个子类。
String
Dart 字符串是 UTF-16 编码的字符序列。
可以在字符串中通过${expression}
语法插入表达式。
可以通过 ==
比较字符串。
bool
if
和assert
表达式里面,它们的值必须是bool
类型。默认值是null
而非false
。
List
List
类似数组,默认接收的类型是dynamic
,可以向其中添加任意对象:
List list = [1, 2, 3];
复制代码
常用的属性和方法:
常用属性:
length 长度
reversed 翻转,结合 toList()
isEmpty 是否为空
isNotEmpty 是否不为空
常用方法:
add(value) 增加
addAll(list) 拼接数组
indexOf(value) 查找,传入 value 返回 index,找不到返回 -1
remove(value) 删除,传入 value
removeAt(index) 删除,传入索引值
fillRange(startIndex,endIndex,value) 修改,[参数1, 参数2)的数据修改为参数3
insert(index,value); 指定位置插入
insertAll(index,list) 指定位置插入List
toList() 其他类型转换成List
join(分隔符) List 转换成 String
split(分隔符) String 转化成 List
forEach
map
where
any
every
这些方法在 Set 和 Map 中大多也可以使用
复制代码
Set
Set
表示容器中的对象唯一,按照添加的先后顺序排序。Set 是没有顺序且去重后的集合,所以没有索引值。
Set set = {'a', 'b', 'a', 'c'};
print(set); // {a, b, c}
print(set.toList()); // [a, b, c]
复制代码
Map
Map
按照添加的先后顺序排序。
Map map = {'foo': 'apple', 'bar': 'banana'};
print(map); // {foo: apple, bar: banana}
print(map.keys.toList()); // [foo, bar]
print(map.values.toList()); // [apple, banana]
map.addAll({'baz':'pear'});
map.remove("baz");
print(map.containsValue("apple")); // true
复制代码
循环数据的方法
forEach
、map
、where
、any
、every
List:
for、for ... in:略
forEach:
List list=["apple", "banana", "pear"];
list.forEach((value){
print(value); // apple banana pear
})
map:
List list=["apple", "banana", "pear"];
List newList = list.map((value){
return value + "1";
}).toList();
print(newList); // [apple1, banana1, pear1]
where:
List list = [1, 2, 3, 4, 5];
List newList = list.where((value){
return value > 3;
}).toList();
print(newList); // [4, 5]
any:只要 value 中有满足条件的就返回 true
bool b = list.any((value){
return value > 3;
});
print(b); // true
every:每一个 value 都要满足条件才返回 true
Set:同 List
Map:
forEach:
Map person = {
"name": "Jack",
"age": 20,
};
person.forEach((key, value){
print("$key - $value"); // name - Jack age - 20
});
复制代码
函数
像JS,函数也是对象,类型为Function
。因此函数也可以作为变量的值当函数的参数或者返回值。
支持使用=>
缩写语法,不同于 ES6 的箭头函数,这里箭头只能指向函数的返回值:
String hello(var name) => 'hello $name';
// 等价于
String hello(var name){
return 'hello $name';
}
复制代码
若没有返回值,默认为null
。
入口函数
入口函数即 main
函数,如果没有返回值可以省略 void。
可选参数
- 可选命名参数
命名参数要用花括号括起来,命名参数可以有默认值,不指定默认值则为 null
:
String add({var a, var b = 1}) => '$a' + '$b';
var a = add(b: 2);
print(a); // null2
复制代码
- 可选位置参数
在方括号内可以设置非必填参数,默认值可加可不加,默认值为 null
:
String add(var a, [var b = 1]) => '$a' + '$b';
var a = add(1);
print(a); // 11
复制代码
匿名函数
即省略函数名的函数:
var func = (a, b) => a + b;
print(func(1, 2)); // 3
复制代码
闭包
在 JS 中闭包这个概念很重要,这里不再重复,后期会专门写一篇文章来说明。
操作符
条件运算符 ?.
下面的例子中,若 p 为空,则返回 null,若 p 不空,则返回 p.name :
class Person{
var name;
}
void main() {
var p = Person();
print(p?.name); // null
}
复制代码
取整除 ~/
相较于/
仅仅多了取整的功能。
类型操作 as
, is
, is!
as
用来转化类型, is
、is!
用来判断类型:
num n1 = 1;
int n2 = n1 as int;
if(n2 is int){
print('n2 is int')
}
复制代码
判空 ??
若 a 为空,则 b = 1;反之 b = a:
void main() {
var a;
var b = a ?? 1;
print(b);
}
复制代码
级联 ..
相当于链式调用,允许对同一对象进行一系列操作。
流程控制
与其他语言一样,这里省略。
类
在 Dart 中所有对象都是某个类的实例,所有类继承了Object
类。
构造函数
import 'dart:math';
// 定义类
class Point {
num x = 0, y = 0;
// Point(this.x, this.y); // 构造器
// 或者
Point(x, y) {
this.x = x;
this.y = y;
print('这是构造函数,在实例化时触发');
}
// 实例方法
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
main() {
// 调用类的构造函数,new 可以省略
Point p1 = new Point(1, 2); // 这是构造函数,在实例化时触发
Point p2 = Point(3, 4); // 这是构造函数,在实例化时触发
// 调用类方法
print(p1.distanceTo(p2)); // 2.8284271247461903
}
复制代码
由于 Dart 2.12 的特性,这样写构造函数会报错:
class Point {
num x, y;
Point(x, y) { // 不可为空的实例字段 'x' 必须被初始化
// 尝试添加一个初始化表达式,或者在这个构造函数中添加一个字段初始化器,或者标记它
this.x = x;
this.y = y;
}
}
复制代码
由于空安全,Dart 不知道你是否为 x,y 分配了变量值。该写法在之前的版本没有问题,解决方法是dart - Non-nullable instance field must be initialized - Stack Overflow。
解决方法之一:
class Point {
num? x, y;
Point(x, y) {
this.x = x;
this.y = y;
}
}
复制代码
num?
表示可空类型,表示变量值可以为空。
使用命名构造函数可以为一个类实现多个构造函数, 或者使用命名构造函数使代码语义化,之前使用的 DateTime.now() 也是命名构造函数。
使用重定向构造函数,用冒号调用其他构造函数。
class Point {
num? x;
num? y;
Point.initX(y) { // 命名构造函数
this.x = 2;
this.y = y;
}
Point.redirctor(num x) : this(x, 0); // 重定向构造函数
}
main() {
Point p1 = Point.initX(2);
print('${p1.x}, ${p1.y}'); // 2, 2
Point p2 = Point.redirctor(1);
print('${p2.x}, ${p2.y}'); // 1, 0
}
复制代码
代码越来越多导致维护性越来越差,所以我们需要把类抽离成文件,在需要的地方使用import
导入库。
// lib\Point.dart
class Point {
num x = 0, y = 0;
Point(this.x, this.y);
}
// main.dart
import 'lib/Point.dart';
main(){
//...
}
复制代码
类及成员可见性
Dart 中的可见性以 library 为单位,每个Dart 文件就是一个库。
使用_
表示属性或方法的私有属性,相当于 Java 中的privite
。
class Point {
num _x = 0, _y = 0;
Point(this.x, this.y);
}
复制代码
当其被抽离成一个文件时,私有属性的作用才生效。
getter,setter
每个变量都有其默认的getter
和setter
方法,final 声明的变量只可读,只有 getter
方法。
在 Dart 中,方法不能重载。
class Rect {
late num width, height;
Rect(this.width, this.height);
area() {
return this.width * this.height;
}
// getter
get area1 {
return this.width * this.height;
}
// setter
set height2(value) {
this.height = value;
}
}
main() {
Rect r1 = Rect(1, 2);
print(r1.area()); // 2
print(r1.area1); // 2
r1.height2 = 3;
print(r1.area1); // 3
}
复制代码
初始化列表
初始化列表是在实例化之前进行的操作:
class Rect {
late num width, height;
// Rect(this.width, this.height); 不能同时使用
Rect()
: width = 2,
height = 3 {
print('in class');
print(this.width * this.height);
}
}
main() {
Rect r1 = Rect();
print('in main');
}
// 输出结果
// in class
// in main
// 6
复制代码
static 静态成员
静态变量和静态方法可以通过类来访问,而不是类的实例。
class Person {
static String name = 'Jackie';
static void show() {
print(name);
}
}
main() {
print(Person.name); //Jackie
Person.show(); // Jackie
}
复制代码
静态的方法不可以访问非静态的成员,非静态的方法可以访问静态的成员。
class Person {
static String name = 'Jackie';
static show() {
print(name);
}
showInfo() {
print(name);
show();
}
}
main() {
Person p = Person();
p.showInfo(); // Jackie Jackie
}
复制代码
继承
-
子类使用
extends
关键词来继承父类; -
子类会继承父类里除构造函数的可见的属性和方法;
-
子类能复写父类的方法。
class Person {
late String name;
late int age;
Person(this.name, this.age);
work() {
print('$name is working.');
}
}
class Web extends Person {
late String sex;
// 子类的构造函数
Web(String name, int age, String sex) : super(name, age) {
this.sex = sex;
}
// 子类可以复写父类的方法
@override // 表示覆写父类的方法,选写
work() {
print('$name is working 996.');
}
}
main() {
Web w = Web('Tom', 20, 'male');
print(w.sex); // male
w.work(); // Tom is working 996.
}
复制代码
抽象类
Dart抽象类主要用于定义标准,子类可以继承抽象类,也可以实现抽象类接口。
- 抽象类通过
abstract
关键字来定义。 - Dart 中没有方法体的方法称为抽象方法。
- 如果子类继承抽象类必须要实现里面的抽象方法。
- 如果把抽象类当做接口实现的话必须要实现抽象类里面定义的所有属性和方法。
- 抽象类不能被实例化,只有继承它的子类可以。
extends
抽象类和 implements
的区别:
- 如果要复用抽象类里面的方法,并且要用抽象方法约束自类的话我们就用
extends
继承抽象类。 - 如果只是把抽象类当做标准的话就用
implements
实现抽象类。
abstract class Person {
late String name;
late int age;
Person(this.name, this.age);
work(); // 抽象方法
}
class Employee extends Person {
Employee(String name, int age) : super(name, age);
@override
work() {
print('$name is working 996.');
}
}
class Employee2 extends Person {
Employee2(String name, int age) : super(name, age);
@override
work() {
print('$name is working 669.');
}
}
main() {
Person e = Employee('Tom', 20);
e.work(); // Tom is working 996.
// Person p = Person(); //报错,抽象类不能实例化
Person e2 = Employee2('Jerry', 20);
e2.work(); // Jerry is working 669.
}
复制代码
多态
是子类的实例赋值给父类的引用,允许将子类类型的指针赋值给父类类型的指针, 同一个函数调用会有不同的执行效果 。
多态就是父类定义一个方法,让继承他的子类去实现,每个子类有不同的表现。
上述例子中,Employee 和 Employee2 类都实现了多态。
接口
Dart 也有接口,但是和 Java 有区别。
Dart 的接口没有 interface 关键字,而是普通类或抽象类都可以作为接口使用 implements
关键字被实现。
如果实现的类是普通类,会将普通类和抽象类中的属性的方法全部需要覆写一遍.而因为抽象类可以定义抽象方法,普通类不可以,所以建议使用抽象类定义接口。
abstract class Db {
late String uri;
add(String data);
}
class Mysql implements Db {
@override
late String uri;
Mysql(this.uri);
@override
add(String data) {
print('This is Mysql. ' + '$data');
}
}
main() {
Mysql m = Mysql('xxx');
m.add('123');
}
复制代码
接口抽离成不同的文件:
// lib\Db.dart
abstract class Db {...}
// lib\Mysql.dart
import 'lib/Db.dart';
class Mysql implements Db {...}
// main.dart
import 'lib/Mysql.dart';
main() {...}
复制代码
一个类可以实现多个接口
class C implements A, B {
// A, B 接口所有的属性和方法
}
复制代码
Mixins
Class 中无法实现多继承,Mixins 不同于继承和接口,可以实现类似多继承的功能:
class C extends A, B {} // 报错
class C with A, B {...} // √
复制代码
- 作为 Mixins 的类只能继承自Object,不能继承其他类。
- 作为 Mixins 的类不能有构造函数。
- 一个类可以 Mixins 多个 Mixins 类。
- Mixins 绝不是继承,也不是接口,而是一种全新的特性。
class A {...}
class B extends A {...}
class C {...}
class D with B, c {...} // 报错
class D extends A with B, C {...} // √
复制代码
后写的类会覆盖掉前面的类:
class C with A, B {...} // B 的方法会覆盖掉 A
class D extends A with B, C {...} // B 的方法会覆盖掉 A,
// C 的方法会覆盖掉 A 和 B
复制代码
泛型
泛型对不特定数据类型的支持使方法、类、接口具有复用性。
泛型方法
T getData<T>(T value) {
return value;
}
main() {
print(getData<num>(123)); // 123
print(getData<String>('Jack')); // Jack
}
复制代码
泛型类
// 调用泛型类
List list = List.filled(2, '');
list[0] = 'Tom';
list[1] = 12;
print(list); // [Tom, 12]
// 要求传入的数据是 String 类型
List list1 = List<String>.filled(2, '');
list1[0] = 'Tom';
list1[1] = 'Jerry';
print(list1); // [Tom, Jerry]
// 定义泛型类
class MyList<T> {
List list = <T>[];
add(T value) {
this.list.add(value);
}
List getList() {
return list;
}
}
复制代码
泛型接口
abstract class Cache<T> {
getByKey(String key);
setByKey(String key, T value);
}
class MemoryCache<T> implements Cache<T> {
@override
getByKey(String key) {
return null;
}
@override
setByKey(String key, T value) {
print("MemoryCache key=${key} value=${value}");
}
}
void main() {
MemoryCache mc = MemoryCache<Map>();
mc.setByKey('index', {"name": "Jack", "age": 20}); // MemoryCache key=index value={name: Jack, age: 20}
}
复制代码
类库
Dart中的库主要有三种:
- 我们自定义的库
import 'lib/class.dart';
复制代码
- 系统内置库
import 'dart:math';
复制代码
- Pub 包管理系统中的库
pub.dev/packages
pub.flutter-io.cn/packages
pub.dartlang.org/flutter
- 需要在自己想项目根目录新建一个 pubspec.yaml
- 在 pubspec.yaml 文件配置名称、描述、依赖等信息
- 在控制台运行
pub get
获取包下载到本地 - 项目中引入库
import 'package:http/http.dart' as http;
引入库的时候使用as
来重命名防止名称重复:
import 'package:http/http.dart' as http;
复制代码
如果你只需要使用库中的部分功能,可以使用show
显示或hide
隐藏部分方法来部分导入:
import 'dart:math' show max;
复制代码
Dart 2.12
Null Safety 空安全
Null safety 可以帮助开发者避免一些日常开发中很难被发现的错误,并且可以改善性能。
int a = 123;
a = null; // 报错,Null 类型的值不能赋值给 int 类型的变量
复制代码
类型后加?
表示改变量是可空类型:
int? a = 123;
a = null;
List<int>? l=[1, 2, 3];
l=null;
print(l); // null
String? getData(value) {
if (value != null) {
return 'This is a string.';
}
return null;
}
复制代码
类型断言 !
用于判断变量是否为空,若为空则抛出异常。在下面的例子中,由于 str 为空,所以抛出异常:
void main() {
String? str = 'String';
str = null;
print(str!.length); // Null check operator used on a null value
}
复制代码
void printLength(String? str) {
try {
print(str!.length);
} catch (e) {
print("str is null");
}
}
void main() {
printLength(null); // str is null
}
复制代码
late
关键字
late
关键字用于延迟初始化。下面的类中没有构造函数,如果没有late
关键字就会报错,加上late
关键字即可:
class Person {
late String name;
void setInfo(String name) {
this.name = name;
}
String getInfo() {
return "${this.name}";
}
}
void main(args) {
Person p = Person();
p.setInfo("Jack");
print(p.getInfo()); // Jack
}
复制代码
required
关键字
required
作为内置修饰符,主要用于允许根据需要标记任何命名参数(函数或类),使其不为空。可选参数必须要有 required 或默认值。
String printInfo(String name, {int age = 20, String sex = "male"}) {
// ...
}
String printInfo2(String name, {required int age, required String sex}) {
// ...
}
复制代码