Dart 作为 Flutter 的开发语言,可以说,Dart 是 Flutter 的一部分,Google 的目标是想让 Flutter 兼容全平台的开发,一统天下,这也是 Google 的野心,不管最终是否成行,那 Flutter 的流行必然带动 Dart 。
一、Dart 语言概述
1.1 Dart 是什么
Dart 语言是由 Google 开发的网络编程语言,于 2011 年 10 月 10 日发布第一个里程碑版本 M1。其诞生的目的是为了让广大 C 类 OOP 程序员们克服 JavaScript 那”复杂”的语言特性,Dart 刚推出的时候,定位是替代 JS 做前端开发,后来逐步扩展到移动端和服务端。
1.2 Dart 特点
- 在 Dart 中,一切都是对象,一切对象都是class的实例,哪怕是数字类型、方法甚至 null 都是对象,所有的对象都是继承自 Object
- 虽然Dart是强类型语言,但变量类型是可选的因为Dart可以自动推断变量类型
- Dart 支持范型,List <int >表示一个整型的数据列表,List <dynamic >则是一个对象的列表,其中可以装任意对象
- Dart 支持顶层方法(如 main 方法),也支持类方法或对象方法,同时也可以在方法内部创建方法
- Dart 支持顶层变量,也支持类变量或对象变量
- Dart 没有 public protected private 等关键字,如果某个变量以下划线(_)开头,代表这个变量在库中是私有的
- Dart 中变量可以以字母或下划线开头,后面跟着任意组合的字符或数字
1.3 Dart 能做什么
Dart 是 Flutter 的开发语言,Flutter 必须遵循 Dart 的语言特性。
二、关键字 变量 及数据类型
2.1 关键字
关键字表中的所有其他词都是保留词。不能使用保留词作为标识符。
- 带有上 标1 的单词是 内置标识符。避免使用内置标识符作为标识符。如果尝试为类或类型名使用内置标识符,则会发生编译时错误。
- 使用上 标2 的单词是在 Dart 的 1.0 版本之后添加的与异步支持相关的更新的、有限的保留字。在以 async, async*, 或者 sync* 标记的任何函数体中,不能使用 async, await, 或者 yield 作为标识符。
2.2 变量
变量 var,常量 final 或 const来
以下代码是 Dart 中定义变量的方法:
main() {
var a = 1;
int b = 10;
String name = "hello dart";
dynamic c = 0.8;
}
可以明确指定某个变量的类型,如 int bool String,也可以用 var 或 dynamic 来声明一个变量,Dart 会自动推断其数据类型。
变量的默认值
注意:没有赋初值的变量都会有默认值null
final & const
如果不想改变一个变量,使用 final 或 const,不要使用 var 或其他类型,一个被 final 修饰的变量只能被赋值一次,一个被 const 修饰的变量是一个编译时常量(const 常量毫无疑问也是 final 常量)。可以这么理解:final 修饰的变量是不可改变的,而 const 修饰的表示一个常量。
注意:实例变量可以是 fina l的但不能是 const 。
下面用代码说明:
var count = 10;
final Num = count; // final 只能赋值一次
const Num1 = 10; // const赋值必须是编译时常量
final 和 const 的区别:
- 区别一:final 要求变量只能初始化一次,并不要求赋的值一定是编译时常量,可以是常量也可以不是。而 const 要求在声明时初始化,并且赋值必需为编译时常量。
- 区别二:final 是惰性初始化,即在运行时第一次使用前才初始化。而 const 是在编译时就确定值了。
2.3 内建数据类型
Dart有如下几种内建的数据类型:
- numbers
- strings
- booleans
- lists(或者是arrays)
- maps
- runes(UTF-32字符集的字符)
- symbols
下面用一段代码来演示以上各类数据类型:
main() {
// numbers
var a = 0;
int b = 1;
double c = 0.1;
// strings
var s1 = 'hello ';
String s2 = "dart";
String s3 = s1 + s2;
// booleans
var real = true;
bool isReal = false;
// lists
var arr = [1, 2, 3, 4, 5];
List<String> arr2 = ['hello', 'world', "123", "456"];
List<dynamic> arr3 = [1, true, 'haha', 1.0];
// maps
var map = new Map();
map['name'] = 'zhangsan';
map['age'] = 10;
Map m = new Map();
m['a'] = 'a';
//runes,Dart 中 使用runes 来获取UTF-32字符集的字符。String的 codeUnitAt and codeUnit属性可以获取UTF-16字符集的字符
var clapping = '\u{1f44f}';
print(clapping); // 打印的是拍手emoji的表情
// symbols
print(#s == new Symbol("s")); // true
}
三、函数
Dart 是一种真正的面向对象语言,所以即使函数也是对象,具有类型和功能。这意味着函数可以分配给变量或作为参数传递给其他函数。
3.1 函数的返回值
所有的函数都有返回值,如果没有指定 return 语句,那么该函数的返回值为 null。虽然 Dart 推荐给函数加上返回值,但是不加返回值的函数同样可以正常工作,另外你还可以用 => 代替 return 语句,比如下面的代码:
// 声明返回值
int add(int a, int b) {
return a + b;
}
// 不声明返回值
add2(int a, int b) {
return a + b;
}
// =>是return语句的简写
add3(a, b) => a + b;
main() {
print(add(1, 2)); // 3
print(add2(2, 3)); // 5
print(add3(1, 2)); // 3
}
3.2 命名参数
定义命名参数时,可以以 {type paramName} 或者 {paramName: type} 两种方式声明参数,而调用命名参数时,需要以 funcName(paramName: paramValue) 的形式调用。
sayHello({String name}) {
print("hello, my name is $name");
}
sayHello2({name: String}) {
print("hello, my name is $name");
}
main() {
// 打印 hello, my name is zhangsan
sayHello(name: 'zhangsan');
// 打印 hello, my name is wangwu
sayHello2(name: 'wangwu');
}
命名参数的参数并不是必须的,所以上面的代码中,如果调用 sayHello() 不带任何参数,也是可以的,只不过最后打印出来的结果是:hello, my name is null.
在 Flutter 开发中,可以使用 @required 注解来标识一个命名参数,这代表该参数是必须的,不传则会报错,比如下面的代码:
const Scrollbar({Key key, @required Widget child})
3.3 位置参数
使用中括号 [] 括起来的参数是函数的位置参数,代表该参数可传可不传,位置参数只能放在函数的参数列表的最后面,如下代码所示:
sayHello(String name, int age, [String hobby]) { // 位置参数可以有多个,比如[String a, int b]
StringBuffer sb = new StringBuffer();
sb.write("hello, this is $name and I am $age years old");
if (hobby != null) {
sb.write(", my hobby is $hobby");
}
print(sb.toString());
}
main() {
// hello, this is zhangsan and I am 20 years old
sayHello("zhangsan", 20);
// hello, this is zhangsan and I am 20 years old, my hobby is play football
sayHello("zhangsan", 20, "play football");
}
3.4 参数默认值
可以为命名参数或者位置参数设置默认值,如下代码所示:
// 命名参数的默认值
int add({int a, int b = 3}) { // 不能写成:int add({a: int, b: int = 3})
return a + b;
}
// 位置参数的默认值
int sum(int a, int b, [int c = 3]) {
return a + b + c;
}
3.5 main() 函数
不论在 Dart 还是Flutter中,必须都需要一个顶层的 main() 函数,它是整个应用的入口函数,main() 函数的返回值是 void,还有一个可选的参数,参数类型是 List < String > 。
函数作为一类对象,可以将一个函数作为参数传给另一个函数,比如下面的代码:
printNum(int a) {
print("$a");
}
main() {
// 依次打印:
// 1
// 2
// 3
var arr = [1, 2, 3];
arr.forEach(printNum);
}
你也可以将一个函数赋值给某个变量,比如下面的代码:
printNum(int a) {
print("$a");
}
main() {
var f1 = printNum;
Function f2 = printNum;
var f3 = (int a) => print("a = $a");
f1(1);
f2(2);
f3(6);
}
3.6 匿名函数
大多数函数都是有名称的,比如 main() printName() 等,但是也可以写匿名函数,如果对 Java 比较熟悉,那下面的 Dart 代码肯定也不会陌生:
test(Function callback) {
callback("hello");
}
main() {
test((param) {
// 打印hello
print(param);
});
}
匿名函数类似于 Java 中的接口,往往在某个函数的参数为函数时使用到。
四、运算符
Dart 中的运算符与 Java 中的类似,比如:
++a a == b
c ? a : b
但是也有一些与 Java 不太一样的运算符,下面用代码说明:
main() {
// 与Java相同的运算符操作
int a = 1;
++a;
a++;
var b = 1;
print(a == b); // false
print(a * b); // 3
bool real = false;
real ? print('real') : print('not real'); // not real
print(real && a == b); // false
print(real || a == 3); // true
print(a != 2); // true
print(a <= b); // false
var c = 9;
c += 10;
print("c = $c"); // c = 19
print(1<<2); // 4
// 与Java不太一样的运算符操作
// is运算符用于判断一个变量是不是某个类型的数据
// is!则是判断变量不是某个类型的数据
var s = "hello";
print(s is String); // true
var num = 6;
print(num is! String); // true
// ~/才是取整运算符,如果使用/则是除法运算,不取整
int k = 1;
int j = 2;
print(k / j); // 0.5
print(k ~/ j); // 0
// as运算符类似于Java中的cast操作,将一个对象强制类型转换
(emp as Person).teach();
// ??=运算符 如果 ??= 运算符前面的变量为null,则赋值,否则不赋值
var param1 = "hello", param2 = null;
param1 ??= "world";
param2 ??= "world";
print("param1 = $param1"); // param1 = hello
print("param2 = $param2"); // param2 = world
// ?.运算符
var str1 = "hello world";
var str2 = null;
print(str1?.length); // 11
print(str2?.length); // null
print(str2.length); // 报错
}
..运算符(级联操作)
如果对 Java 中的建造者模式比较熟悉的话,Dart 中的 .. 运算符也很好理解,先看下面的代码:
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();
}
可以看到,使用 .. 调用某个对象的方法(或者成员变量)时,返回值是这个对象本身,所以你可以接着使用 .. 调用这个对象的其他方法,这不就类似于 Java 中的建造者模式,每次 build 某个属性时,都返回一个 this 对象。
五、流程控制语言
流程控制在大部分语言中大同小异,在 Dart 中只有最后一个 assert 比较特殊,其实 assert 在 C 语言中有使用。
可以使用以下任何一种方法来控制 Dart 代码的流:
- if 和 else
- for 循环
- while 和 do-while循环
- break 和 continue
- switch 和 case
- assert
也可以使用 try-catch 和 throw 来对控制流程作出改变。
if / else switch for /while try / catch 语句跟 Java 中都类似,try / catch 语句可能稍有不同,下面用一段代码说明:
main() {
// if else语句
int score = 80;
if (score < 60) {
print("so bad!");
} else if (score >= 60 && score < 80) {
print("just so so!");
} else if (score >= 80) {
print("good job!");
}
// switch语句
String a = "hello";
// case语句中的数据类型必须是跟switch中的类型一致
switch (a) {
case "hello":
print("haha");
break;
case "world":
print("heihei");
break;
default:
print("WTF");
}
// for语句
List<String> list = ["a", "b", "c"];
for (int i = 0; i < list.length; i++) {
print(list[i]);
}
for (var i in list) {
print(i);
}
// 这里的箭头函数参数必须用圆括号扩起来
list.forEach((item) => print(item));
// while语句
int start = 1;
int sum = 0;
while (start <= 100) {
sum += start;
start++;
}
print(sum);
// try catch语句
try {
print(1 ~/ 0);
} catch (e) {
// IntegerDivisionByZeroException
print(e);
}
try{
var str = null;
assert(str != null);
} catch(e){
print('捕获错误Error: $e');
} finally {
print('最终执行');
}
try {
1 ~/ 0;
} on IntegerDivisionByZeroException { // 捕获指定类型的异常
print("error"); // 打印出error
} finally {
print("over"); // 打印出over
}
}
六、面向对象
Dart 是一种面向对象的语言,具有类和基于 mixin 的继承。
6.1 类的定义与构造方法
Dart 中的类没有访问控制,所以不需要用 private, protected, public 等修饰成员变量或成员函数,一个简单的类如下代码所示:
class Person {
String name;
int age;
String gender;
Person(this.name, this.age, this.gender);
sayHello() {
print("hello, this is $name, I am $age years old, I am a $gender");
}
}
上面的 Person 类中有 3 个成员变量,一个构造方法和一个成员方法,看起来比较奇怪的是 Person 的构造方法,里面传入的 3 个参数都是 this.xxx,而且没有大括号 {} 包裹的方法体,这种语法是 Dart 比较独特而简洁的构造方法声明方式,它等同于下面的代码:
Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
要调用 Person 类的成员变量或成员方法,可以用下面的代码:
var p = new Person("zhangsan", 20, "male");
p.sayHello(); // hello, this is zhangsan, I am 20 years old, I am a male
p.age = 50;
p.gender = "female";
p.sayHello(); // hello, this is zhangsan, I am 50 years old, I am a female
类除了有跟类名相同的构造方法外,还可以添加命名的构造方法,如下代码所示:
class Point {
num x, y;
Point(this.x, this.y);
// 类的命名构造方法
Point.origin() {
x = 0;
y = 0;
}
}
main() {
// 调用Point类的命名构造方法origin()
var p = new Point.origin();
var p2 = new Point(1, 2);
}
Dart 中使用 extends 关键字做类的继承,如果一个类只有命名的构造方法,在继承时需要注意,如下代码:
class Human {
String name;
Human.fromJson(Map data) {
print("Human's fromJson constructor");
}
}
class Man extends Human {
Man.fromJson(Map data) : super.fromJson(data) {
print("Man's fromJson constructor");
}
}
由于 Human 类没有默认构造方法,只有一个命名构造方法 fromJson,所以在 Man 类继承 Human 类时,需要调用父类的 fromJson 方法做初始化,而且必须使用 Man.fromJson(Map data) : super.fromJson(data) 这种写法,而不是像 Java 那样将 super 写到花括号中。
有时候仅仅只是在某个类的构造方法中,调用这个类的另一个构造方法,可以这么写:
class Point {
num x, y;
Point(this.x, this.y);
// 命名构造方法调用了默认的构造方法
Point.alongXAxis(num x) : this(x, 0);
}
6.2 类的成员方法
一个类的成员方法是一个函数,为这个类提供某些行为。上面的代码中已经有了一些类的成员方法的定义,这些定义方式跟 Java 很类似,可以为某个类的成员变量提供 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;
}
6.3 抽象类和抽象方法
使用 abstract 修饰一个类,则这个类是抽象类,抽象类中可以有抽象方法和非抽象方法,抽象方法没有方法体,需要子类去实现,如下代码:
abstract class Doer {
// 抽象方法,没有方法体,需要子类去实现
void doSomething();
// 普通的方法
void greet() {
print("hello world!");
}
}
class EffectiveDoer extends Doer {
// 实现了父类的抽象方法
void doSomething() {
print("I'm doing something...");
}
}
七、泛型
Java 和 C++ 语言都有泛型,Dart 语言也不例外,使用泛型有很多好处,比如:
- 正确指定泛型类型会产生更好的生成代码
- 泛型可以减小代码的复杂度
Dart内置的数据类型List < E > 就是一个泛型数据类型,你可以往 List 中塞任何你想的数据类型比如整型、字符串、布尔值等。根据约定,类型变量具有单字母名称,如E、T、S、K和V。
- E 代表 Element 元素
- T 代表 Type 类型
- K 代表 Key 键
- V 代表 Value 值
main(){
var names = <String>['张三','李四','王五'];
var pages = <String,String>{
'index.html':'Homepage',
'about.html':'About',
};
var veiws = Map<int,Object>();
var lists = List<String>();
lists.addAll(['张三','李四','王五']);
// lists.add(20);//报错
}
//Object缓存
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
//String缓存
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
//范型写法
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
八、异步处理
Dart 语言是目前少数几个支持异步操作的语言,一般使用 async 函数和 await 表达式实现异步操作。Dart 提供了类似 ES7 中的 async await 等异步操作,这种异步操作在 Flutter 开发中会经常遇到,比如网络或其他 IO 操作,文件选择等都需要用到异步的知识。
async 和 await 往往是成对出现的,如果一个方法中有耗时的操作,你需要将这个方法设置成 async,并给其中的耗时操作加上 await 关键字,如果这个方法有返回值,你需要将返回值塞到 Future 中并返回,如下代码所示:
Future checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
下面的代码使用 Dart 从网络获取数据并打印出来:
import 'dart:async';
import 'package:http/http.dart' as http;
Future<String> getNetData() async{
http.Response res = await http.get("http://www.baidu.com");
return res.body;
}
main() {
getNetData().then((str) {
print(str);
});
}
Dart库充满了返回 Future 或 Stream 对象的函数。这些功能是异步的:它们在设置可能耗时的操作(例如 I/O)之后返回,而不等待该操作完成。 在 async 和 await 关键字支持异步编程,让你写异步代码看起来类似于同步代码。
使用 async/await 之前需要引入 dart:async 模块。
import 'dart:async';
Future<String> lookUpVersion() async => '1.0.0';
Future main() async {
await for (var request in requestServer) {
handleRequest(request);
}
}
九、类型定义
在 Dart 中,函数是对象,就像字符串和数字是对象一样。typedef 或 function-type 为函数提供一个类型别名,可以在声明字段和返回类型时使用这个名称。当函数类型被分配给变量时,typedef 保留类型信息。
以下代码不使用 typedef:
class SortedCollection {
Function compare;
SortedCollection(int f(Object a, Object b)) {
compare = f;
}
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
// All we know is that compare is a function,
// but what type of function?
assert(coll.compare is Function);
}
当给 compare 分配f时类型信息会丢失。f 的类型是(Object, Object)->int(int表示返回值类型),当然 compare 的类型是 Function。如果我们更改代码以使用显式名称和保留类型信息,开发人员和工具都可以使用这些信息。
typedef Compare = int Function(Object a, Object b);
class SortedCollection {
Compare compare;
SortedCollection(this.compare);
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
assert(coll.compare is Function);
assert(coll.compare is Compare);
}
注意:目前,typedefs 仅限于函数类型。我们期望这种情况会改变。
因为 typedef 仅仅是别名,所以它们提供了一种检查任何函数类型的方法。例如:
typedef Compare<T> = int Function(T a, T b);
int sort(int a, int b) => a - b;
void main() {
assert(sort is Compare<int>); // True!
}
十、异常处理
Dart 代码可以抛出和捕获异常。异常是指程序执行中发生的意料之外的错误。如果没有捕获异常,引发异常的隔离程序将被挂起,通常隔离程序及其程序将被终止。
与 Java 相反,Dart 的所有异常都是未检查的异常。方法不声明它们可能抛出哪些异常,也不要求您捕获任何异常。
Dart 提供 Exception 和 Error 类型,以及许多预定义的子类型。当然可以自己定义异常。但是,Dart 程序可以抛出任何非空对象不仅仅是异常和错误对象。
Throw
throw FormatException('Expected at least 1 section'); //你也可以抛出任意对象:
throw 'Out of llamas!'; //注意:在正式使用中通常抛出的是实现了Error或Exception类型的对象;
因为抛出异常是一个 表达式 ,您可以在=>语句中抛出异常,也可以在任何允许表达式的地方抛出异常:
void distanceTo(Point other) => throw UnimplementedError();
Catch
捕获异常将阻止异常传播(除非重新抛出异常)。捕获异常后我们可以去判断并处理相应的异常。
try {
breedMoreLlamas();
} on OutOfLlamasException {
buyMoreLlamas();
}
要处理可以抛出多种异常类型的代码,可以指定多个 catch 子句。与抛出对象的类型匹配的第一个 catch 子句处理异常。如果 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');
}
正如前面的代码所示,可以使用 on 或 catch 或 both。在需要指定异常类型时使用。当您的异常处理程序需要异常对象时,请使用 catch。
可以指定 catch() 的一个或两个参数。第一个是抛出的异常,第二个是堆栈跟踪(StackTrace对象)。
try {
// ···
} on Exception catch (e) {
print('Exception details:\n $e');
} catch (e, s) {
print('Exception details:\n $e');
print('Stack trace:\n $s');
}
要在捕获中处理异常,同时允许其继续传播,请使用 rethrow 关键字。
void misbehave() {
try {
dynamic foo = true;
print(foo++); // Runtime error
} catch (e) {
print('misbehave() partially handled ${e.runtimeType}.');
rethrow; // Allow callers to see the exception.
}
}
void main() {
try {
misbehave();
} catch (e) {
print('main() finished handling ${e.runtimeType}.');
}
}
Finally
要确保在抛出异常时运行某些业务代码,请使用 finally 子句。如果没有 catch 子句匹配异常,则在 finally 子句运行后传播异常:
try {
breedMoreLlamas();
} finally {
// Always clean up, even if an exception is thrown.
cleanLlamaStalls();
}
finally 子句在所有匹配到的 catch 子句之后运行:
try {
breedMoreLlamas();
} catch (e) {
print('Error: $e'); // Handle the exception first.
} finally {
cleanLlamaStalls(); // Then clean up.
}