第4节:Dart语言必备基础知识

[TOC]

前言

如果你只是写界面可能你看Flutter的Widget就够了。

可是当你写业务的时候,你不去了解Dart语言那么你讲寸步难行。

本文致力于以最小的篇幅介绍Flutter的敲门砖:Dart最必备的基础知识。

  • 在Dart中,一切都是对象,一切对象都是class的实例,哪怕是数字类型、方法甚至null都是对象,所有的对象都是继承自Object

  • 跟Java不同的是,Dart没有public protected private等关键字,如果某个变量以下划线(_)开头,代表这个变量在库中是私有的

一、变量

1、变量定义

1
2
3
4
int b = 10;
String s = "hello";
var a = 1;
dynamic c = 0.5;

可以明确指定某个变量的类型,如int bool String,也可以用var或 dynamic来声明一个变量,Dart会自动推断其数据类型。

dynamic 则是告诉编译器,我们知道自己在做什么,不用做类型检测

2、变量类型检测

为了在运行时检测进行类型检测,Dart 提供了一个关键字 is:

1
2
3
4
5
6
7
8
dynamic obj = {};
if (obj is Map) {
// 进过类型判断后,Dart 知道 obj 是一个 Map,所以这里再不用强制转换 obj 的类型为Map。
obj['foo'] = 42;
}

// 虽然 Dart 也提供了 as 让我们进行类型的强制转换,但为了进来更安全的转换,更推荐使用 is
var map = obj as Map;

3、类型互转

3.1 String与int互转

1
2
int.parse("100");
123.toString();

二、函数

1、函数的写法

以下三种函数的写法,都是正确的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 写法1:声明返回值(推荐)
int add(int a, int b) {
return a + b;
}

// 写法2:不声明返回值(不加返回值的函数同样可以正常工作)
add2(int a, int b) {
return a + b;
}

// 写法3:你还可以用`=>`代替return语句(=>是return语句的简写)
// 此写法只能执行单行
add3(a, b) => a + b;

main() {
print(add(1, 2)); // 3
print(add2(2, 3)); // 5
print(add3(1, 2)); // 3
}

所有的函数都有返回值,如果没有指定return语句,那么该函数的返回值为null。

1.1、类方法/实例方法

1
2
3
4
5
6
7
8
9
Class A {
int method1(int x); // 实例方法

static int method2(int y); // 类方法
}

// 调用
a = A().method1(1);
b = A.method2(2);

2、函数的参数(可选/默认值/必选)

2.1、基本的函数参数写法

1
2
3
4
5
6
7
8
void main() {
print(defalut_foo1(1, 2)); // 结果为3
}

// 这种常见的写法,参数默认都是必填的,而且没法给默认值
int defalut_foo1(int x, int y) {
return x + y;
}

2.2、标记参数为可选/默认值

知识点:

①要设置参数的默认值,只有该参数为可选的时候,才能够设置。

②参数可选的方法有两种,常规的参数加上[],或者使用具名参数

1、常规的参数加上[],标记为可选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void main() {
print(option_foo1(1)); // 结果为1, x
print(option_foo1(1, 1)); // 结果为11, x+10y,而不会是x+100z
print(option_foo1(1, 1, 1)); // 结果为111, x+10y+100z
}

// 可选参数
// 使用可选参数的函数写法1:含可选参数的函数
int option_foo1(int x, [int y, int z]) {
int iResult = x;
if (y != null) { // 是的,int 也可以是 null
iResult += 10*y;
}

if (z != null) { // 是的,int 也可以是 null
iResult += 100*z;
}

return iResult;
}

// 使用可选参数的函数写法2:可选参数支持有默认值
int option_foo2(int x, [int y = 0]) {
return x + y;
}

2、使用具名参数(named parameters),使得参数为可选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 具名参数(named parameters)的用法,调用时候具名参数的顺序可以是任意的
// 使用具名参数的函数参数写法1:
int named_foo1({int x, int y}) {
return x + y;
}


// 使用具名参数的函数参数写法2:所有的具名参数本身就都是可选的,而且具名参数也可以有默认参数
int named_foo2({int x = 0, int y = 0}) {
return x + y;
}

void main() {
// 具名参数的顺序可以是任意的
print(named_foo1(x: 1, y: 2));
print(named_foo1(y: 3, x: 4));
print(named_foo1()); //错误:会导致 foo() 在运行时抛异常
print(named_foo2(x: 1, y: 2));
print(named_foo2()); // 正确:不会导致 foo() 在运行时抛异常
}

