Flutter应用是采用Dart语言来编写,在学习Flutter之前,我们有必要先学习一下Dart语言。Dart是面向对象的,在语法上我感觉既像Java,又像JavaScript,下面我总结了Dart的一些特别之处。
变量
在Dart中所有能够使用变量引用的都是对象,每个对象都是一个类的实例,甚至连数字、方法和null都是对象,所有的对象都继承于Object类。
Dart支持2种变量声明方式:
- 弱类型:使用var声明;
- 强类型:使用类型声明;
像下面这些声明都是合法的:
var a;
int b;
var c = 'Hello world.';
double d = 0.1234;
Dart结合了强类型语言和弱类型语言,它既像JavaScript一样支持弱类型声明,又像Java一样支持强类型声明,它结合了2者的优势,让不同习惯的开发者也能迅速上手。强类型声明的变量赋值时会做类型检查,弱类型声明的变量则不会,类似JavaScript,看看下面的例子:
var a;
a = 123; //正确
a = "Hello"; //正确
int b;
b = 0; //正确
b = "Hello" //错误
没有初始化的变量会有一个默认值null。类型为数字的变量,如果没有初始化其默认值也为null,因为数字类型也是对象。这点与java差别挺大,java里基本类型都会有一个默认值,如int类型默认值为0,bool类型默认值为false。
final、const
两者修饰的变量都不能被修改,final有点像java里的final作用,而const则有点类似javascript里的const修饰符。
- 一个final变量只能赋值一次;
- 一个const变量是编译时常量,const变量同时肯定也是final变量;
- 如果在类中定义const变量,必须定义为“static const”;
- 类的实例变量不能定义成const,可以定义为final;
- const还能定义构造函数为const类型的,这种类型的构造函数创建的对象是不可改变的,例如:
var foo = const [];
内置类型
Dart内置的基础类型有:
- String
- int
- double
- bool
- List
- Map
需要注意大小写,例如里面的int就是一个类,而不是像java里的基本数据类型。
int和double都是num的子类,num定义了很多基本的数学操作符。
Dart字符串是UTF-16编码的字符串,可以使用单引号或双引号来创建字符串,可以在字符串中使用类似 ${expression} 的表达式。
var a = 'foo';
String b = "bar";
var c = 'a is $a, b is ${b.toUpperCase()}';
可以使用三个单引号或双引号来创建多行字符串,也可以使用一个 r 前缀来创建一个原始字符串:
var s = '''
line1.
line2.
''';
//\n在这里并不会被转义为换行符
var s2 = r"This is a raw string, \n";
在Dart中只有 true 对象才被认为是true,与javascript差别很大,js里有真值假值一说,在js里像 "str"、1 等被认为是真值,而在Dart中则不是。
在Dart中数组就是 List 类型的对象,定义一个List,与javascript中的定义挺相似的:
var list = [1, 2, 3];
var list2 = const [1, 2, 3]; //该List对象不可修改
List<int> list3 = const [1, 2, 3];
在Dart中 Map 是一个健值对相关的对象,健和值都可以是任何类型的对象,这点与javascript中的 Map 也挺类似:
//如何定义 Map
var map = {
'key1': 'name',
'key2': 'age',
1 : 'mobile',
2 : 'address'
};
var map2 = new Map();
Map<String, String> map3 = new Map();
//如何使用
map['key3'] = 'school'; //增加键值对
assert(map['key1'] == 'name');
方法
在Dart中方法也是一个对象,它的类型是 Function。常见的一些定义方式如下:
//标准写法,与 java 类似
bool isZero(int n) {
return n == 0;
}
//箭头函数写法,与 javascript 类似,熟悉 js 的都会懂
bool isZero(int n) => n == 0;
//不指定返回类型,其实就是 void 类型,没有返回值,参数不指定类型,这种为 dynamic 类型
print(element) {
print(element);
}
方法可以有2种类型的参数:必需参数、可选参数,必需参数在参数列表前面,可选参数在后面,可选参数需要特别注意,它分为2种:
1.可选命名参数,需要用大括号 {} 包起来
//定义形式,使用{param1, param2, ...}的形式来指定命名参数
void test(String name, int age, {String addr, String school}) {
}
//使用方式,使用 paramName: value 的形式来指定命名参数
test("hjy", 33, addr: "hangzhou", school:"zju");
2.可选位置参数,需要用中括号 [] 包起来
void test(String name, int age, [String addr]) {
}
test("hjy", 33);
test("hjy", 22, "hangzhou");
3.设置可选参数的默认值
void test(String name, int age, [String addr = "hangzhou"]) {
}
方法可以赋值给变量,也可以当做其他方法的参数,这点与js里的 function 挺类似:
void printElement(var element) {
print(element);
}
var p = printElement; //方法赋值给变量
p("123");
var list = [1, 2, 3];
list.forEach(printElement); //方法当做参数
此外,Dart还支持匿名方法,用法与js里真的是几乎一模一样了。
操作符
大部分操作符都跟java或者js是一样的,这里说一些比较特殊的。
- ~/ 操作符:除号,但是会返回整数,例如 5 ~/ 2 = 2,5 / 2 = 2.5;
- ??= 操作符:对变量进行赋值,例如:b ??= 12,如果b是null,则赋值给b,如果不是null,则b的值保持不变;
- Dart里的数字进行加减乘除操作,不会像java一样对结果进行同类型转换,例如:
int a = 5;
int b = 2;
在Dart里 5 / 2 = 2.5
在Java里 5 / 2 = 2
- Dart里不能像Java一样做数字类型强制转换,例如:
double a = 5.12;
int b = (int)(a * 1.2); //这样会报错,这是Java的写法,
类型判定操作符:
操作符 | 含义 |
---|---|
as | 类型转换 |
is | 如果对象是指定的类型返回 true |
is! | 如果对象是指定的类型返回 false |
- 条件表达式 expr1 ?? expr2:如果expr1是non-null,返回其值,否则执行expr2并返回其结果;
- ?. 条件访问成员:例如 foo?.bar,如果foo为null,则返回null,否则返回bar;
for循环
标准写法:
var collection = [0, 1, 2];
for (var i = 0; i < 2; i++) {
//这里会捕获循环里的index索引值,避免 javascript 中常见问题
}
forEach()方法(必须实现Iterable接口):
var collection = [0, 1, 2];
collection.forEach((item) => {
//....
})
for-in形式的遍历(必须实现Iterable接口):
var collection = [0, 1, 2];
for (var item in collection) {
//....
}
异常
Dart中的异常都是非检查异常,方法不一定声明了他们所抛出的异常,并且不要求捕获任何异常。异常类型包括:Exception 和 Error 两种类型,但是与java不同的是,在Dart中可以抛出任何非null对象为异常。
//抛出一个 Exception 的子类型对象
throw new FormatException('Exception...');
//抛出任意非null对象
throw 'Exception';
使用 try...on、try...catch 来捕获异常,或者两者同时混用,其中 on 来指定异常类型,catch 来指定要捕获的异常对象,使用 rethrow 来重新抛出异常:
try {
} on FormatException {
//指定异常类型
} on Exception catch(e) {
//指定异常类型,捕获异常对象
rethrow(); //重新抛出异常
} finally {
}
类
一个标准的类及其构造函数:
class Point {
num x;
num y;
Point(num x, num y) {
this.x = x;
this.y = y;
}
//上面这个构造函数简化实现,Dart提供的语法糖
Point(this.x, this.y);
}
每个实例变量,都会自动生成一个 getter 方法,除了 final 类型的实例变量还会自动生成一个 setter 方法。
Dart中还有一个很特殊的构造函数,叫命名构造函数,使用命名构造函数可以为一个类实现多个构造函数,Dart不能像java一样可以有多个同名构造函数,这点需要特别注意,例如:
class Point {
num x;
num y;
Point(this.x, this.y);
//前面已有一个构造函数了,下面再定义就会报错了
/*
Point(Map json) {
}
*/
//命名构造函数
Point.fromJson(Map json) {
x = json['x'];
y = json['y'];
}
}
Map map = {'x': 12, 'y': 20};
var p = new Point.fromJson(map);
在构造函数参数后使用冒号(:)可以实现很多功能:
- 可以调用父类构造函数;
- 可以重定向调用本类的其他构造函数;
- 初始化实例参数;
class Person {
String name;
//普通构造函数
Person(this.name);
//初始化实例参数
Person.fromJson(Map data) : name = data['name'] {
}
//调用其他构造函数
Person.empty() : this('Jim Green');
}
class Student extends Person {
//调用父类构造函数
Student.fromJson(Map data) : super.fromJson(data) {
//...
}
}
还有一种叫做常量构造函数:
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin = const ImmutablePoint(0, 0);
}
使用库
使用 import 来使用另外一个库,import 的参数必须为库的 URI。对于内置的库,URI为特殊的 dart:scheme;对于其他的库,可以使用本地文件系统路径,或者 package: scheme 的方式来引用。
//使用内置的数据库
import 'dart:io';
import 'dart:math';
//采用dart包管理工具管理的库
import 'package:flutter/material.dart';
//如果引入的不同库有同名的类,可以为库指定别名
import 'package:flutter/material.dart' as ma;
//导入库的一部分
import 'package:lib1/lib1.dart' show foo;
import 'package:lib2/lib2.dart' hide foo;
延迟载入库:
import 'package:lib1/hello.dart' deferred as hello;
greet() async {
await hello.loadLibrary();
hello.printGreeting();
}