近来,flutter
的热度在上升。flutter应用的主要开发语言是dart
, 因此,欲练flutter, 必先了解dart
.
dart是由google开发的编程语言,可用于开发移动应用,桌面应用,h5应用,后端服务。
本文将简单介绍dart
的语言特性、基础语法,以及在日常开发中非常实用的如何请求数据、如何处理异步、如何序列化与反序列化json等技能。
文章比较长,熟悉的部分各位看官可快速浏览,文末也给出了小小福利,供大家参考。疏漏之处请见谅,错误之处请指正。
语言特性
面向对象
dart是一门纯粹的面向对象语言,在dart中一切皆对象。
- 函数也是对象,函数能被赋值给变量,也可以作为函数的参数或返回值
- 基础类型是对象,字面值也是对象,比如可以像下边这样使用
1.toString();
- 支持接口、抽象类、泛型、多态
支持顶级函数和变量
与java不同的是,java的变量和方法只能在类里边,而dart可以有不在类里的方法和变量。
带有面向过程类语言的特点,比如像c。
强类型弱类型均支持
在dart中你可以显式的指定变量的类型,也可以不指定类型,由dart推断类型。指定类型的话编译器可以做静态类型检查。
在类型方面dart既有弱类型语言(像js)也有强类型(像java)的特点。
兼具解释性语言和编译型语言特点
对开发web应用来讲:dart会被转译成js
对app、服务端、桌面应用讲:
- dart提供了开发模式,通过vm解释执行,支持热更新,具有解释型语言特点
- 也提供了生产模式,通过编译成机器码执行,具有编译型语言特点
小结
dart看起来是希望融合多种主流语言的特性,在dart中能够看到多种语言的影子。
基础语法
程序入口
dart的程序执行入口是main函数,这跟c很像,main函数一个顶级函数。
返回值通常是void, 写为int, double等也不会报错,不过没有意义。
void main() {
print('Hello, World!');
}
变量和常量
- dart中的变量或常量必须先定义后使用
- 未被初始化的变量和常量默认值为null
变量
- var 变量名[=值];
void main() {
var var1 = '1';
print(var1); // 1
// var1 = 1; 这样是错误的, 声明且初始化会推断出类型,后面不能赋值其他类型的值
var var2;
print(var2); // null
var2 = 2;
print(var2); // 2
var2 = '3';
print(var2); // 3 正确,声明时不赋值则等同于声明了一个动态类型(dynamic)变量
}
- 数据类型 变量名[=值];
这种方式可以显式声明变量类型,以便做一些静态检查。
void main() {
// 声明并初始化
int var1 = 1;
print(var1); // 1
// 先声明后初始化
int var2;
var2 = 1;
print(var2); // 1
// var2 = '1'; 这是错误的,类型不匹配
}
- dynamic 变量名[=值]
这个dynamic意思是动态类型,这种变量可以随便给他赋什么类型的值
void main() {
dynamic var1 = 1;
var1 = '2'; // 动态类型可以赋任意类型的值
print(var1); // 2
}
常量
- final [数据类型] 常量名=值
在运行时确定其值, 可以作为类的普通成员变量
- const [数据类型] 常量名=值
必须在编译时确定其值,只能作为类的静态成员变量,不能作为普通成员变量
class User {
final int var1=1;
static const int var2=2;
// const int var3 = 3; 错误,不能作为类的普通成员变量
}
void main() {
int var1 = 1;
int var2 = 2;
final int const1 = 1;
const int const2 = 2;
// const1 = 2; 错误,不能再改变
// const2 = 1; 错误,不能再改变
// final int const3; 错误,必须被初始化
// const int const4; 错误,必须被初始化
final int const5 = var1 + var2;
// const int cont6 = var1+var2; 错误,必须在编译时可以确定值
}
数据类型
- num、int、double
- int类型和double类型都是num类型
- 都是占8byte
- 是对象,有一些方法和属性
- 没有无符号类型
void main() {
int int1 = 1;
double double1 = 1.3;
num num1 = 2.0;
// 以下引发运行时异常
// int1 = num1;
// print(int1);
print(int1.toString()); // 1
print(int1.isEven); // false
print(int1.floor()); // 1
print(int1.isNaN); // false
print(double1.toString()); // 1.3
print(double1.floor()); // 1
print(double1.isNaN); // false
}
- String
字面值表示法
- 单行字面值表示方法:'xx'或"xx"
- 多行字面值表示方法: '''xxx'''或"""xxx"""
- 忽略转译符: r'xx\nxx'
void main() {
String str1 = 'str1';
print(str1); // str1
String str2 = "str2";
print(str2); // str2
String str3 = '''a
b
c
''';
print(str3);
// a
// b
// c
String str4 = "a\nb";
String str5 = r"a\nb";
print(str4);
// a
// b
print(str5); // a\nb
}
常用属性和方法
String 的属性和方法和其他语言类似,这里列举几个常用方法和属性
void main() {
String str1 = "我和我的祖国";
String str2 = ",一刻也不能分割";
String str3 = str1 + str2;
print(str1.length); // 6
print(str1.substring(2)); // 我的祖国
List<String> list = str3.split(",");
print(list); // [我和我的祖国, 一刻也不能分割]
print(str1.startsWith("我")); // true
print(str1.indexOf("我")); // 0
print(str1.lastIndexOf("我")); // 2
print(str1.replaceAll("我", "你")); // 你和你的祖国
print("a".compareTo("b")); // -1
print(str1.contains('祖国')); // true
}
- bool
布尔值,字面值只能是true或false, 不能为其他
void main() {
bool bool1 = true;
print(bool1); // true
}
- List
类似js中的数组, 长度可变。
void main() {
List<int> list = [1, 3, 4];
print(list.length); // 3
print(list); // [1, 3, 4]
list.add(5);
print(list); // [1, 3, 4, 5]
list.addAll([6, 7]);
print(list); // [1, 3, 4, 5, 6, 7]
print(list.removeLast()); // 7
print(list); // [1, 3, 4, 5, 6]
list.removeAt(0);
print(list); // [3, 4, 5, 6]
list.remove(3);
print(list); // [4, 5, 6]
list.sort((item1, item2) {
return item2 - item1;
});
print(list); // [6, 5, 4]
list.forEach((item) {
print(item);
});
// 6
// 5
// 4
print(list.indexOf(4)); // 2
list.clear();
print(list); // []
}
- Set
表示集合,无重复值,加重复值不会报错,但是进不去,无序。
void main() {
Set<int> set = {1, 3, 3, 4};
print(set); // {1, 3, 4}
set.add(9);
print(set); // {1, 3, 4, 9}
print(set.contains(3)); // true
print(set.length); // 4
set.remove(3);
print(set); // {1, 4, 9}
print(set.isEmpty); // true
}
- Map
表示k-v结构数据,key不能重。
void main() {
Map<String, int> map = {
"a": 1,
"b": 2,
};
print(map); // {a: 1, b: 2}
map['c'] = 3;
print(map); // {a: 1, b: 2, c: 3}
map.remove('a');
print(map); // {b: 2, c: 3}
print(map.containsKey('a')); // false
print(map.length); // 2
print(map.containsValue(3)); // true
}
- enum
表示枚举,是一种特殊的类,官方文档没有将其纳入到基础类型,这里为了方便理解放到这里。
enum Color {
red,
blue,
yellow,
}
void main() {
Color c = Color.red;
print(Color.blue); // Color.blue
print(Color.values); // [Color.red, Color.blue, Color.yellow]
print(c); // Color.red
}
数据类型转换
- java中有自动类型转换机制,例如可以将int型自动转为double;dart中类型不匹配时必须强转,没有自动类型转换
- 其他类型转String或数字之间相互转换 toXxx()方法
- String转数字使用数字类(int,double,num)的parse()方法
void main() {
int int1 = 1;
// 错误,不能自动转换
// double double1= int1;
// double 和int的相互转换
double double1 = int1.toDouble();
int1 = double1.toInt();
// num 是int 和 double的父类型, 可以直接转换
num num1 = double1;
num1 = int1;
// String 与double相互转换
String str1 = double1.toString();
double1 = double.parse(str1);
// String 与int相互转换
String str2 = int1.toString();
int1 = int.parse(str2);
// Map,Set,List用其toString方法可以转换为String
Map map = {"a": 1, "b": 2};
List list = [1, 2, 3];
Set set = {1, 2, 3};
String str3 = map.toString();
list.toString();
set.toString();
}
操作符
操作符与其他语言基本相同,这里说明几个特殊的,其他简单罗列。
- 算术运算符
+、-、*、/、%、++、--
比较特殊的是~/
, 表示整除
void main() {
int int1 = 3;
print(int1 ~/ 2); // 1
}
- 比较运算符
==、!=、>、<、<=、>=
- 赋值运算符
=、-=、~/=等等
比较特殊的是 变量名??=值
, 表示如果左边变量是null则赋值,否则不赋值
void main() {
int a;
a ??= 1;
print(a); // 1
int b = 2;
b ??= 1;
print(b); // 2
}
- 逻辑运算符
!、||、&&
- 位运算符
&、|、^、~expr、<<、>>
- 条件表达式
condition ? expr1 : expr2
- 级联运算符
这是比较特殊的运算符,类似于jquery中的连续的.操作, 上一次操作还是返回当前对象,因此可以继续调用其方法。大多语言没有这种操作。
class User {
say() {
print("say");
}
run() {
print("run");
}
}
void main() {
User user = User();
user..say()..run();
// say
// run
}
- 类型运算符
这是比较特殊的运算符,用于判断类型和强转,类似java里边(User) instance
这种
- instance as className
用于将某个类型转换为其他类型,必须满足父子关系,否则报错。
- import 'xxx' as variable;
用于指定导入库的别名,相当于将库里导出来的东西挂到一个map上,可以解决重名问题。
- instance is classNmae
用于判断一个对象是否是某个类的实例
- instance is! classNmae
is 的结果取反
import 'dart:math' as math; // 这里导入库时使用as指定了别名
class Person {
String name;
Person();
say() {
print("person say $name");
}
}
class User extends Person {
String password;
User(this.password);
run() {
print("user run");
}
}
void main() {
print(math.max(4, 5)); // 指定别名的库调用
User u = User("password");
print(u is User); // true
u.name = "name";
u.run(); // user run
Person p = u as Person; // 通过as将子类型强转为父类型
print(p is User); // true
print(p is Person); // true
print(p is List); // fasle
p.say(); // person say name
// p.run(); // 错误,已经被转换成了Person, 不能再调用User的方法
}
流程控制语句
流程控制与其他语言相同,这里简单列举
- if .. else if..else
- for
- while、do..while
- break、continue
- switch .. case
- assert
assert表示断言,判断一个表达式的真假,若为假则抛出一个异常
模块化
dart支持模块化编程,可以导入dart内置库,其他三方库,也可以将自己的代码拆分成不同模块。
- 导入
- 导入内置库
import "dart:math";
- 导入三方库
需要在项目描述文件pubspec.yaml(类似js中的package.json或者java的pom.xml)中声明你要依赖的库,然后安装,最后导入。
pubspec.yaml
dependencies:
http: ^0.12.0+2
安装
pub get
导入
import 'package:http/http.dart';
- 导入本地文件
导入本地的文件后可以使用其中定义的类,变量,函数等
import '../common/Utils.dart';
- 导入时指定别名
相当于将一个库导出的东西挂到一个对象,可以解决重名问题(上边讲as有提到)
import 'package:http/http.dart' as http;
- 条件导入
可以只导入指定内容,或者不导入某些内容
import 'package:lib1/lib1.dart' show foo;
import 'package:lib2/lib2.dart' hide foo;
- 动态导入或者按需加载
可以在运行时导入依赖,针对web应用,类似webpack的按需加载。
- 创建库
内容较多,此处略过,笔者也尚未研究,可参考官网
- dart内置库介绍
- dart:core
dart核心内库,默认导入,类似java的java.lang包。提供内置数据类型等。
- dart:async
异步支持库,大名鼎鼎的Future就在这里边。
- dart:math
复杂数学计算相关
- dart:convert
json序列化、反序列化,字符集转换相关。
- dart:html
web应用相关api
- dart:io
io相关,与发请求相关的HttpClient在这里边
函数
- 函数可以在顶层,也就是不属于任何一个类
- 函数可以是类的成员方法
- 可以有返回值也可以没有
- 函数是对象,可以赋值给变量,可以作为参数或返回值
- 参数可以是常见的位置参数,也可以是命名参数,命名参数传递时不必考虑顺序,命名参数大多数语言没有,听说是从oc借鉴过来的
- 只有一条语句的函数可以使用箭头函数
- 可以使用typedef定义特定的函数类型
- 函数参数可以指定默认值
// 没有返回值的函数
import 'package:flutter/foundation.dart';
void printInfo(String name, int age) {
print('$name:$age');
}
// 有返回值的函数
String getInfo(String name, int age) {
return '$name:$age';
}
// 函数作为参数传递
void excuteFn(var function, String name, int age) {
function(name, age);
}
// 返回一个函数
Function getPrintInfoFn(int age) {
return (String name) {
print('$name:$age');
};
}
// 函数体只有一条语句,可以使用箭头函数
void printInfo2(String name, int age) => print('$name:$age');
// 可以使用命名参数
void printInfo3({String name, int age}) {
print('$name:$age');
}
// 位置参数和命名参数混用,指定默认值
void printInfo4(String name, { int age}) {
print('$name:$age');
}
// 定义一种函数类型
typedef PrintFn = void Function(String name, int age);
// 将特定类型的函数作为参数传递
void excuteFn2(PrintFn function, String name, int age) {
function(name, age);
}
class User {
// 函数作为类的成员方法
say() {
print("hello");
}
}
void main() {
printInfo("张三", 18);
print(getInfo('李四', 18));
User user = User();
user.say();
excuteFn(printInfo, "王五", 18);
Function fn1 = getPrintInfoFn(19);
fn1("小明");
fn1("小花");
printInfo("小张", 18);
printInfo2('小李', 20);
printInfo3(name: "小华", age: 21); // 命名参数函数调用方式
printInfo4("AA");
printInfo4("aa", age: 30);
excuteFn2(printInfo, "王五", 18);
}
注释
- 单行注释
// 我是一行注释
- 多行注释
/**
* 多行注释
* 多行注释
*/
- 文档注释
/// 文档注释
/// 文档注释