2.3、如果想告诉用户某个参数是必须的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 必须参数(如果想告诉用户某个具名参数是必须的,可以使用注解 @require)
// 不是具名参数,默认就是 @required 必填。
int require_foo1(@required int x, @required int y) {
return x + y;
}

// 是具名参数,默认就是 非必填,非 @required
int require_foo2({@required int x, @required int y}) {
return x + y;
}

void main() {
print(require_foo1(1, 2)); // 结果为3

print(require_foo2(x:1, y:2)); // 结果为3
}

3、回调/函数做参数/返回值

3.1、回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 用typedef用于定义函数类型的别名
typedef ClickCallBack = int Function(int x, int y);

// 实例:将a和b两个数,经过指定的操作返回
String onButtonPress1(int a, int b, ClickCallBack callback) {
int iResult = callback(a, b);
return iResult.toString();
}

void main() {
String string = onButtonPress1(1, 2, (x, y) {
return x + y;
});
print("string = " + string);
}

3.2、函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 用typedef用于定义函数类型的别名
typedef Adder = int Function(int, int);

String Function(String msg) cancelHandle;// 实际场景中的应用

Adder makeAdder1(int extra) {
int adder(int x, int y) {
return x + y + extra;
}
return adder;
}

// Dart 里面不仅变量支持类型推断,lambda 的参数也支持自动推断。上面的代码还可以进一步简化为:
Adder makeAdder2(int extra) {
// 我们要返回的类型是 Adder,所以 Dart 知道 x, y 都是 int
return (x, y) => x + y + extra;
}

void main() {
var adder1 = makeAdder1(2);
print(adder1(1, 2));

var adder2 = makeAdder2(2);
print(adder2(1, 2));
}

4、函数类型

Dart是一个面向对象的编程语言,所以即使是函数也是一个对象,也有一种类型Function,这就意味着函数可以赋值给某个变量或者作为参数传给另外的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
printNum(int a) {
print("$a");
}

test(Function callback) {
callback("hello");
}


main() {
var f1 = printNum;
Function f2 = printNum;
var f3 = (int a) => print("a = $a");
f1(1);
f2(2);
f3(6);
}

三、类

1、类的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class User {
// 成员变量写法1:
String name;
int age;
String gender;
// 成员变量写法2:使用getter/setter声明的成员变量
String get uid => "24210853532539238";
set right(String uid) => left = value - width;

// 通过创建一个与其类同名的函数来声明构造函数。如果您未声明构造函数,则会为您提供默认构造函数。默认构造函数没有参数,并在超类中调用无参数构造函数。
// 构造方法写法1:跟类名相同的构造方法
User(this.name, this.age, this.gender);
// 构造方法写法2:命名的构造方法
User.defaultUser() {
name = "dvlp";
age = 20;
}

// 成员方法写法1:有方法体(是非抽象方法)
sayHello() {
print("hello, this is $name, I am $age years old, I am a $gender");
}
// 成员方法写法2:没有方法体(是抽象方法,需要子类去实现)
void doSomething();
}

// 抽象类(无法实例化的类),如果希望抽象类看起来是可实例化的,请定义工厂构造函数。
// 抽象类通常有抽象方法,如下:
// 此类声明为abstract,因此是抽象类,即也就无法实例化
abstract class Test {
//定义构造函数,字段,方法...

// 抽象方法
void test();
}

说明类中的构造方法Person(this.name, this.age, this.gender);的这种语法是Dart比较独特而简洁的构造方法声明方式,它等同于下面的代码:

1
2
3
4
5
User(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}

其他声明成员变量的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用getter/setter声明的成员变量
class Rectangle {
num left, top, width, height;

// 构造方法传入left, top, width, height几个参数
Rectangle(this.left, this.top, this.width, this.height);

// right, bottom两个成员变量提供getter/setter方法
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}

2、类的使用

1
2
3
4
5
6
7
8
main() {
var user1 = new Person("zhangsan", 20, "male");
user.age = 30;
user.gender = "female";
user.sayHello();

var user2 = new Person.defaultUser()
}

当我们调用一个不存在的方法时,会执行 noSuchMethod() 方法,默认情况下它会抛出 NoSuchMethodError。你也可以重写noSuchMethod(),改成你输出你希望的错误提示

1
2
3
4
5
6
7
8
9
10
class Test {
// 除非你重写noSuchMethod,否则使用不存在的成员会导致NoSuchMethodError
// 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}');
}
}

3、类的继承

Dart中使用extends关键字做类的继承。

1
2
3
4
5
6
class Developer extends User {
// 实现了父类的抽象方法
void doSomething() {
print("I'm developing...");
}
}

虽然 Dart 是单继承的,但它也提供了一定程度的多重继承支持:

使用@override注解声明你要重写的函数,在这个函数内部可以使用super调用重写的这个父类的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class FlutterDeveloper extends Developer {
@override // 表示doSomething是重写父类的方法
void doSomething() {
super.doSomething();
print("I'm developing Flutter...");
}
}

class iOSDeveloper extends Developer {
@override // 表示doSomething是重写父类的方法
void doSomething() {
super.doSomething();
print("I'm developing iOS...");
}
}

class FullStackDeveloper extends Developer with iOSDeveloper with FlutterDeveloper {
@override // 表示doSomething是重写父类的方法
void doSomething() {
super.doSomething();
print("I'm developing...");
}
}

mixins

mixins是一个重复使用类中代码的方式,比如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A {
a() {
print("A's a()");
}
}

class B {
b() {
print("B's b()");
}
}

// 使用with关键字,表示类C是由类A和类B混合而构成
class C = A with B;

main() {
C c = new C();
c.a(); // A's a()
c.b(); // B's b()
}

四、库

1
2
3
4
5
6
7
8
9
// 用import语句来导入某个包
// 如果你想导入自己写的某个代码文件,使用相对路径即可
import './util.dart';

// 你可以使用as关键字为导入的某个包设置一个前缀,或者说别名
import 'package:lib2/lib2.dart' as lib2;

// 导入包时使用deferred as可以让这个包懒加载,懒加载的包只会在该包被使用时得到加载,而不是一开始就加载,比如
import 'package:greetings/hello.dart' deferred as hello;

1、问题一:在同一工程中可否有两个同名的类

1
2
3
4
5
6
7
8
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();

2、问题二、如何只允许/只不允许使用某个包中的部分功能

1
2
3
4
5
// 只导入lib1中的foo功能
import 'package:lib1/lib1.dart' show foo;

// 导入lib2中的除了foo外的所有其他部分
import 'package:lib2/lib2.dart' hide foo;

五、运算符

1、常见的

运算符 含义
++var var=var+1表达式的值为var+1
var++ var=var+1表达式的值为var
--var var=var-1表达式的值为var-1
var-- var=var-1表达式的值为var

++a

a == b

b ? a : b

2、特殊的

2.1、..运算符(级联操作)

使用..调用某个对象的方法(或者成员变量)时,返回值是这个对象本身,所以你可以接着使用..调用这个对象的其他方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
eat() {
print("I am eating...");
}

sleep() {
print("I am sleeping...");
}

study() {
print("I am studying...");
}
}

main() {
// 依次打印
// I am eating...
// I am sleeping...
// I am studying...
new Person()..eat()
..sleep()
..study();
}

六、控制流程

语句 说明
if / else
switch 略(switch 也支持 String 和 enum)
for /while
try / catch
1
2
3
4
5
6
7
8
9
10
11
12
13
// switch语句
String a = "hello";
// case语句中的数据类型必须是跟switch中的类型一致
switch (a) {
case "hello":
print("haha");
break;
case "world":
print("heihei");
break;
default:
print("WTF");
}

七、异步

如果一个方法中有耗时的操作,你需要将这个方法设置成async,并给其中的耗时操作加上await关键字,如果这个方法有返回值,你需要将返回值塞到Future中并返回,如下代码所示:

1
2
3
4
Future<String> checkVersion() async {
var version = await lookUpVersion();
return version;
}

八、其他/奇技淫巧

1、静态成员变量和静态成员方法

1
2
3
4
5
6
7
8
9
10
11
12
// 类的静态成员变量和静态成员方法
class Cons {
static const name = "zhangsan";
static sayHello() {
print("hello, this is ${Cons.name}");
}
}

main() {
Cons.sayHello(); // hello, this is zhangsan
print(Cons.name); // zhangsan
}

2、修饰符final和const

final 跟 Java 里的 final 一样,表示一个运行时常量(在程序运行的时候赋值,赋值后值不再改变)。const 表示一个编译时常量,在程序编译的时候它的值就确定了。

修饰符 作用
final 一个被final修饰的变量只能被赋值一次,
const 一个被const修饰的变量是一个编译时常量(const常量毫无疑问也是final常量)

final和const的区别:

区别一:final 要求变量只能初始化一次,并不要求赋的值一定是编译时常量,可以是常量也可以不是。而 const 要求在声明时初始化,并且赋值必需为编译时常量。

区别二:final 是惰性初始化,即在运行时第一次使用前才初始化。而 const 是在编译时就确定值了。

3、泛型

1
var names = List<String>();

其他参考文档: